Upgraded phpmyadmin to 4.0.4 (All Languages) - No modifications yet
[openemr.git] / phpmyadmin / libraries / tcpdf / tcpdf.php
blobe2f511bbeb70e7ffda9a09cc957cac675f9cf2f9
1 <?php
2 //============================================================+
3 // File name : tcpdf.php
4 // Version : 5.9.207
5 // Begin : 2002-08-03
6 // Last Update : 2013-03-04
7 // Author : Nicola Asuni - Tecnick.com LTD - Manor Coach House, Church Hill, Aldershot, Hants, GU12 4RQ, UK - www.tecnick.com - info@tecnick.com
8 // License : http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2013 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 ImagMagick (http://www.imagemagick.org/www/formats.html)
49 // * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50 // * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51 // * automatic page header and footer management;
52 // * document encryption up to 256 bit and digital signature certifications;
53 // * transactions to UNDO commands;
54 // * PDF annotations, including links, text and file attachments;
55 // * text rendering modes (fill, stroke and clipping);
56 // * multiple columns mode;
57 // * no-write page regions;
58 // * bookmarks, named destinations and table of content;
59 // * text hyphenation;
60 // * text stretching and spacing (tracking);
61 // * automatic page break, line break and text alignments including justification;
62 // * automatic page numbering and page groups;
63 // * move and delete pages;
64 // * page compression (requires php-zlib extension);
65 // * XOBject Templates;
66 // * Layers and object visibility.
67 // * PDF/A-1b support.
69 // -----------------------------------------------------------
70 // THANKS TO:
72 // Olivier Plathey (http://www.fpdf.org) for original FPDF.
73 // Efthimios Mavrogeorgiadis (emavro@yahoo.com) for suggestions on RTL language support.
74 // Klemen Vodopivec (http://www.fpdf.de/downloads/addons/37/) for Encryption algorithm.
75 // Warren Sherliker (wsherliker@gmail.com) for better image handling.
76 // dullus for text Justification.
77 // Bob Vincent (pillarsdotnet@users.sourceforge.net) for <li> value attribute.
78 // Patrick Benny for text stretch suggestion on Cell().
79 // Johannes Güntert for JavaScript support.
80 // Denis Van Nuffelen for Dynamic Form.
81 // Jacek Czekaj for multibyte justification
82 // Anthony Ferrara for the reintroduction of legacy image methods.
83 // Sourceforge user 1707880 (hucste) for line-trough mode.
84 // Larry Stanbery for page groups.
85 // Martin Hall-May for transparency.
86 // Aaron C. Spike for Polycurve method.
87 // Mohamad Ali Golkar, Saleh AlMatrafe, Charles Abbott for Arabic and Persian support.
88 // Moritz Wagner and Andreas Wurmser for graphic functions.
89 // Andrew Whitehead for core fonts support.
90 // Esteban Joël Marín for OpenType font conversion.
91 // Teus Hagen for several suggestions and fixes.
92 // Yukihiro Nakadaira for CID-0 CJK fonts fixes.
93 // Kosmas Papachristos for some CSS improvements.
94 // Marcel Partap for some fixes.
95 // Won Kyu Park for several suggestions, fixes and patches.
96 // Dominik Dzienia for QR-code support.
97 // Laurent Minguet for some suggestions.
98 // Christian Deligant for some suggestions and fixes.
99 // Travis Harris for crop mark suggestion.
100 // Aleksey Kuznetsov for some suggestions and text shadows.
101 // Jim Hanlon for several suggestions and patches.
102 // Anyone else that has reported a bug or sent a suggestion.
103 //============================================================+
106 * @file
107 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
108 * 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>
109 * <h3>TCPDF main features are:</h3>
110 * <ul>
111 * <li>no external libraries are required for the basic functions;</li>
112 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
113 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
114 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
115 * <li>font subsetting;</li>
116 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
117 * <li>images, graphic (geometric figures) and transformation methods;
118 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
119 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
120 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
121 * <li>automatic page header and footer management;</li>
122 * <li>document encryption up to 256 bit and digital signature certifications;</li>
123 * <li>transactions to UNDO commands;</li>
124 * <li>PDF annotations, including links, text and file attachments;</li>
125 * <li>text rendering modes (fill, stroke and clipping);</li>
126 * <li>multiple columns mode;</li>
127 * <li>no-write page regions;</li>
128 * <li>bookmarks, named destinations and table of content;</li>
129 * <li>text hyphenation;</li>
130 * <li>text stretching and spacing (tracking);</li>
131 * <li>automatic page break, line break and text alignments including justification;</li>
132 * <li>automatic page numbering and page groups;</li>
133 * <li>move and delete pages;</li>
134 * <li>page compression (requires php-zlib extension);</li>
135 * <li>XOBject Templates;</li>
136 * <li>Layers and object visibility;</li>
137 * <li>PDF/A-1b support.</li>
138 * </ul>
139 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
140 * @package com.tecnick.tcpdf
141 * @author Nicola Asuni
142 * @version 5.9.207
145 // Main configuration file. Define the K_TCPDF_EXTERNAL_CONFIG constant to skip this file.
146 require_once(dirname(__FILE__).'/config/tcpdf_config.php');
149 * @class TCPDF
150 * PHP class for generating PDF documents without requiring external extensions.
151 * 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>
152 * @package com.tecnick.tcpdf
153 * @brief PHP class for generating PDF documents without requiring external extensions.
154 * @version 5.9.207
155 * @author Nicola Asuni - info@tecnick.com
157 class TCPDF {
159 // private properties
162 * Current TCPDF version.
163 * @private
165 private $tcpdf_version = '5.9.207';
167 // Protected properties
170 * Current page number.
171 * @protected
173 protected $page;
176 * Current object number.
177 * @protected
179 protected $n;
182 * Array of object offsets.
183 * @protected
185 protected $offsets = array();
188 * Array of object IDs for each page.
189 * @protected
191 protected $pageobjects = array();
194 * Buffer holding in-memory PDF.
195 * @protected
197 protected $buffer;
200 * Array containing pages.
201 * @protected
203 protected $pages = array();
206 * Current document state.
207 * @protected
209 protected $state;
212 * Compression flag.
213 * @protected
215 protected $compress;
218 * Current page orientation (P = Portrait, L = Landscape).
219 * @protected
221 protected $CurOrientation;
224 * Page dimensions.
225 * @protected
227 protected $pagedim = array();
230 * Scale factor (number of points in user unit).
231 * @protected
233 protected $k;
236 * Width of page format in points.
237 * @protected
239 protected $fwPt;
242 * Height of page format in points.
243 * @protected
245 protected $fhPt;
248 * Current width of page in points.
249 * @protected
251 protected $wPt;
254 * Current height of page in points.
255 * @protected
257 protected $hPt;
260 * Current width of page in user unit.
261 * @protected
263 protected $w;
266 * Current height of page in user unit.
267 * @protected
269 protected $h;
272 * Left margin.
273 * @protected
275 protected $lMargin;
278 * Right margin.
279 * @protected
281 protected $rMargin;
284 * Cell left margin (used by regions).
285 * @protected
287 protected $clMargin;
290 * Cell right margin (used by regions).
291 * @protected
293 protected $crMargin;
296 * Top margin.
297 * @protected
299 protected $tMargin;
302 * Page break margin.
303 * @protected
305 protected $bMargin;
308 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
309 * @since 5.9.000 (2010-10-03)
310 * @protected
312 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
315 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
316 * @since 5.9.000 (2010-10-04)
317 * @protected
319 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
322 * Current horizontal position in user unit for cell positioning.
323 * @protected
325 protected $x;
328 * Current vertical position in user unit for cell positioning.
329 * @protected
331 protected $y;
334 * Height of last cell printed.
335 * @protected
337 protected $lasth;
340 * Line width in user unit.
341 * @protected
343 protected $LineWidth;
346 * Array of standard font names.
347 * @protected
349 protected $CoreFonts;
352 * Array of used fonts.
353 * @protected
355 protected $fonts = array();
358 * Array of font files.
359 * @protected
361 protected $FontFiles = array();
364 * Array of encoding differences.
365 * @protected
367 protected $diffs = array();
370 * Array of used images.
371 * @protected
373 protected $images = array();
376 * Array of cached files.
377 * @protected
379 protected $cached_files = array();
382 * Array of Annotations in pages.
383 * @protected
385 protected $PageAnnots = array();
388 * Array of internal links.
389 * @protected
391 protected $links = array();
394 * Current font family.
395 * @protected
397 protected $FontFamily;
400 * Current font style.
401 * @protected
403 protected $FontStyle;
406 * Current font ascent (distance between font top and baseline).
407 * @protected
408 * @since 2.8.000 (2007-03-29)
410 protected $FontAscent;
413 * Current font descent (distance between font bottom and baseline).
414 * @protected
415 * @since 2.8.000 (2007-03-29)
417 protected $FontDescent;
420 * Underlining flag.
421 * @protected
423 protected $underline;
426 * Overlining flag.
427 * @protected
429 protected $overline;
432 * Current font info.
433 * @protected
435 protected $CurrentFont;
438 * Current font size in points.
439 * @protected
441 protected $FontSizePt;
444 * Current font size in user unit.
445 * @protected
447 protected $FontSize;
450 * Commands for drawing color.
451 * @protected
453 protected $DrawColor;
456 * Commands for filling color.
457 * @protected
459 protected $FillColor;
462 * Commands for text color.
463 * @protected
465 protected $TextColor;
468 * Indicates whether fill and text colors are different.
469 * @protected
471 protected $ColorFlag;
474 * Automatic page breaking.
475 * @protected
477 protected $AutoPageBreak;
480 * Threshold used to trigger page breaks.
481 * @protected
483 protected $PageBreakTrigger;
486 * Flag set when processing page header.
487 * @protected
489 protected $InHeader = false;
492 * Flag set when processing page footer.
493 * @protected
495 protected $InFooter = false;
498 * Zoom display mode.
499 * @protected
501 protected $ZoomMode;
504 * Layout display mode.
505 * @protected
507 protected $LayoutMode;
510 * If true set the document information dictionary in Unicode.
511 * @protected
513 protected $docinfounicode = true;
516 * Document title.
517 * @protected
519 protected $title = '';
522 * Document subject.
523 * @protected
525 protected $subject = '';
528 * Document author.
529 * @protected
531 protected $author = '';
534 * Document keywords.
535 * @protected
537 protected $keywords = '';
540 * Document creator.
541 * @protected
543 protected $creator = '';
546 * Starting page number.
547 * @protected
549 protected $starting_page_number = 1;
552 * String alias for total number of pages.
553 * @protected
555 protected $alias_tot_pages = '{:ptp:}';
558 * String alias for page number.
559 * @protected
561 protected $alias_num_page = '{:pnp:}';
564 * String alias for total number of pages in a single group.
565 * @protected
567 protected $alias_group_tot_pages = '{:ptg:}';
570 * String alias for group page number.
571 * @protected
573 protected $alias_group_num_page = '{:png:}';
576 * String alias for right shift compensation used to correctly align page numbers on the right.
577 * @protected
579 protected $alias_right_shift = '{rsc:';
582 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
583 * @since 2002-07-31
584 * @author Nicola Asuni
585 * @protected
587 protected $img_rb_x;
590 * The right-bottom corner Y coordinate of last inserted image.
591 * @since 2002-07-31
592 * @author Nicola Asuni
593 * @protected
595 protected $img_rb_y;
598 * Adjusting factor to convert pixels to user units.
599 * @since 2004-06-14
600 * @author Nicola Asuni
601 * @protected
603 protected $imgscale = 1;
606 * Boolean flag set to true when the input text is unicode (require unicode fonts).
607 * @since 2005-01-02
608 * @author Nicola Asuni
609 * @protected
611 protected $isunicode = false;
614 * Object containing unicode data.
615 * @since 5.9.004 (2010-10-18)
616 * @author Nicola Asuni
617 * @protected
619 protected $unicode;
622 * Object containing font encoding maps.
623 * @since 5.9.123 (2011-10-01)
624 * @author Nicola Asuni
625 * @protected
627 protected $encmaps;
630 * PDF version.
631 * @since 1.5.3
632 * @protected
634 protected $PDFVersion = '1.7';
637 * ID of the stored default header template (-1 = not set).
638 * @protected
640 protected $header_xobjid = -1;
643 * If true reset the Header Xobject template at each page
644 * @protected
646 protected $header_xobj_autoreset = false;
649 * Minimum distance between header and top page margin.
650 * @protected
652 protected $header_margin;
655 * Minimum distance between footer and bottom page margin.
656 * @protected
658 protected $footer_margin;
661 * Original left margin value.
662 * @protected
663 * @since 1.53.0.TC013
665 protected $original_lMargin;
668 * Original right margin value.
669 * @protected
670 * @since 1.53.0.TC013
672 protected $original_rMargin;
675 * Default font used on page header.
676 * @protected
678 protected $header_font;
681 * Default font used on page footer.
682 * @protected
684 protected $footer_font;
687 * Language templates.
688 * @protected
690 protected $l;
693 * Barcode to print on page footer (only if set).
694 * @protected
696 protected $barcode = false;
699 * Boolean flag to print/hide page header.
700 * @protected
702 protected $print_header = true;
705 * Boolean flag to print/hide page footer.
706 * @protected
708 protected $print_footer = true;
711 * Header image logo.
712 * @protected
714 protected $header_logo = '';
717 * Width of header image logo in user units.
718 * @protected
720 protected $header_logo_width = 30;
723 * Title to be printed on default page header.
724 * @protected
726 protected $header_title = '';
729 * String to pring on page header after title.
730 * @protected
732 protected $header_string = '';
735 * Color for header text (RGB array).
736 * @since 5.9.174 (2012-07-25)
737 * @protected
739 protected $header_text_color = array(0,0,0);
742 * Color for header line (RGB array).
743 * @since 5.9.174 (2012-07-25)
744 * @protected
746 protected $header_line_color = array(0,0,0);
749 * Color for footer text (RGB array).
750 * @since 5.9.174 (2012-07-25)
751 * @protected
753 protected $footer_text_color = array(0,0,0);
756 * Color for footer line (RGB array).
757 * @since 5.9.174 (2012-07-25)
758 * @protected
760 protected $footer_line_color = array(0,0,0);
763 * Text shadow data array.
764 * @since 5.9.174 (2012-07-25)
765 * @protected
767 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
770 * Default number of columns for html table.
771 * @protected
773 protected $default_table_columns = 4;
775 // variables for html parser
778 * HTML PARSER: array to store current link and rendering styles.
779 * @protected
781 protected $HREF = array();
784 * List of available fonts on filesystem.
785 * @protected
787 protected $fontlist = array();
790 * Current foreground color.
791 * @protected
793 protected $fgcolor;
796 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
797 * @protected
799 protected $listordered = array();
802 * HTML PARSER: array count list items on nested lists.
803 * @protected
805 protected $listcount = array();
808 * HTML PARSER: current list nesting level.
809 * @protected
811 protected $listnum = 0;
814 * HTML PARSER: indent amount for lists.
815 * @protected
817 protected $listindent = 0;
820 * HTML PARSER: current list indententation level.
821 * @protected
823 protected $listindentlevel = 0;
826 * Current background color.
827 * @protected
829 protected $bgcolor;
832 * Temporary font size in points.
833 * @protected
835 protected $tempfontsize = 10;
838 * Spacer string for LI tags.
839 * @protected
841 protected $lispacer = '';
844 * Default encoding.
845 * @protected
846 * @since 1.53.0.TC010
848 protected $encoding = 'UTF-8';
851 * PHP internal encoding.
852 * @protected
853 * @since 1.53.0.TC016
855 protected $internal_encoding;
858 * Boolean flag to indicate if the document language is Right-To-Left.
859 * @protected
860 * @since 2.0.000
862 protected $rtl = false;
865 * Boolean flag used to force RTL or LTR string direction.
866 * @protected
867 * @since 2.0.000
869 protected $tmprtl = false;
871 // --- Variables used for document encryption:
874 * IBoolean flag indicating whether document is protected.
875 * @protected
876 * @since 2.0.000 (2008-01-02)
878 protected $encrypted;
881 * Array containing encryption settings.
882 * @protected
883 * @since 5.0.005 (2010-05-11)
885 protected $encryptdata = array();
888 * Last RC4 key encrypted (cached for optimisation).
889 * @protected
890 * @since 2.0.000 (2008-01-02)
892 protected $last_enc_key;
895 * Last RC4 computed key.
896 * @protected
897 * @since 2.0.000 (2008-01-02)
899 protected $last_enc_key_c;
902 * Encryption padding string.
903 * @protected
905 protected $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
908 * File ID (used on document trailer).
909 * @protected
910 * @since 5.0.005 (2010-05-12)
912 protected $file_id;
914 // --- bookmark ---
917 * Outlines for bookmark.
918 * @protected
919 * @since 2.1.002 (2008-02-12)
921 protected $outlines = array();
924 * Outline root for bookmark.
925 * @protected
926 * @since 2.1.002 (2008-02-12)
928 protected $OutlineRoot;
930 // --- javascript and form ---
933 * Javascript code.
934 * @protected
935 * @since 2.1.002 (2008-02-12)
937 protected $javascript = '';
940 * Javascript counter.
941 * @protected
942 * @since 2.1.002 (2008-02-12)
944 protected $n_js;
947 * line trough state
948 * @protected
949 * @since 2.8.000 (2008-03-19)
951 protected $linethrough;
954 * Array with additional document-wide usage rights for the document.
955 * @protected
956 * @since 5.8.014 (2010-08-23)
958 protected $ur = array();
961 * DPI (Dot Per Inch) Document Resolution (do not change).
962 * @protected
963 * @since 3.0.000 (2008-03-27)
965 protected $dpi = 72;
968 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
969 * @protected
970 * @since 3.0.000 (2008-03-27)
972 protected $newpagegroup = array();
975 * Array that contains the number of pages in each page group.
976 * @protected
977 * @since 3.0.000 (2008-03-27)
979 protected $pagegroups = array();
982 * Current page group number.
983 * @protected
984 * @since 3.0.000 (2008-03-27)
986 protected $currpagegroup = 0;
989 * Array of transparency objects and parameters.
990 * @protected
991 * @since 3.0.000 (2008-03-27)
993 protected $extgstates;
996 * Set the default JPEG compression quality (1-100).
997 * @protected
998 * @since 3.0.000 (2008-03-27)
1000 protected $jpeg_quality;
1003 * Default cell height ratio.
1004 * @protected
1005 * @since 3.0.014 (2008-05-23)
1007 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
1010 * PDF viewer preferences.
1011 * @protected
1012 * @since 3.1.000 (2008-06-09)
1014 protected $viewer_preferences;
1017 * A name object specifying how the document should be displayed when opened.
1018 * @protected
1019 * @since 3.1.000 (2008-06-09)
1021 protected $PageMode;
1024 * Array for storing gradient information.
1025 * @protected
1026 * @since 3.1.000 (2008-06-09)
1028 protected $gradients = array();
1031 * Array used to store positions inside the pages buffer (keys are the page numbers).
1032 * @protected
1033 * @since 3.2.000 (2008-06-26)
1035 protected $intmrk = array();
1038 * Array used to store positions inside the pages buffer (keys are the page numbers).
1039 * @protected
1040 * @since 5.7.000 (2010-08-03)
1042 protected $bordermrk = array();
1045 * Array used to store page positions to track empty pages (keys are the page numbers).
1046 * @protected
1047 * @since 5.8.007 (2010-08-18)
1049 protected $emptypagemrk = array();
1052 * Array used to store content positions inside the pages buffer (keys are the page numbers).
1053 * @protected
1054 * @since 4.6.021 (2009-07-20)
1056 protected $cntmrk = array();
1059 * Array used to store footer positions of each page.
1060 * @protected
1061 * @since 3.2.000 (2008-07-01)
1063 protected $footerpos = array();
1066 * Array used to store footer length of each page.
1067 * @protected
1068 * @since 4.0.014 (2008-07-29)
1070 protected $footerlen = array();
1073 * Boolean flag to indicate if a new line is created.
1074 * @protected
1075 * @since 3.2.000 (2008-07-01)
1077 protected $newline = true;
1080 * End position of the latest inserted line.
1081 * @protected
1082 * @since 3.2.000 (2008-07-01)
1084 protected $endlinex = 0;
1087 * PDF string for width value of the last line.
1088 * @protected
1089 * @since 4.0.006 (2008-07-16)
1091 protected $linestyleWidth = '';
1094 * PDF string for CAP value of the last line.
1095 * @protected
1096 * @since 4.0.006 (2008-07-16)
1098 protected $linestyleCap = '0 J';
1101 * PDF string for join value of the last line.
1102 * @protected
1103 * @since 4.0.006 (2008-07-16)
1105 protected $linestyleJoin = '0 j';
1108 * PDF string for dash value of the last line.
1109 * @protected
1110 * @since 4.0.006 (2008-07-16)
1112 protected $linestyleDash = '[] 0 d';
1115 * Boolean flag to indicate if marked-content sequence is open.
1116 * @protected
1117 * @since 4.0.013 (2008-07-28)
1119 protected $openMarkedContent = false;
1122 * Count the latest inserted vertical spaces on HTML.
1123 * @protected
1124 * @since 4.0.021 (2008-08-24)
1126 protected $htmlvspace = 0;
1129 * Array of Spot colors.
1130 * @protected
1131 * @since 4.0.024 (2008-09-12)
1133 protected $spot_colors = array();
1136 * Symbol used for HTML unordered list items.
1137 * @protected
1138 * @since 4.0.028 (2008-09-26)
1140 protected $lisymbol = '';
1143 * String used to mark the beginning and end of EPS image blocks.
1144 * @protected
1145 * @since 4.1.000 (2008-10-18)
1147 protected $epsmarker = 'x#!#EPS#!#x';
1150 * Array of transformation matrix.
1151 * @protected
1152 * @since 4.2.000 (2008-10-29)
1154 protected $transfmatrix = array();
1157 * Current key for transformation matrix.
1158 * @protected
1159 * @since 4.8.005 (2009-09-17)
1161 protected $transfmatrix_key = 0;
1164 * Booklet mode for double-sided pages.
1165 * @protected
1166 * @since 4.2.000 (2008-10-29)
1168 protected $booklet = false;
1171 * Epsilon value used for float calculations.
1172 * @protected
1173 * @since 4.2.000 (2008-10-29)
1175 protected $feps = 0.005;
1178 * Array used for custom vertical spaces for HTML tags.
1179 * @protected
1180 * @since 4.2.001 (2008-10-30)
1182 protected $tagvspaces = array();
1185 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1186 * @protected
1187 * @since 4.2.007 (2008-11-12)
1189 protected $customlistindent = -1;
1192 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1193 * @protected
1194 * @since 4.2.010 (2008-11-14)
1196 protected $opencell = true;
1199 * Array of files to embedd.
1200 * @protected
1201 * @since 4.4.000 (2008-12-07)
1203 protected $embeddedfiles = array();
1206 * Boolean flag to indicate if we are inside a PRE tag.
1207 * @protected
1208 * @since 4.4.001 (2008-12-08)
1210 protected $premode = false;
1213 * Array used to store positions of graphics transformation blocks inside the page buffer.
1214 * keys are the page numbers
1215 * @protected
1216 * @since 4.4.002 (2008-12-09)
1218 protected $transfmrk = array();
1221 * Default color for html links.
1222 * @protected
1223 * @since 4.4.003 (2008-12-09)
1225 protected $htmlLinkColorArray = array(0, 0, 255);
1228 * Default font style to add to html links.
1229 * @protected
1230 * @since 4.4.003 (2008-12-09)
1232 protected $htmlLinkFontStyle = 'U';
1235 * Counts the number of pages.
1236 * @protected
1237 * @since 4.5.000 (2008-12-31)
1239 protected $numpages = 0;
1242 * Array containing page lengths in bytes.
1243 * @protected
1244 * @since 4.5.000 (2008-12-31)
1246 protected $pagelen = array();
1249 * Counts the number of pages.
1250 * @protected
1251 * @since 4.5.000 (2008-12-31)
1253 protected $numimages = 0;
1256 * Store the image keys.
1257 * @protected
1258 * @since 4.5.000 (2008-12-31)
1260 protected $imagekeys = array();
1263 * Length of the buffer in bytes.
1264 * @protected
1265 * @since 4.5.000 (2008-12-31)
1267 protected $bufferlen = 0;
1270 * If true enables disk caching.
1271 * @protected
1272 * @since 4.5.000 (2008-12-31)
1274 protected $diskcache = false;
1277 * Counts the number of fonts.
1278 * @protected
1279 * @since 4.5.000 (2009-01-02)
1281 protected $numfonts = 0;
1284 * Store the font keys.
1285 * @protected
1286 * @since 4.5.000 (2009-01-02)
1288 protected $fontkeys = array();
1291 * Store the font object IDs.
1292 * @protected
1293 * @since 4.8.001 (2009-09-09)
1295 protected $font_obj_ids = array();
1298 * Store the fage status (true when opened, false when closed).
1299 * @protected
1300 * @since 4.5.000 (2009-01-02)
1302 protected $pageopen = array();
1305 * Default monospace font.
1306 * @protected
1307 * @since 4.5.025 (2009-03-10)
1309 protected $default_monospaced_font = 'courier';
1312 * Cloned copy of the current class object.
1313 * @protected
1314 * @since 4.5.029 (2009-03-19)
1316 protected $objcopy;
1319 * Array used to store the lengths of cache files.
1320 * @protected
1321 * @since 4.5.029 (2009-03-19)
1323 protected $cache_file_length = array();
1326 * Table header content to be repeated on each new page.
1327 * @protected
1328 * @since 4.5.030 (2009-03-20)
1330 protected $thead = '';
1333 * Margins used for table header.
1334 * @protected
1335 * @since 4.5.030 (2009-03-20)
1337 protected $theadMargins = array();
1340 * Cache array for UTF8StringToArray() method.
1341 * @protected
1342 * @since 4.5.037 (2009-04-07)
1344 protected $cache_UTF8StringToArray = array();
1347 * Maximum size of cache array used for UTF8StringToArray() method.
1348 * @protected
1349 * @since 4.5.037 (2009-04-07)
1351 protected $cache_maxsize_UTF8StringToArray = 8;
1354 * Current size of cache array used for UTF8StringToArray() method.
1355 * @protected
1356 * @since 4.5.037 (2009-04-07)
1358 protected $cache_size_UTF8StringToArray = 0;
1361 * Boolean flag to enable document digital signature.
1362 * @protected
1363 * @since 4.6.005 (2009-04-24)
1365 protected $sign = false;
1368 * Digital signature data.
1369 * @protected
1370 * @since 4.6.005 (2009-04-24)
1372 protected $signature_data = array();
1375 * Digital signature max length.
1376 * @protected
1377 * @since 4.6.005 (2009-04-24)
1379 protected $signature_max_length = 11742;
1382 * Data for digital signature appearance.
1383 * @protected
1384 * @since 5.3.011 (2010-06-16)
1386 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1389 * Array of empty digital signature appearances.
1390 * @protected
1391 * @since 5.9.101 (2011-07-06)
1393 protected $empty_signature_appearance = array();
1396 * Regular expression used to find blank characters (required for word-wrapping).
1397 * @protected
1398 * @since 4.6.006 (2009-04-28)
1400 protected $re_spaces = '/[^\S\xa0]/';
1403 * Array of $re_spaces parts.
1404 * @protected
1405 * @since 5.5.011 (2010-07-09)
1407 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1410 * Digital signature object ID.
1411 * @protected
1412 * @since 4.6.022 (2009-06-23)
1414 protected $sig_obj_id = 0;
1417 * ByteRange placemark used during digital signature process.
1418 * @protected
1419 * @since 4.6.028 (2009-08-25)
1421 protected $byterange_string = '/ByteRange[0 ********** ********** **********]';
1424 * Placemark used during digital signature process.
1425 * @protected
1426 * @since 4.6.028 (2009-08-25)
1428 protected $sig_annot_ref = '***SIGANNREF*** 0 R';
1431 * ID of page objects.
1432 * @protected
1433 * @since 4.7.000 (2009-08-29)
1435 protected $page_obj_id = array();
1438 * List of form annotations IDs.
1439 * @protected
1440 * @since 4.8.000 (2009-09-07)
1442 protected $form_obj_id = array();
1445 * 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.
1446 * @protected
1447 * @since 4.8.000 (2009-09-07)
1449 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1452 * Javascript objects array.
1453 * @protected
1454 * @since 4.8.000 (2009-09-07)
1456 protected $js_objects = array();
1459 * Current form action (used during XHTML rendering).
1460 * @protected
1461 * @since 4.8.000 (2009-09-07)
1463 protected $form_action = '';
1466 * Current form encryption type (used during XHTML rendering).
1467 * @protected
1468 * @since 4.8.000 (2009-09-07)
1470 protected $form_enctype = 'application/x-www-form-urlencoded';
1473 * Current method to submit forms.
1474 * @protected
1475 * @since 4.8.000 (2009-09-07)
1477 protected $form_mode = 'post';
1480 * List of fonts used on form fields (fontname => fontkey).
1481 * @protected
1482 * @since 4.8.001 (2009-09-09)
1484 protected $annotation_fonts = array();
1487 * List of radio buttons parent objects.
1488 * @protected
1489 * @since 4.8.001 (2009-09-09)
1491 protected $radiobutton_groups = array();
1494 * List of radio group objects IDs.
1495 * @protected
1496 * @since 4.8.001 (2009-09-09)
1498 protected $radio_groups = array();
1501 * Text indentation value (used for text-indent CSS attribute).
1502 * @protected
1503 * @since 4.8.006 (2009-09-23)
1505 protected $textindent = 0;
1508 * Store page number when startTransaction() is called.
1509 * @protected
1510 * @since 4.8.006 (2009-09-23)
1512 protected $start_transaction_page = 0;
1515 * Store Y position when startTransaction() is called.
1516 * @protected
1517 * @since 4.9.001 (2010-03-28)
1519 protected $start_transaction_y = 0;
1522 * True when we are printing the thead section on a new page.
1523 * @protected
1524 * @since 4.8.027 (2010-01-25)
1526 protected $inthead = false;
1529 * Array of column measures (width, space, starting Y position).
1530 * @protected
1531 * @since 4.9.001 (2010-03-28)
1533 protected $columns = array();
1536 * Number of colums.
1537 * @protected
1538 * @since 4.9.001 (2010-03-28)
1540 protected $num_columns = 1;
1543 * Current column number.
1544 * @protected
1545 * @since 4.9.001 (2010-03-28)
1547 protected $current_column = 0;
1550 * Starting page for columns.
1551 * @protected
1552 * @since 4.9.001 (2010-03-28)
1554 protected $column_start_page = 0;
1557 * Maximum page and column selected.
1558 * @protected
1559 * @since 5.8.000 (2010-08-11)
1561 protected $maxselcol = array('page' => 0, 'column' => 0);
1564 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1565 * @protected
1566 * @since 5.8.000 (2010-08-11)
1568 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1571 * 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.
1572 * @protected
1573 * @since 4.9.008 (2010-04-03)
1575 protected $textrendermode = 0;
1578 * Text stroke width in doc units.
1579 * @protected
1580 * @since 4.9.008 (2010-04-03)
1582 protected $textstrokewidth = 0;
1585 * Current stroke color.
1586 * @protected
1587 * @since 4.9.008 (2010-04-03)
1589 protected $strokecolor;
1592 * Default unit of measure for document.
1593 * @protected
1594 * @since 5.0.000 (2010-04-22)
1596 protected $pdfunit = 'mm';
1599 * Boolean flag true when we are on TOC (Table Of Content) page.
1600 * @protected
1602 protected $tocpage = false;
1605 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1606 * @protected
1607 * @since 5.0.000 (2010-04-26)
1609 protected $rasterize_vector_images = false;
1612 * Boolean flag: if true enables font subsetting by default.
1613 * @protected
1614 * @since 5.3.002 (2010-06-07)
1616 protected $font_subsetting = true;
1619 * Array of default graphic settings.
1620 * @protected
1621 * @since 5.5.008 (2010-07-02)
1623 protected $default_graphic_vars = array();
1626 * Array of XObjects.
1627 * @protected
1628 * @since 5.8.014 (2010-08-23)
1630 protected $xobjects = array();
1633 * Boolean value true when we are inside an XObject.
1634 * @protected
1635 * @since 5.8.017 (2010-08-24)
1637 protected $inxobj = false;
1640 * Current XObject ID.
1641 * @protected
1642 * @since 5.8.017 (2010-08-24)
1644 protected $xobjid = '';
1647 * Percentage of character stretching.
1648 * @protected
1649 * @since 5.9.000 (2010-09-29)
1651 protected $font_stretching = 100;
1654 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1655 * @protected
1656 * @since 5.9.000 (2010-09-29)
1658 protected $font_spacing = 0;
1661 * Array of no-write regions.
1662 * ('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)
1663 * @protected
1664 * @since 5.9.003 (2010-10-14)
1666 protected $page_regions = array();
1669 * Boolean value true when page region check is active.
1670 * @protected
1672 protected $check_page_regions = true;
1675 * Array containing HTML color names and values.
1676 * @protected
1677 * @since 5.9.004 (2010-10-18)
1679 protected $webcolor = array();
1682 * Array containing spot color names and values.
1683 * @protected
1684 * @since 5.9.012 (2010-11-11)
1686 protected $spotcolor = array();
1689 * Array of PDF layers data.
1690 * @protected
1691 * @since 5.9.102 (2011-07-13)
1693 protected $pdflayers = array();
1696 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1697 * @protected
1698 * @since 5.9.097 (2011-06-23)
1700 protected $dests = array();
1703 * Object ID for Named Destinations
1704 * @protected
1705 * @since 5.9.097 (2011-06-23)
1707 protected $n_dests;
1710 * Embedded Files Names
1711 * @protected
1712 * @since 5.9.204 (2013-01-23)
1714 protected $efnames = array();
1717 * Directory used for the last SVG image.
1718 * @protected
1719 * @since 5.0.000 (2010-05-05)
1721 protected $svgdir = '';
1724 * Deafult unit of measure for SVG.
1725 * @protected
1726 * @since 5.0.000 (2010-05-02)
1728 protected $svgunit = 'px';
1731 * Array of SVG gradients.
1732 * @protected
1733 * @since 5.0.000 (2010-05-02)
1735 protected $svggradients = array();
1738 * ID of last SVG gradient.
1739 * @protected
1740 * @since 5.0.000 (2010-05-02)
1742 protected $svggradientid = 0;
1745 * Boolean value true when in SVG defs group.
1746 * @protected
1747 * @since 5.0.000 (2010-05-02)
1749 protected $svgdefsmode = false;
1752 * Array of SVG defs.
1753 * @protected
1754 * @since 5.0.000 (2010-05-02)
1756 protected $svgdefs = array();
1759 * Boolean value true when in SVG clipPath tag.
1760 * @protected
1761 * @since 5.0.000 (2010-04-26)
1763 protected $svgclipmode = false;
1766 * Array of SVG clipPath commands.
1767 * @protected
1768 * @since 5.0.000 (2010-05-02)
1770 protected $svgclippaths = array();
1773 * Array of SVG clipPath tranformation matrix.
1774 * @protected
1775 * @since 5.8.022 (2010-08-31)
1777 protected $svgcliptm = array();
1780 * ID of last SVG clipPath.
1781 * @protected
1782 * @since 5.0.000 (2010-05-02)
1784 protected $svgclipid = 0;
1787 * SVG text.
1788 * @protected
1789 * @since 5.0.000 (2010-05-02)
1791 protected $svgtext = '';
1794 * SVG text properties.
1795 * @protected
1796 * @since 5.8.013 (2010-08-23)
1798 protected $svgtextmode = array();
1801 * Array of hinheritable SVG properties.
1802 * @protected
1803 * @since 5.0.000 (2010-05-02)
1805 protected $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode');
1808 * Array of SVG properties.
1809 * @protected
1810 * @since 5.0.000 (2010-05-02)
1812 protected $svgstyles = array(array(
1813 'alignment-baseline' => 'auto',
1814 'baseline-shift' => 'baseline',
1815 'clip' => 'auto',
1816 'clip-path' => 'none',
1817 'clip-rule' => 'nonzero',
1818 'color' => 'black',
1819 'color-interpolation' => 'sRGB',
1820 'color-interpolation-filters' => 'linearRGB',
1821 'color-profile' => 'auto',
1822 'color-rendering' => 'auto',
1823 'cursor' => 'auto',
1824 'direction' => 'ltr',
1825 'display' => 'inline',
1826 'dominant-baseline' => 'auto',
1827 'enable-background' => 'accumulate',
1828 'fill' => 'black',
1829 'fill-opacity' => 1,
1830 'fill-rule' => 'nonzero',
1831 'filter' => 'none',
1832 'flood-color' => 'black',
1833 'flood-opacity' => 1,
1834 'font' => '',
1835 'font-family' => 'helvetica',
1836 'font-size' => 'medium',
1837 'font-size-adjust' => 'none',
1838 'font-stretch' => 'normal',
1839 'font-style' => 'normal',
1840 'font-variant' => 'normal',
1841 'font-weight' => 'normal',
1842 'glyph-orientation-horizontal' => '0deg',
1843 'glyph-orientation-vertical' => 'auto',
1844 'image-rendering' => 'auto',
1845 'kerning' => 'auto',
1846 'letter-spacing' => 'normal',
1847 'lighting-color' => 'white',
1848 'marker' => '',
1849 'marker-end' => 'none',
1850 'marker-mid' => 'none',
1851 'marker-start' => 'none',
1852 'mask' => 'none',
1853 'opacity' => 1,
1854 'overflow' => 'auto',
1855 'pointer-events' => 'visiblePainted',
1856 'shape-rendering' => 'auto',
1857 'stop-color' => 'black',
1858 'stop-opacity' => 1,
1859 'stroke' => 'none',
1860 'stroke-dasharray' => 'none',
1861 'stroke-dashoffset' => 0,
1862 'stroke-linecap' => 'butt',
1863 'stroke-linejoin' => 'miter',
1864 'stroke-miterlimit' => 4,
1865 'stroke-opacity' => 1,
1866 'stroke-width' => 1,
1867 'text-anchor' => 'start',
1868 'text-decoration' => 'none',
1869 'text-rendering' => 'auto',
1870 'unicode-bidi' => 'normal',
1871 'visibility' => 'visible',
1872 'word-spacing' => 'normal',
1873 'writing-mode' => 'lr-tb',
1874 'text-color' => 'black',
1875 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1879 * If true force sRGB color profile for all document.
1880 * @protected
1881 * @since 5.9.121 (2011-09-28)
1883 protected $force_srgb = false;
1886 * If true set the document to PDF/A mode.
1887 * @protected
1888 * @since 5.9.121 (2011-09-27)
1890 protected $pdfa_mode = false;
1893 * Document creation date-time
1894 * @protected
1895 * @since 5.9.152 (2012-03-22)
1897 protected $doc_creation_timestamp;
1900 * Document modification date-time
1901 * @protected
1902 * @since 5.9.152 (2012-03-22)
1904 protected $doc_modification_timestamp;
1907 * Custom XMP data.
1908 * @protected
1909 * @since 5.9.128 (2011-10-06)
1911 protected $custom_xmp = '';
1914 * Overprint mode array.
1915 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1916 * @protected
1917 * @since 5.9.152 (2012-03-23)
1919 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1922 * Alpha mode array.
1923 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1924 * @protected
1925 * @since 5.9.152 (2012-03-23)
1927 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1930 * Define the page boundaries boxes to be set on document.
1931 * @protected
1932 * @since 5.9.152 (2012-03-23)
1934 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1937 * Set the document producer metadata.
1938 * @protected
1939 * @since 5.9.152 (2012-03-23)
1941 protected $pdfproducer;
1944 * If true print TCPDF meta link.
1945 * @protected
1946 * @since 5.9.152 (2012-03-23)
1948 protected $tcpdflink = true;
1951 * Cache array for computed GD gamma values.
1952 * @protected
1953 * @since 5.9.1632 (2012-06-05)
1955 protected $gdgammacache = array();
1957 //------------------------------------------------------------
1958 // METHODS
1959 //------------------------------------------------------------
1962 * This is the class constructor.
1963 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1964 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1965 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1966 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1967 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1968 * @param $encoding (string) Charset encoding; default is UTF-8.
1969 * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1970 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1971 * @public
1972 * @see getPageSizeFromFormat(), setPageFormat()
1974 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1975 /* Set internal character encoding to ASCII */
1976 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1977 $this->internal_encoding = mb_internal_encoding();
1978 mb_internal_encoding('ASCII');
1980 // get array of HTML colors
1981 require(dirname(__FILE__).'/htmlcolors.php');
1982 $this->webcolor = $webcolor;
1983 // get array of custom spot colors
1984 if (file_exists(dirname(__FILE__).'/spotcolors.php')) {
1985 require(dirname(__FILE__).'/spotcolors.php');
1986 $this->spotcolor = $spotcolor;
1987 } else {
1988 $this->spotcolor = array();
1990 require_once(dirname(__FILE__).'/unicode_data.php');
1991 $this->unicode = new TCPDF_UNICODE_DATA();
1992 require_once(dirname(__FILE__).'/encodings_maps.php');
1993 $this->encmaps = new TCPDF_ENCODING_MAPS();
1994 $this->font_obj_ids = array();
1995 $this->page_obj_id = array();
1996 $this->form_obj_id = array();
1997 // set pdf/a mode
1998 $this->pdfa_mode = $pdfa;
1999 $this->force_srgb = false;
2000 // set disk caching
2001 $this->diskcache = $diskcache ? true : false;
2002 // set language direction
2003 $this->rtl = false;
2004 $this->tmprtl = false;
2005 // some checks
2006 $this->_dochecks();
2007 // initialization of properties
2008 $this->isunicode = $unicode;
2009 $this->page = 0;
2010 $this->transfmrk[0] = array();
2011 $this->pagedim = array();
2012 $this->n = 2;
2013 $this->buffer = '';
2014 $this->pages = array();
2015 $this->state = 0;
2016 $this->fonts = array();
2017 $this->FontFiles = array();
2018 $this->diffs = array();
2019 $this->images = array();
2020 $this->links = array();
2021 $this->gradients = array();
2022 $this->InFooter = false;
2023 $this->lasth = 0;
2024 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2025 $this->FontStyle = '';
2026 $this->FontSizePt = 12;
2027 $this->underline = false;
2028 $this->overline = false;
2029 $this->linethrough = false;
2030 $this->DrawColor = '0 G';
2031 $this->FillColor = '0 g';
2032 $this->TextColor = '0 g';
2033 $this->ColorFlag = false;
2034 $this->pdflayers = array();
2035 // encryption values
2036 $this->encrypted = false;
2037 $this->last_enc_key = '';
2038 // standard Unicode fonts
2039 $this->CoreFonts = array(
2040 'courier'=>'Courier',
2041 'courierB'=>'Courier-Bold',
2042 'courierI'=>'Courier-Oblique',
2043 'courierBI'=>'Courier-BoldOblique',
2044 'helvetica'=>'Helvetica',
2045 'helveticaB'=>'Helvetica-Bold',
2046 'helveticaI'=>'Helvetica-Oblique',
2047 'helveticaBI'=>'Helvetica-BoldOblique',
2048 'times'=>'Times-Roman',
2049 'timesB'=>'Times-Bold',
2050 'timesI'=>'Times-Italic',
2051 'timesBI'=>'Times-BoldItalic',
2052 'symbol'=>'Symbol',
2053 'zapfdingbats'=>'ZapfDingbats'
2055 // set scale factor
2056 $this->setPageUnit($unit);
2057 // set page format and orientation
2058 $this->setPageFormat($format, $orientation);
2059 // page margins (1 cm)
2060 $margin = 28.35 / $this->k;
2061 $this->SetMargins($margin, $margin);
2062 $this->clMargin = $this->lMargin;
2063 $this->crMargin = $this->rMargin;
2064 // internal cell padding
2065 $cpadding = $margin / 10;
2066 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
2067 // cell margins
2068 $this->setCellMargins(0, 0, 0, 0);
2069 // line width (0.2 mm)
2070 $this->LineWidth = 0.57 / $this->k;
2071 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
2072 $this->linestyleCap = '0 J';
2073 $this->linestyleJoin = '0 j';
2074 $this->linestyleDash = '[] 0 d';
2075 // automatic page break
2076 $this->SetAutoPageBreak(true, (2 * $margin));
2077 // full width display mode
2078 $this->SetDisplayMode('fullwidth');
2079 // compression
2080 $this->SetCompression();
2081 // set default PDF version number
2082 $this->setPDFVersion();
2083 $this->pdfproducer = "\x54\x43\x50\x44\x46\x20".$this->tcpdf_version."\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
2084 $this->tcpdflink = true;
2085 $this->encoding = $encoding;
2086 $this->HREF = array();
2087 $this->getFontsList();
2088 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
2089 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
2090 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
2091 $this->extgstates = array();
2092 $this->setTextShadow();
2093 // user's rights
2094 $this->sign = false;
2095 $this->ur['enabled'] = false;
2096 $this->ur['document'] = '/FullSave';
2097 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
2098 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
2099 $this->ur['signature'] = '/Modify';
2100 $this->ur['ef'] = '/Create/Delete/Modify/Import';
2101 $this->ur['formex'] = '';
2102 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
2103 $this->empty_signature_appearance = array();
2104 // set default JPEG quality
2105 $this->jpeg_quality = 75;
2106 // initialize some settings
2107 $this->utf8Bidi(array(''), '');
2108 // set default font
2109 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2110 // check if PCRE Unicode support is enabled
2111 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2112 // PCRE unicode support is turned ON
2113 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
2114 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
2115 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
2116 //$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
2117 $this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
2118 } else {
2119 // PCRE unicode support is turned OFF
2120 $this->setSpacesRE('/[^\S\xa0]/');
2122 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2123 // set file ID for trailer
2124 $serformat = (is_array($format) ? serialize($format) : $format);
2125 $this->file_id = md5($this->getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
2126 // set document creation and modification timestamp
2127 $this->doc_creation_timestamp = time();
2128 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
2129 // get default graphic vars
2130 $this->default_graphic_vars = $this->getGraphicVars();
2131 $this->header_xobj_autoreset = false;
2132 $this->custom_xmp = '';
2136 * Default destructor.
2137 * @public
2138 * @since 1.53.0.TC016
2140 public function __destruct() {
2141 // restore internal encoding
2142 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
2143 mb_internal_encoding($this->internal_encoding);
2145 // unset all class variables
2146 $this->_destroy(true);
2150 * Return the current TCPDF version.
2151 * @return TCPDF version string
2152 * @public
2153 * @since 5.9.012 (2010-11-10)
2155 public function getTCPDFVersion() {
2156 return $this->tcpdf_version;
2160 * Set the units of measure for the document.
2161 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2162 * @public
2163 * @since 3.0.015 (2008-06-06)
2165 public function setPageUnit($unit) {
2166 $unit = strtolower($unit);
2167 //Set scale factor
2168 switch ($unit) {
2169 // points
2170 case 'px':
2171 case 'pt': {
2172 $this->k = 1;
2173 break;
2175 // millimeters
2176 case 'mm': {
2177 $this->k = $this->dpi / 25.4;
2178 break;
2180 // centimeters
2181 case 'cm': {
2182 $this->k = $this->dpi / 2.54;
2183 break;
2185 // inches
2186 case 'in': {
2187 $this->k = $this->dpi;
2188 break;
2190 // unsupported unit
2191 default : {
2192 $this->Error('Incorrect unit: '.$unit);
2193 break;
2196 $this->pdfunit = $unit;
2197 if (isset($this->CurOrientation)) {
2198 $this->setPageOrientation($this->CurOrientation);
2203 * Get page dimensions from format name.
2204 * @param $format (mixed) The format name. It can be: <ul>
2205 * <li><b>ISO 216 A Series + 2 SIS 014711 extensions</b></li>
2206 * <li>A0 (841x1189 mm ; 33.11x46.81 in)</li>
2207 * <li>A1 (594x841 mm ; 23.39x33.11 in)</li>
2208 * <li>A2 (420x594 mm ; 16.54x23.39 in)</li>
2209 * <li>A3 (297x420 mm ; 11.69x16.54 in)</li>
2210 * <li>A4 (210x297 mm ; 8.27x11.69 in)</li>
2211 * <li>A5 (148x210 mm ; 5.83x8.27 in)</li>
2212 * <li>A6 (105x148 mm ; 4.13x5.83 in)</li>
2213 * <li>A7 (74x105 mm ; 2.91x4.13 in)</li>
2214 * <li>A8 (52x74 mm ; 2.05x2.91 in)</li>
2215 * <li>A9 (37x52 mm ; 1.46x2.05 in)</li>
2216 * <li>A10 (26x37 mm ; 1.02x1.46 in)</li>
2217 * <li>A11 (18x26 mm ; 0.71x1.02 in)</li>
2218 * <li>A12 (13x18 mm ; 0.51x0.71 in)</li>
2219 * <li><b>ISO 216 B Series + 2 SIS 014711 extensions</b></li>
2220 * <li>B0 (1000x1414 mm ; 39.37x55.67 in)</li>
2221 * <li>B1 (707x1000 mm ; 27.83x39.37 in)</li>
2222 * <li>B2 (500x707 mm ; 19.69x27.83 in)</li>
2223 * <li>B3 (353x500 mm ; 13.90x19.69 in)</li>
2224 * <li>B4 (250x353 mm ; 9.84x13.90 in)</li>
2225 * <li>B5 (176x250 mm ; 6.93x9.84 in)</li>
2226 * <li>B6 (125x176 mm ; 4.92x6.93 in)</li>
2227 * <li>B7 (88x125 mm ; 3.46x4.92 in)</li>
2228 * <li>B8 (62x88 mm ; 2.44x3.46 in)</li>
2229 * <li>B9 (44x62 mm ; 1.73x2.44 in)</li>
2230 * <li>B10 (31x44 mm ; 1.22x1.73 in)</li>
2231 * <li>B11 (22x31 mm ; 0.87x1.22 in)</li>
2232 * <li>B12 (15x22 mm ; 0.59x0.87 in)</li>
2233 * <li><b>ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION</b></li>
2234 * <li>C0 (917x1297 mm ; 36.10x51.06 in)</li>
2235 * <li>C1 (648x917 mm ; 25.51x36.10 in)</li>
2236 * <li>C2 (458x648 mm ; 18.03x25.51 in)</li>
2237 * <li>C3 (324x458 mm ; 12.76x18.03 in)</li>
2238 * <li>C4 (229x324 mm ; 9.02x12.76 in)</li>
2239 * <li>C5 (162x229 mm ; 6.38x9.02 in)</li>
2240 * <li>C6 (114x162 mm ; 4.49x6.38 in)</li>
2241 * <li>C7 (81x114 mm ; 3.19x4.49 in)</li>
2242 * <li>C8 (57x81 mm ; 2.24x3.19 in)</li>
2243 * <li>C9 (40x57 mm ; 1.57x2.24 in)</li>
2244 * <li>C10 (28x40 mm ; 1.10x1.57 in)</li>
2245 * <li>C11 (20x28 mm ; 0.79x1.10 in)</li>
2246 * <li>C12 (14x20 mm ; 0.55x0.79 in)</li>
2247 * <li>C76 (81x162 mm ; 3.19x6.38 in)</li>
2248 * <li>DL (110x220 mm ; 4.33x8.66 in)</li>
2249 * <li><b>SIS 014711 E Series</b></li>
2250 * <li>E0 (879x1241 mm ; 34.61x48.86 in)</li>
2251 * <li>E1 (620x879 mm ; 24.41x34.61 in)</li>
2252 * <li>E2 (440x620 mm ; 17.32x24.41 in)</li>
2253 * <li>E3 (310x440 mm ; 12.20x17.32 in)</li>
2254 * <li>E4 (220x310 mm ; 8.66x12.20 in)</li>
2255 * <li>E5 (155x220 mm ; 6.10x8.66 in)</li>
2256 * <li>E6 (110x155 mm ; 4.33x6.10 in)</li>
2257 * <li>E7 (78x110 mm ; 3.07x4.33 in)</li>
2258 * <li>E8 (55x78 mm ; 2.17x3.07 in)</li>
2259 * <li>E9 (39x55 mm ; 1.54x2.17 in)</li>
2260 * <li>E10 (27x39 mm ; 1.06x1.54 in)</li>
2261 * <li>E11 (19x27 mm ; 0.75x1.06 in)</li>
2262 * <li>E12 (13x19 mm ; 0.51x0.75 in)</li>
2263 * <li><b>SIS 014711 G Series</b></li>
2264 * <li>G0 (958x1354 mm ; 37.72x53.31 in)</li>
2265 * <li>G1 (677x958 mm ; 26.65x37.72 in)</li>
2266 * <li>G2 (479x677 mm ; 18.86x26.65 in)</li>
2267 * <li>G3 (338x479 mm ; 13.31x18.86 in)</li>
2268 * <li>G4 (239x338 mm ; 9.41x13.31 in)</li>
2269 * <li>G5 (169x239 mm ; 6.65x9.41 in)</li>
2270 * <li>G6 (119x169 mm ; 4.69x6.65 in)</li>
2271 * <li>G7 (84x119 mm ; 3.31x4.69 in)</li>
2272 * <li>G8 (59x84 mm ; 2.32x3.31 in)</li>
2273 * <li>G9 (42x59 mm ; 1.65x2.32 in)</li>
2274 * <li>G10 (29x42 mm ; 1.14x1.65 in)</li>
2275 * <li>G11 (21x29 mm ; 0.83x1.14 in)</li>
2276 * <li>G12 (14x21 mm ; 0.55x0.83 in)</li>
2277 * <li><b>ISO Press</b></li>
2278 * <li>RA0 (860x1220 mm ; 33.86x48.03 in)</li>
2279 * <li>RA1 (610x860 mm ; 24.02x33.86 in)</li>
2280 * <li>RA2 (430x610 mm ; 16.93x24.02 in)</li>
2281 * <li>RA3 (305x430 mm ; 12.01x16.93 in)</li>
2282 * <li>RA4 (215x305 mm ; 8.46x12.01 in)</li>
2283 * <li>SRA0 (900x1280 mm ; 35.43x50.39 in)</li>
2284 * <li>SRA1 (640x900 mm ; 25.20x35.43 in)</li>
2285 * <li>SRA2 (450x640 mm ; 17.72x25.20 in)</li>
2286 * <li>SRA3 (320x450 mm ; 12.60x17.72 in)</li>
2287 * <li>SRA4 (225x320 mm ; 8.86x12.60 in)</li>
2288 * <li><b>German DIN 476</b></li>
2289 * <li>4A0 (1682x2378 mm ; 66.22x93.62 in)</li>
2290 * <li>2A0 (1189x1682 mm ; 46.81x66.22 in)</li>
2291 * <li><b>Variations on the ISO Standard</b></li>
2292 * <li>A2_EXTRA (445x619 mm ; 17.52x24.37 in)</li>
2293 * <li>A3+ (329x483 mm ; 12.95x19.02 in)</li>
2294 * <li>A3_EXTRA (322x445 mm ; 12.68x17.52 in)</li>
2295 * <li>A3_SUPER (305x508 mm ; 12.01x20.00 in)</li>
2296 * <li>SUPER_A3 (305x487 mm ; 12.01x19.17 in)</li>
2297 * <li>A4_EXTRA (235x322 mm ; 9.25x12.68 in)</li>
2298 * <li>A4_SUPER (229x322 mm ; 9.02x12.68 in)</li>
2299 * <li>SUPER_A4 (227x356 mm ; 8.94x14.02 in)</li>
2300 * <li>A4_LONG (210x348 mm ; 8.27x13.70 in)</li>
2301 * <li>F4 (210x330 mm ; 8.27x12.99 in)</li>
2302 * <li>SO_B5_EXTRA (202x276 mm ; 7.95x10.87 in)</li>
2303 * <li>A5_EXTRA (173x235 mm ; 6.81x9.25 in)</li>
2304 * <li><b>ANSI Series</b></li>
2305 * <li>ANSI_E (864x1118 mm ; 34.00x44.00 in)</li>
2306 * <li>ANSI_D (559x864 mm ; 22.00x34.00 in)</li>
2307 * <li>ANSI_C (432x559 mm ; 17.00x22.00 in)</li>
2308 * <li>ANSI_B (279x432 mm ; 11.00x17.00 in)</li>
2309 * <li>ANSI_A (216x279 mm ; 8.50x11.00 in)</li>
2310 * <li><b>Traditional 'Loose' North American Paper Sizes</b></li>
2311 * <li>LEDGER, USLEDGER (432x279 mm ; 17.00x11.00 in)</li>
2312 * <li>TABLOID, USTABLOID, BIBLE, ORGANIZERK (279x432 mm ; 11.00x17.00 in)</li>
2313 * <li>LETTER, USLETTER, ORGANIZERM (216x279 mm ; 8.50x11.00 in)</li>
2314 * <li>LEGAL, USLEGAL (216x356 mm ; 8.50x14.00 in)</li>
2315 * <li>GLETTER, GOVERNMENTLETTER (203x267 mm ; 8.00x10.50 in)</li>
2316 * <li>JLEGAL, JUNIORLEGAL (203x127 mm ; 8.00x5.00 in)</li>
2317 * <li><b>Other North American Paper Sizes</b></li>
2318 * <li>QUADDEMY (889x1143 mm ; 35.00x45.00 in)</li>
2319 * <li>SUPER_B (330x483 mm ; 13.00x19.00 in)</li>
2320 * <li>QUARTO (229x279 mm ; 9.00x11.00 in)</li>
2321 * <li>FOLIO, GOVERNMENTLEGAL (216x330 mm ; 8.50x13.00 in)</li>
2322 * <li>EXECUTIVE, MONARCH (184x267 mm ; 7.25x10.50 in)</li>
2323 * <li>MEMO, STATEMENT, ORGANIZERL (140x216 mm ; 5.50x8.50 in)</li>
2324 * <li>FOOLSCAP (210x330 mm ; 8.27x13.00 in)</li>
2325 * <li>COMPACT (108x171 mm ; 4.25x6.75 in)</li>
2326 * <li>ORGANIZERJ (70x127 mm ; 2.75x5.00 in)</li>
2327 * <li><b>Canadian standard CAN 2-9.60M</b></li>
2328 * <li>P1 (560x860 mm ; 22.05x33.86 in)</li>
2329 * <li>P2 (430x560 mm ; 16.93x22.05 in)</li>
2330 * <li>P3 (280x430 mm ; 11.02x16.93 in)</li>
2331 * <li>P4 (215x280 mm ; 8.46x11.02 in)</li>
2332 * <li>P5 (140x215 mm ; 5.51x8.46 in)</li>
2333 * <li>P6 (107x140 mm ; 4.21x5.51 in)</li>
2334 * <li><b>North American Architectural Sizes</b></li>
2335 * <li>ARCH_E (914x1219 mm ; 36.00x48.00 in)</li>
2336 * <li>ARCH_E1 (762x1067 mm ; 30.00x42.00 in)</li>
2337 * <li>ARCH_D (610x914 mm ; 24.00x36.00 in)</li>
2338 * <li>ARCH_C, BROADSHEET (457x610 mm ; 18.00x24.00 in)</li>
2339 * <li>ARCH_B (305x457 mm ; 12.00x18.00 in)</li>
2340 * <li>ARCH_A (229x305 mm ; 9.00x12.00 in)</li>
2341 * <li><b>Announcement Envelopes</b></li>
2342 * <li>ANNENV_A2 (111x146 mm ; 4.37x5.75 in)</li>
2343 * <li>ANNENV_A6 (121x165 mm ; 4.75x6.50 in)</li>
2344 * <li>ANNENV_A7 (133x184 mm ; 5.25x7.25 in)</li>
2345 * <li>ANNENV_A8 (140x206 mm ; 5.50x8.12 in)</li>
2346 * <li>ANNENV_A10 (159x244 mm ; 6.25x9.62 in)</li>
2347 * <li>ANNENV_SLIM (98x225 mm ; 3.87x8.87 in)</li>
2348 * <li><b>Commercial Envelopes</b></li>
2349 * <li>COMMENV_N6_1/4 (89x152 mm ; 3.50x6.00 in)</li>
2350 * <li>COMMENV_N6_3/4 (92x165 mm ; 3.62x6.50 in)</li>
2351 * <li>COMMENV_N8 (98x191 mm ; 3.87x7.50 in)</li>
2352 * <li>COMMENV_N9 (98x225 mm ; 3.87x8.87 in)</li>
2353 * <li>COMMENV_N10 (105x241 mm ; 4.12x9.50 in)</li>
2354 * <li>COMMENV_N11 (114x263 mm ; 4.50x10.37 in)</li>
2355 * <li>COMMENV_N12 (121x279 mm ; 4.75x11.00 in)</li>
2356 * <li>COMMENV_N14 (127x292 mm ; 5.00x11.50 in)</li>
2357 * <li><b>Catalogue Envelopes</b></li>
2358 * <li>CATENV_N1 (152x229 mm ; 6.00x9.00 in)</li>
2359 * <li>CATENV_N1_3/4 (165x241 mm ; 6.50x9.50 in)</li>
2360 * <li>CATENV_N2 (165x254 mm ; 6.50x10.00 in)</li>
2361 * <li>CATENV_N3 (178x254 mm ; 7.00x10.00 in)</li>
2362 * <li>CATENV_N6 (191x267 mm ; 7.50x10.50 in)</li>
2363 * <li>CATENV_N7 (203x279 mm ; 8.00x11.00 in)</li>
2364 * <li>CATENV_N8 (210x286 mm ; 8.25x11.25 in)</li>
2365 * <li>CATENV_N9_1/2 (216x267 mm ; 8.50x10.50 in)</li>
2366 * <li>CATENV_N9_3/4 (222x286 mm ; 8.75x11.25 in)</li>
2367 * <li>CATENV_N10_1/2 (229x305 mm ; 9.00x12.00 in)</li>
2368 * <li>CATENV_N12_1/2 (241x318 mm ; 9.50x12.50 in)</li>
2369 * <li>CATENV_N13_1/2 (254x330 mm ; 10.00x13.00 in)</li>
2370 * <li>CATENV_N14_1/4 (286x311 mm ; 11.25x12.25 in)</li>
2371 * <li>CATENV_N14_1/2 (292x368 mm ; 11.50x14.50 in)</li>
2372 * <li><b>Japanese (JIS P 0138-61) Standard B-Series</b></li>
2373 * <li>JIS_B0 (1030x1456 mm ; 40.55x57.32 in)</li>
2374 * <li>JIS_B1 (728x1030 mm ; 28.66x40.55 in)</li>
2375 * <li>JIS_B2 (515x728 mm ; 20.28x28.66 in)</li>
2376 * <li>JIS_B3 (364x515 mm ; 14.33x20.28 in)</li>
2377 * <li>JIS_B4 (257x364 mm ; 10.12x14.33 in)</li>
2378 * <li>JIS_B5 (182x257 mm ; 7.17x10.12 in)</li>
2379 * <li>JIS_B6 (128x182 mm ; 5.04x7.17 in)</li>
2380 * <li>JIS_B7 (91x128 mm ; 3.58x5.04 in)</li>
2381 * <li>JIS_B8 (64x91 mm ; 2.52x3.58 in)</li>
2382 * <li>JIS_B9 (45x64 mm ; 1.77x2.52 in)</li>
2383 * <li>JIS_B10 (32x45 mm ; 1.26x1.77 in)</li>
2384 * <li>JIS_B11 (22x32 mm ; 0.87x1.26 in)</li>
2385 * <li>JIS_B12 (16x22 mm ; 0.63x0.87 in)</li>
2386 * <li><b>PA Series</b></li>
2387 * <li>PA0 (840x1120 mm ; 33.07x44.09 in)</li>
2388 * <li>PA1 (560x840 mm ; 22.05x33.07 in)</li>
2389 * <li>PA2 (420x560 mm ; 16.54x22.05 in)</li>
2390 * <li>PA3 (280x420 mm ; 11.02x16.54 in)</li>
2391 * <li>PA4 (210x280 mm ; 8.27x11.02 in)</li>
2392 * <li>PA5 (140x210 mm ; 5.51x8.27 in)</li>
2393 * <li>PA6 (105x140 mm ; 4.13x5.51 in)</li>
2394 * <li>PA7 (70x105 mm ; 2.76x4.13 in)</li>
2395 * <li>PA8 (52x70 mm ; 2.05x2.76 in)</li>
2396 * <li>PA9 (35x52 mm ; 1.38x2.05 in)</li>
2397 * <li>PA10 (26x35 mm ; 1.02x1.38 in)</li>
2398 * <li><b>Standard Photographic Print Sizes</b></li>
2399 * <li>PASSPORT_PHOTO (35x45 mm ; 1.38x1.77 in)</li>
2400 * <li>E (82x120 mm ; 3.25x4.72 in)</li>
2401 * <li>3R, L (89x127 mm ; 3.50x5.00 in)</li>
2402 * <li>4R, KG (102x152 mm ; 4.02x5.98 in)</li>
2403 * <li>4D (120x152 mm ; 4.72x5.98 in)</li>
2404 * <li>5R, 2L (127x178 mm ; 5.00x7.01 in)</li>
2405 * <li>6R, 8P (152x203 mm ; 5.98x7.99 in)</li>
2406 * <li>8R, 6P (203x254 mm ; 7.99x10.00 in)</li>
2407 * <li>S8R, 6PW (203x305 mm ; 7.99x12.01 in)</li>
2408 * <li>10R, 4P (254x305 mm ; 10.00x12.01 in)</li>
2409 * <li>S10R, 4PW (254x381 mm ; 10.00x15.00 in)</li>
2410 * <li>11R (279x356 mm ; 10.98x14.02 in)</li>
2411 * <li>S11R (279x432 mm ; 10.98x17.01 in)</li>
2412 * <li>12R (305x381 mm ; 12.01x15.00 in)</li>
2413 * <li>S12R (305x456 mm ; 12.01x17.95 in)</li>
2414 * <li><b>Common Newspaper Sizes</b></li>
2415 * <li>NEWSPAPER_BROADSHEET (750x600 mm ; 29.53x23.62 in)</li>
2416 * <li>NEWSPAPER_BERLINER (470x315 mm ; 18.50x12.40 in)</li>
2417 * <li>NEWSPAPER_COMPACT, NEWSPAPER_TABLOID (430x280 mm ; 16.93x11.02 in)</li>
2418 * <li><b>Business Cards</b></li>
2419 * <li>CREDIT_CARD, BUSINESS_CARD, BUSINESS_CARD_ISO7810 (54x86 mm ; 2.13x3.37 in)</li>
2420 * <li>BUSINESS_CARD_ISO216 (52x74 mm ; 2.05x2.91 in)</li>
2421 * <li>BUSINESS_CARD_IT, BUSINESS_CARD_UK, BUSINESS_CARD_FR, BUSINESS_CARD_DE, BUSINESS_CARD_ES (55x85 mm ; 2.17x3.35 in)</li>
2422 * <li>BUSINESS_CARD_US, BUSINESS_CARD_CA (51x89 mm ; 2.01x3.50 in)</li>
2423 * <li>BUSINESS_CARD_JP (55x91 mm ; 2.17x3.58 in)</li>
2424 * <li>BUSINESS_CARD_HK (54x90 mm ; 2.13x3.54 in)</li>
2425 * <li>BUSINESS_CARD_AU, BUSINESS_CARD_DK, BUSINESS_CARD_SE (55x90 mm ; 2.17x3.54 in)</li>
2426 * <li>BUSINESS_CARD_RU, BUSINESS_CARD_CZ, BUSINESS_CARD_FI, BUSINESS_CARD_HU, BUSINESS_CARD_IL (50x90 mm ; 1.97x3.54 in)</li>
2427 * <li><b>Billboards</b></li>
2428 * <li>4SHEET (1016x1524 mm ; 40.00x60.00 in)</li>
2429 * <li>6SHEET (1200x1800 mm ; 47.24x70.87 in)</li>
2430 * <li>12SHEET (3048x1524 mm ; 120.00x60.00 in)</li>
2431 * <li>16SHEET (2032x3048 mm ; 80.00x120.00 in)</li>
2432 * <li>32SHEET (4064x3048 mm ; 160.00x120.00 in)</li>
2433 * <li>48SHEET (6096x3048 mm ; 240.00x120.00 in)</li>
2434 * <li>64SHEET (8128x3048 mm ; 320.00x120.00 in)</li>
2435 * <li>96SHEET (12192x3048 mm ; 480.00x120.00 in)</li>
2436 * <li><b>Old Imperial English (some are still used in USA)</b></li>
2437 * <li>EN_EMPEROR (1219x1829 mm ; 48.00x72.00 in)</li>
2438 * <li>EN_ANTIQUARIAN (787x1346 mm ; 31.00x53.00 in)</li>
2439 * <li>EN_GRAND_EAGLE (730x1067 mm ; 28.75x42.00 in)</li>
2440 * <li>EN_DOUBLE_ELEPHANT (679x1016 mm ; 26.75x40.00 in)</li>
2441 * <li>EN_ATLAS (660x864 mm ; 26.00x34.00 in)</li>
2442 * <li>EN_COLOMBIER (597x876 mm ; 23.50x34.50 in)</li>
2443 * <li>EN_ELEPHANT (584x711 mm ; 23.00x28.00 in)</li>
2444 * <li>EN_DOUBLE_DEMY (572x902 mm ; 22.50x35.50 in)</li>
2445 * <li>EN_IMPERIAL (559x762 mm ; 22.00x30.00 in)</li>
2446 * <li>EN_PRINCESS (546x711 mm ; 21.50x28.00 in)</li>
2447 * <li>EN_CARTRIDGE (533x660 mm ; 21.00x26.00 in)</li>
2448 * <li>EN_DOUBLE_LARGE_POST (533x838 mm ; 21.00x33.00 in)</li>
2449 * <li>EN_ROYAL (508x635 mm ; 20.00x25.00 in)</li>
2450 * <li>EN_SHEET, EN_HALF_POST (495x597 mm ; 19.50x23.50 in)</li>
2451 * <li>EN_SUPER_ROYAL (483x686 mm ; 19.00x27.00 in)</li>
2452 * <li>EN_DOUBLE_POST (483x775 mm ; 19.00x30.50 in)</li>
2453 * <li>EN_MEDIUM (445x584 mm ; 17.50x23.00 in)</li>
2454 * <li>EN_DEMY (445x572 mm ; 17.50x22.50 in)</li>
2455 * <li>EN_LARGE_POST (419x533 mm ; 16.50x21.00 in)</li>
2456 * <li>EN_COPY_DRAUGHT (406x508 mm ; 16.00x20.00 in)</li>
2457 * <li>EN_POST (394x489 mm ; 15.50x19.25 in)</li>
2458 * <li>EN_CROWN (381x508 mm ; 15.00x20.00 in)</li>
2459 * <li>EN_PINCHED_POST (375x470 mm ; 14.75x18.50 in)</li>
2460 * <li>EN_BRIEF (343x406 mm ; 13.50x16.00 in)</li>
2461 * <li>EN_FOOLSCAP (343x432 mm ; 13.50x17.00 in)</li>
2462 * <li>EN_SMALL_FOOLSCAP (337x419 mm ; 13.25x16.50 in)</li>
2463 * <li>EN_POTT (318x381 mm ; 12.50x15.00 in)</li>
2464 * <li><b>Old Imperial Belgian</b></li>
2465 * <li>BE_GRAND_AIGLE (700x1040 mm ; 27.56x40.94 in)</li>
2466 * <li>BE_COLOMBIER (620x850 mm ; 24.41x33.46 in)</li>
2467 * <li>BE_DOUBLE_CARRE (620x920 mm ; 24.41x36.22 in)</li>
2468 * <li>BE_ELEPHANT (616x770 mm ; 24.25x30.31 in)</li>
2469 * <li>BE_PETIT_AIGLE (600x840 mm ; 23.62x33.07 in)</li>
2470 * <li>BE_GRAND_JESUS (550x730 mm ; 21.65x28.74 in)</li>
2471 * <li>BE_JESUS (540x730 mm ; 21.26x28.74 in)</li>
2472 * <li>BE_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2473 * <li>BE_GRAND_MEDIAN (460x605 mm ; 18.11x23.82 in)</li>
2474 * <li>BE_DOUBLE_POSTE (435x565 mm ; 17.13x22.24 in)</li>
2475 * <li>BE_COQUILLE (430x560 mm ; 16.93x22.05 in)</li>
2476 * <li>BE_PETIT_MEDIAN (415x530 mm ; 16.34x20.87 in)</li>
2477 * <li>BE_RUCHE (360x460 mm ; 14.17x18.11 in)</li>
2478 * <li>BE_PROPATRIA (345x430 mm ; 13.58x16.93 in)</li>
2479 * <li>BE_LYS (317x397 mm ; 12.48x15.63 in)</li>
2480 * <li>BE_POT (307x384 mm ; 12.09x15.12 in)</li>
2481 * <li>BE_ROSETTE (270x347 mm ; 10.63x13.66 in)</li>
2482 * <li><b>Old Imperial French</b></li>
2483 * <li>FR_UNIVERS (1000x1300 mm ; 39.37x51.18 in)</li>
2484 * <li>FR_DOUBLE_COLOMBIER (900x1260 mm ; 35.43x49.61 in)</li>
2485 * <li>FR_GRANDE_MONDE (900x1260 mm ; 35.43x49.61 in)</li>
2486 * <li>FR_DOUBLE_SOLEIL (800x1200 mm ; 31.50x47.24 in)</li>
2487 * <li>FR_DOUBLE_JESUS (760x1120 mm ; 29.92x44.09 in)</li>
2488 * <li>FR_GRAND_AIGLE (750x1060 mm ; 29.53x41.73 in)</li>
2489 * <li>FR_PETIT_AIGLE (700x940 mm ; 27.56x37.01 in)</li>
2490 * <li>FR_DOUBLE_RAISIN (650x1000 mm ; 25.59x39.37 in)</li>
2491 * <li>FR_JOURNAL (650x940 mm ; 25.59x37.01 in)</li>
2492 * <li>FR_COLOMBIER_AFFICHE (630x900 mm ; 24.80x35.43 in)</li>
2493 * <li>FR_DOUBLE_CAVALIER (620x920 mm ; 24.41x36.22 in)</li>
2494 * <li>FR_CLOCHE (600x800 mm ; 23.62x31.50 in)</li>
2495 * <li>FR_SOLEIL (600x800 mm ; 23.62x31.50 in)</li>
2496 * <li>FR_DOUBLE_CARRE (560x900 mm ; 22.05x35.43 in)</li>
2497 * <li>FR_DOUBLE_COQUILLE (560x880 mm ; 22.05x34.65 in)</li>
2498 * <li>FR_JESUS (560x760 mm ; 22.05x29.92 in)</li>
2499 * <li>FR_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2500 * <li>FR_CAVALIER (460x620 mm ; 18.11x24.41 in)</li>
2501 * <li>FR_DOUBLE_COURONNE (460x720 mm ; 18.11x28.35 in)</li>
2502 * <li>FR_CARRE (450x560 mm ; 17.72x22.05 in)</li>
2503 * <li>FR_COQUILLE (440x560 mm ; 17.32x22.05 in)</li>
2504 * <li>FR_DOUBLE_TELLIERE (440x680 mm ; 17.32x26.77 in)</li>
2505 * <li>FR_DOUBLE_CLOCHE (400x600 mm ; 15.75x23.62 in)</li>
2506 * <li>FR_DOUBLE_POT (400x620 mm ; 15.75x24.41 in)</li>
2507 * <li>FR_ECU (400x520 mm ; 15.75x20.47 in)</li>
2508 * <li>FR_COURONNE (360x460 mm ; 14.17x18.11 in)</li>
2509 * <li>FR_TELLIERE (340x440 mm ; 13.39x17.32 in)</li>
2510 * <li>FR_POT (310x400 mm ; 12.20x15.75 in)</li>
2511 * </ul>
2512 * @return array containing page width and height in points
2513 * @public
2514 * @since 5.0.010 (2010-05-17)
2516 public function getPageSizeFromFormat($format) {
2517 // Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
2518 switch (strtoupper($format)) {
2519 // ISO 216 A Series + 2 SIS 014711 extensions
2520 case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
2521 case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
2522 case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
2523 case 'A3' : {$pf = array( 841.890, 1190.551); break;}
2524 case 'A4' : {$pf = array( 595.276, 841.890); break;}
2525 case 'A5' : {$pf = array( 419.528, 595.276); break;}
2526 case 'A6' : {$pf = array( 297.638, 419.528); break;}
2527 case 'A7' : {$pf = array( 209.764, 297.638); break;}
2528 case 'A8' : {$pf = array( 147.402, 209.764); break;}
2529 case 'A9' : {$pf = array( 104.882, 147.402); break;}
2530 case 'A10': {$pf = array( 73.701, 104.882); break;}
2531 case 'A11': {$pf = array( 51.024, 73.701); break;}
2532 case 'A12': {$pf = array( 36.850, 51.024); break;}
2533 // ISO 216 B Series + 2 SIS 014711 extensions
2534 case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
2535 case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
2536 case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
2537 case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
2538 case 'B4' : {$pf = array( 708.661, 1000.630); break;}
2539 case 'B5' : {$pf = array( 498.898, 708.661); break;}
2540 case 'B6' : {$pf = array( 354.331, 498.898); break;}
2541 case 'B7' : {$pf = array( 249.449, 354.331); break;}
2542 case 'B8' : {$pf = array( 175.748, 249.449); break;}
2543 case 'B9' : {$pf = array( 124.724, 175.748); break;}
2544 case 'B10': {$pf = array( 87.874, 124.724); break;}
2545 case 'B11': {$pf = array( 62.362, 87.874); break;}
2546 case 'B12': {$pf = array( 42.520, 62.362); break;}
2547 // ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
2548 case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
2549 case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
2550 case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
2551 case 'C3' : {$pf = array( 918.425, 1298.268); break;}
2552 case 'C4' : {$pf = array( 649.134, 918.425); break;}
2553 case 'C5' : {$pf = array( 459.213, 649.134); break;}
2554 case 'C6' : {$pf = array( 323.150, 459.213); break;}
2555 case 'C7' : {$pf = array( 229.606, 323.150); break;}
2556 case 'C8' : {$pf = array( 161.575, 229.606); break;}
2557 case 'C9' : {$pf = array( 113.386, 161.575); break;}
2558 case 'C10': {$pf = array( 79.370, 113.386); break;}
2559 case 'C11': {$pf = array( 56.693, 79.370); break;}
2560 case 'C12': {$pf = array( 39.685, 56.693); break;}
2561 case 'C76': {$pf = array( 229.606, 459.213); break;}
2562 case 'DL' : {$pf = array( 311.811, 623.622); break;}
2563 // SIS 014711 E Series
2564 case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
2565 case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
2566 case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
2567 case 'E3' : {$pf = array( 878.740, 1247.244); break;}
2568 case 'E4' : {$pf = array( 623.622, 878.740); break;}
2569 case 'E5' : {$pf = array( 439.370, 623.622); break;}
2570 case 'E6' : {$pf = array( 311.811, 439.370); break;}
2571 case 'E7' : {$pf = array( 221.102, 311.811); break;}
2572 case 'E8' : {$pf = array( 155.906, 221.102); break;}
2573 case 'E9' : {$pf = array( 110.551, 155.906); break;}
2574 case 'E10': {$pf = array( 76.535, 110.551); break;}
2575 case 'E11': {$pf = array( 53.858, 76.535); break;}
2576 case 'E12': {$pf = array( 36.850, 53.858); break;}
2577 // SIS 014711 G Series
2578 case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
2579 case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
2580 case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
2581 case 'G3' : {$pf = array( 958.110, 1357.795); break;}
2582 case 'G4' : {$pf = array( 677.480, 958.110); break;}
2583 case 'G5' : {$pf = array( 479.055, 677.480); break;}
2584 case 'G6' : {$pf = array( 337.323, 479.055); break;}
2585 case 'G7' : {$pf = array( 238.110, 337.323); break;}
2586 case 'G8' : {$pf = array( 167.244, 238.110); break;}
2587 case 'G9' : {$pf = array( 119.055, 167.244); break;}
2588 case 'G10': {$pf = array( 82.205, 119.055); break;}
2589 case 'G11': {$pf = array( 59.528, 82.205); break;}
2590 case 'G12': {$pf = array( 39.685, 59.528); break;}
2591 // ISO Press
2592 case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
2593 case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
2594 case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
2595 case 'RA3': {$pf = array( 864.567, 1218.898); break;}
2596 case 'RA4': {$pf = array( 609.449, 864.567); break;}
2597 case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
2598 case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
2599 case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
2600 case 'SRA3': {$pf = array( 907.087, 1275.591); break;}
2601 case 'SRA4': {$pf = array( 637.795, 907.087); break;}
2602 // German DIN 476
2603 case '4A0': {$pf = array( 4767.874, 6740.787); break;}
2604 case '2A0': {$pf = array( 3370.394, 4767.874); break;}
2605 // Variations on the ISO Standard
2606 case 'A2_EXTRA' : {$pf = array( 1261.417, 1754.646); break;}
2607 case 'A3+' : {$pf = array( 932.598, 1369.134); break;}
2608 case 'A3_EXTRA' : {$pf = array( 912.756, 1261.417); break;}
2609 case 'A3_SUPER' : {$pf = array( 864.567, 1440.000); break;}
2610 case 'SUPER_A3' : {$pf = array( 864.567, 1380.472); break;}
2611 case 'A4_EXTRA' : {$pf = array( 666.142, 912.756); break;}
2612 case 'A4_SUPER' : {$pf = array( 649.134, 912.756); break;}
2613 case 'SUPER_A4' : {$pf = array( 643.465, 1009.134); break;}
2614 case 'A4_LONG' : {$pf = array( 595.276, 986.457); break;}
2615 case 'F4' : {$pf = array( 595.276, 935.433); break;}
2616 case 'SO_B5_EXTRA': {$pf = array( 572.598, 782.362); break;}
2617 case 'A5_EXTRA' : {$pf = array( 490.394, 666.142); break;}
2618 // ANSI Series
2619 case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
2620 case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
2621 case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
2622 case 'ANSI_B': {$pf = array( 792.000, 1224.000); break;}
2623 case 'ANSI_A': {$pf = array( 612.000, 792.000); break;}
2624 // Traditional 'Loose' North American Paper Sizes
2625 case 'USLEDGER':
2626 case 'LEDGER' : {$pf = array( 1224.000, 792.000); break;}
2627 case 'ORGANIZERK':
2628 case 'BIBLE':
2629 case 'USTABLOID':
2630 case 'TABLOID': {$pf = array( 792.000, 1224.000); break;}
2631 case 'ORGANIZERM':
2632 case 'USLETTER':
2633 case 'LETTER' : {$pf = array( 612.000, 792.000); break;}
2634 case 'USLEGAL':
2635 case 'LEGAL' : {$pf = array( 612.000, 1008.000); break;}
2636 case 'GOVERNMENTLETTER':
2637 case 'GLETTER': {$pf = array( 576.000, 756.000); break;}
2638 case 'JUNIORLEGAL':
2639 case 'JLEGAL' : {$pf = array( 576.000, 360.000); break;}
2640 // Other North American Paper Sizes
2641 case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
2642 case 'SUPER_B': {$pf = array( 936.000, 1368.000); break;}
2643 case 'QUARTO': {$pf = array( 648.000, 792.000); break;}
2644 case 'GOVERNMENTLEGAL':
2645 case 'FOLIO': {$pf = array( 612.000, 936.000); break;}
2646 case 'MONARCH':
2647 case 'EXECUTIVE': {$pf = array( 522.000, 756.000); break;}
2648 case 'ORGANIZERL':
2649 case 'STATEMENT':
2650 case 'MEMO': {$pf = array( 396.000, 612.000); break;}
2651 case 'FOOLSCAP': {$pf = array( 595.440, 936.000); break;}
2652 case 'COMPACT': {$pf = array( 306.000, 486.000); break;}
2653 case 'ORGANIZERJ': {$pf = array( 198.000, 360.000); break;}
2654 // Canadian standard CAN 2-9.60M
2655 case 'P1': {$pf = array( 1587.402, 2437.795); break;}
2656 case 'P2': {$pf = array( 1218.898, 1587.402); break;}
2657 case 'P3': {$pf = array( 793.701, 1218.898); break;}
2658 case 'P4': {$pf = array( 609.449, 793.701); break;}
2659 case 'P5': {$pf = array( 396.850, 609.449); break;}
2660 case 'P6': {$pf = array( 303.307, 396.850); break;}
2661 // North American Architectural Sizes
2662 case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
2663 case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
2664 case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
2665 case 'BROADSHEET':
2666 case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
2667 case 'ARCH_B' : {$pf = array( 864.000, 1296.000); break;}
2668 case 'ARCH_A' : {$pf = array( 648.000, 864.000); break;}
2669 // --- North American Envelope Sizes ---
2670 // - Announcement Envelopes
2671 case 'ANNENV_A2' : {$pf = array( 314.640, 414.000); break;}
2672 case 'ANNENV_A6' : {$pf = array( 342.000, 468.000); break;}
2673 case 'ANNENV_A7' : {$pf = array( 378.000, 522.000); break;}
2674 case 'ANNENV_A8' : {$pf = array( 396.000, 584.640); break;}
2675 case 'ANNENV_A10' : {$pf = array( 450.000, 692.640); break;}
2676 case 'ANNENV_SLIM': {$pf = array( 278.640, 638.640); break;}
2677 // - Commercial Envelopes
2678 case 'COMMENV_N6_1/4': {$pf = array( 252.000, 432.000); break;}
2679 case 'COMMENV_N6_3/4': {$pf = array( 260.640, 468.000); break;}
2680 case 'COMMENV_N8' : {$pf = array( 278.640, 540.000); break;}
2681 case 'COMMENV_N9' : {$pf = array( 278.640, 638.640); break;}
2682 case 'COMMENV_N10' : {$pf = array( 296.640, 684.000); break;}
2683 case 'COMMENV_N11' : {$pf = array( 324.000, 746.640); break;}
2684 case 'COMMENV_N12' : {$pf = array( 342.000, 792.000); break;}
2685 case 'COMMENV_N14' : {$pf = array( 360.000, 828.000); break;}
2686 // - Catalogue Envelopes
2687 case 'CATENV_N1' : {$pf = array( 432.000, 648.000); break;}
2688 case 'CATENV_N1_3/4' : {$pf = array( 468.000, 684.000); break;}
2689 case 'CATENV_N2' : {$pf = array( 468.000, 720.000); break;}
2690 case 'CATENV_N3' : {$pf = array( 504.000, 720.000); break;}
2691 case 'CATENV_N6' : {$pf = array( 540.000, 756.000); break;}
2692 case 'CATENV_N7' : {$pf = array( 576.000, 792.000); break;}
2693 case 'CATENV_N8' : {$pf = array( 594.000, 810.000); break;}
2694 case 'CATENV_N9_1/2' : {$pf = array( 612.000, 756.000); break;}
2695 case 'CATENV_N9_3/4' : {$pf = array( 630.000, 810.000); break;}
2696 case 'CATENV_N10_1/2': {$pf = array( 648.000, 864.000); break;}
2697 case 'CATENV_N12_1/2': {$pf = array( 684.000, 900.000); break;}
2698 case 'CATENV_N13_1/2': {$pf = array( 720.000, 936.000); break;}
2699 case 'CATENV_N14_1/4': {$pf = array( 810.000, 882.000); break;}
2700 case 'CATENV_N14_1/2': {$pf = array( 828.000, 1044.000); break;}
2701 // Japanese (JIS P 0138-61) Standard B-Series
2702 case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
2703 case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
2704 case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
2705 case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
2706 case 'JIS_B4' : {$pf = array( 728.504, 1031.811); break;}
2707 case 'JIS_B5' : {$pf = array( 515.906, 728.504); break;}
2708 case 'JIS_B6' : {$pf = array( 362.835, 515.906); break;}
2709 case 'JIS_B7' : {$pf = array( 257.953, 362.835); break;}
2710 case 'JIS_B8' : {$pf = array( 181.417, 257.953); break;}
2711 case 'JIS_B9' : {$pf = array( 127.559, 181.417); break;}
2712 case 'JIS_B10': {$pf = array( 90.709, 127.559); break;}
2713 case 'JIS_B11': {$pf = array( 62.362, 90.709); break;}
2714 case 'JIS_B12': {$pf = array( 45.354, 62.362); break;}
2715 // PA Series
2716 case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
2717 case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
2718 case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
2719 case 'PA3' : {$pf = array( 793.701, 1190.551); break;}
2720 case 'PA4' : {$pf = array( 595.276, 793.701); break;}
2721 case 'PA5' : {$pf = array( 396.850, 595.276); break;}
2722 case 'PA6' : {$pf = array( 297.638, 396.850); break;}
2723 case 'PA7' : {$pf = array( 198.425, 297.638); break;}
2724 case 'PA8' : {$pf = array( 147.402, 198.425); break;}
2725 case 'PA9' : {$pf = array( 99.213, 147.402); break;}
2726 case 'PA10': {$pf = array( 73.701, 99.213); break;}
2727 // Standard Photographic Print Sizes
2728 case 'PASSPORT_PHOTO': {$pf = array( 99.213, 127.559); break;}
2729 case 'E' : {$pf = array( 233.858, 340.157); break;}
2730 case 'L':
2731 case '3R' : {$pf = array( 252.283, 360.000); break;}
2732 case 'KG':
2733 case '4R' : {$pf = array( 289.134, 430.866); break;}
2734 case '4D' : {$pf = array( 340.157, 430.866); break;}
2735 case '2L':
2736 case '5R' : {$pf = array( 360.000, 504.567); break;}
2737 case '8P':
2738 case '6R' : {$pf = array( 430.866, 575.433); break;}
2739 case '6P':
2740 case '8R' : {$pf = array( 575.433, 720.000); break;}
2741 case '6PW':
2742 case 'S8R' : {$pf = array( 575.433, 864.567); break;}
2743 case '4P':
2744 case '10R' : {$pf = array( 720.000, 864.567); break;}
2745 case '4PW':
2746 case 'S10R': {$pf = array( 720.000, 1080.000); break;}
2747 case '11R' : {$pf = array( 790.866, 1009.134); break;}
2748 case 'S11R': {$pf = array( 790.866, 1224.567); break;}
2749 case '12R' : {$pf = array( 864.567, 1080.000); break;}
2750 case 'S12R': {$pf = array( 864.567, 1292.598); break;}
2751 // Common Newspaper Sizes
2752 case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
2753 case 'NEWSPAPER_BERLINER' : {$pf = array( 1332.283, 892.913); break;}
2754 case 'NEWSPAPER_TABLOID':
2755 case 'NEWSPAPER_COMPACT' : {$pf = array( 1218.898, 793.701); break;}
2756 // Business Cards
2757 case 'CREDIT_CARD':
2758 case 'BUSINESS_CARD':
2759 case 'BUSINESS_CARD_ISO7810': {$pf = array( 153.014, 242.646); break;}
2760 case 'BUSINESS_CARD_ISO216' : {$pf = array( 147.402, 209.764); break;}
2761 case 'BUSINESS_CARD_IT':
2762 case 'BUSINESS_CARD_UK':
2763 case 'BUSINESS_CARD_FR':
2764 case 'BUSINESS_CARD_DE':
2765 case 'BUSINESS_CARD_ES' : {$pf = array( 155.906, 240.945); break;}
2766 case 'BUSINESS_CARD_CA':
2767 case 'BUSINESS_CARD_US' : {$pf = array( 144.567, 252.283); break;}
2768 case 'BUSINESS_CARD_JP' : {$pf = array( 155.906, 257.953); break;}
2769 case 'BUSINESS_CARD_HK' : {$pf = array( 153.071, 255.118); break;}
2770 case 'BUSINESS_CARD_AU':
2771 case 'BUSINESS_CARD_DK':
2772 case 'BUSINESS_CARD_SE' : {$pf = array( 155.906, 255.118); break;}
2773 case 'BUSINESS_CARD_RU':
2774 case 'BUSINESS_CARD_CZ':
2775 case 'BUSINESS_CARD_FI':
2776 case 'BUSINESS_CARD_HU':
2777 case 'BUSINESS_CARD_IL' : {$pf = array( 141.732, 255.118); break;}
2778 // Billboards
2779 case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
2780 case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
2781 case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
2782 case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
2783 case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
2784 case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
2785 case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
2786 case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
2787 // Old European Sizes
2788 // - Old Imperial English Sizes
2789 case 'EN_EMPEROR' : {$pf = array( 3456.000, 5184.000); break;}
2790 case 'EN_ANTIQUARIAN' : {$pf = array( 2232.000, 3816.000); break;}
2791 case 'EN_GRAND_EAGLE' : {$pf = array( 2070.000, 3024.000); break;}
2792 case 'EN_DOUBLE_ELEPHANT' : {$pf = array( 1926.000, 2880.000); break;}
2793 case 'EN_ATLAS' : {$pf = array( 1872.000, 2448.000); break;}
2794 case 'EN_COLOMBIER' : {$pf = array( 1692.000, 2484.000); break;}
2795 case 'EN_ELEPHANT' : {$pf = array( 1656.000, 2016.000); break;}
2796 case 'EN_DOUBLE_DEMY' : {$pf = array( 1620.000, 2556.000); break;}
2797 case 'EN_IMPERIAL' : {$pf = array( 1584.000, 2160.000); break;}
2798 case 'EN_PRINCESS' : {$pf = array( 1548.000, 2016.000); break;}
2799 case 'EN_CARTRIDGE' : {$pf = array( 1512.000, 1872.000); break;}
2800 case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
2801 case 'EN_ROYAL' : {$pf = array( 1440.000, 1800.000); break;}
2802 case 'EN_SHEET':
2803 case 'EN_HALF_POST' : {$pf = array( 1404.000, 1692.000); break;}
2804 case 'EN_SUPER_ROYAL' : {$pf = array( 1368.000, 1944.000); break;}
2805 case 'EN_DOUBLE_POST' : {$pf = array( 1368.000, 2196.000); break;}
2806 case 'EN_MEDIUM' : {$pf = array( 1260.000, 1656.000); break;}
2807 case 'EN_DEMY' : {$pf = array( 1260.000, 1620.000); break;}
2808 case 'EN_LARGE_POST' : {$pf = array( 1188.000, 1512.000); break;}
2809 case 'EN_COPY_DRAUGHT' : {$pf = array( 1152.000, 1440.000); break;}
2810 case 'EN_POST' : {$pf = array( 1116.000, 1386.000); break;}
2811 case 'EN_CROWN' : {$pf = array( 1080.000, 1440.000); break;}
2812 case 'EN_PINCHED_POST' : {$pf = array( 1062.000, 1332.000); break;}
2813 case 'EN_BRIEF' : {$pf = array( 972.000, 1152.000); break;}
2814 case 'EN_FOOLSCAP' : {$pf = array( 972.000, 1224.000); break;}
2815 case 'EN_SMALL_FOOLSCAP' : {$pf = array( 954.000, 1188.000); break;}
2816 case 'EN_POTT' : {$pf = array( 900.000, 1080.000); break;}
2817 // - Old Imperial Belgian Sizes
2818 case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
2819 case 'BE_COLOMBIER' : {$pf = array( 1757.480, 2409.449); break;}
2820 case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
2821 case 'BE_ELEPHANT' : {$pf = array( 1746.142, 2182.677); break;}
2822 case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
2823 case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
2824 case 'BE_JESUS' : {$pf = array( 1530.709, 2069.291); break;}
2825 case 'BE_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2826 case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
2827 case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
2828 case 'BE_COQUILLE' : {$pf = array( 1218.898, 1587.402); break;}
2829 case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
2830 case 'BE_RUCHE' : {$pf = array( 1020.472, 1303.937); break;}
2831 case 'BE_PROPATRIA' : {$pf = array( 977.953, 1218.898); break;}
2832 case 'BE_LYS' : {$pf = array( 898.583, 1125.354); break;}
2833 case 'BE_POT' : {$pf = array( 870.236, 1088.504); break;}
2834 case 'BE_ROSETTE' : {$pf = array( 765.354, 983.622); break;}
2835 // - Old Imperial French Sizes
2836 case 'FR_UNIVERS' : {$pf = array( 2834.646, 3685.039); break;}
2837 case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
2838 case 'FR_GRANDE_MONDE' : {$pf = array( 2551.181, 3571.654); break;}
2839 case 'FR_DOUBLE_SOLEIL' : {$pf = array( 2267.717, 3401.575); break;}
2840 case 'FR_DOUBLE_JESUS' : {$pf = array( 2154.331, 3174.803); break;}
2841 case 'FR_GRAND_AIGLE' : {$pf = array( 2125.984, 3004.724); break;}
2842 case 'FR_PETIT_AIGLE' : {$pf = array( 1984.252, 2664.567); break;}
2843 case 'FR_DOUBLE_RAISIN' : {$pf = array( 1842.520, 2834.646); break;}
2844 case 'FR_JOURNAL' : {$pf = array( 1842.520, 2664.567); break;}
2845 case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
2846 case 'FR_DOUBLE_CAVALIER' : {$pf = array( 1757.480, 2607.874); break;}
2847 case 'FR_CLOCHE' : {$pf = array( 1700.787, 2267.717); break;}
2848 case 'FR_SOLEIL' : {$pf = array( 1700.787, 2267.717); break;}
2849 case 'FR_DOUBLE_CARRE' : {$pf = array( 1587.402, 2551.181); break;}
2850 case 'FR_DOUBLE_COQUILLE' : {$pf = array( 1587.402, 2494.488); break;}
2851 case 'FR_JESUS' : {$pf = array( 1587.402, 2154.331); break;}
2852 case 'FR_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2853 case 'FR_CAVALIER' : {$pf = array( 1303.937, 1757.480); break;}
2854 case 'FR_DOUBLE_COURONNE' : {$pf = array( 1303.937, 2040.945); break;}
2855 case 'FR_CARRE' : {$pf = array( 1275.591, 1587.402); break;}
2856 case 'FR_COQUILLE' : {$pf = array( 1247.244, 1587.402); break;}
2857 case 'FR_DOUBLE_TELLIERE' : {$pf = array( 1247.244, 1927.559); break;}
2858 case 'FR_DOUBLE_CLOCHE' : {$pf = array( 1133.858, 1700.787); break;}
2859 case 'FR_DOUBLE_POT' : {$pf = array( 1133.858, 1757.480); break;}
2860 case 'FR_ECU' : {$pf = array( 1133.858, 1474.016); break;}
2861 case 'FR_COURONNE' : {$pf = array( 1020.472, 1303.937); break;}
2862 case 'FR_TELLIERE' : {$pf = array( 963.780, 1247.244); break;}
2863 case 'FR_POT' : {$pf = array( 878.740, 1133.858); break;}
2864 // DEFAULT ISO A4
2865 default: {$pf = array( 595.276, 841.890); break;}
2867 return $pf;
2871 * Change the format of the current page
2872 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numners (width, height) or an array containing the following measures and options:<ul>
2873 * <li>['format'] = page format name (one of the above);</li>
2874 * <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>
2875 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2876 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2877 * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
2878 * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
2879 * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
2880 * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
2881 * <li>['CropBox'] : the visible region of default user space:</li>
2882 * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
2883 * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
2884 * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
2885 * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
2886 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2887 * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
2888 * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
2889 * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
2890 * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
2891 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2892 * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
2893 * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
2894 * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
2895 * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
2896 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2897 * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
2898 * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
2899 * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
2900 * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
2901 * <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>
2902 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2903 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2904 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2905 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2906 * <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>
2907 * <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>
2908 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2909 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2910 * <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>
2911 * <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>
2912 * <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>
2913 * <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>
2914 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2915 * </ul>
2916 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2917 * <li>P or Portrait (default)</li>
2918 * <li>L or Landscape</li>
2919 * <li>'' (empty string) for automatic orientation</li>
2920 * </ul>
2921 * @protected
2922 * @since 3.0.015 (2008-06-06)
2923 * @see getPageSizeFromFormat()
2925 protected function setPageFormat($format, $orientation='P') {
2926 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2927 // remove inherited values
2928 unset($this->pagedim[$this->page]);
2930 if (is_string($format)) {
2931 // get page measures from format name
2932 $pf = $this->getPageSizeFromFormat($format);
2933 $this->fwPt = $pf[0];
2934 $this->fhPt = $pf[1];
2935 } else {
2936 // the boundaries of the physical medium on which the page shall be displayed or printed
2937 if (isset($format['MediaBox'])) {
2938 $this->setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false);
2939 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2940 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2941 } else {
2942 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2943 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2944 } else {
2945 if (!isset($format['format'])) {
2946 // default value
2947 $format['format'] = 'A4';
2949 $pf = $this->getPageSizeFromFormat($format['format']);
2951 $this->fwPt = $pf[0];
2952 $this->fhPt = $pf[1];
2953 $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2955 // the visible region of default user space
2956 if (isset($format['CropBox'])) {
2957 $this->setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false);
2959 // the region to which the contents of the page shall be clipped when output in a production environment
2960 if (isset($format['BleedBox'])) {
2961 $this->setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false);
2963 // the intended dimensions of the finished page after trimming
2964 if (isset($format['TrimBox'])) {
2965 $this->setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false);
2967 // the page's meaningful content (including potential white space)
2968 if (isset($format['ArtBox'])) {
2969 $this->setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false);
2971 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2972 if (isset($format['BoxColorInfo'])) {
2973 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2975 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2976 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2977 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2979 if (isset($format['PZ'])) {
2980 // The page's preferred zoom (magnification) factor
2981 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2983 if (isset($format['trans'])) {
2984 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2985 if (isset($format['trans']['Dur'])) {
2986 // The page's display duration
2987 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2989 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2990 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2991 // The transition style that shall be used when moving to this page from another during a presentation
2992 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2993 $valid_effect = array('Split', 'Blinds');
2994 $valid_vals = array('H', 'V');
2995 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2996 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2998 $valid_effect = array('Split', 'Box', 'Fly');
2999 $valid_vals = array('I', 'O');
3000 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
3001 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
3003 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
3004 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
3005 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
3006 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
3007 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
3008 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
3011 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
3012 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
3014 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
3015 $this->pagedim[$this->page]['trans']['B'] = 'true';
3017 } else {
3018 $this->pagedim[$this->page]['trans']['S'] = 'R';
3020 if (isset($format['trans']['D'])) {
3021 // The duration of the transition effect, in seconds
3022 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
3023 } else {
3024 $this->pagedim[$this->page]['trans']['D'] = 1;
3028 $this->setPageOrientation($orientation);
3032 * Set page boundaries.
3033 * @param $page (int) page number
3034 * @param $type (string) valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
3035 * @param $llx (float) lower-left x coordinate in user units
3036 * @param $lly (float) lower-left y coordinate in user units
3037 * @param $urx (float) upper-right x coordinate in user units
3038 * @param $ury (float) upper-right y coordinate in user units
3039 * @param $points (boolean) if true uses user units as unit of measure, otherwise uses PDF points
3040 * @public
3041 * @since 5.0.010 (2010-05-17)
3043 public function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false) {
3044 if (!isset($this->pagedim[$page])) {
3045 // initialize array
3046 $this->pagedim[$page] = array();
3048 $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
3049 if (!in_array($type, $pageboxes)) {
3050 return;
3052 if ($points) {
3053 $k = 1;
3054 } else {
3055 $k = $this->k;
3057 $this->pagedim[$page][$type]['llx'] = ($llx * $k);
3058 $this->pagedim[$page][$type]['lly'] = ($lly * $k);
3059 $this->pagedim[$page][$type]['urx'] = ($urx * $k);
3060 $this->pagedim[$page][$type]['ury'] = ($ury * $k);
3064 * Swap X and Y coordinates of page boxes (change page boxes orientation).
3065 * @param $page (int) page number
3066 * @protected
3067 * @since 5.0.010 (2010-05-17)
3069 protected function swapPageBoxCoordinates($page) {
3070 $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
3071 foreach ($pageboxes as $type) {
3072 // swap X and Y coordinates
3073 if (isset($this->pagedim[$page][$type])) {
3074 $tmp = $this->pagedim[$page][$type]['llx'];
3075 $this->pagedim[$page][$type]['llx'] = $this->pagedim[$page][$type]['lly'];
3076 $this->pagedim[$page][$type]['lly'] = $tmp;
3077 $tmp = $this->pagedim[$page][$type]['urx'];
3078 $this->pagedim[$page][$type]['urx'] = $this->pagedim[$page][$type]['ury'];
3079 $this->pagedim[$page][$type]['ury'] = $tmp;
3085 * Set page orientation.
3086 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
3087 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
3088 * @param $bottommargin (float) bottom margin of the page.
3089 * @public
3090 * @since 3.0.015 (2008-06-06)
3092 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
3093 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
3094 // the boundaries of the physical medium on which the page shall be displayed or printed
3095 $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
3097 if (!isset($this->pagedim[$this->page]['CropBox'])) {
3098 // the visible region of default user space
3099 $this->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);
3101 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
3102 // the region to which the contents of the page shall be clipped when output in a production environment
3103 $this->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);
3105 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
3106 // the intended dimensions of the finished page after trimming
3107 $this->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);
3109 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
3110 // the page's meaningful content (including potential white space)
3111 $this->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);
3113 if (!isset($this->pagedim[$this->page]['Rotate'])) {
3114 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
3115 $this->pagedim[$this->page]['Rotate'] = 0;
3117 if (!isset($this->pagedim[$this->page]['PZ'])) {
3118 // The page's preferred zoom (magnification) factor
3119 $this->pagedim[$this->page]['PZ'] = 1;
3121 if ($this->fwPt > $this->fhPt) {
3122 // landscape
3123 $default_orientation = 'L';
3124 } else {
3125 // portrait
3126 $default_orientation = 'P';
3128 $valid_orientations = array('P', 'L');
3129 if (empty($orientation)) {
3130 $orientation = $default_orientation;
3131 } else {
3132 $orientation = strtoupper($orientation{0});
3134 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
3135 $this->CurOrientation = $orientation;
3136 $this->wPt = $this->fhPt;
3137 $this->hPt = $this->fwPt;
3138 } else {
3139 $this->CurOrientation = $default_orientation;
3140 $this->wPt = $this->fwPt;
3141 $this->hPt = $this->fhPt;
3143 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
3144 // swap X and Y coordinates (change page orientation)
3145 $this->swapPageBoxCoordinates($this->page);
3147 $this->w = ($this->wPt / $this->k);
3148 $this->h = ($this->hPt / $this->k);
3149 if ($this->empty_string($autopagebreak)) {
3150 if (isset($this->AutoPageBreak)) {
3151 $autopagebreak = $this->AutoPageBreak;
3152 } else {
3153 $autopagebreak = true;
3156 if ($this->empty_string($bottommargin)) {
3157 if (isset($this->bMargin)) {
3158 $bottommargin = $this->bMargin;
3159 } else {
3160 // default value = 2 cm
3161 $bottommargin = 2 * 28.35 / $this->k;
3164 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
3165 // store page dimensions
3166 $this->pagedim[$this->page]['w'] = $this->wPt;
3167 $this->pagedim[$this->page]['h'] = $this->hPt;
3168 $this->pagedim[$this->page]['wk'] = $this->w;
3169 $this->pagedim[$this->page]['hk'] = $this->h;
3170 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3171 $this->pagedim[$this->page]['bm'] = $bottommargin;
3172 $this->pagedim[$this->page]['lm'] = $this->lMargin;
3173 $this->pagedim[$this->page]['rm'] = $this->rMargin;
3174 $this->pagedim[$this->page]['pb'] = $autopagebreak;
3175 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
3176 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
3177 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
3181 * Set regular expression to detect withespaces or word separators.
3182 * The pattern delimiter must be the forward-slash character "/".
3183 * Some example patterns are:
3184 * <pre>
3185 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
3186 * Unicode and PCRE unicode support: "/[^\S\P{Z}\xa0]/u"
3187 * Unicode and PCRE unicode support in Chinese mode: "/[^\S\P{Z}\P{Lo}\xa0]/u"
3188 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
3189 * "\p{Z}" or "\p{Separator}": any kind of Unicode whitespace or invisible separator.
3190 * "\p{Lo}" or "\p{Other_Letter}": a Unicode letter or ideograph that does not have lowercase and uppercase variants.
3191 * "\p{Lo}" is needed for Chinese characters because are packed next to each other without spaces in between.
3192 * </pre>
3193 * @param $re (string) regular expression (leave empty for default).
3194 * @public
3195 * @since 4.6.016 (2009-06-15)
3197 public function setSpacesRE($re='/[^\S\xa0]/') {
3198 $this->re_spaces = $re;
3199 $re_parts = explode('/', $re);
3200 // get pattern parts
3201 $this->re_space = array();
3202 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
3203 $this->re_space['p'] = $re_parts[1];
3204 } else {
3205 $this->re_space['p'] = '[\s]';
3207 // set pattern modifiers
3208 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
3209 $this->re_space['m'] = $re_parts[2];
3210 } else {
3211 $this->re_space['m'] = '';
3216 * Enable or disable Right-To-Left language mode
3217 * @param $enable (Boolean) if true enable Right-To-Left language mode.
3218 * @param $resetx (Boolean) if true reset the X position on direction change.
3219 * @public
3220 * @since 2.0.000 (2008-01-03)
3222 public function setRTL($enable, $resetx=true) {
3223 $enable = $enable ? true : false;
3224 $resetx = ($resetx AND ($enable != $this->rtl));
3225 $this->rtl = $enable;
3226 $this->tmprtl = false;
3227 if ($resetx) {
3228 $this->Ln(0);
3233 * Return the RTL status
3234 * @return boolean
3235 * @public
3236 * @since 4.0.012 (2008-07-24)
3238 public function getRTL() {
3239 return $this->rtl;
3243 * Force temporary RTL language direction
3244 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
3245 * @public
3246 * @since 2.1.000 (2008-01-09)
3248 public function setTempRTL($mode) {
3249 $newmode = false;
3250 switch (strtoupper($mode)) {
3251 case 'LTR':
3252 case 'L': {
3253 if ($this->rtl) {
3254 $newmode = 'L';
3256 break;
3258 case 'RTL':
3259 case 'R': {
3260 if (!$this->rtl) {
3261 $newmode = 'R';
3263 break;
3265 case false:
3266 default: {
3267 $newmode = false;
3268 break;
3271 $this->tmprtl = $newmode;
3275 * Return the current temporary RTL status
3276 * @return boolean
3277 * @public
3278 * @since 4.8.014 (2009-11-04)
3280 public function isRTLTextDir() {
3281 return ($this->rtl OR ($this->tmprtl == 'R'));
3285 * Set the last cell height.
3286 * @param $h (float) cell height.
3287 * @author Nicola Asuni
3288 * @public
3289 * @since 1.53.0.TC034
3291 public function setLastH($h) {
3292 $this->lasth = $h;
3296 * Reset the last cell height.
3297 * @public
3298 * @since 5.9.000 (2010-10-03)
3300 public function resetLastH() {
3301 $this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
3305 * Get the last cell height.
3306 * @return last cell height
3307 * @public
3308 * @since 4.0.017 (2008-08-05)
3310 public function getLastH() {
3311 return $this->lasth;
3315 * Set the adjusting factor to convert pixels to user units.
3316 * @param $scale (float) adjusting factor to convert pixels to user units.
3317 * @author Nicola Asuni
3318 * @public
3319 * @since 1.5.2
3321 public function setImageScale($scale) {
3322 $this->imgscale = $scale;
3326 * Returns the adjusting factor to convert pixels to user units.
3327 * @return float adjusting factor to convert pixels to user units.
3328 * @author Nicola Asuni
3329 * @public
3330 * @since 1.5.2
3332 public function getImageScale() {
3333 return $this->imgscale;
3337 * Returns an array of page dimensions:
3338 * <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>
3339 * @param $pagenum (int) page number (empty = current page)
3340 * @return array of page dimensions.
3341 * @author Nicola Asuni
3342 * @public
3343 * @since 4.5.027 (2009-03-16)
3345 public function getPageDimensions($pagenum='') {
3346 if (empty($pagenum)) {
3347 $pagenum = $this->page;
3349 return $this->pagedim[$pagenum];
3353 * Returns the page width in units.
3354 * @param $pagenum (int) page number (empty = current page)
3355 * @return int page width.
3356 * @author Nicola Asuni
3357 * @public
3358 * @since 1.5.2
3359 * @see getPageDimensions()
3361 public function getPageWidth($pagenum='') {
3362 if (empty($pagenum)) {
3363 return $this->w;
3365 return $this->pagedim[$pagenum]['w'];
3369 * Returns the page height in units.
3370 * @param $pagenum (int) page number (empty = current page)
3371 * @return int page height.
3372 * @author Nicola Asuni
3373 * @public
3374 * @since 1.5.2
3375 * @see getPageDimensions()
3377 public function getPageHeight($pagenum='') {
3378 if (empty($pagenum)) {
3379 return $this->h;
3381 return $this->pagedim[$pagenum]['h'];
3385 * Returns the page break margin.
3386 * @param $pagenum (int) page number (empty = current page)
3387 * @return int page break margin.
3388 * @author Nicola Asuni
3389 * @public
3390 * @since 1.5.2
3391 * @see getPageDimensions()
3393 public function getBreakMargin($pagenum='') {
3394 if (empty($pagenum)) {
3395 return $this->bMargin;
3397 return $this->pagedim[$pagenum]['bm'];
3401 * Returns the scale factor (number of points in user unit).
3402 * @return int scale factor.
3403 * @author Nicola Asuni
3404 * @public
3405 * @since 1.5.2
3407 public function getScaleFactor() {
3408 return $this->k;
3412 * Defines the left, top and right margins.
3413 * @param $left (float) Left margin.
3414 * @param $top (float) Top margin.
3415 * @param $right (float) Right margin. Default value is the left one.
3416 * @param $keepmargins (boolean) if true overwrites the default page margins
3417 * @public
3418 * @since 1.0
3419 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
3421 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
3422 //Set left, top and right margins
3423 $this->lMargin = $left;
3424 $this->tMargin = $top;
3425 if ($right == -1) {
3426 $right = $left;
3428 $this->rMargin = $right;
3429 if ($keepmargins) {
3430 // overwrite original values
3431 $this->original_lMargin = $this->lMargin;
3432 $this->original_rMargin = $this->rMargin;
3437 * 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.
3438 * @param $margin (float) The margin.
3439 * @public
3440 * @since 1.4
3441 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3443 public function SetLeftMargin($margin) {
3444 //Set left margin
3445 $this->lMargin = $margin;
3446 if (($this->page > 0) AND ($this->x < $margin)) {
3447 $this->x = $margin;
3452 * Defines the top margin. The method can be called before creating the first page.
3453 * @param $margin (float) The margin.
3454 * @public
3455 * @since 1.5
3456 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3458 public function SetTopMargin($margin) {
3459 //Set top margin
3460 $this->tMargin = $margin;
3461 if (($this->page > 0) AND ($this->y < $margin)) {
3462 $this->y = $margin;
3467 * Defines the right margin. The method can be called before creating the first page.
3468 * @param $margin (float) The margin.
3469 * @public
3470 * @since 1.5
3471 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
3473 public function SetRightMargin($margin) {
3474 $this->rMargin = $margin;
3475 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
3476 $this->x = $this->w - $margin;
3481 * Set the same internal Cell padding for top, right, bottom, left-
3482 * @param $pad (float) internal padding.
3483 * @public
3484 * @since 2.1.000 (2008-01-09)
3485 * @see getCellPaddings(), setCellPaddings()
3487 public function SetCellPadding($pad) {
3488 if ($pad >= 0) {
3489 $this->cell_padding['L'] = $pad;
3490 $this->cell_padding['T'] = $pad;
3491 $this->cell_padding['R'] = $pad;
3492 $this->cell_padding['B'] = $pad;
3497 * Set the internal Cell paddings.
3498 * @param $left (float) left padding
3499 * @param $top (float) top padding
3500 * @param $right (float) right padding
3501 * @param $bottom (float) bottom padding
3502 * @public
3503 * @since 5.9.000 (2010-10-03)
3504 * @see getCellPaddings(), SetCellPadding()
3506 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
3507 if (($left !== '') AND ($left >= 0)) {
3508 $this->cell_padding['L'] = $left;
3510 if (($top !== '') AND ($top >= 0)) {
3511 $this->cell_padding['T'] = $top;
3513 if (($right !== '') AND ($right >= 0)) {
3514 $this->cell_padding['R'] = $right;
3516 if (($bottom !== '') AND ($bottom >= 0)) {
3517 $this->cell_padding['B'] = $bottom;
3522 * Get the internal Cell padding array.
3523 * @return array of padding values
3524 * @public
3525 * @since 5.9.000 (2010-10-03)
3526 * @see setCellPaddings(), SetCellPadding()
3528 public function getCellPaddings() {
3529 return $this->cell_padding;
3533 * Set the internal Cell margins.
3534 * @param $left (float) left margin
3535 * @param $top (float) top margin
3536 * @param $right (float) right margin
3537 * @param $bottom (float) bottom margin
3538 * @public
3539 * @since 5.9.000 (2010-10-03)
3540 * @see getCellMargins()
3542 public function setCellMargins($left='', $top='', $right='', $bottom='') {
3543 if (($left !== '') AND ($left >= 0)) {
3544 $this->cell_margin['L'] = $left;
3546 if (($top !== '') AND ($top >= 0)) {
3547 $this->cell_margin['T'] = $top;
3549 if (($right !== '') AND ($right >= 0)) {
3550 $this->cell_margin['R'] = $right;
3552 if (($bottom !== '') AND ($bottom >= 0)) {
3553 $this->cell_margin['B'] = $bottom;
3558 * Get the internal Cell margin array.
3559 * @return array of margin values
3560 * @public
3561 * @since 5.9.000 (2010-10-03)
3562 * @see setCellMargins()
3564 public function getCellMargins() {
3565 return $this->cell_margin;
3569 * Adjust the internal Cell padding array to take account of the line width.
3570 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
3571 * @return array of adjustments
3572 * @public
3573 * @since 5.9.000 (2010-10-03)
3575 protected function adjustCellPadding($brd=0) {
3576 if (empty($brd)) {
3577 return;
3579 if (is_string($brd)) {
3580 // convert string to array
3581 $slen = strlen($brd);
3582 $newbrd = array();
3583 for ($i = 0; $i < $slen; ++$i) {
3584 $newbrd[$brd[$i]] = true;
3586 $brd = $newbrd;
3587 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
3588 $brd = array('LRTB' => true);
3590 if (!is_array($brd)) {
3591 return;
3593 // store current cell padding
3594 $cp = $this->cell_padding;
3595 // select border mode
3596 if (isset($brd['mode'])) {
3597 $mode = $brd['mode'];
3598 unset($brd['mode']);
3599 } else {
3600 $mode = 'normal';
3602 // process borders
3603 foreach ($brd as $border => $style) {
3604 $line_width = $this->LineWidth;
3605 if (is_array($style) AND isset($style['width'])) {
3606 // get border width
3607 $line_width = $style['width'];
3609 $adj = 0; // line width inside the cell
3610 switch ($mode) {
3611 case 'ext': {
3612 $adj = 0;
3613 break;
3615 case 'int': {
3616 $adj = $line_width;
3617 break;
3619 case 'normal':
3620 default: {
3621 $adj = ($line_width / 2);
3622 break;
3625 // correct internal cell padding if required to avoid overlap between text and lines
3626 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
3627 $this->cell_padding['T'] = $adj;
3629 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
3630 $this->cell_padding['R'] = $adj;
3632 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
3633 $this->cell_padding['B'] = $adj;
3635 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
3636 $this->cell_padding['L'] = $adj;
3639 return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
3643 * 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.
3644 * @param $auto (boolean) Boolean indicating if mode should be on or off.
3645 * @param $margin (float) Distance from the bottom of the page.
3646 * @public
3647 * @since 1.0
3648 * @see Cell(), MultiCell(), AcceptPageBreak()
3650 public function SetAutoPageBreak($auto, $margin=0) {
3651 $this->AutoPageBreak = $auto ? true : false;
3652 $this->bMargin = $margin;
3653 $this->PageBreakTrigger = $this->h - $margin;
3657 * Return the auto-page-break mode (true or false).
3658 * @return boolean auto-page-break mode
3659 * @public
3660 * @since 5.9.088
3662 public function getAutoPageBreak() {
3663 return $this->AutoPageBreak;
3667 * Defines the way the document is to be displayed by the viewer.
3668 * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
3669 * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
3670 * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
3671 * @public
3672 * @since 1.2
3674 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
3675 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
3676 $this->ZoomMode = $zoom;
3677 } else {
3678 $this->Error('Incorrect zoom display mode: '.$zoom);
3680 switch ($layout) {
3681 case 'default':
3682 case 'single':
3683 case 'SinglePage': {
3684 $this->LayoutMode = 'SinglePage';
3685 break;
3687 case 'continuous':
3688 case 'OneColumn': {
3689 $this->LayoutMode = 'OneColumn';
3690 break;
3692 case 'two':
3693 case 'TwoColumnLeft': {
3694 $this->LayoutMode = 'TwoColumnLeft';
3695 break;
3697 case 'TwoColumnRight': {
3698 $this->LayoutMode = 'TwoColumnRight';
3699 break;
3701 case 'TwoPageLeft': {
3702 $this->LayoutMode = 'TwoPageLeft';
3703 break;
3705 case 'TwoPageRight': {
3706 $this->LayoutMode = 'TwoPageRight';
3707 break;
3709 default: {
3710 $this->LayoutMode = 'SinglePage';
3713 switch ($mode) {
3714 case 'UseNone': {
3715 $this->PageMode = 'UseNone';
3716 break;
3718 case 'UseOutlines': {
3719 $this->PageMode = 'UseOutlines';
3720 break;
3722 case 'UseThumbs': {
3723 $this->PageMode = 'UseThumbs';
3724 break;
3726 case 'FullScreen': {
3727 $this->PageMode = 'FullScreen';
3728 break;
3730 case 'UseOC': {
3731 $this->PageMode = 'UseOC';
3732 break;
3734 case '': {
3735 $this->PageMode = 'UseAttachments';
3736 break;
3738 default: {
3739 $this->PageMode = 'UseNone';
3745 * 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.
3746 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
3747 * @param $compress (boolean) Boolean indicating if compression must be enabled.
3748 * @public
3749 * @since 1.4
3751 public function SetCompression($compress=true) {
3752 if (function_exists('gzcompress')) {
3753 $this->compress = $compress ? true : false;
3754 } else {
3755 $this->compress = false;
3760 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
3761 * @param $mode (boolean) If true force sRGB output intent.
3762 * @public
3763 * @since 5.9.121 (2011-09-28)
3765 public function setSRGBmode($mode=false) {
3766 $this->force_srgb = $mode ? true : false;
3770 * Turn on/off Unicode mode for document information dictionary (meta tags).
3771 * This has effect only when unicode mode is set to false.
3772 * @param $unicode (boolean) if true set the meta information in Unicode
3773 * @since 5.9.027 (2010-12-01)
3774 * @public
3776 public function SetDocInfoUnicode($unicode=true) {
3777 $this->docinfounicode = $unicode ? true : false;
3781 * Defines the title of the document.
3782 * @param $title (string) The title.
3783 * @public
3784 * @since 1.2
3785 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
3787 public function SetTitle($title) {
3788 $this->title = $title;
3792 * Defines the subject of the document.
3793 * @param $subject (string) The subject.
3794 * @public
3795 * @since 1.2
3796 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
3798 public function SetSubject($subject) {
3799 $this->subject = $subject;
3803 * Defines the author of the document.
3804 * @param $author (string) The name of the author.
3805 * @public
3806 * @since 1.2
3807 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
3809 public function SetAuthor($author) {
3810 $this->author = $author;
3814 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
3815 * @param $keywords (string) The list of keywords.
3816 * @public
3817 * @since 1.2
3818 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
3820 public function SetKeywords($keywords) {
3821 $this->keywords = $keywords;
3825 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
3826 * @param $creator (string) The name of the creator.
3827 * @public
3828 * @since 1.2
3829 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
3831 public function SetCreator($creator) {
3832 $this->creator = $creator;
3836 * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
3837 * 2004-06-11 :: Nicola Asuni : changed bold tag with strong
3838 * @param $msg (string) The error message
3839 * @public
3840 * @since 1.0
3842 public function Error($msg) {
3843 // unset all class variables
3844 $this->_destroy(true);
3845 $phpmainver = PHP_VERSION;
3846 // exit program and print error
3847 if ((intval($phpmainver[0]) < 5) OR !defined('K_TCPDF_THROW_EXCEPTION_ERROR') OR !K_TCPDF_THROW_EXCEPTION_ERROR) {
3848 die('<strong>TCPDF ERROR: </strong>'.$msg);
3849 } else {
3850 throw new Exception('TCPDF ERROR: '.$msg);
3855 * This method begins the generation of the PDF document.
3856 * It is not necessary to call it explicitly because AddPage() does it automatically.
3857 * Note: no page is created by this method
3858 * @public
3859 * @since 1.0
3860 * @see AddPage(), Close()
3862 public function Open() {
3863 $this->state = 1;
3867 * Terminates the PDF document.
3868 * It is not necessary to call this method explicitly because Output() does it automatically.
3869 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3870 * @public
3871 * @since 1.0
3872 * @see Open(), Output()
3874 public function Close() {
3875 if ($this->state == 3) {
3876 return;
3878 if ($this->page == 0) {
3879 $this->AddPage();
3881 $this->endLayer();
3882 if ($this->tcpdflink) {
3883 // save current graphic settings
3884 $gvars = $this->getGraphicVars();
3885 $this->setEqualColumns();
3886 $this->lastpage(true);
3887 $this->SetAutoPageBreak(false);
3888 $this->x = 0;
3889 $this->y = $this->h - (1 / $this->k);
3890 $this->lMargin = 0;
3891 $this->_out('q');
3892 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3893 $this->SetFont($font, '', 1);
3894 $this->setTextRenderingMode(0, false, false);
3895 $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";
3896 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3897 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3898 $this->_out('Q');
3899 // restore graphic settings
3900 $this->setGraphicVars($gvars);
3902 // close page
3903 $this->endPage();
3904 // close document
3905 $this->_enddoc();
3906 // unset all class variables (except critical ones)
3907 $this->_destroy(false);
3911 * Move pointer at the specified document page and update page dimensions.
3912 * @param $pnum (int) page number (1 ... numpages)
3913 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3914 * @public
3915 * @since 2.1.000 (2008-01-07)
3916 * @see getPage(), lastpage(), getNumPages()
3918 public function setPage($pnum, $resetmargins=false) {
3919 if (($pnum == $this->page) AND ($this->state == 2)) {
3920 return;
3922 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3923 $this->state = 2;
3924 // save current graphic settings
3925 //$gvars = $this->getGraphicVars();
3926 $oldpage = $this->page;
3927 $this->page = $pnum;
3928 $this->wPt = $this->pagedim[$this->page]['w'];
3929 $this->hPt = $this->pagedim[$this->page]['h'];
3930 $this->w = $this->pagedim[$this->page]['wk'];
3931 $this->h = $this->pagedim[$this->page]['hk'];
3932 $this->tMargin = $this->pagedim[$this->page]['tm'];
3933 $this->bMargin = $this->pagedim[$this->page]['bm'];
3934 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3935 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3936 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3937 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3938 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3939 // restore graphic settings
3940 //$this->setGraphicVars($gvars);
3941 if ($resetmargins) {
3942 $this->lMargin = $this->pagedim[$this->page]['olm'];
3943 $this->rMargin = $this->pagedim[$this->page]['orm'];
3944 $this->SetY($this->tMargin);
3945 } else {
3946 // account for booklet mode
3947 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3948 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3949 $this->lMargin += $deltam;
3950 $this->rMargin -= $deltam;
3953 } else {
3954 $this->Error('Wrong page number on setPage() function: '.$pnum);
3959 * Reset pointer to the last document page.
3960 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3961 * @public
3962 * @since 2.0.000 (2008-01-04)
3963 * @see setPage(), getPage(), getNumPages()
3965 public function lastPage($resetmargins=false) {
3966 $this->setPage($this->getNumPages(), $resetmargins);
3970 * Get current document page number.
3971 * @return int page number
3972 * @public
3973 * @since 2.1.000 (2008-01-07)
3974 * @see setPage(), lastpage(), getNumPages()
3976 public function getPage() {
3977 return $this->page;
3981 * Get the total number of insered pages.
3982 * @return int number of pages
3983 * @public
3984 * @since 2.1.000 (2008-01-07)
3985 * @see setPage(), getPage(), lastpage()
3987 public function getNumPages() {
3988 return $this->numpages;
3992 * Adds a new TOC (Table Of Content) page to the document.
3993 * @param $orientation (string) page orientation.
3994 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3995 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3996 * @public
3997 * @since 5.0.001 (2010-05-06)
3998 * @see AddPage(), startPage(), endPage(), endTOCPage()
4000 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
4001 $this->AddPage($orientation, $format, $keepmargins, true);
4005 * Terminate the current TOC (Table Of Content) page
4006 * @public
4007 * @since 5.0.001 (2010-05-06)
4008 * @see AddPage(), startPage(), endPage(), addTOCPage()
4010 public function endTOCPage() {
4011 $this->endPage(true);
4015 * 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).
4016 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
4017 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
4018 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
4019 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
4020 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
4021 * @public
4022 * @since 1.0
4023 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
4025 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
4026 if ($this->inxobj) {
4027 // we are inside an XObject template
4028 return;
4030 if (!isset($this->original_lMargin) OR $keepmargins) {
4031 $this->original_lMargin = $this->lMargin;
4033 if (!isset($this->original_rMargin) OR $keepmargins) {
4034 $this->original_rMargin = $this->rMargin;
4036 // terminate previous page
4037 $this->endPage();
4038 // start new page
4039 $this->startPage($orientation, $format, $tocpage);
4043 * Terminate the current page
4044 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
4045 * @public
4046 * @since 4.2.010 (2008-11-14)
4047 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
4049 public function endPage($tocpage=false) {
4050 // check if page is already closed
4051 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
4052 return;
4054 // print page footer
4055 $this->setFooter();
4056 // close page
4057 $this->_endpage();
4058 // mark page as closed
4059 $this->pageopen[$this->page] = false;
4060 if ($tocpage) {
4061 $this->tocpage = false;
4066 * Starts a new page to the document. The page must be closed using the endPage() function.
4067 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
4068 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
4069 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
4070 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
4071 * @since 4.2.010 (2008-11-14)
4072 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
4073 * @public
4075 public function startPage($orientation='', $format='', $tocpage=false) {
4076 if ($tocpage) {
4077 $this->tocpage = true;
4079 // move page numbers of documents to be attached
4080 if ($this->tocpage) {
4081 // move reference to unexistent pages (used for page attachments)
4082 // adjust outlines
4083 $tmpoutlines = $this->outlines;
4084 foreach ($tmpoutlines as $key => $outline) {
4085 if ($outline['p'] > $this->numpages) {
4086 $this->outlines[$key]['p'] = ($outline['p'] + 1);
4089 // adjust dests
4090 $tmpdests = $this->dests;
4091 foreach ($tmpdests as $key => $dest) {
4092 if ($dest['p'] > $this->numpages) {
4093 $this->dests[$key]['p'] = ($dest['p'] + 1);
4096 // adjust links
4097 $tmplinks = $this->links;
4098 foreach ($tmplinks as $key => $link) {
4099 if ($link[0] > $this->numpages) {
4100 $this->links[$key][0] = ($link[0] + 1);
4104 if ($this->numpages > $this->page) {
4105 // this page has been already added
4106 $this->setPage($this->page + 1);
4107 $this->SetY($this->tMargin);
4108 return;
4110 // start a new page
4111 if ($this->state == 0) {
4112 $this->Open();
4114 ++$this->numpages;
4115 $this->swapMargins($this->booklet);
4116 // save current graphic settings
4117 $gvars = $this->getGraphicVars();
4118 // start new page
4119 $this->_beginpage($orientation, $format);
4120 // mark page as open
4121 $this->pageopen[$this->page] = true;
4122 // restore graphic settings
4123 $this->setGraphicVars($gvars);
4124 // mark this point
4125 $this->setPageMark();
4126 // print page header
4127 $this->setHeader();
4128 // restore graphic settings
4129 $this->setGraphicVars($gvars);
4130 // mark this point
4131 $this->setPageMark();
4132 // print table header (if any)
4133 $this->setTableHeader();
4134 // set mark for empty page check
4135 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
4139 * Set start-writing mark on current page stream used to put borders and fills.
4140 * Borders and fills are always created after content and inserted on the position marked by this method.
4141 * This function must be called after calling Image() function for a background image.
4142 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
4143 * @public
4144 * @since 4.0.016 (2008-07-30)
4146 public function setPageMark() {
4147 $this->intmrk[$this->page] = $this->pagelen[$this->page];
4148 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
4149 $this->setContentMark();
4153 * Set start-writing mark on selected page.
4154 * Borders and fills are always created after content and inserted on the position marked by this method.
4155 * @param $page (int) page number (default is the current page)
4156 * @protected
4157 * @since 4.6.021 (2009-07-20)
4159 protected function setContentMark($page=0) {
4160 if ($page <= 0) {
4161 $page = $this->page;
4163 if (isset($this->footerlen[$page])) {
4164 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
4165 } else {
4166 $this->cntmrk[$page] = $this->pagelen[$page];
4171 * Set header data.
4172 * @param $ln (string) header image logo
4173 * @param $lw (string) header image logo width in mm
4174 * @param $ht (string) string to print as title on document header
4175 * @param $hs (string) string to print on document header
4176 * @param $tc (array) RGB array color for text.
4177 * @param $lc (array) RGB array color for line.
4178 * @public
4180 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
4181 $this->header_logo = $ln;
4182 $this->header_logo_width = $lw;
4183 $this->header_title = $ht;
4184 $this->header_string = $hs;
4185 $this->header_text_color = $tc;
4186 $this->header_line_color = $lc;
4190 * Set footer data.
4191 * @param $tc (array) RGB array color for text.
4192 * @param $lc (array) RGB array color for line.
4193 * @public
4195 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
4196 $this->footer_text_color = $tc;
4197 $this->footer_line_color = $lc;
4201 * Returns header data:
4202 * <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>
4203 * @return array()
4204 * @public
4205 * @since 4.0.012 (2008-07-24)
4207 public function getHeaderData() {
4208 $ret = array();
4209 $ret['logo'] = $this->header_logo;
4210 $ret['logo_width'] = $this->header_logo_width;
4211 $ret['title'] = $this->header_title;
4212 $ret['string'] = $this->header_string;
4213 $ret['text_color'] = $this->header_text_color;
4214 $ret['line_color'] = $this->header_line_color;
4215 return $ret;
4219 * Set header margin.
4220 * (minimum distance between header and top page margin)
4221 * @param $hm (int) distance in user units
4222 * @public
4224 public function setHeaderMargin($hm=10) {
4225 $this->header_margin = $hm;
4229 * Returns header margin in user units.
4230 * @return float
4231 * @since 4.0.012 (2008-07-24)
4232 * @public
4234 public function getHeaderMargin() {
4235 return $this->header_margin;
4239 * Set footer margin.
4240 * (minimum distance between footer and bottom page margin)
4241 * @param $fm (int) distance in user units
4242 * @public
4244 public function setFooterMargin($fm=10) {
4245 $this->footer_margin = $fm;
4249 * Returns footer margin in user units.
4250 * @return float
4251 * @since 4.0.012 (2008-07-24)
4252 * @public
4254 public function getFooterMargin() {
4255 return $this->footer_margin;
4258 * Set a flag to print page header.
4259 * @param $val (boolean) set to true to print the page header (default), false otherwise.
4260 * @public
4262 public function setPrintHeader($val=true) {
4263 $this->print_header = $val ? true : false;
4267 * Set a flag to print page footer.
4268 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
4269 * @public
4271 public function setPrintFooter($val=true) {
4272 $this->print_footer = $val ? true : false;
4276 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
4277 * @return float
4278 * @public
4280 public function getImageRBX() {
4281 return $this->img_rb_x;
4285 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
4286 * @return float
4287 * @public
4289 public function getImageRBY() {
4290 return $this->img_rb_y;
4294 * Reset the xobject template used by Header() method.
4295 * @public
4297 public function resetHeaderTemplate() {
4298 $this->header_xobjid = -1;
4302 * Set a flag to automatically reset the xobject template used by Header() method at each page.
4303 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
4304 * @public
4306 public function setHeaderTemplateAutoreset($val=true) {
4307 $this->header_xobj_autoreset = $val ? true : false;
4311 * This method is used to render the page header.
4312 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4313 * @public
4315 public function Header() {
4316 if ($this->header_xobjid < 0) {
4317 // start a new XObject Template
4318 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
4319 $headerfont = $this->getHeaderFont();
4320 $headerdata = $this->getHeaderData();
4321 $this->y = $this->header_margin;
4322 if ($this->rtl) {
4323 $this->x = $this->w - $this->original_rMargin;
4324 } else {
4325 $this->x = $this->original_lMargin;
4327 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
4328 $imgtype = $this->getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
4329 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
4330 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4331 } elseif ($imgtype == 'svg') {
4332 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4333 } else {
4334 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4336 $imgy = $this->getImageRBY();
4337 } else {
4338 $imgy = $this->y;
4340 $cell_height = round(($this->cell_height_ratio * $headerfont[2]) / $this->k, 2);
4341 // set starting margin for text data cell
4342 if ($this->getRTL()) {
4343 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
4344 } else {
4345 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
4347 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
4348 $this->SetTextColorArray($this->header_text_color);
4349 // header title
4350 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
4351 $this->SetX($header_x);
4352 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
4353 // header string
4354 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
4355 $this->SetX($header_x);
4356 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
4357 // print an ending header line
4358 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
4359 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
4360 if ($this->rtl) {
4361 $this->SetX($this->original_rMargin);
4362 } else {
4363 $this->SetX($this->original_lMargin);
4365 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
4366 $this->endTemplate();
4368 // print header template
4369 $x = 0;
4370 $dx = 0;
4371 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
4372 // adjust margins for booklet mode
4373 $dx = ($this->original_lMargin - $this->original_rMargin);
4375 if ($this->rtl) {
4376 $x = $this->w + $dx;
4377 } else {
4378 $x = 0 + $dx;
4380 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
4381 if ($this->header_xobj_autoreset) {
4382 // reset header xobject template at each page
4383 $this->header_xobjid = -1;
4388 * This method is used to render the page footer.
4389 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4390 * @public
4392 public function Footer() {
4393 $cur_y = $this->y;
4394 $this->SetTextColorArray($this->footer_text_color);
4395 //set style for cell border
4396 $line_width = (0.85 / $this->k);
4397 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
4398 //print document barcode
4399 $barcode = $this->getBarcode();
4400 if (!empty($barcode)) {
4401 $this->Ln($line_width);
4402 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
4403 $style = array(
4404 'position' => $this->rtl?'R':'L',
4405 'align' => $this->rtl?'R':'L',
4406 'stretch' => false,
4407 'fitwidth' => true,
4408 'cellfitalign' => '',
4409 'border' => false,
4410 'padding' => 0,
4411 'fgcolor' => array(0,0,0),
4412 'bgcolor' => false,
4413 'text' => false
4415 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
4417 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
4418 if (empty($this->pagegroups)) {
4419 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
4420 } else {
4421 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
4423 $this->SetY($cur_y);
4424 //Print page number
4425 if ($this->getRTL()) {
4426 $this->SetX($this->original_rMargin);
4427 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
4428 } else {
4429 $this->SetX($this->original_lMargin);
4430 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
4435 * This method is used to render the page header.
4436 * @protected
4437 * @since 4.0.012 (2008-07-24)
4439 protected function setHeader() {
4440 if (!$this->print_header OR ($this->state != 2)) {
4441 return;
4443 $this->InHeader = true;
4444 $this->setGraphicVars($this->default_graphic_vars);
4445 $temp_thead = $this->thead;
4446 $temp_theadMargins = $this->theadMargins;
4447 $lasth = $this->lasth;
4448 $this->_out('q');
4449 $this->rMargin = $this->original_rMargin;
4450 $this->lMargin = $this->original_lMargin;
4451 $this->SetCellPadding(0);
4452 //set current position
4453 if ($this->rtl) {
4454 $this->SetXY($this->original_rMargin, $this->header_margin);
4455 } else {
4456 $this->SetXY($this->original_lMargin, $this->header_margin);
4458 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
4459 $this->Header();
4460 //restore position
4461 if ($this->rtl) {
4462 $this->SetXY($this->original_rMargin, $this->tMargin);
4463 } else {
4464 $this->SetXY($this->original_lMargin, $this->tMargin);
4466 $this->_out('Q');
4467 $this->lasth = $lasth;
4468 $this->thead = $temp_thead;
4469 $this->theadMargins = $temp_theadMargins;
4470 $this->newline = false;
4471 $this->InHeader = false;
4475 * This method is used to render the page footer.
4476 * @protected
4477 * @since 4.0.012 (2008-07-24)
4479 protected function setFooter() {
4480 if ($this->state != 2) {
4481 return;
4483 $this->InFooter = true;
4484 // save current graphic settings
4485 $gvars = $this->getGraphicVars();
4486 // mark this point
4487 $this->footerpos[$this->page] = $this->pagelen[$this->page];
4488 $this->_out("\n");
4489 if ($this->print_footer) {
4490 $this->setGraphicVars($this->default_graphic_vars);
4491 $this->current_column = 0;
4492 $this->num_columns = 1;
4493 $temp_thead = $this->thead;
4494 $temp_theadMargins = $this->theadMargins;
4495 $lasth = $this->lasth;
4496 $this->_out('q');
4497 $this->rMargin = $this->original_rMargin;
4498 $this->lMargin = $this->original_lMargin;
4499 $this->SetCellPadding(0);
4500 //set current position
4501 $footer_y = $this->h - $this->footer_margin;
4502 if ($this->rtl) {
4503 $this->SetXY($this->original_rMargin, $footer_y);
4504 } else {
4505 $this->SetXY($this->original_lMargin, $footer_y);
4507 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
4508 $this->Footer();
4509 //restore position
4510 if ($this->rtl) {
4511 $this->SetXY($this->original_rMargin, $this->tMargin);
4512 } else {
4513 $this->SetXY($this->original_lMargin, $this->tMargin);
4515 $this->_out('Q');
4516 $this->lasth = $lasth;
4517 $this->thead = $temp_thead;
4518 $this->theadMargins = $temp_theadMargins;
4520 // restore graphic settings
4521 $this->setGraphicVars($gvars);
4522 $this->current_column = $gvars['current_column'];
4523 $this->num_columns = $gvars['num_columns'];
4524 // calculate footer length
4525 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
4526 $this->InFooter = false;
4530 * Check if we are on the page body (excluding page header and footer).
4531 * @return true if we are not in page header nor in page footer, false otherwise.
4532 * @protected
4533 * @since 5.9.091 (2011-06-15)
4535 protected function inPageBody() {
4536 return (($this->InHeader === false) AND ($this->InFooter === false));
4540 * This method is used to render the table header on new page (if any).
4541 * @protected
4542 * @since 4.5.030 (2009-03-25)
4544 protected function setTableHeader() {
4545 if ($this->num_columns > 1) {
4546 // multi column mode
4547 return;
4549 if (isset($this->theadMargins['top'])) {
4550 // restore the original top-margin
4551 $this->tMargin = $this->theadMargins['top'];
4552 $this->pagedim[$this->page]['tm'] = $this->tMargin;
4553 $this->y = $this->tMargin;
4555 if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
4556 // set margins
4557 $prev_lMargin = $this->lMargin;
4558 $prev_rMargin = $this->rMargin;
4559 $prev_cell_padding = $this->cell_padding;
4560 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
4561 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
4562 $this->cell_padding = $this->theadMargins['cell_padding'];
4563 if ($this->rtl) {
4564 $this->x = $this->w - $this->rMargin;
4565 } else {
4566 $this->x = $this->lMargin;
4568 // account for special "cell" mode
4569 if ($this->theadMargins['cell']) {
4570 if ($this->rtl) {
4571 $this->x -= $this->cell_padding['R'];
4572 } else {
4573 $this->x += $this->cell_padding['L'];
4576 // print table header
4577 $this->writeHTML($this->thead, false, false, false, false, '');
4578 // set new top margin to skip the table headers
4579 if (!isset($this->theadMargins['top'])) {
4580 $this->theadMargins['top'] = $this->tMargin;
4582 // store end of header position
4583 if (!isset($this->columns[0]['th'])) {
4584 $this->columns[0]['th'] = array();
4586 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
4587 $this->tMargin = $this->y;
4588 $this->pagedim[$this->page]['tm'] = $this->tMargin;
4589 $this->lasth = 0;
4590 $this->lMargin = $prev_lMargin;
4591 $this->rMargin = $prev_rMargin;
4592 $this->cell_padding = $prev_cell_padding;
4597 * Returns the current page number.
4598 * @return int page number
4599 * @public
4600 * @since 1.0
4601 * @see getAliasNbPages()
4603 public function PageNo() {
4604 return $this->page;
4608 * Defines a new spot color.
4609 * It can be expressed in RGB components or gray scale.
4610 * The method can be called before the first page is created and the value is retained from page to page.
4611 * @param $name (string) Full name of the spot color.
4612 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
4613 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
4614 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
4615 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
4616 * @public
4617 * @since 4.0.024 (2008-09-12)
4618 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4620 public function AddSpotColor($name, $c, $m, $y, $k) {
4621 if (!isset($this->spot_colors[$name])) {
4622 $i = (1 + count($this->spot_colors));
4623 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
4628 * Return the Spot color array.
4629 * @param $name (string) Name of the spot color.
4630 * @return (array) Spot color array or false if not defined.
4631 * @public
4632 * @since 5.9.125 (2011-10-03)
4634 public function getSpotColor($name) {
4635 if (isset($this->spot_colors[$name])) {
4636 return $this->spot_colors[$name];
4638 $color = preg_replace('/[\s]*/', '', $name); // remove extra spaces
4639 $color = strtolower($color);
4640 if (isset($this->spotcolor[$color])) {
4641 $this->AddSpotColor($this->spotcolor[$color][4], $this->spotcolor[$color][0], $this->spotcolor[$color][1], $this->spotcolor[$color][2], $this->spotcolor[$color][3]);
4642 return $this->spot_colors[$this->spotcolor[$color][4]];
4644 return false;
4648 * Set the spot color for the specified type ('draw', 'fill', 'text').
4649 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4650 * @param $name (string) Name of the spot color.
4651 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4652 * @return (string) PDF color command.
4653 * @public
4654 * @since 5.9.125 (2011-10-03)
4656 public function setSpotColor($type, $name, $tint=100) {
4657 $spotcolor = $this->getSpotColor($name);
4658 if ($spotcolor === false) {
4659 $this->Error('Undefined spot color: '.$name.', you must add it on the spotcolors.php file.');
4661 $tint = (max(0, min(100, $tint)) / 100);
4662 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
4663 switch ($type) {
4664 case 'draw': {
4665 $pdfcolor .= sprintf('CS %F SCN', $tint);
4666 $this->DrawColor = $pdfcolor;
4667 $this->strokecolor = $spotcolor;
4668 break;
4670 case 'fill': {
4671 $pdfcolor .= sprintf('cs %F scn', $tint);
4672 $this->FillColor = $pdfcolor;
4673 $this->bgcolor = $spotcolor;
4674 break;
4676 case 'text': {
4677 $pdfcolor .= sprintf('cs %F scn', $tint);
4678 $this->TextColor = $pdfcolor;
4679 $this->fgcolor = $spotcolor;
4680 break;
4683 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4684 if ($this->state == 2) {
4685 $this->_out($pdfcolor);
4687 if ($this->inxobj) {
4688 // we are inside an XObject template
4689 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
4691 return $pdfcolor;
4695 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
4696 * @param $name (string) Name of the spot color.
4697 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4698 * @public
4699 * @since 4.0.024 (2008-09-12)
4700 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4702 public function SetDrawSpotColor($name, $tint=100) {
4703 $this->setSpotColor('draw', $name, $tint);
4707 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
4708 * @param $name (string) Name of the spot color.
4709 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4710 * @public
4711 * @since 4.0.024 (2008-09-12)
4712 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
4714 public function SetFillSpotColor($name, $tint=100) {
4715 $this->setSpotColor('fill', $name, $tint);
4719 * Defines the spot color used for text.
4720 * @param $name (string) Name of the spot color.
4721 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4722 * @public
4723 * @since 4.0.024 (2008-09-12)
4724 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
4726 public function SetTextSpotColor($name, $tint=100) {
4727 $this->setSpotColor('text', $name, $tint);
4731 * Set the color array for the specified type ('draw', 'fill', 'text').
4732 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4733 * The method can be called before the first page is created and the value is retained from page to page.
4734 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4735 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
4736 * @param $ret (boolean) If true do not send the PDF command.
4737 * @return (string) The PDF command or empty string.
4738 * @public
4739 * @since 3.1.000 (2008-06-11)
4741 public function setColorArray($type, $color, $ret=false) {
4742 if (is_array($color)) {
4743 $color = array_values($color);
4744 // component: grey, RGB red or CMYK cyan
4745 $c = isset($color[0]) ? $color[0] : -1;
4746 // component: RGB green or CMYK magenta
4747 $m = isset($color[1]) ? $color[1] : -1;
4748 // component: RGB blue or CMYK yellow
4749 $y = isset($color[2]) ? $color[2] : -1;
4750 // component: CMYK black
4751 $k = isset($color[3]) ? $color[3] : -1;
4752 // color name
4753 $name = isset($color[4]) ? $color[4] : '';
4754 if ($c >= 0) {
4755 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
4758 return '';
4762 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
4763 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4764 * The method can be called before the first page is created and the value is retained from page to page.
4765 * @param $color (array) Array of colors (1, 3 or 4 values).
4766 * @param $ret (boolean) If true do not send the PDF command.
4767 * @return string the PDF command
4768 * @public
4769 * @since 3.1.000 (2008-06-11)
4770 * @see SetDrawColor()
4772 public function SetDrawColorArray($color, $ret=false) {
4773 return $this->setColorArray('draw', $color, $ret);
4777 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
4778 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4779 * The method can be called before the first page is created and the value is retained from page to page.
4780 * @param $color (array) Array of colors (1, 3 or 4 values).
4781 * @param $ret (boolean) If true do not send the PDF command.
4782 * @public
4783 * @since 3.1.000 (2008-6-11)
4784 * @see SetFillColor()
4786 public function SetFillColorArray($color, $ret=false) {
4787 return $this->setColorArray('fill', $color, $ret);
4791 * Defines the color used for text. It can be expressed in RGB components or gray scale.
4792 * The method can be called before the first page is created and the value is retained from page to page.
4793 * @param $color (array) Array of colors (1, 3 or 4 values).
4794 * @param $ret (boolean) If true do not send the PDF command.
4795 * @public
4796 * @since 3.1.000 (2008-6-11)
4797 * @see SetFillColor()
4799 public function SetTextColorArray($color, $ret=false) {
4800 return $this->setColorArray('text', $color, $ret);
4804 * Defines the color used by the specified type ('draw', 'fill', 'text').
4805 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4806 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4807 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4808 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4809 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4810 * @param $ret (boolean) If true do not send the command.
4811 * @param $name (string) spot color name (if any)
4812 * @return (string) The PDF command or empty string.
4813 * @public
4814 * @since 5.9.125 (2011-10-03)
4816 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4817 // set default values
4818 if (!is_numeric($col1)) {
4819 $col1 = 0;
4821 if (!is_numeric($col2)) {
4822 $col2 = -1;
4824 if (!is_numeric($col3)) {
4825 $col3 = -1;
4827 if (!is_numeric($col4)) {
4828 $col4 = -1;
4830 // set color by case
4831 $suffix = '';
4832 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4833 // Grey scale
4834 $col1 = max(0, min(255, $col1));
4835 $intcolor = array('G' => $col1);
4836 $pdfcolor = sprintf('%F ', ($col1 / 255));
4837 $suffix = 'g';
4838 } elseif ($col4 == -1) {
4839 // RGB
4840 $col1 = max(0, min(255, $col1));
4841 $col2 = max(0, min(255, $col2));
4842 $col3 = max(0, min(255, $col3));
4843 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4844 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4845 $suffix = 'rg';
4846 } else {
4847 $col1 = max(0, min(100, $col1));
4848 $col2 = max(0, min(100, $col2));
4849 $col3 = max(0, min(100, $col3));
4850 $col4 = max(0, min(100, $col4));
4851 if (empty($name)) {
4852 // CMYK
4853 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4854 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4855 $suffix = 'k';
4856 } else {
4857 // SPOT COLOR
4858 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4859 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4860 $pdfcolor = $this->setSpotColor($type, $name, 100);
4863 switch ($type) {
4864 case 'draw': {
4865 $pdfcolor .= strtoupper($suffix);
4866 $this->DrawColor = $pdfcolor;
4867 $this->strokecolor = $intcolor;
4868 break;
4870 case 'fill': {
4871 $pdfcolor .= $suffix;
4872 $this->FillColor = $pdfcolor;
4873 $this->bgcolor = $intcolor;
4874 break;
4876 case 'text': {
4877 $pdfcolor .= $suffix;
4878 $this->TextColor = $pdfcolor;
4879 $this->fgcolor = $intcolor;
4880 break;
4883 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4884 if (($type != 'text') AND ($this->state == 2)) {
4885 if (!$ret) {
4886 $this->_out($pdfcolor);
4888 return $pdfcolor;
4890 return '';
4894 * Convert a color array into a string representation.
4895 * @param $c (array) Array of colors.
4896 * @return (string) The color array representation.
4897 * @protected
4898 * @since 5.9.137 (2011-12-01)
4900 protected function getColorStringFromArray($c) {
4901 $c = array_values($c);
4902 $color = '[';
4903 switch (count($c)) {
4904 case 4: {
4905 // CMYK
4906 $color .= sprintf('%F %F %F %F', (max(0, min(100, floatval($c[0]))) / 100), (max(0, min(100, floatval($c[1]))) / 100), (max(0, min(100, floatval($c[2]))) / 100), (max(0, min(100, floatval($c[3]))) / 100));
4907 break;
4909 case 3: {
4910 // RGB
4911 $color .= sprintf('%F %F %F', (max(0, min(255, floatval($c[0]))) / 255), (max(0, min(255, floatval($c[1]))) / 255), (max(0, min(255, floatval($c[2]))) / 255));
4912 break;
4914 case 1: {
4915 // grayscale
4916 $color .= sprintf('%F', (max(0, min(255, floatval($c[0]))) / 255));
4917 break;
4920 $color .= ']';
4921 return $color;
4925 * 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.
4926 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4927 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4928 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4929 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4930 * @param $ret (boolean) If true do not send the command.
4931 * @param $name (string) spot color name (if any)
4932 * @return string the PDF command
4933 * @public
4934 * @since 1.3
4935 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4937 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4938 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4942 * 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.
4943 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4944 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4945 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4946 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4947 * @param $ret (boolean) If true do not send the command.
4948 * @param $name (string) Spot color name (if any).
4949 * @return (string) The PDF command.
4950 * @public
4951 * @since 1.3
4952 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4954 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4955 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4959 * 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.
4960 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4961 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4962 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4963 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4964 * @param $ret (boolean) If true do not send the command.
4965 * @param $name (string) Spot color name (if any).
4966 * @return (string) Empty string.
4967 * @public
4968 * @since 1.3
4969 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4971 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4972 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4976 * Returns the length of a string in user unit. A font must be selected.<br>
4977 * @param $s (string) The string whose length is to be computed
4978 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4979 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4980 * @param $fontsize (float) Font size in points. The default value is the current size.
4981 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4982 * @return mixed int total string length or array of characted widths
4983 * @author Nicola Asuni
4984 * @public
4985 * @since 1.2
4987 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4988 return $this->GetArrStringWidth($this->utf8Bidi($this->UTF8StringToArray($s), $s, $this->tmprtl), $fontname, $fontstyle, $fontsize, $getarray);
4992 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4993 * @param $sa (string) The array of chars whose total length is to be computed
4994 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4995 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4996 * @param $fontsize (float) Font size in points. The default value is the current size.
4997 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4998 * @return mixed int total string length or array of characted widths
4999 * @author Nicola Asuni
5000 * @public
5001 * @since 2.4.000 (2008-03-06)
5003 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
5004 // store current values
5005 if (!$this->empty_string($fontname)) {
5006 $prev_FontFamily = $this->FontFamily;
5007 $prev_FontStyle = $this->FontStyle;
5008 $prev_FontSizePt = $this->FontSizePt;
5009 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
5011 // convert UTF-8 array to Latin1 if required
5012 $sa = $this->UTF8ArrToLatin1($sa);
5013 $w = 0; // total width
5014 $wa = array(); // array of characters widths
5015 foreach ($sa as $ck => $char) {
5016 // character width
5017 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
5018 $wa[] = $cw;
5019 $w += $cw;
5021 // restore previous values
5022 if (!$this->empty_string($fontname)) {
5023 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
5025 if ($getarray) {
5026 return $wa;
5028 return $w;
5032 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
5033 * @param $char (int) The char code whose length is to be returned
5034 * @param $notlast (boolean) If false ignore the font-spacing.
5035 * @return float char width
5036 * @author Nicola Asuni
5037 * @public
5038 * @since 2.4.000 (2008-03-06)
5040 public function GetCharWidth($char, $notlast=true) {
5041 // get raw width
5042 $chw = $this->getRawCharWidth($char);
5043 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
5044 // increase/decrease font spacing
5045 $chw += $this->font_spacing;
5047 if ($this->font_stretching != 100) {
5048 // fixed stretching mode
5049 $chw *= ($this->font_stretching / 100);
5051 return $chw;
5055 * Returns the length of the char in user unit for the current font.
5056 * @param $char (int) The char code whose length is to be returned
5057 * @return float char width
5058 * @author Nicola Asuni
5059 * @public
5060 * @since 5.9.000 (2010-09-28)
5062 public function getRawCharWidth($char) {
5063 if ($char == 173) {
5064 // SHY character will not be printed
5065 return (0);
5067 if (isset($this->CurrentFont['cw'][$char])) {
5068 $w = $this->CurrentFont['cw'][$char];
5069 } elseif (isset($this->CurrentFont['dw'])) {
5070 // default width
5071 $w = $this->CurrentFont['dw'];
5072 } elseif (isset($this->CurrentFont['cw'][32])) {
5073 // default width
5074 $w = $this->CurrentFont['cw'][32];
5075 } else {
5076 $w = 600;
5078 return $this->getAbsFontMeasure($w);
5082 * Returns the numbero of characters in a string.
5083 * @param $s (string) The input string.
5084 * @return int number of characters
5085 * @public
5086 * @since 2.0.0001 (2008-01-07)
5088 public function GetNumChars($s) {
5089 if ($this->isUnicodeFont()) {
5090 return count($this->UTF8StringToArray($s));
5092 return strlen($s);
5096 * Fill the list of available fonts ($this->fontlist).
5097 * @protected
5098 * @since 4.0.013 (2008-07-28)
5100 protected function getFontsList() {
5101 if (($fontsdir = opendir($this->_getfontpath())) !== false) {
5102 while (($file = readdir($fontsdir)) !== false) {
5103 if (substr($file, -4) == '.php') {
5104 array_push($this->fontlist, strtolower(basename($file, '.php')));
5107 closedir($fontsdir);
5112 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
5113 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
5114 * 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.
5115 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
5116 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
5117 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
5118 * @return array containing the font data, or false in case of error.
5119 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
5120 * @public
5121 * @since 1.5
5122 * @see SetFont(), setFontSubsetting()
5124 public function AddFont($family, $style='', $fontfile='', $subset='default') {
5125 if ($subset === 'default') {
5126 $subset = $this->font_subsetting;
5128 if ($this->pdfa_mode) {
5129 $subset = false;
5131 if ($this->empty_string($family)) {
5132 if (!$this->empty_string($this->FontFamily)) {
5133 $family = $this->FontFamily;
5134 } else {
5135 $this->Error('Empty font family');
5138 // move embedded styles on $style
5139 if (substr($family, -1) == 'I') {
5140 $style .= 'I';
5141 $family = substr($family, 0, -1);
5143 if (substr($family, -1) == 'B') {
5144 $style .= 'B';
5145 $family = substr($family, 0, -1);
5147 // normalize family name
5148 $family = strtolower($family);
5149 if ((!$this->isunicode) AND ($family == 'arial')) {
5150 $family = 'helvetica';
5152 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
5153 $style = '';
5155 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
5156 // all fonts must be embedded
5157 $family = 'pdfa'.$family;
5159 $tempstyle = strtoupper($style);
5160 $style = '';
5161 // underline
5162 if (strpos($tempstyle, 'U') !== false) {
5163 $this->underline = true;
5164 } else {
5165 $this->underline = false;
5167 // line-through (deleted)
5168 if (strpos($tempstyle, 'D') !== false) {
5169 $this->linethrough = true;
5170 } else {
5171 $this->linethrough = false;
5173 // overline
5174 if (strpos($tempstyle, 'O') !== false) {
5175 $this->overline = true;
5176 } else {
5177 $this->overline = false;
5179 // bold
5180 if (strpos($tempstyle, 'B') !== false) {
5181 $style .= 'B';
5183 // oblique
5184 if (strpos($tempstyle, 'I') !== false) {
5185 $style .= 'I';
5187 $bistyle = $style;
5188 $fontkey = $family.$style;
5189 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
5190 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
5191 // check if the font has been already added
5192 $fb = $this->getFontBuffer($fontkey);
5193 if ($fb !== false) {
5194 if ($this->inxobj) {
5195 // we are inside an XObject template
5196 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
5198 return $fontdata;
5200 if (isset($type)) {
5201 unset($type);
5203 if (isset($cw)) {
5204 unset($cw);
5206 // get specified font directory (if any)
5207 $fontdir = false;
5208 if (!$this->empty_string($fontfile)) {
5209 $fontdir = dirname($fontfile);
5210 if ($this->empty_string($fontdir) OR ($fontdir == '.')) {
5211 $fontdir = '';
5212 } else {
5213 $fontdir .= '/';
5216 $missing_style = false; // true when the font style variation is missing
5217 // search and include font file
5218 if ($this->empty_string($fontfile) OR (!file_exists($fontfile))) {
5219 // build a standard filenames for specified font
5220 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
5221 // search files on various directories
5222 if (($fontdir !== false) AND file_exists($fontdir.$tmp_fontfile)) {
5223 $fontfile = $fontdir.$tmp_fontfile;
5224 } elseif (file_exists($this->_getfontpath().$tmp_fontfile)) {
5225 $fontfile = $this->_getfontpath().$tmp_fontfile;
5226 } elseif (file_exists($tmp_fontfile)) {
5227 $fontfile = $tmp_fontfile;
5228 } elseif (!$this->empty_string($style)) {
5229 $missing_style = true;
5230 // try to remove the style part
5231 $tmp_fontfile = str_replace(' ', '', $family).'.php';
5232 if (($fontdir !== false) AND file_exists($fontdir.$tmp_fontfile)) {
5233 $fontfile = $fontdir.$tmp_fontfile;
5234 } elseif (file_exists($this->_getfontpath().$tmp_fontfile)) {
5235 $fontfile = $this->_getfontpath().$tmp_fontfile;
5236 } else {
5237 $fontfile = $tmp_fontfile;
5241 // include font file
5242 if (file_exists($fontfile)) {
5243 include($fontfile);
5244 } else {
5245 $this->Error('Could not include font definition file: '.$family.'');
5247 // check font parameters
5248 if ((!isset($type)) OR (!isset($cw))) {
5249 $this->Error('The font definition file has a bad format: '.$fontfile.'');
5251 // SET default parameters
5252 if (!isset($file) OR $this->empty_string($file)) {
5253 $file = '';
5255 if (!isset($enc) OR $this->empty_string($enc)) {
5256 $enc = '';
5258 if (!isset($cidinfo) OR $this->empty_string($cidinfo)) {
5259 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
5260 $cidinfo['uni2cid'] = array();
5262 if (!isset($ctg) OR $this->empty_string($ctg)) {
5263 $ctg = '';
5265 if (!isset($desc) OR $this->empty_string($desc)) {
5266 $desc = array();
5268 if (!isset($up) OR $this->empty_string($up)) {
5269 $up = -100;
5271 if (!isset($ut) OR $this->empty_string($ut)) {
5272 $ut = 50;
5274 if (!isset($cw) OR $this->empty_string($cw)) {
5275 $cw = array();
5277 if (!isset($dw) OR $this->empty_string($dw)) {
5278 // set default width
5279 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
5280 $dw = $desc['MissingWidth'];
5281 } elseif (isset($cw[32])) {
5282 $dw = $cw[32];
5283 } else {
5284 $dw = 600;
5287 ++$this->numfonts;
5288 if ($type == 'core') {
5289 $name = $this->CoreFonts[$fontkey];
5290 $subset = false;
5291 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
5292 $subset = false;
5293 } elseif ($type == 'TrueTypeUnicode') {
5294 $enc = 'Identity-H';
5295 } elseif ($type == 'cidfont0') {
5296 if ($this->pdfa_mode) {
5297 $this->Error('All fonts must be embedded in PDF/A mode!');
5299 } else {
5300 $this->Error('Unknow font type: '.$type.'');
5302 // set name if unset
5303 if (!isset($name) OR empty($name)) {
5304 $name = $fontkey;
5306 // create artificial font style variations if missing (only works with non-embedded fonts)
5307 if (($type != 'core') AND $missing_style) {
5308 // style variations
5309 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
5310 $name .= $styles[$bistyle];
5311 // artificial bold
5312 if (strpos($bistyle, 'B') !== false) {
5313 if (isset($desc['StemV'])) {
5314 // from normal to bold
5315 $desc['StemV'] = round($desc['StemV'] * 1.75);
5316 } else {
5317 // bold
5318 $desc['StemV'] = 123;
5321 // artificial italic
5322 if (strpos($bistyle, 'I') !== false) {
5323 if (isset($desc['ItalicAngle'])) {
5324 $desc['ItalicAngle'] -= 11;
5325 } else {
5326 $desc['ItalicAngle'] = -11;
5328 if (isset($desc['Flags'])) {
5329 $desc['Flags'] |= 64; //bit 7
5330 } else {
5331 $desc['Flags'] = 64;
5335 // check if the array of characters bounding boxes is defined
5336 if (!isset($cbbox)) {
5337 $cbbox = array();
5339 // initialize subsetchars
5340 $subsetchars = array_fill(0, 255, true);
5341 $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));
5342 if ($this->inxobj) {
5343 // we are inside an XObject template
5344 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
5346 if (isset($diff) AND (!empty($diff))) {
5347 //Search existing encodings
5348 $d = 0;
5349 $nb = count($this->diffs);
5350 for ($i=1; $i <= $nb; ++$i) {
5351 if ($this->diffs[$i] == $diff) {
5352 $d = $i;
5353 break;
5356 if ($d == 0) {
5357 $d = $nb + 1;
5358 $this->diffs[$d] = $diff;
5360 $this->setFontSubBuffer($fontkey, 'diff', $d);
5362 if (!$this->empty_string($file)) {
5363 if (!isset($this->FontFiles[$file])) {
5364 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
5365 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
5366 } elseif ($type != 'core') {
5367 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
5369 } else {
5370 // update fontkeys that are sharing this font file
5371 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
5372 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
5373 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
5377 return $fontdata;
5381 * Sets the font used to print character strings.
5382 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
5383 * The method can be called before the first page is created and the font is retained from page to page.
5384 * If you just wish to change the current font size, it is simpler to call SetFontSize().
5385 * 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 />
5386 * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
5387 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</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.
5388 * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
5389 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
5390 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
5391 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
5392 * @author Nicola Asuni
5393 * @public
5394 * @since 1.0
5395 * @see AddFont(), SetFontSize()
5397 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
5398 //Select a font; size given in points
5399 if ($size === null) {
5400 $size = $this->FontSizePt;
5402 if ($size < 0) {
5403 $size = 0;
5405 // try to add font (if not already added)
5406 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
5407 $this->FontFamily = $fontdata['family'];
5408 $this->FontStyle = $fontdata['style'];
5409 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
5410 $this->SetFontSize($size, $out);
5414 * Defines the size of the current font.
5415 * @param $size (float) The font size in points.
5416 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
5417 * @public
5418 * @since 1.0
5419 * @see SetFont()
5421 public function SetFontSize($size, $out=true) {
5422 // font size in points
5423 $this->FontSizePt = $size;
5424 // font size in user units
5425 $this->FontSize = $size / $this->k;
5426 // calculate some font metrics
5427 if (isset($this->CurrentFont['desc']['FontBBox'])) {
5428 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
5429 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
5430 } else {
5431 $font_height = $size * 1.219;
5433 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
5434 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
5436 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
5437 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
5439 if (!isset($font_ascent) AND !isset($font_descent)) {
5440 // core font
5441 $font_ascent = 0.76 * $font_height;
5442 $font_descent = $font_height - $font_ascent;
5443 } elseif (!isset($font_descent)) {
5444 $font_descent = $font_height - $font_ascent;
5445 } elseif (!isset($font_ascent)) {
5446 $font_ascent = $font_height - $font_descent;
5448 $this->FontAscent = ($font_ascent / $this->k);
5449 $this->FontDescent = ($font_descent / $this->k);
5450 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
5451 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
5456 * Returns the bounding box of the current font in user units.
5457 * @return array
5458 * @public
5459 * @since 5.9.152 (2012-03-23)
5461 public function getFontBBox() {
5462 $fbbox = array();
5463 if (isset($this->CurrentFont['desc']['FontBBox'])) {
5464 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
5465 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
5466 } else {
5467 // Find max width
5468 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
5469 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
5470 } else {
5471 $maxw = 0;
5472 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
5473 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
5475 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
5476 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
5478 if (isset($this->CurrentFont['dw'])) {
5479 $maxw = max($maxw, $this->CurrentFont['dw']);
5481 foreach ($this->CurrentFont['cw'] as $char => $w) {
5482 $maxw = max($maxw, $w);
5484 if ($maxw == 0) {
5485 $maxw = 600;
5487 $maxw = $this->getAbsFontMeasure($maxw);
5489 $fbbox = array(0, -$this->FontDescent, $maxw, $this->FontAscent);
5491 return $fbbox;
5495 * Convert a relative font measure into absolute value.
5496 * @param $s (int) Font measure.
5497 * @return float Absolute measure.
5498 * @since 5.9.186 (2012-09-13)
5500 public function getAbsFontMeasure($s) {
5501 return ($s * $this->FontSize / 1000);
5505 * Returns the glyph bounding box of the specified character in the current font in user units.
5506 * @param $char (int) Input character code.
5507 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
5508 * @since 5.9.186 (2012-09-13)
5510 public function getCharBBox($char) {
5511 if (isset($this->CurrentFont['cbbox'][$char])) {
5512 return array_map(array($this,'getAbsFontMeasure'), $this->CurrentFont['cbbox'][intval($char)]);
5514 return false;
5518 * Return the font descent value
5519 * @param $font (string) font name
5520 * @param $style (string) font style
5521 * @param $size (float) The size (in points)
5522 * @return int font descent
5523 * @public
5524 * @author Nicola Asuni
5525 * @since 4.9.003 (2010-03-30)
5527 public function getFontDescent($font, $style='', $size=0) {
5528 $fontdata = $this->AddFont($font, $style);
5529 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5530 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
5531 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
5532 } else {
5533 $descent = (1.219 * 0.24 * $size);
5535 return ($descent / $this->k);
5539 * Return the font ascent value.
5540 * @param $font (string) font name
5541 * @param $style (string) font style
5542 * @param $size (float) The size (in points)
5543 * @return int font ascent
5544 * @public
5545 * @author Nicola Asuni
5546 * @since 4.9.003 (2010-03-30)
5548 public function getFontAscent($font, $style='', $size=0) {
5549 $fontdata = $this->AddFont($font, $style);
5550 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5551 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
5552 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
5553 } else {
5554 $ascent = 1.219 * 0.76 * $size;
5556 return ($ascent / $this->k);
5560 * Return true in the character is present in the specified font.
5561 * @param $char (mixed) Character to check (integer value or string)
5562 * @param $font (string) Font name (family name).
5563 * @param $style (string) Font style.
5564 * @return (boolean) true if the char is defined, false otherwise.
5565 * @public
5566 * @since 5.9.153 (2012-03-28)
5568 public function isCharDefined($char, $font='', $style='') {
5569 if (is_string($char)) {
5570 // get character code
5571 $char = $this->UTF8StringToArray($char);
5572 $char = $char[0];
5574 if ($this->empty_string($font)) {
5575 if ($this->empty_string($style)) {
5576 return (isset($this->CurrentFont['cw'][intval($char)]));
5578 $font = $this->FontFamily;
5580 $fontdata = $this->AddFont($font, $style);
5581 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5582 return (isset($fontinfo['cw'][intval($char)]));
5586 * Replace missing font characters on selected font with specified substitutions.
5587 * @param $text (string) Text to process.
5588 * @param $font (string) Font name (family name).
5589 * @param $style (string) Font style.
5590 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
5591 * @return (string) Processed text.
5592 * @public
5593 * @since 5.9.153 (2012-03-28)
5595 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
5596 if (empty($subs)) {
5597 return $text;
5599 if ($this->empty_string($font)) {
5600 $font = $this->FontFamily;
5602 $fontdata = $this->AddFont($font, $style);
5603 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5604 $uniarr = $this->UTF8StringToArray($text);
5605 foreach ($uniarr as $k => $chr) {
5606 if (!isset($fontinfo['cw'][$chr])) {
5607 // this character is missing on the selected font
5608 if (isset($subs[$chr])) {
5609 // we have available substitutions
5610 if (is_array($subs[$chr])) {
5611 foreach($subs[$chr] as $s) {
5612 if (isset($fontinfo['cw'][$s])) {
5613 $uniarr[$k] = $s;
5614 break;
5617 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
5618 $uniarr[$k] = $subs[$chr];
5623 return $this->UniArrSubString($this->UTF8ArrayToUniArray($uniarr));
5627 * Defines the default monospaced font.
5628 * @param $font (string) Font name.
5629 * @public
5630 * @since 4.5.025
5632 public function SetDefaultMonospacedFont($font) {
5633 $this->default_monospaced_font = $font;
5637 * 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 />
5638 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
5639 * @public
5640 * @since 1.5
5641 * @see Cell(), Write(), Image(), Link(), SetLink()
5643 public function AddLink() {
5644 //Create a new internal link
5645 $n = count($this->links) + 1;
5646 $this->links[$n] = array(0, 0);
5647 return $n;
5651 * Defines the page and position a link points to.
5652 * @param $link (int) The link identifier returned by AddLink()
5653 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
5654 * @param $page (int) Number of target page; -1 indicates the current page. This is the default value
5655 * @public
5656 * @since 1.5
5657 * @see AddLink()
5659 public function SetLink($link, $y=0, $page=-1) {
5660 if ($y == -1) {
5661 $y = $this->y;
5663 if ($page == -1) {
5664 $page = $this->page;
5666 $this->links[$link] = array($page, $y);
5670 * Puts a link on a rectangular area of the page.
5671 * 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.
5672 * @param $x (float) Abscissa of the upper-left corner of the rectangle
5673 * @param $y (float) Ordinate of the upper-left corner of the rectangle
5674 * @param $w (float) Width of the rectangle
5675 * @param $h (float) Height of the rectangle
5676 * @param $link (mixed) URL or identifier returned by AddLink()
5677 * @param $spaces (int) number of spaces on the text to link
5678 * @public
5679 * @since 1.5
5680 * @see AddLink(), Annotation(), Cell(), Write(), Image()
5682 public function Link($x, $y, $w, $h, $link, $spaces=0) {
5683 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
5687 * Check if the URL exist.
5688 * @param $ur (string) URL to check.
5689 * @return Boolean true if the URl exist, false otherwise.
5690 * @public
5691 * @since 5.9.204 (2013-01-28)
5693 public function isValidURL($url) {
5694 $headers = @get_headers($url);
5695 return (strpos($headers[0], '200') !== false);
5699 * Puts a markup annotation on a rectangular area of the page.
5700 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
5701 * @param $x (float) Abscissa of the upper-left corner of the rectangle
5702 * @param $y (float) Ordinate of the upper-left corner of the rectangle
5703 * @param $w (float) Width of the rectangle
5704 * @param $h (float) Height of the rectangle
5705 * @param $text (string) annotation text or alternate content
5706 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
5707 * @param $spaces (int) number of spaces on the text to link
5708 * @public
5709 * @since 4.0.018 (2008-08-06)
5711 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
5712 if ($this->inxobj) {
5713 // store parameters for later use on template
5714 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
5715 return;
5717 if ($x === '') {
5718 $x = $this->x;
5720 if ($y === '') {
5721 $y = $this->y;
5723 // check page for no-write regions and adapt page margins if necessary
5724 list($x, $y) = $this->checkPageRegions($h, $x, $y);
5725 // recalculate coordinates to account for graphic transformations
5726 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
5727 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
5728 $maxid = count($this->transfmatrix[$i]) - 1;
5729 for ($j=$maxid; $j >= 0; --$j) {
5730 $ctm = $this->transfmatrix[$i][$j];
5731 if (isset($ctm['a'])) {
5732 $x = $x * $this->k;
5733 $y = ($this->h - $y) * $this->k;
5734 $w = $w * $this->k;
5735 $h = $h * $this->k;
5736 // top left
5737 $xt = $x;
5738 $yt = $y;
5739 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5740 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5741 // top right
5742 $xt = $x + $w;
5743 $yt = $y;
5744 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5745 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5746 // bottom left
5747 $xt = $x;
5748 $yt = $y - $h;
5749 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5750 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5751 // bottom right
5752 $xt = $x + $w;
5753 $yt = $y - $h;
5754 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5755 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5756 // new coordinates (rectangle area)
5757 $x = min($x1, $x2, $x3, $x4);
5758 $y = max($y1, $y2, $y3, $y4);
5759 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
5760 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
5761 $x = $x / $this->k;
5762 $y = $this->h - ($y / $this->k);
5767 if ($this->page <= 0) {
5768 $page = 1;
5769 } else {
5770 $page = $this->page;
5772 if (!isset($this->PageAnnots[$page])) {
5773 $this->PageAnnots[$page] = array();
5775 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
5776 if (!$this->pdfa_mode) {
5777 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!$this->empty_string($opt['FS']))
5778 AND (file_exists($opt['FS']) OR $this->isValidURL($opt['FS']))
5779 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
5780 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
5783 // Add widgets annotation's icons
5784 if (isset($opt['mk']['i']) AND file_exists($opt['mk']['i'])) {
5785 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
5787 if (isset($opt['mk']['ri']) AND file_exists($opt['mk']['ri'])) {
5788 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5790 if (isset($opt['mk']['ix']) AND file_exists($opt['mk']['ix'])) {
5791 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5796 * Embedd the attached files.
5797 * @since 4.4.000 (2008-12-07)
5798 * @protected
5799 * @see Annotation()
5801 protected function _putEmbeddedFiles() {
5802 if ($this->pdfa_mode) {
5803 // embedded files are not allowed in PDF/A mode
5804 return;
5806 reset($this->embeddedfiles);
5807 foreach ($this->embeddedfiles as $filename => $filedata) {
5808 // update name tree
5809 $this->efnames[$filename] = $filedata['f'].' 0 R';
5810 // embedded file specification object
5811 $out = $this->_getobj($filedata['f'])."\n";
5812 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
5813 $out .= "\n".'endobj';
5814 $this->_out($out);
5815 // embedded file object
5816 $data = file_get_contents($filedata['file']);
5817 $filter = '';
5818 $rawsize = strlen($data);
5819 if ($this->compress) {
5820 $data = gzcompress($data);
5821 $filter = ' /Filter /FlateDecode';
5823 $stream = $this->_getrawstream($data, $filedata['n']);
5824 $out = $this->_getobj($filedata['n'])."\n";
5825 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
5826 $out .= ' stream'."\n".$stream."\n".'endstream';
5827 $out .= "\n".'endobj';
5828 $this->_out($out);
5833 * Prints a text cell at the specified position.
5834 * This method allows to place a string precisely on the page.
5835 * @param $x (float) Abscissa of the cell origin
5836 * @param $y (float) Ordinate of the cell origin
5837 * @param $txt (string) String to print
5838 * @param $fstroke (int) outline size in user units (false = disable)
5839 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
5840 * @param $ffill (boolean) if true fills the text
5841 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5842 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5843 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5844 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5845 * @param $link (mixed) URL or identifier returned by AddLink().
5846 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5847 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5848 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
5849 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5850 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
5851 * @public
5852 * @since 1.0
5853 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5855 public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5856 $textrendermode = $this->textrendermode;
5857 $textstrokewidth = $this->textstrokewidth;
5858 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
5859 $this->SetXY($x, $y, $rtloff);
5860 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5861 // restore previous rendering mode
5862 $this->textrendermode = $textrendermode;
5863 $this->textstrokewidth = $textstrokewidth;
5867 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5868 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5869 * This method is called automatically and should not be called directly by the application.
5870 * @return boolean
5871 * @public
5872 * @since 1.4
5873 * @see SetAutoPageBreak()
5875 public function AcceptPageBreak() {
5876 if ($this->num_columns > 1) {
5877 // multi column mode
5878 if ($this->current_column < ($this->num_columns - 1)) {
5879 // go to next column
5880 $this->selectColumn($this->current_column + 1);
5881 } elseif ($this->AutoPageBreak) {
5882 // add a new page
5883 $this->AddPage();
5884 // set first column
5885 $this->selectColumn(0);
5887 // avoid page breaking from checkPageBreak()
5888 return false;
5890 return $this->AutoPageBreak;
5894 * Add page if needed.
5895 * @param $h (float) Cell height. Default value: 0.
5896 * @param $y (mixed) starting y position, leave empty for current position.
5897 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
5898 * @return boolean true in case of page break, false otherwise.
5899 * @since 3.2.000 (2008-07-01)
5900 * @protected
5902 protected function checkPageBreak($h=0, $y='', $addpage=true) {
5903 if ($this->empty_string($y)) {
5904 $y = $this->y;
5906 $current_page = $this->page;
5907 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5908 if ($addpage) {
5909 //Automatic page break
5910 $x = $this->x;
5911 $this->AddPage($this->CurOrientation);
5912 $this->y = $this->tMargin;
5913 $oldpage = $this->page - 1;
5914 if ($this->rtl) {
5915 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5916 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5917 } else {
5918 $this->x = $x;
5920 } else {
5921 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5922 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5923 } else {
5924 $this->x = $x;
5928 return true;
5930 if ($current_page != $this->page) {
5931 // account for columns mode
5932 return true;
5934 return false;
5938 * Removes SHY characters from text.
5939 * Unicode Data:<ul>
5940 * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
5941 * <li>HTML Entity (decimal): "&amp;#173;"</li>
5942 * <li>HTML Entity (hex): "&amp;#xad;"</li>
5943 * <li>HTML Entity (named): "&amp;shy;"</li>
5944 * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
5945 * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
5946 * <li>UTF-8 character: chr(194).chr(173)</li>
5947 * </ul>
5948 * @param $txt (string) input string
5949 * @return string without SHY characters.
5950 * @public
5951 * @since (4.5.019) 2009-02-28
5953 public function removeSHY($txt='') {
5954 $txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
5955 if (!$this->isunicode) {
5956 $txt = preg_replace('/([\\xad]{1})/', '', $txt);
5958 return $txt;
5962 * 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 />
5963 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5964 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5965 * @param $h (float) Cell height. Default value: 0.
5966 * @param $txt (string) String to print. Default value: empty string.
5967 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5968 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5969 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5970 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5971 * @param $link (mixed) URL or identifier returned by AddLink().
5972 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5973 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5974 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5975 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5976 * @public
5977 * @since 1.0
5978 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5980 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') {
5981 $prev_cell_margin = $this->cell_margin;
5982 $prev_cell_padding = $this->cell_padding;
5983 $this->adjustCellPadding($border);
5984 if (!$ignore_min_height) {
5985 $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5986 if ($h < $min_cell_height) {
5987 $h = $min_cell_height;
5990 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5991 // apply text shadow if enabled
5992 if ($this->txtshadow['enabled']) {
5993 // save data
5994 $x = $this->x;
5995 $y = $this->y;
5996 $bc = $this->bgcolor;
5997 $fc = $this->fgcolor;
5998 $sc = $this->strokecolor;
5999 $alpha = $this->alpha;
6000 // print shadow
6001 $this->x += $this->txtshadow['depth_w'];
6002 $this->y += $this->txtshadow['depth_h'];
6003 $this->SetFillColorArray($this->txtshadow['color']);
6004 $this->SetTextColorArray($this->txtshadow['color']);
6005 $this->SetDrawColorArray($this->txtshadow['color']);
6006 if ($this->txtshadow['opacity'] != $alpha['CA']) {
6007 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
6009 if ($this->state == 2) {
6010 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
6012 //restore data
6013 $this->x = $x;
6014 $this->y = $y;
6015 $this->SetFillColorArray($bc);
6016 $this->SetTextColorArray($fc);
6017 $this->SetDrawColorArray($sc);
6018 if ($this->txtshadow['opacity'] != $alpha['CA']) {
6019 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
6022 if ($this->state == 2) {
6023 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
6025 $this->cell_padding = $prev_cell_padding;
6026 $this->cell_margin = $prev_cell_margin;
6030 * 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 />
6031 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
6032 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
6033 * @param $h (float) Cell height. Default value: 0.
6034 * @param $txt (string) String to print. Default value: empty string.
6035 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6036 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
6037 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6038 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6039 * @param $link (mixed) URL or identifier returned by AddLink().
6040 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6041 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
6042 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
6043 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
6044 * @return string containing cell code
6045 * @protected
6046 * @since 1.0
6047 * @see Cell()
6049 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') {
6050 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
6051 $txt = str_replace($this->unichr(160), ' ', $txt);
6052 $prev_cell_margin = $this->cell_margin;
6053 $prev_cell_padding = $this->cell_padding;
6054 $txt = $this->removeSHY($txt);
6055 $rs = ''; //string to be returned
6056 $this->adjustCellPadding($border);
6057 if (!$ignore_min_height) {
6058 $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
6059 if ($h < $min_cell_height) {
6060 $h = $min_cell_height;
6063 $k = $this->k;
6064 // check page for no-write regions and adapt page margins if necessary
6065 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6066 if ($this->rtl) {
6067 $x = $this->x - $this->cell_margin['R'];
6068 } else {
6069 $x = $this->x + $this->cell_margin['L'];
6071 $y = $this->y + $this->cell_margin['T'];
6072 $prev_font_stretching = $this->font_stretching;
6073 $prev_font_spacing = $this->font_spacing;
6074 // cell vertical alignment
6075 switch ($calign) {
6076 case 'A': {
6077 // font top
6078 switch ($valign) {
6079 case 'T': {
6080 // top
6081 $y -= $this->cell_padding['T'];
6082 break;
6084 case 'B': {
6085 // bottom
6086 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
6087 break;
6089 default:
6090 case 'C':
6091 case 'M': {
6092 // center
6093 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
6094 break;
6097 break;
6099 case 'L': {
6100 // font baseline
6101 switch ($valign) {
6102 case 'T': {
6103 // top
6104 $y -= ($this->cell_padding['T'] + $this->FontAscent);
6105 break;
6107 case 'B': {
6108 // bottom
6109 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
6110 break;
6112 default:
6113 case 'C':
6114 case 'M': {
6115 // center
6116 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
6117 break;
6120 break;
6122 case 'D': {
6123 // font bottom
6124 switch ($valign) {
6125 case 'T': {
6126 // top
6127 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
6128 break;
6130 case 'B': {
6131 // bottom
6132 $y -= ($h - $this->cell_padding['B']);
6133 break;
6135 default:
6136 case 'C':
6137 case 'M': {
6138 // center
6139 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
6140 break;
6143 break;
6145 case 'B': {
6146 // cell bottom
6147 $y -= $h;
6148 break;
6150 case 'C':
6151 case 'M': {
6152 // cell center
6153 $y -= ($h / 2);
6154 break;
6156 default:
6157 case 'T': {
6158 // cell top
6159 break;
6162 // text vertical alignment
6163 switch ($valign) {
6164 case 'T': {
6165 // top
6166 $yt = $y + $this->cell_padding['T'];
6167 break;
6169 case 'B': {
6170 // bottom
6171 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
6172 break;
6174 default:
6175 case 'C':
6176 case 'M': {
6177 // center
6178 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
6179 break;
6182 $basefonty = $yt + $this->FontAscent;
6183 if ($this->empty_string($w) OR ($w <= 0)) {
6184 if ($this->rtl) {
6185 $w = $x - $this->lMargin;
6186 } else {
6187 $w = $this->w - $this->rMargin - $x;
6190 $s = '';
6191 // fill and borders
6192 if (is_string($border) AND (strlen($border) == 4)) {
6193 // full border
6194 $border = 1;
6196 if ($fill OR ($border == 1)) {
6197 if ($fill) {
6198 $op = ($border == 1) ? 'B' : 'f';
6199 } else {
6200 $op = 'S';
6202 if ($this->rtl) {
6203 $xk = (($x - $w) * $k);
6204 } else {
6205 $xk = ($x * $k);
6207 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
6209 // draw borders
6210 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
6211 if ($txt != '') {
6212 $txt2 = $txt;
6213 if ($this->isunicode) {
6214 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
6215 $txt2 = $this->UTF8ToLatin1($txt2);
6216 } else {
6217 $unicode = $this->UTF8StringToArray($txt); // array of UTF-8 unicode values
6218 $unicode = $this->utf8Bidi($unicode, '', $this->tmprtl);
6219 // replace thai chars (if any)
6220 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
6221 // number of chars
6222 $numchars = count($unicode);
6223 // po pla, for far, for fan
6224 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
6225 // do chada, to patak
6226 $lowtail = array(0x0e0e, 0x0e0f);
6227 // mai hun arkad, sara i, sara ii, sara ue, sara uee
6228 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
6229 // mai ek, mai tho, mai tri, mai chattawa, karan
6230 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
6231 // sara u, sara uu, pinthu
6232 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
6233 $output = array();
6234 for ($i = 0; $i < $numchars; $i++) {
6235 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
6236 $ch0 = $unicode[$i];
6237 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
6238 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
6239 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
6240 if (in_array($ch0, $tonemark)) {
6241 if ($chn == 0x0e33) {
6242 // sara um
6243 if (in_array($ch1, $longtail)) {
6244 // tonemark at upper left
6245 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
6246 } else {
6247 // tonemark at upper right (normal position)
6248 $output[] = $ch0;
6250 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
6251 // tonemark at lower left
6252 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
6253 } elseif (in_array($ch1, $upvowel)) {
6254 if (in_array($ch2, $longtail)) {
6255 // tonemark at upper left
6256 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
6257 } else {
6258 // tonemark at upper right (normal position)
6259 $output[] = $ch0;
6261 } else {
6262 // tonemark at lower right
6263 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
6265 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
6266 // add lower left nikhahit and sara aa
6267 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
6268 $output[] = 0xf711;
6269 $this->CurrentFont['subsetchars'][0xf711] = true;
6270 $output[] = 0x0e32;
6271 $this->CurrentFont['subsetchars'][0x0e32] = true;
6272 } else {
6273 $output[] = $ch0;
6275 } elseif (in_array($ch1, $longtail)) {
6276 if ($ch0 == 0x0e31) {
6277 // lower left mai hun arkad
6278 $output[] = $this->replaceChar($ch0, 0xf710);
6279 } elseif (in_array($ch0, $upvowel)) {
6280 // lower left
6281 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
6282 } elseif ($ch0 == 0x0e47) {
6283 // lower left mai tai koo
6284 $output[] = $this->replaceChar($ch0, 0xf712);
6285 } else {
6286 // normal character
6287 $output[] = $ch0;
6289 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
6290 // lower vowel
6291 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
6292 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
6293 // yo ying without lower part
6294 $output[] = $this->replaceChar($ch0, 0xf70f);
6295 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
6296 // tho santan without lower part
6297 $output[] = $this->replaceChar($ch0, 0xf700);
6298 } else {
6299 $output[] = $ch0;
6301 } else {
6302 // non-thai character
6303 $output[] = $unicode[$i];
6306 $unicode = $output;
6307 // update font subsetchars
6308 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
6309 } // end of K_THAI_TOPCHARS
6310 $txt2 = $this->arrUTF8ToUTF16BE($unicode, false);
6313 $txt2 = $this->_escape($txt2);
6314 // get current text width (considering general font stretching and spacing)
6315 $txwidth = $this->GetStringWidth($txt);
6316 $width = $txwidth;
6317 // check for stretch mode
6318 if ($stretch > 0) {
6319 // calculate ratio between cell width and text width
6320 if ($width <= 0) {
6321 $ratio = 1;
6322 } else {
6323 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
6325 // check if stretching is required
6326 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
6327 // the text will be stretched to fit cell width
6328 if ($stretch > 2) {
6329 // set new character spacing
6330 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
6331 } else {
6332 // set new horizontal stretching
6333 $this->font_stretching *= $ratio;
6335 // recalculate text width (the text fills the entire cell)
6336 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6337 // reset alignment
6338 $align = '';
6341 if ($this->font_stretching != 100) {
6342 // apply font stretching
6343 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
6345 if ($this->font_spacing != 0) {
6346 // increase/decrease font spacing
6347 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
6349 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
6350 $s .= 'q '.$this->TextColor.' ';
6352 // rendering mode
6353 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
6354 // count number of spaces
6355 $ns = substr_count($txt, chr(32));
6356 // Justification
6357 $spacewidth = 0;
6358 if (($align == 'J') AND ($ns > 0)) {
6359 if ($this->isUnicodeFont()) {
6360 // get string width without spaces
6361 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
6362 // calculate average space width
6363 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / $this->FontSize;
6364 if ($this->font_stretching != 100) {
6365 // word spacing is affected by stretching
6366 $spacewidth /= ($this->font_stretching / 100);
6368 // set word position to be used with TJ operator
6369 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
6370 $unicode_justification = true;
6371 } else {
6372 // get string width
6373 $width = $txwidth;
6374 // new space width
6375 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
6376 if ($this->font_stretching != 100) {
6377 // word spacing (Tw) is affected by stretching
6378 $spacewidth /= ($this->font_stretching / 100);
6380 // set word spacing
6381 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
6383 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6385 // replace carriage return characters
6386 $txt2 = str_replace("\r", ' ', $txt2);
6387 switch ($align) {
6388 case 'C': {
6389 $dx = ($w - $width) / 2;
6390 break;
6392 case 'R': {
6393 if ($this->rtl) {
6394 $dx = $this->cell_padding['R'];
6395 } else {
6396 $dx = $w - $width - $this->cell_padding['R'];
6398 break;
6400 case 'L': {
6401 if ($this->rtl) {
6402 $dx = $w - $width - $this->cell_padding['L'];
6403 } else {
6404 $dx = $this->cell_padding['L'];
6406 break;
6408 case 'J':
6409 default: {
6410 if ($this->rtl) {
6411 $dx = $this->cell_padding['R'];
6412 } else {
6413 $dx = $this->cell_padding['L'];
6415 break;
6418 if ($this->rtl) {
6419 $xdx = $x - $dx - $width;
6420 } else {
6421 $xdx = $x + $dx;
6423 $xdk = $xdx * $k;
6424 // print text
6425 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
6426 if (isset($uniblock)) {
6427 // print overlapping characters as separate string
6428 $xshift = 0; // horizontal shift
6429 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
6430 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
6431 foreach ($uniblock as $uk => $uniarr) {
6432 if (($uk % 2) == 0) {
6433 // x space to skip
6434 if ($spacewidth != 0) {
6435 // justification shift
6436 $xshift += (count(array_keys($uniarr, 32)) * $spw);
6438 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
6439 } else {
6440 // character to print
6441 $topchr = $this->arrUTF8ToUTF16BE($uniarr, false);
6442 $topchr = $this->_escape($topchr);
6443 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
6447 if ($this->underline) {
6448 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
6450 if ($this->linethrough) {
6451 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
6453 if ($this->overline) {
6454 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
6456 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
6457 $s .= ' Q';
6459 if ($link) {
6460 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
6463 // output cell
6464 if ($s) {
6465 // output cell
6466 $rs .= $s;
6467 if ($this->font_spacing != 0) {
6468 // reset font spacing mode
6469 $rs .= ' BT 0 Tc ET';
6471 if ($this->font_stretching != 100) {
6472 // reset font stretching mode
6473 $rs .= ' BT 100 Tz ET';
6476 // reset word spacing
6477 if (!$this->isUnicodeFont() AND ($align == 'J')) {
6478 $rs .= ' BT 0 Tw ET';
6480 // reset stretching and spacing
6481 $this->font_stretching = $prev_font_stretching;
6482 $this->font_spacing = $prev_font_spacing;
6483 $this->lasth = $h;
6484 if ($ln > 0) {
6485 //Go to the beginning of the next line
6486 $this->y = $y + $h + $this->cell_margin['B'];
6487 if ($ln == 1) {
6488 if ($this->rtl) {
6489 $this->x = $this->w - $this->rMargin;
6490 } else {
6491 $this->x = $this->lMargin;
6494 } else {
6495 // go left or right by case
6496 if ($this->rtl) {
6497 $this->x = $x - $w - $this->cell_margin['L'];
6498 } else {
6499 $this->x = $x + $w + $this->cell_margin['R'];
6502 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
6503 $rs = $gstyles.$rs;
6504 $this->cell_padding = $prev_cell_padding;
6505 $this->cell_margin = $prev_cell_margin;
6506 return $rs;
6510 * Replace a char if is defined on the current font.
6511 * @param $oldchar (int) Integer code (unicode) of the character to replace.
6512 * @param $newchar (int) Integer code (unicode) of the new character.
6513 * @return int the replaced char or the old char in case the new char i not defined
6514 * @protected
6515 * @since 5.9.167 (2012-06-22)
6517 protected function replaceChar($oldchar, $newchar) {
6518 if ($this->isCharDefined($newchar)) {
6519 // add the new char on the subset list
6520 $this->CurrentFont['subsetchars'][$newchar] = true;
6521 // return the new character
6522 return $newchar;
6524 // return the old char
6525 return $oldchar;
6529 * Returns the code to draw the cell border
6530 * @param $x (float) X coordinate.
6531 * @param $y (float) Y coordinate.
6532 * @param $w (float) Cell width.
6533 * @param $h (float) Cell height.
6534 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6535 * @return string containing cell border code
6536 * @protected
6537 * @see SetLineStyle()
6538 * @since 5.7.000 (2010-08-02)
6540 protected function getCellBorder($x, $y, $w, $h, $brd) {
6541 $s = ''; // string to be returned
6542 if (empty($brd)) {
6543 return $s;
6545 if ($brd == 1) {
6546 $brd = array('LRTB' => true);
6548 // calculate coordinates for border
6549 $k = $this->k;
6550 if ($this->rtl) {
6551 $xeL = ($x - $w) * $k;
6552 $xeR = $x * $k;
6553 } else {
6554 $xeL = $x * $k;
6555 $xeR = ($x + $w) * $k;
6557 $yeL = (($this->h - ($y + $h)) * $k);
6558 $yeT = (($this->h - $y) * $k);
6559 $xeT = $xeL;
6560 $xeB = $xeR;
6561 $yeR = $yeT;
6562 $yeB = $yeL;
6563 if (is_string($brd)) {
6564 // convert string to array
6565 $slen = strlen($brd);
6566 $newbrd = array();
6567 for ($i = 0; $i < $slen; ++$i) {
6568 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
6570 $brd = $newbrd;
6572 if (isset($brd['mode'])) {
6573 $mode = $brd['mode'];
6574 unset($brd['mode']);
6575 } else {
6576 $mode = 'normal';
6578 foreach ($brd as $border => $style) {
6579 if (is_array($style) AND !empty($style)) {
6580 // apply border style
6581 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
6582 $s .= $this->SetLineStyle($style, true)."\n";
6584 switch ($mode) {
6585 case 'ext': {
6586 $off = (($this->LineWidth / 2) * $k);
6587 $xL = $xeL - $off;
6588 $xR = $xeR + $off;
6589 $yT = $yeT + $off;
6590 $yL = $yeL - $off;
6591 $xT = $xL;
6592 $xB = $xR;
6593 $yR = $yT;
6594 $yB = $yL;
6595 $w += $this->LineWidth;
6596 $h += $this->LineWidth;
6597 break;
6599 case 'int': {
6600 $off = ($this->LineWidth / 2) * $k;
6601 $xL = $xeL + $off;
6602 $xR = $xeR - $off;
6603 $yT = $yeT - $off;
6604 $yL = $yeL + $off;
6605 $xT = $xL;
6606 $xB = $xR;
6607 $yR = $yT;
6608 $yB = $yL;
6609 $w -= $this->LineWidth;
6610 $h -= $this->LineWidth;
6611 break;
6613 case 'normal':
6614 default: {
6615 $xL = $xeL;
6616 $xT = $xeT;
6617 $xB = $xeB;
6618 $xR = $xeR;
6619 $yL = $yeL;
6620 $yT = $yeT;
6621 $yB = $yeB;
6622 $yR = $yeR;
6623 break;
6626 // draw borders by case
6627 if (strlen($border) == 4) {
6628 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
6629 } elseif (strlen($border) == 3) {
6630 if (strpos($border,'B') === false) { // LTR
6631 $s .= sprintf('%F %F m ', $xL, $yL);
6632 $s .= sprintf('%F %F l ', $xT, $yT);
6633 $s .= sprintf('%F %F l ', $xR, $yR);
6634 $s .= sprintf('%F %F l ', $xB, $yB);
6635 $s .= 'S ';
6636 } elseif (strpos($border,'L') === false) { // TRB
6637 $s .= sprintf('%F %F m ', $xT, $yT);
6638 $s .= sprintf('%F %F l ', $xR, $yR);
6639 $s .= sprintf('%F %F l ', $xB, $yB);
6640 $s .= sprintf('%F %F l ', $xL, $yL);
6641 $s .= 'S ';
6642 } elseif (strpos($border,'T') === false) { // RBL
6643 $s .= sprintf('%F %F m ', $xR, $yR);
6644 $s .= sprintf('%F %F l ', $xB, $yB);
6645 $s .= sprintf('%F %F l ', $xL, $yL);
6646 $s .= sprintf('%F %F l ', $xT, $yT);
6647 $s .= 'S ';
6648 } elseif (strpos($border,'R') === false) { // BLT
6649 $s .= sprintf('%F %F m ', $xB, $yB);
6650 $s .= sprintf('%F %F l ', $xL, $yL);
6651 $s .= sprintf('%F %F l ', $xT, $yT);
6652 $s .= sprintf('%F %F l ', $xR, $yR);
6653 $s .= 'S ';
6655 } elseif (strlen($border) == 2) {
6656 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
6657 $s .= sprintf('%F %F m ', $xL, $yL);
6658 $s .= sprintf('%F %F l ', $xT, $yT);
6659 $s .= sprintf('%F %F l ', $xR, $yR);
6660 $s .= 'S ';
6661 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
6662 $s .= sprintf('%F %F m ', $xT, $yT);
6663 $s .= sprintf('%F %F l ', $xR, $yR);
6664 $s .= sprintf('%F %F l ', $xB, $yB);
6665 $s .= 'S ';
6666 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
6667 $s .= sprintf('%F %F m ', $xR, $yR);
6668 $s .= sprintf('%F %F l ', $xB, $yB);
6669 $s .= sprintf('%F %F l ', $xL, $yL);
6670 $s .= 'S ';
6671 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
6672 $s .= sprintf('%F %F m ', $xB, $yB);
6673 $s .= sprintf('%F %F l ', $xL, $yL);
6674 $s .= sprintf('%F %F l ', $xT, $yT);
6675 $s .= 'S ';
6676 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
6677 $s .= sprintf('%F %F m ', $xL, $yL);
6678 $s .= sprintf('%F %F l ', $xT, $yT);
6679 $s .= 'S ';
6680 $s .= sprintf('%F %F m ', $xR, $yR);
6681 $s .= sprintf('%F %F l ', $xB, $yB);
6682 $s .= 'S ';
6683 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
6684 $s .= sprintf('%F %F m ', $xT, $yT);
6685 $s .= sprintf('%F %F l ', $xR, $yR);
6686 $s .= 'S ';
6687 $s .= sprintf('%F %F m ', $xB, $yB);
6688 $s .= sprintf('%F %F l ', $xL, $yL);
6689 $s .= 'S ';
6691 } else { // strlen($border) == 1
6692 if (strpos($border,'L') !== false) { // L
6693 $s .= sprintf('%F %F m ', $xL, $yL);
6694 $s .= sprintf('%F %F l ', $xT, $yT);
6695 $s .= 'S ';
6696 } elseif (strpos($border,'T') !== false) { // T
6697 $s .= sprintf('%F %F m ', $xT, $yT);
6698 $s .= sprintf('%F %F l ', $xR, $yR);
6699 $s .= 'S ';
6700 } elseif (strpos($border,'R') !== false) { // R
6701 $s .= sprintf('%F %F m ', $xR, $yR);
6702 $s .= sprintf('%F %F l ', $xB, $yB);
6703 $s .= 'S ';
6704 } elseif (strpos($border,'B') !== false) { // B
6705 $s .= sprintf('%F %F m ', $xB, $yB);
6706 $s .= sprintf('%F %F l ', $xL, $yL);
6707 $s .= 'S ';
6710 if (is_array($style) AND !empty($style)) {
6711 // reset border style to previous value
6712 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
6715 return $s;
6719 * This method allows printing text with line breaks.
6720 * 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 />
6721 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
6722 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6723 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
6724 * @param $txt (string) String to print
6725 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6726 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
6727 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6728 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
6729 * @param $x (float) x position in user units
6730 * @param $y (float) y position in user units
6731 * @param $reseth (boolean) if true reset the last cell height (default true).
6732 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6733 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
6734 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
6735 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
6736 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
6737 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode).
6738 * @return int Return the number of cells or 1 for html mode.
6739 * @public
6740 * @since 1.3
6741 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
6743 public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
6744 $prev_cell_margin = $this->cell_margin;
6745 $prev_cell_padding = $this->cell_padding;
6746 // adjust internal padding
6747 $this->adjustCellPadding($border);
6748 $mc_padding = $this->cell_padding;
6749 $mc_margin = $this->cell_margin;
6750 $this->cell_padding['T'] = 0;
6751 $this->cell_padding['B'] = 0;
6752 $this->setCellMargins(0, 0, 0, 0);
6753 if ($this->empty_string($this->lasth) OR $reseth) {
6754 // reset row height
6755 $this->resetLastH();
6757 if (!$this->empty_string($y)) {
6758 $this->SetY($y);
6759 } else {
6760 $y = $this->GetY();
6762 $resth = 0;
6763 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
6764 // spit cell in more pages/columns
6765 $newh = ($this->PageBreakTrigger - $y);
6766 $resth = ($h - $newh); // cell to be printed on the next page/column
6767 $h = $newh;
6769 // get current page number
6770 $startpage = $this->page;
6771 // get current column
6772 $startcolumn = $this->current_column;
6773 if (!$this->empty_string($x)) {
6774 $this->SetX($x);
6775 } else {
6776 $x = $this->GetX();
6778 // check page for no-write regions and adapt page margins if necessary
6779 list($x, $y) = $this->checkPageRegions(0, $x, $y);
6780 // apply margins
6781 $oy = $y + $mc_margin['T'];
6782 if ($this->rtl) {
6783 $ox = ($this->w - $x - $mc_margin['R']);
6784 } else {
6785 $ox = ($x + $mc_margin['L']);
6787 $this->x = $ox;
6788 $this->y = $oy;
6789 // set width
6790 if ($this->empty_string($w) OR ($w <= 0)) {
6791 if ($this->rtl) {
6792 $w = ($this->x - $this->lMargin - $mc_margin['L']);
6793 } else {
6794 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
6797 // store original margin values
6798 $lMargin = $this->lMargin;
6799 $rMargin = $this->rMargin;
6800 if ($this->rtl) {
6801 $this->rMargin = ($this->w - $this->x);
6802 $this->lMargin = ($this->x - $w);
6803 } else {
6804 $this->lMargin = ($this->x);
6805 $this->rMargin = ($this->w - $this->x - $w);
6807 $this->clMargin = $this->lMargin;
6808 $this->crMargin = $this->rMargin;
6809 if ($autopadding) {
6810 // add top padding
6811 $this->y += $mc_padding['T'];
6813 if ($ishtml) { // ******* Write HTML text
6814 $this->writeHTML($txt, true, false, $reseth, true, $align);
6815 $nl = 1;
6816 } else { // ******* Write simple text
6817 $prev_FontSizePt = $this->FontSizePt;
6818 // vertical alignment
6819 if ($maxh > 0) {
6820 // get text height
6821 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6822 if ($fitcell) {
6823 // try to reduce font size to fit text on cell (use a quick search algorithm)
6824 $fmin = 1;
6825 $fmax = $this->FontSizePt;
6826 $prev_text_height = $text_height;
6827 $maxit = 100; // max number of iterations
6828 while ($maxit > 0) {
6829 $fmid = (($fmax + $fmin) / 2);
6830 $this->SetFontSize($fmid, false);
6831 $this->resetLastH();
6832 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6833 if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
6834 break;
6835 } elseif ($text_height < $maxh) {
6836 $fmin = $fmid;
6837 } else {
6838 $fmax = $fmid;
6840 --$maxit;
6842 $this->SetFontSize($this->FontSizePt);
6844 if ($text_height < $maxh) {
6845 if ($valign == 'M') {
6846 // text vertically centered
6847 $this->y += (($maxh - $text_height) / 2);
6848 } elseif ($valign == 'B') {
6849 // text vertically aligned on bottom
6850 $this->y += ($maxh - $text_height);
6854 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6855 if ($fitcell) {
6856 // restore font size
6857 $this->SetFontSize($prev_FontSizePt);
6860 if ($autopadding) {
6861 // add bottom padding
6862 $this->y += $mc_padding['B'];
6864 // Get end-of-text Y position
6865 $currentY = $this->y;
6866 // get latest page number
6867 $endpage = $this->page;
6868 if ($resth > 0) {
6869 $skip = ($endpage - $startpage);
6870 $tmpresth = $resth;
6871 while ($tmpresth > 0) {
6872 if ($skip <= 0) {
6873 // add a page (or trig AcceptPageBreak() for multicolumn mode)
6874 $this->checkPageBreak($this->PageBreakTrigger + 1);
6876 if ($this->num_columns > 1) {
6877 $tmpresth -= ($this->h - $this->y - $this->bMargin);
6878 } else {
6879 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6881 --$skip;
6883 $currentY = $this->y;
6884 $endpage = $this->page;
6886 // get latest column
6887 $endcolumn = $this->current_column;
6888 if ($this->num_columns == 0) {
6889 $this->num_columns = 1;
6891 // disable page regions check
6892 $check_page_regions = $this->check_page_regions;
6893 $this->check_page_regions = false;
6894 // get border modes
6895 $border_start = $this->getBorderMode($border, $position='start');
6896 $border_end = $this->getBorderMode($border, $position='end');
6897 $border_middle = $this->getBorderMode($border, $position='middle');
6898 // design borders around HTML cells.
6899 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6900 $ccode = '';
6901 $this->setPage($page);
6902 if ($this->num_columns < 2) {
6903 // single-column mode
6904 $this->SetX($x);
6905 $this->y = $this->tMargin;
6907 // account for margin changes
6908 if ($page > $startpage) {
6909 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6910 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6911 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6912 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6915 if ($startpage == $endpage) {
6916 // single page
6917 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6918 $this->selectColumn($column);
6919 if ($this->rtl) {
6920 $this->x -= $mc_margin['R'];
6921 } else {
6922 $this->x += $mc_margin['L'];
6924 if ($startcolumn == $endcolumn) { // single column
6925 $cborder = $border;
6926 $h = max($h, ($currentY - $oy));
6927 $this->y = $oy;
6928 } elseif ($column == $startcolumn) { // first column
6929 $cborder = $border_start;
6930 $this->y = $oy;
6931 $h = $this->h - $this->y - $this->bMargin;
6932 } elseif ($column == $endcolumn) { // end column
6933 $cborder = $border_end;
6934 $h = $currentY - $this->y;
6935 if ($resth > $h) {
6936 $h = $resth;
6938 } else { // middle column
6939 $cborder = $border_middle;
6940 $h = $this->h - $this->y - $this->bMargin;
6941 $resth -= $h;
6943 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6944 } // end for each column
6945 } elseif ($page == $startpage) { // first page
6946 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6947 $this->selectColumn($column);
6948 if ($this->rtl) {
6949 $this->x -= $mc_margin['R'];
6950 } else {
6951 $this->x += $mc_margin['L'];
6953 if ($column == $startcolumn) { // first column
6954 $cborder = $border_start;
6955 $this->y = $oy;
6956 $h = $this->h - $this->y - $this->bMargin;
6957 } else { // middle column
6958 $cborder = $border_middle;
6959 $h = $this->h - $this->y - $this->bMargin;
6960 $resth -= $h;
6962 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6963 } // end for each column
6964 } elseif ($page == $endpage) { // last page
6965 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6966 $this->selectColumn($column);
6967 if ($this->rtl) {
6968 $this->x -= $mc_margin['R'];
6969 } else {
6970 $this->x += $mc_margin['L'];
6972 if ($column == $endcolumn) {
6973 // end column
6974 $cborder = $border_end;
6975 $h = $currentY - $this->y;
6976 if ($resth > $h) {
6977 $h = $resth;
6979 } else {
6980 // middle column
6981 $cborder = $border_middle;
6982 $h = $this->h - $this->y - $this->bMargin;
6983 $resth -= $h;
6985 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6986 } // end for each column
6987 } else { // middle page
6988 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6989 $this->selectColumn($column);
6990 if ($this->rtl) {
6991 $this->x -= $mc_margin['R'];
6992 } else {
6993 $this->x += $mc_margin['L'];
6995 $cborder = $border_middle;
6996 $h = $this->h - $this->y - $this->bMargin;
6997 $resth -= $h;
6998 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6999 } // end for each column
7001 if ($cborder OR $fill) {
7002 $offsetlen = strlen($ccode);
7003 // draw border and fill
7004 if ($this->inxobj) {
7005 // we are inside an XObject template
7006 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
7007 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
7008 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
7009 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
7010 } else {
7011 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
7012 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
7014 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
7015 $pstart = substr($pagebuff, 0, $pagemark);
7016 $pend = substr($pagebuff, $pagemark);
7017 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
7018 } else {
7019 if (end($this->transfmrk[$this->page]) !== false) {
7020 $pagemarkkey = key($this->transfmrk[$this->page]);
7021 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
7022 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
7023 } elseif ($this->InFooter) {
7024 $pagemark = $this->footerpos[$this->page];
7025 $this->footerpos[$this->page] += $offsetlen;
7026 } else {
7027 $pagemark = $this->intmrk[$this->page];
7028 $this->intmrk[$this->page] += $offsetlen;
7030 $pagebuff = $this->getPageBuffer($this->page);
7031 $pstart = substr($pagebuff, 0, $pagemark);
7032 $pend = substr($pagebuff, $pagemark);
7033 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
7036 } // end for each page
7037 // restore page regions check
7038 $this->check_page_regions = $check_page_regions;
7039 // Get end-of-cell Y position
7040 $currentY = $this->GetY();
7041 // restore previous values
7042 if ($this->num_columns > 1) {
7043 $this->selectColumn();
7044 } else {
7045 // restore original margins
7046 $this->lMargin = $lMargin;
7047 $this->rMargin = $rMargin;
7048 if ($this->page > $startpage) {
7049 // check for margin variations between pages (i.e. booklet mode)
7050 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
7051 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
7052 if (($dl != 0) OR ($dr != 0)) {
7053 $this->lMargin += $dl;
7054 $this->rMargin += $dr;
7058 if ($ln > 0) {
7059 //Go to the beginning of the next line
7060 $this->SetY($currentY + $mc_margin['B']);
7061 if ($ln == 2) {
7062 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
7064 } else {
7065 // go left or right by case
7066 $this->setPage($startpage);
7067 $this->y = $y;
7068 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
7070 $this->setContentMark();
7071 $this->cell_padding = $prev_cell_padding;
7072 $this->cell_margin = $prev_cell_margin;
7073 $this->clMargin = $this->lMargin;
7074 $this->crMargin = $this->rMargin;
7075 return $nl;
7079 * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
7080 * @param $brd (mixed) Indicates if borders must be drawn around the cell block. 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: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
7081 * @param $position (string) multicell position: 'start', 'middle', 'end'
7082 * @return border mode array
7083 * @protected
7084 * @since 4.4.002 (2008-12-09)
7086 protected function getBorderMode($brd, $position='start') {
7087 if ((!$this->opencell) OR empty($brd)) {
7088 return $brd;
7090 if ($brd == 1) {
7091 $brd = 'LTRB';
7093 if (is_string($brd)) {
7094 // convert string to array
7095 $slen = strlen($brd);
7096 $newbrd = array();
7097 for ($i = 0; $i < $slen; ++$i) {
7098 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
7100 $brd = $newbrd;
7102 foreach ($brd as $border => $style) {
7103 switch ($position) {
7104 case 'start': {
7105 if (strpos($border, 'B') !== false) {
7106 // remove bottom line
7107 $newkey = str_replace('B', '', $border);
7108 if (strlen($newkey) > 0) {
7109 $brd[$newkey] = $style;
7111 unset($brd[$border]);
7113 break;
7115 case 'middle': {
7116 if (strpos($border, 'B') !== false) {
7117 // remove bottom line
7118 $newkey = str_replace('B', '', $border);
7119 if (strlen($newkey) > 0) {
7120 $brd[$newkey] = $style;
7122 unset($brd[$border]);
7123 $border = $newkey;
7125 if (strpos($border, 'T') !== false) {
7126 // remove bottom line
7127 $newkey = str_replace('T', '', $border);
7128 if (strlen($newkey) > 0) {
7129 $brd[$newkey] = $style;
7131 unset($brd[$border]);
7133 break;
7135 case 'end': {
7136 if (strpos($border, 'T') !== false) {
7137 // remove bottom line
7138 $newkey = str_replace('T', '', $border);
7139 if (strlen($newkey) > 0) {
7140 $brd[$newkey] = $style;
7142 unset($brd[$border]);
7144 break;
7148 return $brd;
7152 * This method return the estimated number of lines for print a simple text string using Multicell() method.
7153 * @param $txt (string) String for calculating his height
7154 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
7155 * @param $reseth (boolean) if true reset the last cell height (default false).
7156 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
7157 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
7158 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
7159 * @return float Return the minimal height needed for multicell method for printing the $txt param.
7160 * @author Alexander Escalona Fernández, Nicola Asuni
7161 * @public
7162 * @since 4.5.011
7164 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
7165 if ($txt === '') {
7166 // empty string
7167 return 1;
7169 // adjust internal padding
7170 $prev_cell_padding = $this->cell_padding;
7171 $prev_lasth = $this->lasth;
7172 if (is_array($cellpadding)) {
7173 $this->cell_padding = $cellpadding;
7175 $this->adjustCellPadding($border);
7176 if ($this->empty_string($w) OR ($w <= 0)) {
7177 if ($this->rtl) {
7178 $w = $this->x - $this->lMargin;
7179 } else {
7180 $w = $this->w - $this->rMargin - $this->x;
7183 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
7184 if ($reseth) {
7185 // reset row height
7186 $this->resetLastH();
7188 $lines = 1;
7189 $sum = 0;
7190 $chars = $this->utf8Bidi($this->UTF8StringToArray($txt), $txt, $this->tmprtl);
7191 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
7192 $length = count($chars);
7193 $lastSeparator = -1;
7194 for ($i = 0; $i < $length; ++$i) {
7195 $charWidth = $charsWidth[$i];
7196 if (preg_match($this->re_spaces, $this->unichr($chars[$i]))) {
7197 $lastSeparator = $i;
7199 if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
7200 ++$lines;
7201 if ($chars[$i] == 10) {
7202 $lastSeparator = -1;
7203 $sum = 0;
7204 } elseif ($lastSeparator != -1) {
7205 $i = $lastSeparator;
7206 $lastSeparator = -1;
7207 $sum = 0;
7208 } else {
7209 $sum = $charWidth;
7211 } else {
7212 $sum += $charWidth;
7215 if ($chars[($length - 1)] == 10) {
7216 --$lines;
7218 $this->cell_padding = $prev_cell_padding;
7219 $this->lasth = $prev_lasth;
7220 return $lines;
7224 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
7225 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
7226 * @pre
7227 * // store current object
7228 * $pdf->startTransaction();
7229 * // store starting values
7230 * $start_y = $pdf->GetY();
7231 * $start_page = $pdf->getPage();
7232 * // call your printing functions with your parameters
7233 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7234 * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
7235 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
7236 * // get the new Y
7237 * $end_y = $pdf->GetY();
7238 * $end_page = $pdf->getPage();
7239 * // calculate height
7240 * $height = 0;
7241 * if ($end_page == $start_page) {
7242 * $height = $end_y - $start_y;
7243 * } else {
7244 * for ($page=$start_page; $page <= $end_page; ++$page) {
7245 * $this->setPage($page);
7246 * if ($page == $start_page) {
7247 * // first page
7248 * $height = $this->h - $start_y - $this->bMargin;
7249 * } elseif ($page == $end_page) {
7250 * // last page
7251 * $height = $end_y - $this->tMargin;
7252 * } else {
7253 * $height = $this->h - $this->tMargin - $this->bMargin;
7257 * // restore previous object
7258 * $pdf = $pdf->rollbackTransaction();
7260 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
7261 * @param $txt (string) String for calculating his height
7262 * @param $reseth (boolean) if true reset the last cell height (default false).
7263 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
7264 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
7265 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
7266 * @return float Return the minimal height needed for multicell method for printing the $txt param.
7267 * @author Nicola Asuni, Alexander Escalona Fernández
7268 * @public
7270 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
7271 // adjust internal padding
7272 $prev_cell_padding = $this->cell_padding;
7273 $prev_lasth = $this->lasth;
7274 if (is_array($cellpadding)) {
7275 $this->cell_padding = $cellpadding;
7277 $this->adjustCellPadding($border);
7278 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
7279 $height = $lines * ($this->FontSize * $this->cell_height_ratio);
7280 if ($autopadding) {
7281 // add top and bottom padding
7282 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
7284 $this->cell_padding = $prev_cell_padding;
7285 $this->lasth = $prev_lasth;
7286 return $height;
7290 * This method prints text from the current position.<br />
7291 * @param $h (float) Line height
7292 * @param $txt (string) String to print
7293 * @param $link (mixed) URL or identifier returned by AddLink()
7294 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
7295 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
7296 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
7297 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
7298 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
7299 * @param $firstblock (boolean) if true the string is the starting of a line.
7300 * @param $maxh (float) maximum height. The remaining unprinted text will be returned. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
7301 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
7302 * @param $margin (array) margin array of the parent container
7303 * @return mixed Return the number of cells or the remaining string if $firstline = true.
7304 * @public
7305 * @since 1.5
7307 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
7308 // check page for no-write regions and adapt page margins if necessary
7309 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
7310 if (strlen($txt) == 0) {
7311 // fix empty text
7312 $txt = ' ';
7314 if ($margin === '') {
7315 // set default margins
7316 $margin = $this->cell_margin;
7318 // remove carriage returns
7319 $s = str_replace("\r", '', $txt);
7320 // check if string contains arabic text
7321 if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $s)) {
7322 $arabic = true;
7323 } else {
7324 $arabic = false;
7326 // check if string contains RTL text
7327 if ($arabic OR ($this->tmprtl == 'R') OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $s)) {
7328 $rtlmode = true;
7329 } else {
7330 $rtlmode = false;
7332 // get a char width
7333 $chrwidth = $this->GetCharWidth(46); // dot character
7334 // get array of unicode values
7335 $chars = $this->UTF8StringToArray($s);
7336 // calculate maximum width for a single character on string
7337 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
7338 array_walk($chrw, array($this, 'getRawCharWidth'));
7339 $maxchwidth = max($chrw);
7340 // get array of chars
7341 $uchars = $this->UTF8ArrayToUniArray($chars);
7342 // get the number of characters
7343 $nb = count($chars);
7344 // replacement for SHY character (minus symbol)
7345 $shy_replacement = 45;
7346 $shy_replacement_char = $this->unichr($shy_replacement);
7347 // widht for SHY replacement
7348 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
7349 // max Y
7350 $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
7351 // page width
7352 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
7353 // calculate remaining line width ($w)
7354 if ($this->rtl) {
7355 $w = $this->x - $this->lMargin;
7356 } else {
7357 $w = $this->w - $this->rMargin - $this->x;
7359 // max column width
7360 $wmax = ($w - $wadj);
7361 if (!$firstline) {
7362 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
7364 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
7365 // the maximum width character do not fit on column
7366 return '';
7368 // minimum row height
7369 $row_height = max($h, $this->FontSize * $this->cell_height_ratio);
7370 $start_page = $this->page;
7371 $i = 0; // character position
7372 $j = 0; // current starting position
7373 $sep = -1; // position of the last blank space
7374 $shy = false; // true if the last blank is a soft hypen (SHY)
7375 $l = 0; // current string length
7376 $nl = 0; //number of lines
7377 $linebreak = false;
7378 $pc = 0; // previous character
7379 // for each character
7380 while ($i < $nb) {
7381 if (($maxh > 0) AND ($this->y >= $maxy) ) {
7382 break;
7384 //Get the current character
7385 $c = $chars[$i];
7386 if ($c == 10) { // 10 = "\n" = new line
7387 //Explicit line break
7388 if ($align == 'J') {
7389 if ($this->rtl) {
7390 $talign = 'R';
7391 } else {
7392 $talign = 'L';
7394 } else {
7395 $talign = $align;
7397 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7398 if ($firstline) {
7399 $startx = $this->x;
7400 $tmparr = array_slice($chars, $j, ($i - $j));
7401 if ($rtlmode) {
7402 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7404 $linew = $this->GetArrStringWidth($tmparr);
7405 unset($tmparr);
7406 if ($this->rtl) {
7407 $this->endlinex = $startx - $linew;
7408 } else {
7409 $this->endlinex = $startx + $linew;
7411 $w = $linew;
7412 $tmpcellpadding = $this->cell_padding;
7413 if ($maxh == 0) {
7414 $this->SetCellPadding(0);
7417 if ($firstblock AND $this->isRTLTextDir()) {
7418 $tmpstr = $this->stringRightTrim($tmpstr);
7420 // Skip newlines at the begining of a page or column
7421 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
7422 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
7424 unset($tmpstr);
7425 if ($firstline) {
7426 $this->cell_padding = $tmpcellpadding;
7427 return ($this->UniArrSubString($uchars, $i));
7429 ++$nl;
7430 $j = $i + 1;
7431 $l = 0;
7432 $sep = -1;
7433 $shy = false;
7434 // account for margin changes
7435 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
7436 $this->AcceptPageBreak();
7437 if ($this->rtl) {
7438 $this->x -= $margin['R'];
7439 } else {
7440 $this->x += $margin['L'];
7442 $this->lMargin += $margin['L'];
7443 $this->rMargin += $margin['R'];
7445 $w = $this->getRemainingWidth();
7446 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
7447 } else {
7448 // 160 is the non-breaking space.
7449 // 173 is SHY (Soft Hypen).
7450 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
7451 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
7452 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
7453 if (($c != 160)
7454 AND (($c == 173)
7455 OR preg_match($this->re_spaces, $this->unichr($c))
7456 OR (($c == 45)
7457 AND ($i < ($nb - 1))
7458 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], $this->unichr($pc))
7459 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], $this->unichr($chars[($i + 1)]))
7463 // update last blank space position
7464 $sep = $i;
7465 // check if is a SHY
7466 if (($c == 173) OR ($c == 45)) {
7467 $shy = true;
7468 if ($pc == 45) {
7469 $tmp_shy_replacement_width = 0;
7470 $tmp_shy_replacement_char = '';
7471 } else {
7472 $tmp_shy_replacement_width = $shy_replacement_width;
7473 $tmp_shy_replacement_char = $shy_replacement_char;
7475 } else {
7476 $shy = false;
7479 // update string length
7480 if ($this->isUnicodeFont() AND ($arabic)) {
7481 // with bidirectional algorithm some chars may be changed affecting the line length
7482 // *** very slow ***
7483 $l = $this->GetArrStringWidth($this->utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl));
7484 } else {
7485 $l += $this->GetCharWidth($c);
7487 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
7488 // we have reached the end of column
7489 if ($sep == -1) {
7490 // check if the line was already started
7491 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
7492 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
7493 // print a void cell and go to next line
7494 $this->Cell($w, $h, '', 0, 1);
7495 $linebreak = true;
7496 if ($firstline) {
7497 return ($this->UniArrSubString($uchars, $j));
7499 } else {
7500 // truncate the word because do not fit on column
7501 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7502 if ($firstline) {
7503 $startx = $this->x;
7504 $tmparr = array_slice($chars, $j, ($i - $j));
7505 if ($rtlmode) {
7506 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7508 $linew = $this->GetArrStringWidth($tmparr);
7509 unset($tmparr);
7510 if ($this->rtl) {
7511 $this->endlinex = $startx - $linew;
7512 } else {
7513 $this->endlinex = $startx + $linew;
7515 $w = $linew;
7516 $tmpcellpadding = $this->cell_padding;
7517 if ($maxh == 0) {
7518 $this->SetCellPadding(0);
7521 if ($firstblock AND $this->isRTLTextDir()) {
7522 $tmpstr = $this->stringRightTrim($tmpstr);
7524 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
7525 unset($tmpstr);
7526 if ($firstline) {
7527 $this->cell_padding = $tmpcellpadding;
7528 return ($this->UniArrSubString($uchars, $i));
7530 $j = $i;
7531 --$i;
7533 } else {
7534 // word wrapping
7535 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
7536 $endspace = 1;
7537 } else {
7538 $endspace = 0;
7540 // check the length of the next string
7541 $strrest = $this->UniArrSubString($uchars, ($sep + $endspace));
7542 $nextstr = preg_split('/'.$this->re_space['p'].'/'.$this->re_space['m'], $this->stringTrim($strrest));
7543 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
7544 // truncate the word because do not fit on a full page width
7545 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7546 if ($firstline) {
7547 $startx = $this->x;
7548 $tmparr = array_slice($chars, $j, ($i - $j));
7549 if ($rtlmode) {
7550 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7552 $linew = $this->GetArrStringWidth($tmparr);
7553 unset($tmparr);
7554 if ($this->rtl) {
7555 $this->endlinex = ($startx - $linew);
7556 } else {
7557 $this->endlinex = ($startx + $linew);
7559 $w = $linew;
7560 $tmpcellpadding = $this->cell_padding;
7561 if ($maxh == 0) {
7562 $this->SetCellPadding(0);
7565 if ($firstblock AND $this->isRTLTextDir()) {
7566 $tmpstr = $this->stringRightTrim($tmpstr);
7568 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
7569 unset($tmpstr);
7570 if ($firstline) {
7571 $this->cell_padding = $tmpcellpadding;
7572 return ($this->UniArrSubString($uchars, $i));
7574 $j = $i;
7575 --$i;
7576 } else {
7577 // word wrapping
7578 if ($shy) {
7579 // add hypen (minus symbol) at the end of the line
7580 $shy_width = $tmp_shy_replacement_width;
7581 if ($this->rtl) {
7582 $shy_char_left = $tmp_shy_replacement_char;
7583 $shy_char_right = '';
7584 } else {
7585 $shy_char_left = '';
7586 $shy_char_right = $tmp_shy_replacement_char;
7588 } else {
7589 $shy_width = 0;
7590 $shy_char_left = '';
7591 $shy_char_right = '';
7593 $tmpstr = $this->UniArrSubString($uchars, $j, ($sep + $endspace));
7594 if ($firstline) {
7595 $startx = $this->x;
7596 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
7597 if ($rtlmode) {
7598 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7600 $linew = $this->GetArrStringWidth($tmparr);
7601 unset($tmparr);
7602 if ($this->rtl) {
7603 $this->endlinex = $startx - $linew - $shy_width;
7604 } else {
7605 $this->endlinex = $startx + $linew + $shy_width;
7607 $w = $linew;
7608 $tmpcellpadding = $this->cell_padding;
7609 if ($maxh == 0) {
7610 $this->SetCellPadding(0);
7613 // print the line
7614 if ($firstblock AND $this->isRTLTextDir()) {
7615 $tmpstr = $this->stringRightTrim($tmpstr);
7617 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
7618 unset($tmpstr);
7619 if ($firstline) {
7620 if ($chars[$sep] == 45) {
7621 $endspace += 1;
7623 // return the remaining text
7624 $this->cell_padding = $tmpcellpadding;
7625 return ($this->UniArrSubString($uchars, ($sep + $endspace)));
7627 $i = $sep;
7628 $sep = -1;
7629 $shy = false;
7630 $j = ($i + 1);
7633 // account for margin changes
7634 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
7635 $this->AcceptPageBreak();
7636 if ($this->rtl) {
7637 $this->x -= $margin['R'];
7638 } else {
7639 $this->x += $margin['L'];
7641 $this->lMargin += $margin['L'];
7642 $this->rMargin += $margin['R'];
7644 $w = $this->getRemainingWidth();
7645 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
7646 if ($linebreak) {
7647 $linebreak = false;
7648 } else {
7649 ++$nl;
7650 $l = 0;
7654 // save last character
7655 $pc = $c;
7656 ++$i;
7657 } // end while i < nb
7658 // print last substring (if any)
7659 if ($l > 0) {
7660 switch ($align) {
7661 case 'J':
7662 case 'C': {
7663 $w = $w;
7664 break;
7666 case 'L': {
7667 if ($this->rtl) {
7668 $w = $w;
7669 } else {
7670 $w = $l;
7672 break;
7674 case 'R': {
7675 if ($this->rtl) {
7676 $w = $l;
7677 } else {
7678 $w = $w;
7680 break;
7682 default: {
7683 $w = $l;
7684 break;
7687 $tmpstr = $this->UniArrSubString($uchars, $j, $nb);
7688 if ($firstline) {
7689 $startx = $this->x;
7690 $tmparr = array_slice($chars, $j, ($nb - $j));
7691 if ($rtlmode) {
7692 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7694 $linew = $this->GetArrStringWidth($tmparr);
7695 unset($tmparr);
7696 if ($this->rtl) {
7697 $this->endlinex = $startx - $linew;
7698 } else {
7699 $this->endlinex = $startx + $linew;
7701 $w = $linew;
7702 $tmpcellpadding = $this->cell_padding;
7703 if ($maxh == 0) {
7704 $this->SetCellPadding(0);
7707 if ($firstblock AND $this->isRTLTextDir()) {
7708 $tmpstr = $this->stringRightTrim($tmpstr);
7710 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
7711 unset($tmpstr);
7712 if ($firstline) {
7713 $this->cell_padding = $tmpcellpadding;
7714 return ($this->UniArrSubString($uchars, $nb));
7716 ++$nl;
7718 if ($firstline) {
7719 return '';
7721 return $nl;
7725 * Returns the remaining width between the current position and margins.
7726 * @return int Return the remaining width
7727 * @protected
7729 protected function getRemainingWidth() {
7730 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
7731 if ($this->rtl) {
7732 return ($this->x - $this->lMargin);
7733 } else {
7734 return ($this->w - $this->rMargin - $this->x);
7739 * Extract a slice of the $strarr array and return it as string.
7740 * @param $strarr (string) The input array of characters.
7741 * @param $start (int) the starting element of $strarr.
7742 * @param $end (int) first element that will not be returned.
7743 * @return Return part of a string
7744 * @public
7746 public function UTF8ArrSubString($strarr, $start='', $end='') {
7747 if (strlen($start) == 0) {
7748 $start = 0;
7750 if (strlen($end) == 0) {
7751 $end = count($strarr);
7753 $string = '';
7754 for ($i=$start; $i < $end; ++$i) {
7755 $string .= $this->unichr($strarr[$i]);
7757 return $string;
7761 * Extract a slice of the $uniarr array and return it as string.
7762 * @param $uniarr (string) The input array of characters.
7763 * @param $start (int) the starting element of $strarr.
7764 * @param $end (int) first element that will not be returned.
7765 * @return Return part of a string
7766 * @public
7767 * @since 4.5.037 (2009-04-07)
7769 public function UniArrSubString($uniarr, $start='', $end='') {
7770 if (strlen($start) == 0) {
7771 $start = 0;
7773 if (strlen($end) == 0) {
7774 $end = count($uniarr);
7776 $string = '';
7777 for ($i=$start; $i < $end; ++$i) {
7778 $string .= $uniarr[$i];
7780 return $string;
7784 * Convert an array of UTF8 values to array of unicode characters
7785 * @param $ta (string) The input array of UTF8 values.
7786 * @return Return array of unicode characters
7787 * @public
7788 * @since 4.5.037 (2009-04-07)
7790 public function UTF8ArrayToUniArray($ta) {
7791 return array_map(array($this, 'unichr'), $ta);
7795 * Returns the unicode caracter specified by UTF-8 value
7796 * @param $c (int) UTF-8 value
7797 * @return Returns the specified character.
7798 * @author Miguel Perez, Nicola Asuni
7799 * @public
7800 * @since 2.3.000 (2008-03-05)
7802 public function unichr($c) {
7803 if (!$this->isunicode) {
7804 return chr($c);
7805 } elseif ($c <= 0x7F) {
7806 // one byte
7807 return chr($c);
7808 } elseif ($c <= 0x7FF) {
7809 // two bytes
7810 return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
7811 } elseif ($c <= 0xFFFF) {
7812 // three bytes
7813 return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
7814 } elseif ($c <= 0x10FFFF) {
7815 // four bytes
7816 return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
7817 } else {
7818 return '';
7823 * Return the image type given the file name or array returned by getimagesize() function.
7824 * @param $imgfile (string) image file name
7825 * @param $iminfo (array) array of image information returned by getimagesize() function.
7826 * @return string image type
7827 * @since 4.8.017 (2009-11-27)
7829 public function getImageFileType($imgfile, $iminfo=array()) {
7830 $type = '';
7831 if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
7832 $mime = explode('/', $iminfo['mime']);
7833 if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
7834 $type = strtolower(trim($mime[1]));
7837 if (empty($type)) {
7838 $fileinfo = pathinfo($imgfile);
7839 if (isset($fileinfo['extension']) AND (!$this->empty_string($fileinfo['extension']))) {
7840 $type = strtolower(trim($fileinfo['extension']));
7843 if ($type == 'jpg') {
7844 $type = 'jpeg';
7846 return $type;
7850 * Set the block dimensions accounting for page breaks and page/column fitting
7851 * @param $w (float) width
7852 * @param $h (float) height
7853 * @param $x (float) X coordinate
7854 * @param $y (float) Y coodiante
7855 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
7856 * @return array($w, $h, $x, $y)
7857 * @protected
7858 * @since 5.5.009 (2010-07-05)
7860 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
7861 if ($w <= 0) {
7862 // set maximum width
7863 $w = ($this->w - $this->lMargin - $this->rMargin);
7865 if ($h <= 0) {
7866 // set maximum height
7867 $h = ($this->PageBreakTrigger - $this->tMargin);
7869 // resize the block to be vertically contained on a single page or single column
7870 if ($fitonpage OR $this->AutoPageBreak) {
7871 $ratio_wh = ($w / $h);
7872 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
7873 $h = $this->PageBreakTrigger - $this->tMargin;
7874 $w = ($h * $ratio_wh);
7876 // resize the block to be horizontally contained on a single page or single column
7877 if ($fitonpage) {
7878 $maxw = ($this->w - $this->lMargin - $this->rMargin);
7879 if ($w > $maxw) {
7880 $w = $maxw;
7881 $h = ($w / $ratio_wh);
7885 // Check whether we need a new page or new column first as this does not fit
7886 $prev_x = $this->x;
7887 $prev_y = $this->y;
7888 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
7889 $y = $this->y;
7890 if ($this->rtl) {
7891 $x += ($prev_x - $this->x);
7892 } else {
7893 $x += ($this->x - $prev_x);
7895 $this->newline = true;
7897 // resize the block to be contained on the remaining available page or column space
7898 if ($fitonpage) {
7899 $ratio_wh = ($w / $h);
7900 if (($y + $h) > $this->PageBreakTrigger) {
7901 $h = $this->PageBreakTrigger - $y;
7902 $w = ($h * $ratio_wh);
7904 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
7905 $w = $this->w - $this->rMargin - $x;
7906 $h = ($w / $ratio_wh);
7907 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
7908 $w = $x - $this->lMargin;
7909 $h = ($w / $ratio_wh);
7912 return array($w, $h, $x, $y);
7916 * Puts an image in the page.
7917 * The upper-left corner must be given.
7918 * The dimensions can be specified in different ways:<ul>
7919 * <li>explicit width and height (expressed in user unit)</li>
7920 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
7921 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
7922 * 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;
7923 * The format can be specified explicitly or inferred from the file extension.<br />
7924 * It is possible to put a link on the image.<br />
7925 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
7926 * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
7927 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
7928 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
7929 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7930 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7931 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7932 * @param $link (mixed) URL or identifier returned by AddLink().
7933 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7934 * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
7935 * @param $dpi (int) dot-per-inch resolution used on resize
7936 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7937 * @param $ismask (boolean) true if this image is a mask, false otherwise
7938 * @param $imgmask (mixed) image object returned by this function or false
7939 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
7940 * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
7941 * @param $hidden (boolean) If true do not display the image.
7942 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
7943 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
7944 * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
7945 * @return image information
7946 * @public
7947 * @since 1.1
7949 public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
7950 if ($this->state != 2) {
7951 return;
7953 if ($x === '') {
7954 $x = $this->x;
7956 if ($y === '') {
7957 $y = $this->y;
7959 // check page for no-write regions and adapt page margins if necessary
7960 list($x, $y) = $this->checkPageRegions($h, $x, $y);
7961 $exurl = ''; // external streams
7962 // check if we are passing an image as file or string
7963 if ($file[0] === '@') {
7964 // image from string
7965 $imgdata = substr($file, 1);
7966 $file = $this->getObjFilename('img');
7967 $fp = fopen($file, 'w');
7968 fwrite($fp, $imgdata);
7969 fclose($fp);
7970 unset($imgdata);
7971 $imsize = @getimagesize($file);
7972 if ($imsize === FALSE) {
7973 unlink($file);
7974 } else {
7975 $this->cached_files[] = $file;
7977 } else { // image file
7978 if ($file{0} === '*') {
7979 // image as external stream
7980 $file = substr($file, 1);
7981 $exurl = $file;
7983 // check if is local file
7984 if (!@file_exists($file)) {
7985 // encode spaces on filename (file is probably an URL)
7986 $file = str_replace(' ', '%20', $file);
7988 if (@file_exists($file)) {
7989 // get image dimensions
7990 $imsize = @getimagesize($file);
7991 } else {
7992 $imsize = false;
7994 if ($imsize === FALSE) {
7995 if (function_exists('curl_init')) {
7996 // try to get remote file data using cURL
7997 $cs = curl_init(); // curl session
7998 curl_setopt($cs, CURLOPT_URL, $file);
7999 curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
8000 curl_setopt($cs, CURLOPT_FAILONERROR, true);
8001 curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
8002 if ((ini_get('open_basedir') == '') AND (!ini_get('safe_mode'))) {
8003 curl_setopt($cs, CURLOPT_FOLLOWLOCATION, true);
8005 curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
8006 curl_setopt($cs, CURLOPT_TIMEOUT, 30);
8007 curl_setopt($cs, CURLOPT_SSL_VERIFYPEER, false);
8008 curl_setopt($cs, CURLOPT_SSL_VERIFYHOST, false);
8009 curl_setopt($cs, CURLOPT_USERAGENT, 'TCPDF');
8010 $imgdata = curl_exec($cs);
8011 curl_close($cs);
8012 if ($imgdata !== FALSE) {
8013 // copy image to cache
8014 $file = $this->getObjFilename('img');
8015 $fp = fopen($file, 'w');
8016 fwrite($fp, $imgdata);
8017 fclose($fp);
8018 unset($imgdata);
8019 $imsize = @getimagesize($file);
8020 if ($imsize === FALSE) {
8021 unlink($file);
8022 } else {
8023 $this->cached_files[] = $file;
8026 } elseif (($w > 0) AND ($h > 0)) {
8027 // get measures from specified data
8028 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
8029 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
8030 $imsize = array($pw, $ph);
8034 if ($imsize === FALSE) {
8035 if (substr($file, 0, -34) == K_PATH_CACHE.'msk') { // mask file
8036 // get measures from specified data
8037 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
8038 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
8039 $imsize = array($pw, $ph);
8040 } else {
8041 $this->Error('[Image] Unable to get image: '.$file);
8044 // file hash
8045 $filehash = md5($this->file_id.$file);
8046 // get original image width and height in pixels
8047 list($pixw, $pixh) = $imsize;
8048 // calculate image width and height on document
8049 if (($w <= 0) AND ($h <= 0)) {
8050 // convert image size to document unit
8051 $w = $this->pixelsToUnits($pixw);
8052 $h = $this->pixelsToUnits($pixh);
8053 } elseif ($w <= 0) {
8054 $w = $h * $pixw / $pixh;
8055 } elseif ($h <= 0) {
8056 $h = $w * $pixh / $pixw;
8057 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
8058 if (strlen($fitbox) !== 2) {
8059 // set default alignment
8060 $fitbox = '--';
8062 // scale image dimensions proportionally to fit within the ($w, $h) box
8063 if ((($w * $pixh) / ($h * $pixw)) < 1) {
8064 // store current height
8065 $oldh = $h;
8066 // calculate new height
8067 $h = $w * $pixh / $pixw;
8068 // height difference
8069 $hdiff = ($oldh - $h);
8070 // vertical alignment
8071 switch (strtoupper($fitbox{1})) {
8072 case 'T': {
8073 break;
8075 case 'M': {
8076 $y += ($hdiff / 2);
8077 break;
8079 case 'B': {
8080 $y += $hdiff;
8081 break;
8084 } else {
8085 // store current width
8086 $oldw = $w;
8087 // calculate new width
8088 $w = $h * $pixw / $pixh;
8089 // width difference
8090 $wdiff = ($oldw - $w);
8091 // horizontal alignment
8092 switch (strtoupper($fitbox{0})) {
8093 case 'L': {
8094 if ($this->rtl) {
8095 $x -= $wdiff;
8097 break;
8099 case 'C': {
8100 if ($this->rtl) {
8101 $x -= ($wdiff / 2);
8102 } else {
8103 $x += ($wdiff / 2);
8105 break;
8107 case 'R': {
8108 if (!$this->rtl) {
8109 $x += $wdiff;
8111 break;
8116 // fit the image on available space
8117 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
8118 // calculate new minimum dimensions in pixels
8119 $neww = round($w * $this->k * $dpi / $this->dpi);
8120 $newh = round($h * $this->k * $dpi / $this->dpi);
8121 // check if resize is necessary (resize is used only to reduce the image)
8122 $newsize = ($neww * $newh);
8123 $pixsize = ($pixw * $pixh);
8124 if (intval($resize) == 2) {
8125 $resize = true;
8126 } elseif ($newsize >= $pixsize) {
8127 $resize = false;
8129 // check if image has been already added on document
8130 $newimage = true;
8131 if (in_array($file, $this->imagekeys)) {
8132 $newimage = false;
8133 // get existing image data
8134 $info = $this->getImageBuffer($file);
8135 if (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
8136 // check if the newer image is larger
8137 $oldsize = ($info['w'] * $info['h']);
8138 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
8139 $newimage = true;
8142 } elseif (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
8143 // check for cached images with alpha channel
8144 $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
8145 $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
8146 if (in_array($tempfile_plain, $this->imagekeys)) {
8147 // get existing image data
8148 $info = $this->getImageBuffer($tempfile_plain);
8149 // check if the newer image is larger
8150 $oldsize = ($info['w'] * $info['h']);
8151 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
8152 $newimage = true;
8153 } else {
8154 $newimage = false;
8155 // embed mask image
8156 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
8157 // embed image, masked with previously embedded mask
8158 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
8162 if ($newimage) {
8163 //First use of image, get info
8164 $type = strtolower($type);
8165 if ($type == '') {
8166 $type = $this->getImageFileType($file, $imsize);
8167 } elseif ($type == 'jpg') {
8168 $type = 'jpeg';
8170 $mqr = $this->get_mqr();
8171 $this->set_mqr(false);
8172 // Specific image handlers
8173 $mtd = '_parse'.$type;
8174 // GD image handler function
8175 $gdfunction = 'imagecreatefrom'.$type;
8176 $info = false;
8177 if ((method_exists($this, $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
8178 // TCPDF image functions
8179 $info = $this->$mtd($file);
8180 if ($info == 'pngalpha') {
8181 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
8184 if (!$info) {
8185 if (function_exists($gdfunction)) {
8186 // GD library
8187 $img = $gdfunction($file);
8188 if ($resize) {
8189 $imgr = imagecreatetruecolor($neww, $newh);
8190 if (($type == 'gif') OR ($type == 'png')) {
8191 $imgr = $this->_setGDImageTransparency($imgr, $img);
8193 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
8194 if (($type == 'gif') OR ($type == 'png')) {
8195 $info = $this->_toPNG($imgr);
8196 } else {
8197 $info = $this->_toJPEG($imgr);
8199 } else {
8200 if (($type == 'gif') OR ($type == 'png')) {
8201 $info = $this->_toPNG($img);
8202 } else {
8203 $info = $this->_toJPEG($img);
8206 } elseif (extension_loaded('imagick')) {
8207 // ImageMagick library
8208 $img = new Imagick();
8209 if ($type == 'SVG') {
8210 // get SVG file content
8211 $svgimg = file_get_contents($file);
8212 // get width and height
8213 $regs = array();
8214 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
8215 $svgtag = $regs[1];
8216 $tmp = array();
8217 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
8218 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
8219 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
8220 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
8221 } else {
8222 $ow = $w;
8224 $tmp = array();
8225 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
8226 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
8227 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
8228 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
8229 } else {
8230 $oh = $h;
8232 $tmp = array();
8233 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
8234 $vbw = ($ow * $this->imgscale * $this->k);
8235 $vbh = ($oh * $this->imgscale * $this->k);
8236 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
8237 $svgtag = $vbox.$svgtag;
8239 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
8241 $img->readImageBlob($svgimg);
8242 } else {
8243 $img->readImage($file);
8245 if ($resize) {
8246 $img->resizeImage($neww, $newh, 10, 1, false);
8248 $img->setCompressionQuality($this->jpeg_quality);
8249 $img->setImageFormat('jpeg');
8250 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
8251 $img->writeImage($tempname);
8252 $info = $this->_parsejpeg($tempname);
8253 unlink($tempname);
8254 $img->destroy();
8255 } else {
8256 return;
8259 if ($info === false) {
8260 //If false, we cannot process image
8261 return;
8263 $this->set_mqr($mqr);
8264 if ($ismask) {
8265 // force grayscale
8266 $info['cs'] = 'DeviceGray';
8268 if ($imgmask !== false) {
8269 $info['masked'] = $imgmask;
8271 if (!empty($exurl)) {
8272 $info['exurl'] = $exurl;
8274 // array of alternative images
8275 $info['altimgs'] = $altimgs;
8276 // add image to document
8277 $info['i'] = $this->setImageBuffer($file, $info);
8279 // set alignment
8280 $this->img_rb_y = $y + $h;
8281 // set alignment
8282 if ($this->rtl) {
8283 if ($palign == 'L') {
8284 $ximg = $this->lMargin;
8285 } elseif ($palign == 'C') {
8286 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
8287 } elseif ($palign == 'R') {
8288 $ximg = $this->w - $this->rMargin - $w;
8289 } else {
8290 $ximg = $x - $w;
8292 $this->img_rb_x = $ximg;
8293 } else {
8294 if ($palign == 'L') {
8295 $ximg = $this->lMargin;
8296 } elseif ($palign == 'C') {
8297 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
8298 } elseif ($palign == 'R') {
8299 $ximg = $this->w - $this->rMargin - $w;
8300 } else {
8301 $ximg = $x;
8303 $this->img_rb_x = $ximg + $w;
8305 if ($ismask OR $hidden) {
8306 // image is not displayed
8307 return $info['i'];
8309 $xkimg = $ximg * $this->k;
8310 if (!$alt) {
8311 // only non-alternative immages will be set
8312 $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']));
8314 if (!empty($border)) {
8315 $bx = $this->x;
8316 $by = $this->y;
8317 $this->x = $ximg;
8318 if ($this->rtl) {
8319 $this->x += $w;
8321 $this->y = $y;
8322 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
8323 $this->x = $bx;
8324 $this->y = $by;
8326 if ($link) {
8327 $this->Link($ximg, $y, $w, $h, $link, 0);
8329 // set pointer to align the next text/objects
8330 switch($align) {
8331 case 'T': {
8332 $this->y = $y;
8333 $this->x = $this->img_rb_x;
8334 break;
8336 case 'M': {
8337 $this->y = $y + round($h/2);
8338 $this->x = $this->img_rb_x;
8339 break;
8341 case 'B': {
8342 $this->y = $this->img_rb_y;
8343 $this->x = $this->img_rb_x;
8344 break;
8346 case 'N': {
8347 $this->SetY($this->img_rb_y);
8348 break;
8350 default:{
8351 break;
8354 $this->endlinex = $this->img_rb_x;
8355 if ($this->inxobj) {
8356 // we are inside an XObject template
8357 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
8359 return $info['i'];
8363 * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist)
8364 * @param $mqr (boolean) FALSE for off, TRUE for on.
8365 * @since 4.6.025 (2009-08-17)
8367 public function set_mqr($mqr) {
8368 if (!defined('PHP_VERSION_ID')) {
8369 $version = PHP_VERSION;
8370 define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
8372 if (PHP_VERSION_ID < 50300) {
8373 @set_magic_quotes_runtime($mqr);
8378 * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist)
8379 * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise.
8380 * @since 4.6.025 (2009-08-17)
8382 public function get_mqr() {
8383 if (!defined('PHP_VERSION_ID')) {
8384 $version = PHP_VERSION;
8385 define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
8387 if (PHP_VERSION_ID < 50300) {
8388 return @get_magic_quotes_runtime();
8390 return 0;
8394 * Convert the loaded image to a JPEG and then return a structure for the PDF creator.
8395 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
8396 * @param $image (image) Image object.
8397 * return image JPEG image object.
8398 * @protected
8400 protected function _toJPEG($image) {
8401 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
8402 imagejpeg($image, $tempname, $this->jpeg_quality);
8403 imagedestroy($image);
8404 $retvars = $this->_parsejpeg($tempname);
8405 // tidy up by removing temporary image
8406 unlink($tempname);
8407 return $retvars;
8411 * Convert the loaded image to a PNG and then return a structure for the PDF creator.
8412 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
8413 * @param $image (image) Image object.
8414 * return image PNG image object.
8415 * @protected
8416 * @since 4.9.016 (2010-04-20)
8418 protected function _toPNG($image) {
8419 // set temporary image file name
8420 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
8421 // turn off interlaced mode
8422 imageinterlace($image, 0);
8423 // create temporary PNG image
8424 imagepng($image, $tempname);
8425 // remove image from memory
8426 imagedestroy($image);
8427 // get PNG image data
8428 $retvars = $this->_parsepng($tempname);
8429 // tidy up by removing temporary image
8430 unlink($tempname);
8431 return $retvars;
8435 * Set the transparency for the given GD image.
8436 * @param $new_image (image) GD image object
8437 * @param $image (image) GD image object.
8438 * return GD image object.
8439 * @protected
8440 * @since 4.9.016 (2010-04-20)
8442 protected function _setGDImageTransparency($new_image, $image) {
8443 // transparency index
8444 $tid = imagecolortransparent($image);
8445 // default transparency color
8446 $tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
8447 if ($tid >= 0) {
8448 // get the colors for the transparency index
8449 $tcol = imagecolorsforindex($image, $tid);
8451 $tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
8452 imagefill($new_image, 0, 0, $tid);
8453 imagecolortransparent($new_image, $tid);
8454 return $new_image;
8458 * Extract info from a JPEG file without using the GD library.
8459 * @param $file (string) image file to parse
8460 * @return array structure containing the image data
8461 * @protected
8463 protected function _parsejpeg($file) {
8464 $a = getimagesize($file);
8465 if (empty($a)) {
8466 $this->Error('Missing or incorrect image file: '.$file);
8468 if ($a[2] != 2) {
8469 $this->Error('Not a JPEG file: '.$file);
8471 // bits per pixel
8472 $bpc = isset($a['bits']) ? intval($a['bits']) : 8;
8473 // number of image channels
8474 if (!isset($a['channels'])) {
8475 $channels = 3;
8476 } else {
8477 $channels = intval($a['channels']);
8479 // default colour space
8480 switch ($channels) {
8481 case 1: {
8482 $colspace = 'DeviceGray';
8483 break;
8485 case 3: {
8486 $colspace = 'DeviceRGB';
8487 break;
8489 case 4: {
8490 $colspace = 'DeviceCMYK';
8491 break;
8493 default: {
8494 $channels = 3;
8495 $colspace = 'DeviceRGB';
8496 break;
8499 // get file content
8500 $data = file_get_contents($file);
8501 // check for embedded ICC profile
8502 $icc = array();
8503 $offset = 0;
8504 while (($pos = strpos($data, "ICC_PROFILE\0", $offset)) !== false) {
8505 // get ICC sequence length
8506 $length = ($this->_getUSHORT($data, ($pos - 2)) - 16);
8507 // marker sequence number
8508 $msn = max(1, ord($data[($pos + 12)]));
8509 // number of markers (total of APP2 used)
8510 $nom = max(1, ord($data[($pos + 13)]));
8511 // get sequence segment
8512 $icc[($msn - 1)] = substr($data, ($pos + 14), $length);
8513 // move forward to next sequence
8514 $offset = ($pos + 14 + $length);
8516 // order and compact ICC segments
8517 if (count($icc) > 0) {
8518 ksort($icc);
8519 $icc = implode('', $icc);
8520 if ((ord($icc{36}) != 0x61) OR (ord($icc{37}) != 0x63) OR (ord($icc{38}) != 0x73) OR (ord($icc{39}) != 0x70)) {
8521 // invalid ICC profile
8522 $icc = false;
8524 } else {
8525 $icc = false;
8527 return array('w' => $a[0], 'h' => $a[1], 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
8531 * Extract info from a PNG file without using the GD library.
8532 * @param $file (string) image file to parse
8533 * @return array structure containing the image data
8534 * @protected
8536 protected function _parsepng($file) {
8537 $f = fopen($file, 'rb');
8538 if ($f === false) {
8539 $this->Error('Can\'t open image file: '.$file);
8541 //Check signature
8542 if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
8543 $this->Error('Not a PNG file: '.$file);
8545 //Read header chunk
8546 fread($f, 4);
8547 if (fread($f, 4) != 'IHDR') {
8548 $this->Error('Incorrect PNG file: '.$file);
8550 $w = $this->_freadint($f);
8551 $h = $this->_freadint($f);
8552 $bpc = ord(fread($f, 1));
8553 if ($bpc > 8) {
8554 //$this->Error('16-bit depth not supported: '.$file);
8555 fclose($f);
8556 return false;
8558 $ct = ord(fread($f, 1));
8559 if ($ct == 0) {
8560 $colspace = 'DeviceGray';
8561 } elseif ($ct == 2) {
8562 $colspace = 'DeviceRGB';
8563 } elseif ($ct == 3) {
8564 $colspace = 'Indexed';
8565 } else {
8566 // alpha channel
8567 fclose($f);
8568 return 'pngalpha';
8570 if (ord(fread($f, 1)) != 0) {
8571 //$this->Error('Unknown compression method: '.$file);
8572 fclose($f);
8573 return false;
8575 if (ord(fread($f, 1)) != 0) {
8576 //$this->Error('Unknown filter method: '.$file);
8577 fclose($f);
8578 return false;
8580 if (ord(fread($f, 1)) != 0) {
8581 //$this->Error('Interlacing not supported: '.$file);
8582 fclose($f);
8583 return false;
8585 fread($f, 4);
8586 $channels = ($ct == 2 ? 3 : 1);
8587 $parms = '/DecodeParms << /Predictor 15 /Colors '.$channels.' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
8588 //Scan chunks looking for palette, transparency and image data
8589 $pal = '';
8590 $trns = '';
8591 $data = '';
8592 $icc = false;
8593 do {
8594 $n = $this->_freadint($f);
8595 $type = fread($f, 4);
8596 if ($type == 'PLTE') {
8597 // read palette
8598 $pal = $this->rfread($f, $n);
8599 fread($f, 4);
8600 } elseif ($type == 'tRNS') {
8601 // read transparency info
8602 $t = $this->rfread($f, $n);
8603 if ($ct == 0) {
8604 $trns = array(ord($t{1}));
8605 } elseif ($ct == 2) {
8606 $trns = array(ord($t{1}), ord($t{3}), ord($t{5}));
8607 } else {
8608 $pos = strpos($t, chr(0));
8609 if ($pos !== false) {
8610 $trns = array($pos);
8613 fread($f, 4);
8614 } elseif ($type == 'IDAT') {
8615 // read image data block
8616 $data .= $this->rfread($f, $n);
8617 fread($f, 4);
8618 } elseif ($type == 'iCCP') {
8619 // skip profile name
8620 $len = 0;
8621 while ((ord(fread($f, 1)) > 0) AND ($len < 80)) {
8622 ++$len;
8624 // skip null separator
8625 fread($f, 1);
8626 // get compression method
8627 if (ord(fread($f, 1)) != 0) {
8628 //$this->Error('Unknown filter method: '.$file);
8629 fclose($f);
8630 return false;
8632 // read ICC Color Profile
8633 $icc = $this->rfread($f, ($n - $len - 2));
8634 // decompress profile
8635 $icc = gzuncompress($icc);
8636 fread($f, 4);
8637 } elseif ($type == 'IEND') {
8638 break;
8639 } else {
8640 $this->rfread($f, $n + 4);
8642 } while ($n);
8643 if (($colspace == 'Indexed') AND (empty($pal))) {
8644 //$this->Error('Missing palette in '.$file);
8645 fclose($f);
8646 return false;
8648 fclose($f);
8649 return array('w' => $w, 'h' => $h, 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
8653 * Binary-safe and URL-safe file read.
8654 * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
8655 * @param $handle (resource)
8656 * @param $length (int)
8657 * @return Returns the read string or FALSE in case of error.
8658 * @author Nicola Asuni
8659 * @protected
8660 * @since 4.5.027 (2009-03-16)
8662 protected function rfread($handle, $length) {
8663 $data = fread($handle, $length);
8664 if ($data === false) {
8665 return false;
8667 $rest = $length - strlen($data);
8668 if ($rest > 0) {
8669 $data .= $this->rfread($handle, $rest);
8671 return $data;
8675 * Extract info from a PNG image with alpha channel using the GD library.
8676 * @param $file (string) Name of the file containing the image.
8677 * @param $x (float) Abscissa of the upper-left corner.
8678 * @param $y (float) Ordinate of the upper-left corner.
8679 * @param $wpx (float) Original width of the image in pixels.
8680 * @param $hpx (float) original height of the image in pixels.
8681 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
8682 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
8683 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
8684 * @param $link (mixed) URL or identifier returned by AddLink().
8685 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
8686 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
8687 * @param $dpi (int) dot-per-inch resolution used on resize
8688 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
8689 * @param $filehash (string) File hash used to build unique file names.
8690 * @author Nicola Asuni
8691 * @protected
8692 * @since 4.3.007 (2008-12-04)
8693 * @see Image()
8695 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
8696 if (empty($filehash)) {
8697 $filehash = md5($this->file_id.$file);
8699 // create temp image file (without alpha channel)
8700 $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
8701 // create temp alpha file
8702 $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
8703 if (extension_loaded('imagick')) { // ImageMagick extension
8704 // ImageMagick library
8705 $img = new Imagick();
8706 $img->readImage($file);
8707 // clone image object
8708 $imga = $this->objclone($img);
8709 // extract alpha channel
8710 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
8711 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
8712 } else {
8713 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
8714 $img->negateImage(true);
8716 $img->setImageFormat('png');
8717 $img->writeImage($tempfile_alpha);
8718 // remove alpha channel
8719 if (method_exists($imga, 'setImageMatte')) {
8720 $imga->setImageMatte(false);
8721 } else {
8722 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
8724 $imga->setImageFormat('png');
8725 $imga->writeImage($tempfile_plain);
8726 } elseif (function_exists('imagecreatefrompng')) { // GD extension
8727 // generate images
8728 $img = imagecreatefrompng($file);
8729 $imgalpha = imagecreate($wpx, $hpx);
8730 // generate gray scale palette (0 -> 255)
8731 for ($c = 0; $c < 256; ++$c) {
8732 ImageColorAllocate($imgalpha, $c, $c, $c);
8734 // extract alpha channel
8735 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
8736 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
8737 $color = imagecolorat($img, $xpx, $ypx);
8738 $alpha = $this->getGDgamma($color); // correct gamma
8739 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
8742 imagepng($imgalpha, $tempfile_alpha);
8743 imagedestroy($imgalpha);
8744 // extract image without alpha channel
8745 $imgplain = imagecreatetruecolor($wpx, $hpx);
8746 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
8747 imagepng($imgplain, $tempfile_plain);
8748 imagedestroy($imgplain);
8749 } else {
8750 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
8752 // embed mask image
8753 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
8754 // embed image, masked with previously embedded mask
8755 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
8756 // remove temp files
8757 unlink($tempfile_alpha);
8758 unlink($tempfile_plain);
8762 * Get the GD-corrected PNG gamma value from alpha color
8763 * @param $c (int) alpha color
8764 * @protected
8765 * @since 4.3.007 (2008-12-04)
8767 protected function getGDgamma($c) {
8768 if (!isset($this->gdgammacache["'".$c."'"])) {
8769 // shifts off the first 24 bits (where 8x3 are used for each color),
8770 // and returns the remaining 7 allocated bits (commonly used for alpha)
8771 $alpha = ($c >> 24);
8772 // GD alpha is only 7 bit (0 -> 127)
8773 $alpha = (((127 - $alpha) / 127) * 255);
8774 // correct gamma
8775 $this->gdgammacache["'".$c."'"] = (pow(($alpha / 255), 2.2) * 255);
8776 // store the latest values on cache to improve performances
8777 if (count($this->gdgammacache) > 8) {
8778 // remove one element from the cache array
8779 array_shift($this->gdgammacache);
8782 return $this->gdgammacache["'".$c."'"];
8786 * Performs a line break.
8787 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
8788 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
8789 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
8790 * @public
8791 * @since 1.0
8792 * @see Cell()
8794 public function Ln($h='', $cell=false) {
8795 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'])) {
8796 // revove vertical space from the top of the column
8797 return;
8799 if ($cell) {
8800 if ($this->rtl) {
8801 $cellpadding = $this->cell_padding['R'];
8802 } else {
8803 $cellpadding = $this->cell_padding['L'];
8805 } else {
8806 $cellpadding = 0;
8808 if ($this->rtl) {
8809 $this->x = $this->w - $this->rMargin - $cellpadding;
8810 } else {
8811 $this->x = $this->lMargin + $cellpadding;
8813 if (is_string($h)) {
8814 $this->y += $this->lasth;
8815 } else {
8816 $this->y += $h;
8818 $this->newline = true;
8822 * Returns the relative X value of current position.
8823 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
8824 * @return float
8825 * @public
8826 * @since 1.2
8827 * @see SetX(), GetY(), SetY()
8829 public function GetX() {
8830 //Get x position
8831 if ($this->rtl) {
8832 return ($this->w - $this->x);
8833 } else {
8834 return $this->x;
8839 * Returns the absolute X value of current position.
8840 * @return float
8841 * @public
8842 * @since 1.2
8843 * @see SetX(), GetY(), SetY()
8845 public function GetAbsX() {
8846 return $this->x;
8850 * Returns the ordinate of the current position.
8851 * @return float
8852 * @public
8853 * @since 1.0
8854 * @see SetY(), GetX(), SetX()
8856 public function GetY() {
8857 return $this->y;
8861 * Defines the abscissa of the current position.
8862 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
8863 * @param $x (float) The value of the abscissa in user units.
8864 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8865 * @public
8866 * @since 1.2
8867 * @see GetX(), GetY(), SetY(), SetXY()
8869 public function SetX($x, $rtloff=false) {
8870 $x = floatval($x);
8871 if (!$rtloff AND $this->rtl) {
8872 if ($x >= 0) {
8873 $this->x = $this->w - $x;
8874 } else {
8875 $this->x = abs($x);
8877 } else {
8878 if ($x >= 0) {
8879 $this->x = $x;
8880 } else {
8881 $this->x = $this->w + $x;
8884 if ($this->x < 0) {
8885 $this->x = 0;
8887 if ($this->x > $this->w) {
8888 $this->x = $this->w;
8893 * Moves the current abscissa back to the left margin and sets the ordinate.
8894 * If the passed value is negative, it is relative to the bottom of the page.
8895 * @param $y (float) The value of the ordinate in user units.
8896 * @param $resetx (bool) if true (default) reset the X position.
8897 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8898 * @public
8899 * @since 1.0
8900 * @see GetX(), GetY(), SetY(), SetXY()
8902 public function SetY($y, $resetx=true, $rtloff=false) {
8903 $y = floatval($y);
8904 if ($resetx) {
8905 //reset x
8906 if (!$rtloff AND $this->rtl) {
8907 $this->x = $this->w - $this->rMargin;
8908 } else {
8909 $this->x = $this->lMargin;
8912 if ($y >= 0) {
8913 $this->y = $y;
8914 } else {
8915 $this->y = $this->h + $y;
8917 if ($this->y < 0) {
8918 $this->y = 0;
8920 if ($this->y > $this->h) {
8921 $this->y = $this->h;
8926 * Defines the abscissa and ordinate of the current position.
8927 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
8928 * @param $x (float) The value of the abscissa.
8929 * @param $y (float) The value of the ordinate.
8930 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8931 * @public
8932 * @since 1.2
8933 * @see SetX(), SetY()
8935 public function SetXY($x, $y, $rtloff=false) {
8936 $this->SetY($y, false, $rtloff);
8937 $this->SetX($x, $rtloff);
8941 * Set the absolute X coordinate of the current pointer.
8942 * @param $x (float) The value of the abscissa in user units.
8943 * @public
8944 * @since 5.9.186 (2012-09-13)
8945 * @see setAbsX(), setAbsY(), SetAbsXY()
8947 public function SetAbsX($x) {
8948 $this->x = floatval($x);
8952 * Set the absolute Y coordinate of the current pointer.
8953 * @param $y (float) (float) The value of the ordinate in user units.
8954 * @public
8955 * @since 5.9.186 (2012-09-13)
8956 * @see setAbsX(), setAbsY(), SetAbsXY()
8958 public function SetAbsY($y) {
8959 $this->y = floatval($y);
8963 * Set the absolute X and Y coordinates of the current pointer.
8964 * @param $x (float) The value of the abscissa in user units.
8965 * @param $y (float) (float) The value of the ordinate in user units.
8966 * @public
8967 * @since 5.9.186 (2012-09-13)
8968 * @see setAbsX(), setAbsY(), SetAbsXY()
8970 public function SetAbsXY($x, $y) {
8971 $this->SetAbsX($x);
8972 $this->SetAbsY($y);
8976 * Ouput input data and compress it if possible.
8977 * @param $data (string) Data to output.
8978 * @param $length (int) Data length in bytes.
8979 * @protected
8980 * @since 5.9.086
8982 protected function sendOutputData($data, $length) {
8983 if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) OR empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
8984 // the content length may vary if the server is using compression
8985 header('Content-Length: '.$length);
8987 echo $data;
8991 * Send the document to a given destination: string, local file or browser.
8992 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
8993 * The method first calls Close() if necessary to terminate the document.
8994 * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
8995 * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
8996 * @public
8997 * @since 1.0
8998 * @see Close()
9000 public function Output($name='doc.pdf', $dest='I') {
9001 //Output PDF to some destination
9002 //Finish document if necessary
9003 if ($this->state < 3) {
9004 $this->Close();
9006 //Normalize parameters
9007 if (is_bool($dest)) {
9008 $dest = $dest ? 'D' : 'F';
9010 $dest = strtoupper($dest);
9011 if ($dest{0} != 'F') {
9012 $name = preg_replace('/[\s]+/', '_', $name);
9013 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
9015 if ($this->sign) {
9016 // *** apply digital signature to the document ***
9017 // get the document content
9018 $pdfdoc = $this->getBuffer();
9019 // remove last newline
9020 $pdfdoc = substr($pdfdoc, 0, -1);
9021 // Remove the original buffer
9022 if (isset($this->diskcache) AND $this->diskcache) {
9023 // remove buffer file from cache
9024 unlink($this->buffer);
9026 unset($this->buffer);
9027 // remove filler space
9028 $byterange_string_len = strlen($this->byterange_string);
9029 // define the ByteRange
9030 $byte_range = array();
9031 $byte_range[0] = 0;
9032 $byte_range[1] = strpos($pdfdoc, $this->byterange_string) + $byterange_string_len + 10;
9033 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
9034 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
9035 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
9036 // replace the ByteRange
9037 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
9038 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
9039 $pdfdoc = str_replace($this->byterange_string, $byterange, $pdfdoc);
9040 // write the document to a temporary folder
9041 $tempdoc = tempnam(K_PATH_CACHE, 'tmppdf_');
9042 $f = fopen($tempdoc, 'wb');
9043 if (!$f) {
9044 $this->Error('Unable to create temporary file: '.$tempdoc);
9046 $pdfdoc_length = strlen($pdfdoc);
9047 fwrite($f, $pdfdoc, $pdfdoc_length);
9048 fclose($f);
9049 // get digital signature via openssl library
9050 $tempsign = tempnam(K_PATH_CACHE, 'tmpsig_');
9051 if (empty($this->signature_data['extracerts'])) {
9052 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
9053 } else {
9054 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']);
9056 unlink($tempdoc);
9057 // read signature
9058 $signature = file_get_contents($tempsign);
9059 unlink($tempsign);
9060 // extract signature
9061 $signature = substr($signature, $pdfdoc_length);
9062 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
9063 $tmparr = explode("\n\n", $signature);
9064 $signature = $tmparr[1];
9065 unset($tmparr);
9066 // decode signature
9067 $signature = base64_decode(trim($signature));
9068 // convert signature to hex
9069 $signature = current(unpack('H*', $signature));
9070 $signature = str_pad($signature, $this->signature_max_length, '0');
9071 // disable disk caching
9072 $this->diskcache = false;
9073 // Add signature to the document
9074 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
9075 $this->bufferlen = strlen($this->buffer);
9077 switch($dest) {
9078 case 'I': {
9079 // Send PDF to the standard output
9080 if (ob_get_contents()) {
9081 $this->Error('Some data has already been output, can\'t send PDF file');
9083 if (php_sapi_name() != 'cli') {
9084 // send output to a browser
9085 header('Content-Type: application/pdf');
9086 if (headers_sent()) {
9087 $this->Error('Some data has already been output to browser, can\'t send PDF file');
9089 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
9090 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
9091 header('Pragma: public');
9092 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
9093 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
9094 header('Content-Disposition: inline; filename="'.basename($name).'"');
9095 $this->sendOutputData($this->getBuffer(), $this->bufferlen);
9096 } else {
9097 echo $this->getBuffer();
9099 break;
9101 case 'D': {
9102 // download PDF as file
9103 if (ob_get_contents()) {
9104 $this->Error('Some data has already been output, can\'t send PDF file');
9106 header('Content-Description: File Transfer');
9107 if (headers_sent()) {
9108 $this->Error('Some data has already been output to browser, can\'t send PDF file');
9110 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
9111 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
9112 header('Pragma: public');
9113 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
9114 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
9115 // force download dialog
9116 if (strpos(php_sapi_name(), 'cgi') === false) {
9117 header('Content-Type: application/force-download');
9118 header('Content-Type: application/octet-stream', false);
9119 header('Content-Type: application/download', false);
9120 header('Content-Type: application/pdf', false);
9121 } else {
9122 header('Content-Type: application/pdf');
9124 // use the Content-Disposition header to supply a recommended filename
9125 header('Content-Disposition: attachment; filename="'.basename($name).'"');
9126 header('Content-Transfer-Encoding: binary');
9127 $this->sendOutputData($this->getBuffer(), $this->bufferlen);
9128 break;
9130 case 'F':
9131 case 'FI':
9132 case 'FD': {
9133 // save PDF to a local file
9134 if ($this->diskcache) {
9135 copy($this->buffer, $name);
9136 } else {
9137 $f = fopen($name, 'wb');
9138 if (!$f) {
9139 $this->Error('Unable to create output file: '.$name);
9141 fwrite($f, $this->getBuffer(), $this->bufferlen);
9142 fclose($f);
9144 if ($dest == 'FI') {
9145 // send headers to browser
9146 header('Content-Type: application/pdf');
9147 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
9148 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
9149 header('Pragma: public');
9150 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
9151 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
9152 header('Content-Disposition: inline; filename="'.basename($name).'"');
9153 $this->sendOutputData(file_get_contents($name), filesize($name));
9154 } elseif ($dest == 'FD') {
9155 // send headers to browser
9156 if (ob_get_contents()) {
9157 $this->Error('Some data has already been output, can\'t send PDF file');
9159 header('Content-Description: File Transfer');
9160 if (headers_sent()) {
9161 $this->Error('Some data has already been output to browser, can\'t send PDF file');
9163 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
9164 header('Pragma: public');
9165 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
9166 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
9167 // force download dialog
9168 if (strpos(php_sapi_name(), 'cgi') === false) {
9169 header('Content-Type: application/force-download');
9170 header('Content-Type: application/octet-stream', false);
9171 header('Content-Type: application/download', false);
9172 header('Content-Type: application/pdf', false);
9173 } else {
9174 header('Content-Type: application/pdf');
9176 // use the Content-Disposition header to supply a recommended filename
9177 header('Content-Disposition: attachment; filename="'.basename($name).'"');
9178 header('Content-Transfer-Encoding: binary');
9179 $this->sendOutputData(file_get_contents($name), filesize($name));
9181 break;
9183 case 'E': {
9184 // return PDF as base64 mime multi-part email attachment (RFC 2045)
9185 $retval = 'Content-Type: application/pdf;'."\r\n";
9186 $retval .= ' name="'.$name.'"'."\r\n";
9187 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
9188 $retval .= 'Content-Disposition: attachment;'."\r\n";
9189 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
9190 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
9191 return $retval;
9193 case 'S': {
9194 // returns PDF as a string
9195 return $this->getBuffer();
9197 default: {
9198 $this->Error('Incorrect output destination: '.$dest);
9201 return '';
9205 * Unset all class variables except the following critical variables.
9206 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
9207 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
9208 * @public
9209 * @since 4.5.016 (2009-02-24)
9211 public function _destroy($destroyall=false, $preserve_objcopy=false) {
9212 if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!$this->empty_string($this->buffer))) {
9213 // remove buffer file from cache
9214 unlink($this->buffer);
9216 if ($destroyall AND isset($this->cached_files) AND !empty($this->cached_files)) {
9217 // remove cached files
9218 foreach ($this->cached_files as $cachefile) {
9219 if (is_file($cachefile)) {
9220 unlink($cachefile);
9223 unset($this->cached_files);
9225 foreach (array_keys(get_object_vars($this)) as $val) {
9226 if ($destroyall OR (
9227 ($val != 'internal_encoding')
9228 AND ($val != 'state')
9229 AND ($val != 'bufferlen')
9230 AND ($val != 'buffer')
9231 AND ($val != 'diskcache')
9232 AND ($val != 'cached_files')
9233 AND ($val != 'sign')
9234 AND ($val != 'signature_data')
9235 AND ($val != 'signature_max_length')
9236 AND ($val != 'byterange_string')
9237 )) {
9238 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
9239 unset($this->$val);
9246 * Check for locale-related bug
9247 * @protected
9249 protected function _dochecks() {
9250 //Check for locale-related bug
9251 if (1.1 == 1) {
9252 $this->Error('Don\'t alter the locale before including class file');
9254 //Check for decimal separator
9255 if (sprintf('%.1F', 1.0) != '1.0') {
9256 setlocale(LC_NUMERIC, 'C');
9261 * Return fonts path
9262 * @return string
9263 * @protected
9265 protected function _getfontpath() {
9266 if (!defined('K_PATH_FONTS') AND is_dir(dirname(__FILE__).'/fonts')) {
9267 define('K_PATH_FONTS', dirname(__FILE__).'/fonts/');
9269 return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
9273 * Return an array containing variations for the basic page number alias.
9274 * @param $a (string) Base alias.
9275 * @return array of page number aliases
9276 * @protected
9278 protected function getInternalPageNumberAliases($a= '') {
9279 $alias = array();
9280 // build array of Unicode + ASCII variants (the order is important)
9281 $alias = array('u' => array(), 'a' => array());
9282 $u = '{'.$a.'}';
9283 $alias['u'][] = $this->_escape($u);
9284 if ($this->isunicode) {
9285 $alias['u'][] = $this->_escape($this->UTF8ToLatin1($u));
9286 $alias['u'][] = $this->_escape($this->utf8StrRev($u, false, $this->tmprtl));
9287 $alias['a'][] = $this->_escape($this->UTF8ToLatin1($a));
9288 $alias['a'][] = $this->_escape($this->utf8StrRev($a, false, $this->tmprtl));
9290 $alias['a'][] = $this->_escape($a);
9291 return $alias;
9295 * Return an array containing all internal page aliases.
9296 * @return array of page number aliases
9297 * @protected
9299 protected function getAllInternalPageNumberAliases() {
9300 $basic_alias = array($this->alias_tot_pages, $this->alias_num_page, $this->alias_group_tot_pages, $this->alias_group_num_page, $this->alias_right_shift);
9301 $pnalias = array();
9302 foreach($basic_alias as $k => $a) {
9303 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
9305 return $pnalias;
9309 * Replace page number aliases with number.
9310 * @param $page (string) Page content.
9311 * @param $replace (array) Array of replacements (array keys are replacement strings, values are alias arrays).
9312 * @param $diff (int) If passed, this will be set to the total char number difference between alias and replacements.
9313 * @return replaced page content and updated $diff parameter as array.
9314 * @protected
9316 protected function replacePageNumAliases($page, $replace, $diff=0) {
9317 foreach ($replace as $rep) {
9318 foreach ($rep[3] as $a) {
9319 if (strpos($page, $a) !== false) {
9320 $page = str_replace($a, $rep[0], $page);
9321 $diff += ($rep[2] - $rep[1]);
9325 return array($page, $diff);
9329 * Replace right shift page number aliases with spaces to correct right alignment.
9330 * This works perfectly only when using monospaced fonts.
9331 * @param $page (string) Page content.
9332 * @param $aliases (array) Array of page aliases.
9333 * @param $diff (int) initial difference to add.
9334 * @return replaced page content.
9335 * @protected
9337 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
9338 foreach ($aliases as $type => $alias) {
9339 foreach ($alias as $a) {
9340 // find position of compensation factor
9341 $startnum = (strpos($a, ':') + 1);
9342 $a = substr($a, 0, $startnum);
9343 if (($pos = strpos($page, $a)) !== false) {
9344 // end of alias
9345 $endnum = strpos($page, '}', $pos);
9346 // string to be replaced
9347 $aa = substr($page, $pos, ($endnum - $pos + 1));
9348 // get compensation factor
9349 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
9350 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
9351 $ratio = floatval($ratio);
9352 if ($type == 'u') {
9353 $chrdiff = floor(($diff + 12) * $ratio);
9354 $shift = str_repeat(' ', $chrdiff);
9355 $shift = $this->UTF8ToUTF16BE($shift, false);
9356 } else {
9357 $chrdiff = floor(($diff + 11) * $ratio);
9358 $shift = str_repeat(' ', $chrdiff);
9360 $page = str_replace($aa, $shift, $page);
9364 return $page;
9368 * Set page boxes to be included on page descriptions.
9369 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
9370 * @protected
9372 protected function setPageBoxTypes($boxes) {
9373 $validboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
9374 $this->page_boxes = array();
9375 foreach ($boxes as $box) {
9376 if (in_array($box, $validboxes)) {
9377 $this->page_boxes[] = $box;
9383 * Output pages (and replace page number aliases).
9384 * @protected
9386 protected function _putpages() {
9387 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9388 // get internal aliases for page numbers
9389 $pnalias = $this->getAllInternalPageNumberAliases();
9390 $num_pages = $this->numpages;
9391 $ptpa = $this->formatPageNumber(($this->starting_page_number + $num_pages - 1));
9392 $ptpu = $this->UTF8ToUTF16BE($ptpa, false);
9393 $ptp_num_chars = $this->GetNumChars($ptpa);
9394 $pagegroupnum = 0;
9395 $groupnum = 0;
9396 $ptgu = 1;
9397 $ptga = 1;
9398 for ($n = 1; $n <= $num_pages; ++$n) {
9399 // get current page
9400 $temppage = $this->getPageBuffer($n);
9401 $pagelen = strlen($temppage);
9402 // set replacements for total pages number
9403 $pnpa = $this->formatPageNumber(($this->starting_page_number + $n - 1));
9404 $pnpu = $this->UTF8ToUTF16BE($pnpa, false);
9405 $pnp_num_chars = $this->GetNumChars($pnpa);
9406 $pdiff = 0; // difference used for right shift alignment of page numbers
9407 $gdiff = 0; // difference used for right shift alignment of page group numbers
9408 if (!empty($this->pagegroups)) {
9409 if (isset($this->newpagegroup[$n])) {
9410 $pagegroupnum = 0;
9411 ++$groupnum;
9412 $ptga = $this->formatPageNumber($this->pagegroups[$groupnum]);
9413 $ptgu = $this->UTF8ToUTF16BE($ptga, false);
9414 $ptg_num_chars = $this->GetNumChars($ptga);
9416 ++$pagegroupnum;
9417 $pnga = $this->formatPageNumber($pagegroupnum);
9418 $pngu = $this->UTF8ToUTF16BE($pnga, false);
9419 $png_num_chars = $this->GetNumChars($pnga);
9420 // replace page numbers
9421 $replace = array();
9422 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
9423 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
9424 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
9425 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
9426 list($temppage, $gdiff) = $this->replacePageNumAliases($temppage, $replace, $gdiff);
9428 // replace page numbers
9429 $replace = array();
9430 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
9431 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
9432 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
9433 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
9434 list($temppage, $pdiff) = $this->replacePageNumAliases($temppage, $replace, $pdiff);
9435 // replace right shift alias
9436 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
9437 // replace EPS marker
9438 $temppage = str_replace($this->epsmarker, '', $temppage);
9439 //Page
9440 $this->page_obj_id[$n] = $this->_newobj();
9441 $out = '<<';
9442 $out .= ' /Type /Page';
9443 $out .= ' /Parent 1 0 R';
9444 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
9445 $out .= ' /Resources 2 0 R';
9446 foreach ($this->page_boxes as $box) {
9447 $out .= ' /'.$box;
9448 $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']);
9450 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
9451 $out .= ' /BoxColorInfo <<';
9452 foreach ($this->page_boxes as $box) {
9453 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
9454 $out .= ' /'.$box.' <<';
9455 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
9456 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
9457 $out .= ' /C [';
9458 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
9459 $out .= ' ]';
9461 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
9462 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
9464 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
9465 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
9467 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
9468 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
9469 $out .= ' /D [';
9470 foreach ($dashes as $dash) {
9471 $out .= sprintf(' %F', ($dash * $this->k));
9473 $out .= ' ]';
9475 $out .= ' >>';
9478 $out .= ' >>';
9480 $out .= ' /Contents '.($this->n + 1).' 0 R';
9481 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
9482 if (!$this->pdfa_mode) {
9483 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
9485 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
9486 // page transitions
9487 if (isset($this->pagedim[$n]['trans']['Dur'])) {
9488 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
9490 $out .= ' /Trans <<';
9491 $out .= ' /Type /Trans';
9492 if (isset($this->pagedim[$n]['trans']['S'])) {
9493 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
9495 if (isset($this->pagedim[$n]['trans']['D'])) {
9496 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
9498 if (isset($this->pagedim[$n]['trans']['Dm'])) {
9499 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
9501 if (isset($this->pagedim[$n]['trans']['M'])) {
9502 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
9504 if (isset($this->pagedim[$n]['trans']['Di'])) {
9505 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
9507 if (isset($this->pagedim[$n]['trans']['SS'])) {
9508 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
9510 if (isset($this->pagedim[$n]['trans']['B'])) {
9511 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
9513 $out .= ' >>';
9515 $out .= $this->_getannotsrefs($n);
9516 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
9517 $out .= ' >>';
9518 $out .= "\n".'endobj';
9519 $this->_out($out);
9520 //Page content
9521 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
9522 $this->_newobj();
9523 $p = $this->_getrawstream($p);
9524 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
9525 if ($this->diskcache) {
9526 // remove temporary files
9527 unlink($this->pages[$n]);
9530 //Pages root
9531 $out = $this->_getobj(1)."\n";
9532 $out .= '<< /Type /Pages /Kids [';
9533 foreach($this->page_obj_id as $page_obj) {
9534 $out .= ' '.$page_obj.' 0 R';
9536 $out .= ' ] /Count '.$num_pages.' >>';
9537 $out .= "\n".'endobj';
9538 $this->_out($out);
9542 * Output references to page annotations
9543 * @param $n (int) page number
9544 * @protected
9545 * @author Nicola Asuni
9546 * @since 4.7.000 (2008-08-29)
9547 * @deprecated
9549 protected function _putannotsrefs($n) {
9550 $this->_out($this->_getannotsrefs($n));
9554 * Get references to page annotations.
9555 * @param $n (int) page number
9556 * @return string
9557 * @protected
9558 * @author Nicola Asuni
9559 * @since 5.0.010 (2010-05-17)
9561 protected function _getannotsrefs($n) {
9562 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
9563 return '';
9565 $out = ' /Annots [';
9566 if (isset($this->PageAnnots[$n])) {
9567 foreach ($this->PageAnnots[$n] as $key => $val) {
9568 if (!in_array($val['n'], $this->radio_groups)) {
9569 $out .= ' '.$val['n'].' 0 R';
9572 // add radiobutton groups
9573 if (isset($this->radiobutton_groups[$n])) {
9574 foreach ($this->radiobutton_groups[$n] as $key => $data) {
9575 if (isset($data['n'])) {
9576 $out .= ' '.$data['n'].' 0 R';
9581 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
9582 // set reference for signature object
9583 $out .= ' '.$this->sig_obj_id.' 0 R';
9585 if (!empty($this->empty_signature_appearance)) {
9586 foreach ($this->empty_signature_appearance as $esa) {
9587 if ($esa['page'] == $n) {
9588 // set reference for empty signature objects
9589 $out .= ' '.$esa['objid'].' 0 R';
9593 $out .= ' ]';
9594 return $out;
9598 * Output annotations objects for all pages.
9599 * !!! THIS METHOD IS NOT YET COMPLETED !!!
9600 * See section 12.5 of PDF 32000_2008 reference.
9601 * @protected
9602 * @author Nicola Asuni
9603 * @since 4.0.018 (2008-08-06)
9605 protected function _putannotsobjs() {
9606 // reset object counter
9607 for ($n=1; $n <= $this->numpages; ++$n) {
9608 if (isset($this->PageAnnots[$n])) {
9609 // set page annotations
9610 foreach ($this->PageAnnots[$n] as $key => $pl) {
9611 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
9612 // create annotation object for grouping radiobuttons
9613 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
9614 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
9615 $annots = '<<';
9616 $annots .= ' /Type /Annot';
9617 $annots .= ' /Subtype /Widget';
9618 $annots .= ' /Rect [0 0 0 0]';
9619 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
9620 // read only
9621 $annots .= ' /F 68';
9622 $annots .= ' /Ff 49153';
9623 } else {
9624 $annots .= ' /F 4'; // default print for PDF/A
9625 $annots .= ' /Ff 49152';
9627 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
9628 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
9629 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
9631 $annots .= ' /FT /Btn';
9632 $annots .= ' /Kids [';
9633 $defval = '';
9634 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
9635 if (isset($data['kid'])) {
9636 $annots .= ' '.$data['kid'].' 0 R';
9637 if ($data['def'] !== 'Off') {
9638 $defval = $data['def'];
9642 $annots .= ' ]';
9643 if (!empty($defval)) {
9644 $annots .= ' /V /'.$defval;
9646 $annots .= ' >>';
9647 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
9648 $this->form_obj_id[] = $radio_button_obj_id;
9649 // store object id to be used on Parent entry of Kids
9650 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
9652 $formfield = false;
9653 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
9654 $a = $pl['x'] * $this->k;
9655 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
9656 $c = $pl['w'] * $this->k;
9657 $d = $pl['h'] * $this->k;
9658 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
9659 // create new annotation object
9660 $annots = '<</Type /Annot';
9661 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
9662 $annots .= ' /Rect ['.$rect.']';
9663 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
9664 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
9665 $annots .= ' /FT /'.$pl['opt']['ft'];
9666 $formfield = true;
9668 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
9669 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
9670 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
9671 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
9672 if (isset($pl['opt']['f'])) {
9673 $fval = 0;
9674 if (is_array($pl['opt']['f'])) {
9675 foreach ($pl['opt']['f'] as $f) {
9676 switch (strtolower($f)) {
9677 case 'invisible': {
9678 $fval += 1 << 0;
9679 break;
9681 case 'hidden': {
9682 $fval += 1 << 1;
9683 break;
9685 case 'print': {
9686 $fval += 1 << 2;
9687 break;
9689 case 'nozoom': {
9690 $fval += 1 << 3;
9691 break;
9693 case 'norotate': {
9694 $fval += 1 << 4;
9695 break;
9697 case 'noview': {
9698 $fval += 1 << 5;
9699 break;
9701 case 'readonly': {
9702 $fval += 1 << 6;
9703 break;
9705 case 'locked': {
9706 $fval += 1 << 8;
9707 break;
9709 case 'togglenoview': {
9710 $fval += 1 << 9;
9711 break;
9713 case 'lockedcontents': {
9714 $fval += 1 << 10;
9715 break;
9717 default: {
9718 break;
9722 } else {
9723 $fval = intval($pl['opt']['f']);
9725 } else {
9726 $fval = 4;
9728 if ($this->pdfa_mode) {
9729 // force print flag for PDF/A mode
9730 $fval |= 4;
9732 $annots .= ' /F '.intval($fval);
9733 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
9734 $annots .= ' /AS /'.$pl['opt']['as'];
9736 if (isset($pl['opt']['ap'])) {
9737 // appearance stream
9738 $annots .= ' /AP <<';
9739 if (is_array($pl['opt']['ap'])) {
9740 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
9741 // $apmode can be: n = normal; r = rollover; d = down;
9742 $annots .= ' /'.strtoupper($apmode);
9743 if (is_array($apdef)) {
9744 $annots .= ' <<';
9745 foreach ($apdef as $apstate => $stream) {
9746 // reference to XObject that define the appearance for this mode-state
9747 $apsobjid = $this->_putAPXObject($c, $d, $stream);
9748 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
9750 $annots .= ' >>';
9751 } else {
9752 // reference to XObject that define the appearance for this mode
9753 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
9754 $annots .= ' '.$apsobjid.' 0 R';
9757 } else {
9758 $annots .= $pl['opt']['ap'];
9760 $annots .= ' >>';
9762 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
9763 $annots .= ' /BS <<';
9764 $annots .= ' /Type /Border';
9765 if (isset($pl['opt']['bs']['w'])) {
9766 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
9768 $bstyles = array('S', 'D', 'B', 'I', 'U');
9769 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
9770 $annots .= ' /S /'.$pl['opt']['bs']['s'];
9772 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
9773 $annots .= ' /D [';
9774 foreach ($pl['opt']['bs']['d'] as $cord) {
9775 $annots .= ' '.intval($cord);
9777 $annots .= ']';
9779 $annots .= ' >>';
9780 } else {
9781 $annots .= ' /Border [';
9782 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
9783 $annots .= intval($pl['opt']['border'][0]).' ';
9784 $annots .= intval($pl['opt']['border'][1]).' ';
9785 $annots .= intval($pl['opt']['border'][2]);
9786 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
9787 $annots .= ' [';
9788 foreach ($pl['opt']['border'][3] as $dash) {
9789 $annots .= intval($dash).' ';
9791 $annots .= ']';
9793 } else {
9794 $annots .= '0 0 0';
9796 $annots .= ']';
9798 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
9799 $annots .= ' /BE <<';
9800 $bstyles = array('S', 'C');
9801 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
9802 $annots .= ' /S /'.$pl['opt']['bs']['s'];
9803 } else {
9804 $annots .= ' /S /S';
9806 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
9807 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
9809 $annots .= '>>';
9811 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
9812 $annots .= ' /C '.$this->getColorStringFromArray($pl['opt']['c']);
9814 //$annots .= ' /StructParent ';
9815 //$annots .= ' /OC ';
9816 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
9817 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
9818 // this is a markup type
9819 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
9820 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
9822 //$annots .= ' /Popup ';
9823 if (isset($pl['opt']['ca'])) {
9824 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
9826 if (isset($pl['opt']['rc'])) {
9827 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
9829 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
9830 //$annots .= ' /IRT ';
9831 if (isset($pl['opt']['subj'])) {
9832 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
9834 //$annots .= ' /RT ';
9835 //$annots .= ' /IT ';
9836 //$annots .= ' /ExData ';
9838 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
9839 // Annotation types
9840 switch (strtolower($pl['opt']['subtype'])) {
9841 case 'text': {
9842 if (isset($pl['opt']['open'])) {
9843 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
9845 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
9846 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
9847 $annots .= ' /Name /'.$pl['opt']['name'];
9848 } else {
9849 $annots .= ' /Name /Note';
9851 $statemodels = array('Marked', 'Review');
9852 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
9853 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
9854 } else {
9855 $pl['opt']['statemodel'] = 'Marked';
9856 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
9858 if ($pl['opt']['statemodel'] == 'Marked') {
9859 $states = array('Accepted', 'Unmarked');
9860 } else {
9861 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
9863 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
9864 $annots .= ' /State /'.$pl['opt']['state'];
9865 } else {
9866 if ($pl['opt']['statemodel'] == 'Marked') {
9867 $annots .= ' /State /Unmarked';
9868 } else {
9869 $annots .= ' /State /None';
9872 break;
9874 case 'link': {
9875 if (is_string($pl['txt'])) {
9876 if ($pl['txt'][0] == '#') {
9877 // internal destination
9878 $annots .= ' /Dest /'.$this->encodeNameObject(substr($pl['txt'], 1));
9879 } elseif ($pl['txt'][0] == '%') {
9880 // embedded PDF file
9881 $filename = basename(substr($pl['txt'], 1));
9882 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
9883 } elseif ($pl['txt'][0] == '*') {
9884 // embedded generic file
9885 $filename = basename(substr($pl['txt'], 1));
9886 $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});';
9887 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
9888 } else {
9889 // external URI link
9890 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
9892 } elseif (isset($this->links[$pl['txt']])) {
9893 // internal link ID
9894 $l = $this->links[$pl['txt']];
9895 if (isset($this->page_obj_id[($l[0])])) {
9896 $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
9899 $hmodes = array('N', 'I', 'O', 'P');
9900 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
9901 $annots .= ' /H /'.$pl['opt']['h'];
9902 } else {
9903 $annots .= ' /H /I';
9905 //$annots .= ' /PA ';
9906 //$annots .= ' /Quadpoints ';
9907 break;
9909 case 'freetext': {
9910 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
9911 $annots .= ' /DA ('.$pl['opt']['da'].')';
9913 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
9914 $annots .= ' /Q '.intval($pl['opt']['q']);
9916 if (isset($pl['opt']['rc'])) {
9917 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
9919 if (isset($pl['opt']['ds'])) {
9920 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
9922 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
9923 $annots .= ' /CL [';
9924 foreach ($pl['opt']['cl'] as $cl) {
9925 $annots .= sprintf('%F ', $cl * $this->k);
9927 $annots .= ']';
9929 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
9930 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
9931 $annots .= ' /IT /'.$pl['opt']['it'];
9933 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
9934 $l = $pl['opt']['rd'][0] * $this->k;
9935 $r = $pl['opt']['rd'][1] * $this->k;
9936 $t = $pl['opt']['rd'][2] * $this->k;
9937 $b = $pl['opt']['rd'][3] * $this->k;
9938 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
9940 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
9941 $annots .= ' /LE /'.$pl['opt']['le'];
9943 break;
9945 case 'line': {
9946 break;
9948 case 'square': {
9949 break;
9951 case 'circle': {
9952 break;
9954 case 'polygon': {
9955 break;
9957 case 'polyline': {
9958 break;
9960 case 'highlight': {
9961 break;
9963 case 'underline': {
9964 break;
9966 case 'squiggly': {
9967 break;
9969 case 'strikeout': {
9970 break;
9972 case 'stamp': {
9973 break;
9975 case 'caret': {
9976 break;
9978 case 'ink': {
9979 break;
9981 case 'popup': {
9982 break;
9984 case 'fileattachment': {
9985 if ($this->pdfa_mode) {
9986 // embedded files are not allowed in PDF/A mode
9987 break;
9989 if (!isset($pl['opt']['fs'])) {
9990 break;
9992 $filename = basename($pl['opt']['fs']);
9993 if (isset($this->embeddedfiles[$filename]['f'])) {
9994 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
9995 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
9996 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
9997 $annots .= ' /Name /'.$pl['opt']['name'];
9998 } else {
9999 $annots .= ' /Name /PushPin';
10001 // index (zero-based) of the annotation in the Annots array of this page
10002 $this->embeddedfiles[$filename]['a'] = $key;
10004 break;
10006 case 'sound': {
10007 if (!isset($pl['opt']['fs'])) {
10008 break;
10010 $filename = basename($pl['opt']['fs']);
10011 if (isset($this->embeddedfiles[$filename]['f'])) {
10012 // ... TO BE COMPLETED ...
10013 // /R /C /B /E /CO /CP
10014 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
10015 $iconsapp = array('Speaker', 'Mic');
10016 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
10017 $annots .= ' /Name /'.$pl['opt']['name'];
10018 } else {
10019 $annots .= ' /Name /Speaker';
10022 break;
10024 case 'movie': {
10025 break;
10027 case 'widget': {
10028 $hmode = array('N', 'I', 'O', 'P', 'T');
10029 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
10030 $annots .= ' /H /'.$pl['opt']['h'];
10032 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
10033 $annots .= ' /MK <<';
10034 if (isset($pl['opt']['mk']['r'])) {
10035 $annots .= ' /R '.$pl['opt']['mk']['r'];
10037 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
10038 $annots .= ' /BC '.$this->getColorStringFromArray($pl['opt']['mk']['bc']);
10040 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
10041 $annots .= ' /BG '.$this->getColorStringFromArray($pl['opt']['mk']['bg']);
10043 if (isset($pl['opt']['mk']['ca'])) {
10044 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
10046 if (isset($pl['opt']['mk']['rc'])) {
10047 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
10049 if (isset($pl['opt']['mk']['ac'])) {
10050 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
10052 if (isset($pl['opt']['mk']['i'])) {
10053 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
10054 if ($info !== false) {
10055 $annots .= ' /I '.$info['n'].' 0 R';
10058 if (isset($pl['opt']['mk']['ri'])) {
10059 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
10060 if ($info !== false) {
10061 $annots .= ' /RI '.$info['n'].' 0 R';
10064 if (isset($pl['opt']['mk']['ix'])) {
10065 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
10066 if ($info !== false) {
10067 $annots .= ' /IX '.$info['n'].' 0 R';
10070 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
10071 $annots .= ' /IF <<';
10072 $if_sw = array('A', 'B', 'S', 'N');
10073 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
10074 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
10076 $if_s = array('A', 'P');
10077 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
10078 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
10080 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
10081 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
10083 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
10084 $annots .= ' /FB true';
10086 $annots .= '>>';
10088 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
10089 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
10091 $annots .= '>>';
10092 } // end MK
10093 // --- Entries for field dictionaries ---
10094 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
10095 // set parent
10096 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
10098 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
10099 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
10101 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
10102 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
10104 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
10105 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
10107 if (isset($pl['opt']['ff'])) {
10108 if (is_array($pl['opt']['ff'])) {
10109 // array of bit settings
10110 $flag = 0;
10111 foreach($pl['opt']['ff'] as $val) {
10112 $flag += 1 << ($val - 1);
10114 } else {
10115 $flag = intval($pl['opt']['ff']);
10117 $annots .= ' /Ff '.$flag;
10119 if (isset($pl['opt']['maxlen'])) {
10120 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
10122 if (isset($pl['opt']['v'])) {
10123 $annots .= ' /V';
10124 if (is_array($pl['opt']['v'])) {
10125 foreach ($pl['opt']['v'] AS $optval) {
10126 if (is_float($optval)) {
10127 $optval = sprintf('%F', $optval);
10129 $annots .= ' '.$optval;
10131 } else {
10132 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
10135 if (isset($pl['opt']['dv'])) {
10136 $annots .= ' /DV';
10137 if (is_array($pl['opt']['dv'])) {
10138 foreach ($pl['opt']['dv'] AS $optval) {
10139 if (is_float($optval)) {
10140 $optval = sprintf('%F', $optval);
10142 $annots .= ' '.$optval;
10144 } else {
10145 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
10148 if (isset($pl['opt']['rv'])) {
10149 $annots .= ' /RV';
10150 if (is_array($pl['opt']['rv'])) {
10151 foreach ($pl['opt']['rv'] AS $optval) {
10152 if (is_float($optval)) {
10153 $optval = sprintf('%F', $optval);
10155 $annots .= ' '.$optval;
10157 } else {
10158 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
10161 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
10162 $annots .= ' /A << '.$pl['opt']['a'].' >>';
10164 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
10165 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
10167 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
10168 $annots .= ' /DA ('.$pl['opt']['da'].')';
10170 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
10171 $annots .= ' /Q '.intval($pl['opt']['q']);
10173 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
10174 $annots .= ' /Opt [';
10175 foreach($pl['opt']['opt'] AS $copt) {
10176 if (is_array($copt)) {
10177 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
10178 } else {
10179 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
10182 $annots .= ']';
10184 if (isset($pl['opt']['ti'])) {
10185 $annots .= ' /TI '.intval($pl['opt']['ti']);
10187 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
10188 $annots .= ' /I [';
10189 foreach($pl['opt']['i'] AS $copt) {
10190 $annots .= intval($copt).' ';
10192 $annots .= ']';
10194 break;
10196 case 'screen': {
10197 break;
10199 case 'printermark': {
10200 break;
10202 case 'trapnet': {
10203 break;
10205 case 'watermark': {
10206 break;
10208 case '3d': {
10209 break;
10211 default: {
10212 break;
10215 $annots .= '>>';
10216 // create new annotation object
10217 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
10218 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
10219 // store reference of form object
10220 $this->form_obj_id[] = $annot_obj_id;
10224 } // end for each page
10228 * Put appearance streams XObject used to define annotation's appearance states.
10229 * @param $w (int) annotation width
10230 * @param $h (int) annotation height
10231 * @param $stream (string) appearance stream
10232 * @return int object ID
10233 * @protected
10234 * @since 4.8.001 (2009-09-09)
10236 protected function _putAPXObject($w=0, $h=0, $stream='') {
10237 $stream = trim($stream);
10238 $out = $this->_getobj()."\n";
10239 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
10240 $out .= '<<';
10241 $out .= ' /Type /XObject';
10242 $out .= ' /Subtype /Form';
10243 $out .= ' /FormType 1';
10244 if ($this->compress) {
10245 $stream = gzcompress($stream);
10246 $out .= ' /Filter /FlateDecode';
10248 $rect = sprintf('%F %F', $w, $h);
10249 $out .= ' /BBox [0 0 '.$rect.']';
10250 $out .= ' /Matrix [1 0 0 1 0 0]';
10251 $out .= ' /Resources 2 0 R';
10252 $stream = $this->_getrawstream($stream);
10253 $out .= ' /Length '.strlen($stream);
10254 $out .= ' >>';
10255 $out .= ' stream'."\n".$stream."\n".'endstream';
10256 $out .= "\n".'endobj';
10257 $this->_out($out);
10258 return $this->n;
10262 * Get ULONG from string (Big Endian 32-bit unsigned integer).
10263 * @param $str (string) string from where to extract value
10264 * @param $offset (int) point from where to read the data
10265 * @return int 32 bit value
10266 * @author Nicola Asuni
10267 * @protected
10268 * @since 5.2.000 (2010-06-02)
10270 protected function _getULONG($str, $offset) {
10271 $v = unpack('Ni', substr($str, $offset, 4));
10272 return $v['i'];
10276 * Get USHORT from string (Big Endian 16-bit unsigned integer).
10277 * @param $str (string) string from where to extract value
10278 * @param $offset (int) point from where to read the data
10279 * @return int 16 bit value
10280 * @author Nicola Asuni
10281 * @protected
10282 * @since 5.2.000 (2010-06-02)
10284 protected function _getUSHORT($str, $offset) {
10285 $v = unpack('ni', substr($str, $offset, 2));
10286 return $v['i'];
10290 * Get SHORT from string (Big Endian 16-bit signed integer).
10291 * @param $str (string) String from where to extract value.
10292 * @param $offset (int) Point from where to read the data.
10293 * @return int 16 bit value
10294 * @author Nicola Asuni
10295 * @protected
10296 * @since 5.2.000 (2010-06-02)
10298 protected function _getSHORT($str, $offset) {
10299 $v = unpack('si', substr($str, $offset, 2));
10300 return $v['i'];
10304 * Get FWORD from string (Big Endian 16-bit signed integer).
10305 * @param $str (string) String from where to extract value.
10306 * @param $offset (int) Point from where to read the data.
10307 * @return int 16 bit value
10308 * @author Nicola Asuni
10309 * @protected
10310 * @since 5.9.123 (2011-09-30)
10312 protected function _getFWORD($str, $offset) {
10313 $v = $this->_getUSHORT($str, $offset);
10314 if ($v > 0x7fff) {
10315 $v -= 0x10000;
10317 return $v;
10321 * Get UFWORD from string (Big Endian 16-bit unsigned integer).
10322 * @param $str (string) string from where to extract value
10323 * @param $offset (int) point from where to read the data
10324 * @return int 16 bit value
10325 * @author Nicola Asuni
10326 * @protected
10327 * @since 5.9.123 (2011-09-30)
10329 protected function _getUFWORD($str, $offset) {
10330 $v = $this->_getUSHORT($str, $offset);
10331 return $v;
10335 * Get FIXED from string (32-bit signed fixed-point number (16.16).
10336 * @param $str (string) string from where to extract value
10337 * @param $offset (int) point from where to read the data
10338 * @return int 16 bit value
10339 * @author Nicola Asuni
10340 * @protected
10341 * @since 5.9.123 (2011-09-30)
10343 protected function _getFIXED($str, $offset) {
10344 // mantissa
10345 $m = $this->_getFWORD($str, $offset);
10346 // fraction
10347 $f = $this->_getUSHORT($str, ($offset + 2));
10348 $v = floatval(''.$m.'.'.$f.'');
10349 return $v;
10353 * Get BYTE from string (8-bit unsigned integer).
10354 * @param $str (string) String from where to extract value.
10355 * @param $offset (int) Point from where to read the data.
10356 * @return int 8 bit value
10357 * @author Nicola Asuni
10358 * @protected
10359 * @since 5.2.000 (2010-06-02)
10361 protected function _getBYTE($str, $offset) {
10362 $v = unpack('Ci', substr($str, $offset, 1));
10363 return $v['i'];
10366 * Update the CIDToGIDMap string with a new value.
10367 * @param $map (string) CIDToGIDMap.
10368 * @param $cid (int) CID value.
10369 * @param $gid (int) GID value.
10370 * @return (string) CIDToGIDMap.
10371 * @author Nicola Asuni
10372 * @protected
10373 * @since 5.9.123 (2011-09-29)
10375 protected function updateCIDtoGIDmap($map, $cid, $gid) {
10376 if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
10377 if ($gid > 0xFFFF) {
10378 $gid -= 0x10000;
10380 $map[($cid * 2)] = chr($gid >> 8);
10381 $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
10383 return $map;
10387 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
10388 * @param $fontfile (string) Font file (full path).
10389 * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
10390 * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
10391 * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
10392 * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
10393 * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
10394 * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
10395 * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
10396 * @return (string) TCPDF font name.
10397 * @author Nicola Asuni
10398 * @public
10399 * @since 5.9.123 (2010-09-30)
10401 public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false) {
10402 if (!file_exists($fontfile)) {
10403 $this->Error('Could not find file: '.$fontfile.'');
10405 // font metrics
10406 $fmetric = array();
10407 // build new font name for TCPDF compatibility
10408 $font_path_parts = pathinfo($fontfile);
10409 if (!isset($font_path_parts['filename'])) {
10410 $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
10412 $font_name = strtolower($font_path_parts['filename']);
10413 $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
10414 $search = array('bold', 'oblique', 'italic', 'regular');
10415 $replace = array('b', 'i', 'i', '');
10416 $font_name = str_replace($search, $replace, $font_name);
10417 if (empty($font_name)) {
10418 // set generic name
10419 $font_name = 'tcpdffont';
10421 // set output path
10422 if (empty($outpath)) {
10423 $outpath = $this->_getfontpath();
10425 // check if this font already exist
10426 if (file_exists($outpath.$font_name.'.php')) {
10427 // this font already exist (delete it from fonts folder to rebuild it)
10428 return $font_name;
10430 $fmetric['file'] = $font_name.'.z';
10431 $fmetric['ctg'] = $font_name.'.ctg.z';
10432 // get font data
10433 $font = file_get_contents($fontfile);
10434 $fmetric['originalsize'] = strlen($font);
10435 // autodetect font type
10436 if (empty($fonttype)) {
10437 if ($this->_getULONG($font, 0) == 0x10000) {
10438 // True Type (Unicode or not)
10439 $fonttype = 'TrueTypeUnicode';
10440 } elseif (substr($font, 0, 4) == 'OTTO') {
10441 // Open Type (Unicode or not)
10442 $this->Error('Unsupported font format: OpenType with CFF data.');
10443 } else {
10444 // Type 1
10445 $fonttype = 'Type1';
10448 // set font type
10449 switch ($fonttype) {
10450 case 'CID0CT':
10451 case 'CID0CS':
10452 case 'CID0KR':
10453 case 'CID0JP': {
10454 $fmetric['type'] = 'cidfont0';
10455 break;
10457 case 'Type1': {
10458 $fmetric['type'] = 'Type1';
10459 if (empty($enc) AND (($flags & 4) == 0)) {
10460 $enc = 'cp1252';
10462 break;
10464 case 'TrueType': {
10465 $fmetric['type'] = 'TrueType';
10466 break;
10468 case 'TrueTypeUnicode':
10469 default: {
10470 $fmetric['type'] = 'TrueTypeUnicode';
10471 break;
10474 // set encoding maps (if any)
10475 $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
10476 $fmetric['diff'] = '';
10477 if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
10478 if (!empty($enc) AND ($enc != 'cp1252') AND isset($this->encmaps->encmap[$enc])) {
10479 // build differences from reference encoding
10480 $enc_ref = $this->encmaps->encmap['cp1252'];
10481 $enc_target = $this->encmaps->encmap[$enc];
10482 $last = 0;
10483 for ($i = 32; $i <= 255; ++$i) {
10484 if ($enc_target != $enc_ref[$i]) {
10485 if ($i != ($last + 1)) {
10486 $fmetric['diff'] .= $i.' ';
10488 $last = $i;
10489 $fmetric['diff'] .= '/'.$enc_target[$i].' ';
10494 // parse the font by type
10495 if ($fmetric['type'] == 'Type1') {
10496 // ---------- TYPE 1 ----------
10497 // read first segment
10498 $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
10499 if ($a['marker'] != 128) {
10500 $this->Error('Font file is not a valid binary Type1');
10502 $fmetric['size1'] = $a['size'];
10503 $data = substr($font, 6, $fmetric['size1']);
10504 // read second segment
10505 $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
10506 if ($a['marker'] != 128) {
10507 $this->Error('Font file is not a valid binary Type1');
10509 $fmetric['size2'] = $a['size'];
10510 $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
10511 $data .= $encrypted;
10512 // store compressed font
10513 $fp = fopen($outpath.$fmetric['file'], 'wb');
10514 fwrite($fp, gzcompress($data));
10515 fclose($fp);
10516 // get font info
10517 $fmetric['Flags'] = $flags;
10518 preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
10519 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
10520 preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
10521 $fmetric['bbox'] = trim($matches[1]);
10522 $bv = explode(' ', $fmetric['bbox']);
10523 $fmetric['Ascent'] = intval($bv[3]);
10524 $fmetric['Descent'] = intval($bv[1]);
10525 preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
10526 $fmetric['italicAngle'] = intval($matches[1]);
10527 if ($fmetric['italicAngle'] != 0) {
10528 $fmetric['Flags'] |= 64;
10530 preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
10531 $fmetric['underlinePosition'] = intval($matches[1]);
10532 preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
10533 $fmetric['underlineThickness'] = intval($matches[1]);
10534 preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
10535 if ($matches[1] == 'true') {
10536 $fmetric['Flags'] |= 1;
10538 // get internal map
10539 $imap = array();
10540 if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
10541 foreach ($fmap as $v) {
10542 $imap[$v[2]] = $v[1];
10545 // decrypt eexec encrypted part
10546 $r = 55665; // eexec encryption constant
10547 $c1 = 52845;
10548 $c2 = 22719;
10549 $elen = strlen($encrypted);
10550 $eplain = '';
10551 for ($i = 0; $i < $elen; ++$i) {
10552 $chr = ord($encrypted[$i]);
10553 $eplain .= chr($chr ^ ($r >> 8));
10554 $r = ((($chr + $r) * $c1 + $c2) % 65536);
10556 if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
10557 if ($matches[1] == 'true') {
10558 $fmetric['Flags'] |= 0x40000;
10561 if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10562 $fmetric['StemV'] = intval($matches[1]);
10563 } else {
10564 $fmetric['StemV'] = 70;
10566 if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10567 $fmetric['StemH'] = intval($matches[1]);
10568 } else {
10569 $fmetric['StemH'] = 30;
10571 if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10572 $bv = explode(' ', $matches[1]);
10573 if (count($bv) >= 6) {
10574 $v1 = intval($bv[2]);
10575 $v2 = intval($bv[4]);
10576 if ($v1 <= $v2) {
10577 $fmetric['XHeight'] = $v1;
10578 $fmetric['CapHeight'] = $v2;
10579 } else {
10580 $fmetric['XHeight'] = $v2;
10581 $fmetric['CapHeight'] = $v1;
10583 } else {
10584 $fmetric['XHeight'] = 450;
10585 $fmetric['CapHeight'] = 700;
10587 } else {
10588 $fmetric['XHeight'] = 450;
10589 $fmetric['CapHeight'] = 700;
10591 // get the number of random bytes at the beginning of charstrings
10592 if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
10593 $lenIV = intval($matches[1]);
10594 } else {
10595 $lenIV = 4;
10597 $fmetric['Leading'] = 0;
10598 // get charstring data
10599 $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
10600 preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
10601 if (!empty($enc) AND isset($this->encmaps->encmap[$enc])) {
10602 $enc_map = $this->encmaps->encmap[$enc];
10603 } else {
10604 $enc_map = false;
10606 $fmetric['cw'] = '';
10607 $fmetric['MaxWidth'] = 0;
10608 $cwidths = array();
10609 foreach ($matches as $k => $v) {
10610 $cid = 0;
10611 if (isset($imap[$v[1]])) {
10612 $cid = $imap[$v[1]];
10613 } elseif ($enc_map !== false) {
10614 $cid = array_search($v[1], $enc_map);
10615 if ($cid === false) {
10616 $cid = 0;
10617 } elseif ($cid > 1000) {
10618 $cid -= 1000;
10621 // decrypt charstring encrypted part
10622 $r = 4330; // charstring encryption constant
10623 $c1 = 52845;
10624 $c2 = 22719;
10625 $cd = $v[2];
10626 $clen = strlen($cd);
10627 $ccom = array();
10628 for ($i = 0; $i < $clen; ++$i) {
10629 $chr = ord($cd[$i]);
10630 $ccom[] = ($chr ^ ($r >> 8));
10631 $r = ((($chr + $r) * $c1 + $c2) % 65536);
10633 // decode numbers
10634 $cdec = array();
10635 $ck = 0;
10636 $i = $lenIV;
10637 while ($i < $clen) {
10638 if ($ccom[$i] < 32) {
10639 $cdec[$ck] = $ccom[$i];
10640 if (($ck > 0) AND ($cdec[$ck] == 13)) {
10641 // hsbw command: update width
10642 $cwidths[$cid] = $cdec[($ck - 1)];
10644 ++$i;
10645 } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
10646 $cdec[$ck] = ($ccom[$i] - 139);
10647 ++$i;
10648 } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
10649 $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
10650 $i += 2;
10651 } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
10652 $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
10653 $i += 2;
10654 } elseif ($ccom[$i] == 255) {
10655 $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
10656 $vsval = unpack('li', $sval);
10657 $cdec[$ck] = $vsval['i'];
10658 $i += 5;
10660 ++$ck;
10662 } // end for each matches
10663 $fmetric['MissingWidth'] = $cwidths[0];
10664 $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
10665 $fmetric['AvgWidth'] = 0;
10666 // set chars widths
10667 for ($cid = 0; $cid <= 255; ++$cid) {
10668 if (isset($cwidths[$cid])) {
10669 if ($cwidths[$cid] > $fmetric['MaxWidth']) {
10670 $fmetric['MaxWidth'] = $cwidths[$cid];
10672 $fmetric['AvgWidth'] += $cwidths[$cid];
10673 $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
10674 } else {
10675 $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
10678 $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
10679 } else {
10680 // ---------- TRUE TYPE ----------
10681 if ($fmetric['type'] != 'cidfont0') {
10682 // store compressed font
10683 $fp = fopen($outpath.$fmetric['file'], 'wb');
10684 fwrite($fp, gzcompress($font));
10685 fclose($fp);
10687 $offset = 0; // offset position of the font data
10688 if ($this->_getULONG($font, $offset) != 0x10000) {
10689 // sfnt version must be 0x00010000 for TrueType version 1.0.
10690 return $font;
10692 $offset += 4;
10693 // get number of tables
10694 $numTables = $this->_getUSHORT($font, $offset);
10695 $offset += 2;
10696 // skip searchRange, entrySelector and rangeShift
10697 $offset += 6;
10698 // tables array
10699 $table = array();
10700 // ---------- get tables ----------
10701 for ($i = 0; $i < $numTables; ++$i) {
10702 // get table info
10703 $tag = substr($font, $offset, 4);
10704 $offset += 4;
10705 $table[$tag] = array();
10706 $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
10707 $offset += 4;
10708 $table[$tag]['offset'] = $this->_getULONG($font, $offset);
10709 $offset += 4;
10710 $table[$tag]['length'] = $this->_getULONG($font, $offset);
10711 $offset += 4;
10713 // check magicNumber
10714 $offset = $table['head']['offset'] + 12;
10715 if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
10716 // magicNumber must be 0x5F0F3CF5
10717 return $font;
10719 $offset += 4;
10720 $offset += 2; // skip flags
10721 // get FUnits
10722 $fmetric['unitsPerEm'] = $this->_getUSHORT($font, $offset);
10723 $offset += 2;
10724 // units ratio constant
10725 $urk = (1000 / $fmetric['unitsPerEm']);
10726 $offset += 16; // skip created, modified
10727 $xMin = round($this->_getFWORD($font, $offset) * $urk);
10728 $offset += 2;
10729 $yMin = round($this->_getFWORD($font, $offset) * $urk);
10730 $offset += 2;
10731 $xMax = round($this->_getFWORD($font, $offset) * $urk);
10732 $offset += 2;
10733 $yMax = round($this->_getFWORD($font, $offset) * $urk);
10734 $offset += 2;
10735 $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
10736 $macStyle = $this->_getUSHORT($font, $offset);
10737 $offset += 2;
10738 // PDF font flags
10739 $fmetric['Flags'] = $flags;
10740 if (($macStyle & 2) == 2) {
10741 // italic flag
10742 $fmetric['Flags'] |= 64;
10744 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
10745 $offset = $table['head']['offset'] + 50;
10746 $short_offset = ($this->_getSHORT($font, $offset) == 0);
10747 $offset += 2;
10748 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
10749 $indexToLoc = array();
10750 $offset = $table['loca']['offset'];
10751 if ($short_offset) {
10752 // short version
10753 $tot_num_glyphs = ($table['loca']['length'] / 2); // numGlyphs + 1
10754 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10755 $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
10756 $offset += 2;
10758 } else {
10759 // long version
10760 $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
10761 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10762 $indexToLoc[$i] = $this->_getULONG($font, $offset);
10763 $offset += 4;
10766 // get glyphs indexes of chars from cmap table
10767 $offset = $table['cmap']['offset'] + 2;
10768 $numEncodingTables = $this->_getUSHORT($font, $offset);
10769 $offset += 2;
10770 $encodingTables = array();
10771 for ($i = 0; $i < $numEncodingTables; ++$i) {
10772 $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
10773 $offset += 2;
10774 $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
10775 $offset += 2;
10776 $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
10777 $offset += 4;
10779 // ---------- get os/2 metrics ----------
10780 $offset = $table['OS/2']['offset'];
10781 $offset += 2; // skip version
10782 // xAvgCharWidth
10783 $fmetric['AvgWidth'] = round($this->_getFWORD($font, $offset) * $urk);
10784 $offset += 2;
10785 // usWeightClass
10786 $usWeightClass = round($this->_getUFWORD($font, $offset) * $urk);
10787 // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
10788 $fmetric['StemV'] = round((70 * $usWeightClass) / 400);
10789 $fmetric['StemH'] = round((30 * $usWeightClass) / 400);
10790 $offset += 2;
10791 $offset += 2; // usWidthClass
10792 $fsType = $this->_getSHORT($font, $offset);
10793 $offset += 2;
10794 if ($fsType == 2) {
10795 $this->Error('This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.');
10797 // ---------- get font name ----------
10798 $fmetric['name'] = '';
10799 $offset = $table['name']['offset'];
10800 $offset += 2; // skip Format selector (=0).
10801 // Number of NameRecords that follow n.
10802 $numNameRecords = $this->_getUSHORT($font, $offset);
10803 $offset += 2;
10804 // Offset to start of string storage (from start of table).
10805 $stringStorageOffset = $this->_getUSHORT($font, $offset);
10806 $offset += 2;
10807 for ($i = 0; $i < $numNameRecords; ++$i) {
10808 $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
10809 // Name ID.
10810 $nameID = $this->_getUSHORT($font, $offset);
10811 $offset += 2;
10812 if ($nameID == 6) {
10813 // String length (in bytes).
10814 $stringLength = $this->_getUSHORT($font, $offset);
10815 $offset += 2;
10816 // String offset from start of storage area (in bytes).
10817 $stringOffset = $this->_getUSHORT($font, $offset);
10818 $offset += 2;
10819 $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
10820 $fmetric['name'] = substr($font, $offset, $stringLength);
10821 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
10822 break;
10823 } else {
10824 $offset += 4; // skip String length, String offset
10827 if (empty($fmetric['name'])) {
10828 $fmetric['name'] = $font_name;
10830 // ---------- get post data ----------
10831 $offset = $table['post']['offset'];
10832 $offset += 4; // skip Format Type
10833 $fmetric['italicAngle'] = $this->_getFIXED($font, $offset);
10834 $offset += 4;
10835 $fmetric['underlinePosition'] = round($this->_getFWORD($font, $offset) * $urk);
10836 $offset += 2;
10837 $fmetric['underlineThickness'] = round($this->_getFWORD($font, $offset) * $urk);
10838 $offset += 2;
10839 $isFixedPitch = ($this->_getULONG($font, $offset) == 0) ? false : true;
10840 $offset += 2;
10841 if ($isFixedPitch) {
10842 $fmetric['Flags'] |= 1;
10844 // ---------- get hhea data ----------
10845 $offset = $table['hhea']['offset'];
10846 $offset += 4; // skip Table version number
10847 // Ascender
10848 $fmetric['Ascent'] = round($this->_getFWORD($font, $offset) * $urk);
10849 $offset += 2;
10850 // Descender
10851 $fmetric['Descent'] = round($this->_getFWORD($font, $offset) * $urk);
10852 $offset += 2;
10853 // LineGap
10854 $fmetric['Leading'] = round($this->_getFWORD($font, $offset) * $urk);
10855 $offset += 2;
10856 // advanceWidthMax
10857 $fmetric['MaxWidth'] = round($this->_getUFWORD($font, $offset) * $urk);
10858 $offset += 2;
10859 $offset += 22; // skip some values
10860 // get the number of hMetric entries in hmtx table
10861 $numberOfHMetrics = $this->_getUSHORT($font, $offset);
10862 // ---------- get maxp data ----------
10863 $offset = $table['maxp']['offset'];
10864 $offset += 4; // skip Table version number
10865 // get the the number of glyphs in the font.
10866 $numGlyphs = $this->_getUSHORT($font, $offset);
10867 // ---------- get CIDToGIDMap ----------
10868 $ctg = array();
10869 foreach ($encodingTables as $enctable) {
10870 // get only specified Platform ID and Encoding ID
10871 if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) {
10872 $offset = $table['cmap']['offset'] + $enctable['offset'];
10873 $format = $this->_getUSHORT($font, $offset);
10874 $offset += 2;
10875 switch ($format) {
10876 case 0: { // Format 0: Byte encoding table
10877 $offset += 4; // skip length and version/language
10878 for ($c = 0; $c < 256; ++$c) {
10879 $g = $this->_getBYTE($font, $offset);
10880 $ctg[$c] = $g;
10881 ++$offset;
10883 break;
10885 case 2: { // Format 2: High-byte mapping through table
10886 $offset += 4; // skip length and version/language
10887 $numSubHeaders = 0;
10888 for ($i = 0; $i < 256; ++$i) {
10889 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
10890 $subHeaderKeys[$i] = ($this->_getUSHORT($font, $offset) / 8);
10891 $offset += 2;
10892 if ($numSubHeaders < $subHeaderKeys[$i]) {
10893 $numSubHeaders = $subHeaderKeys[$i];
10896 // the number of subHeaders is equal to the max of subHeaderKeys + 1
10897 ++$numSubHeaders;
10898 // read subHeader structures
10899 $subHeaders = array();
10900 $numGlyphIndexArray = 0;
10901 for ($k = 0; $k < $numSubHeaders; ++$k) {
10902 $subHeaders[$k]['firstCode'] = $this->_getUSHORT($font, $offset);
10903 $offset += 2;
10904 $subHeaders[$k]['entryCount'] = $this->_getUSHORT($font, $offset);
10905 $offset += 2;
10906 $subHeaders[$k]['idDelta'] = $this->_getUSHORT($font, $offset);
10907 $offset += 2;
10908 $subHeaders[$k]['idRangeOffset'] = $this->_getUSHORT($font, $offset);
10909 $offset += 2;
10910 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
10911 $subHeaders[$k]['idRangeOffset'] /= 2;
10912 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
10914 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
10915 $glyphIndexArray[$k] = $this->_getUSHORT($font, $offset);
10916 $offset += 2;
10918 for ($i = 0; $i < 256; ++$i) {
10919 $k = $subHeaderKeys[$i];
10920 if ($k == 0) {
10921 // one byte code
10922 $c = $i;
10923 $g = $glyphIndexArray[0];
10924 $ctg[$c] = $g;
10925 } else {
10926 // two bytes code
10927 $start_byte = $subHeaders[$k]['firstCode'];
10928 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
10929 for ($j = $start_byte; $j < $end_byte; ++$j) {
10930 // combine high and low bytes
10931 $c = (($i << 8) + $j);
10932 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
10933 $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
10934 if ($g < 0) {
10935 $g = 0;
10937 $ctg[$c] = $g;
10941 break;
10943 case 4: { // Format 4: Segment mapping to delta values
10944 $length = $this->_getUSHORT($font, $offset);
10945 $offset += 2;
10946 $offset += 2; // skip version/language
10947 $segCount = ($this->_getUSHORT($font, $offset) / 2);
10948 $offset += 2;
10949 $offset += 6; // skip searchRange, entrySelector, rangeShift
10950 $endCount = array(); // array of end character codes for each segment
10951 for ($k = 0; $k < $segCount; ++$k) {
10952 $endCount[$k] = $this->_getUSHORT($font, $offset);
10953 $offset += 2;
10955 $offset += 2; // skip reservedPad
10956 $startCount = array(); // array of start character codes for each segment
10957 for ($k = 0; $k < $segCount; ++$k) {
10958 $startCount[$k] = $this->_getUSHORT($font, $offset);
10959 $offset += 2;
10961 $idDelta = array(); // delta for all character codes in segment
10962 for ($k = 0; $k < $segCount; ++$k) {
10963 $idDelta[$k] = $this->_getUSHORT($font, $offset);
10964 $offset += 2;
10966 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
10967 for ($k = 0; $k < $segCount; ++$k) {
10968 $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
10969 $offset += 2;
10971 $gidlen = ($length / 2) - 8 - (4 * $segCount);
10972 $glyphIdArray = array(); // glyph index array
10973 for ($k = 0; $k < $gidlen; ++$k) {
10974 $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
10975 $offset += 2;
10977 for ($k = 0; $k < $segCount; ++$k) {
10978 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
10979 if ($idRangeOffset[$k] == 0) {
10980 $g = ($idDelta[$k] + $c) % 65536;
10981 } else {
10982 $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
10983 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
10985 if ($g < 0) {
10986 $g = 0;
10988 $ctg[$c] = $g;
10991 break;
10993 case 6: { // Format 6: Trimmed table mapping
10994 $offset += 4; // skip length and version/language
10995 $firstCode = $this->_getUSHORT($font, $offset);
10996 $offset += 2;
10997 $entryCount = $this->_getUSHORT($font, $offset);
10998 $offset += 2;
10999 for ($k = 0; $k < $entryCount; ++$k) {
11000 $c = ($k + $firstCode);
11001 $g = $this->_getUSHORT($font, $offset);
11002 $offset += 2;
11003 $ctg[$c] = $g;
11005 break;
11007 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
11008 $offset += 10; // skip reserved, length and version/language
11009 for ($k = 0; $k < 8192; ++$k) {
11010 $is32[$k] = $this->_getBYTE($font, $offset);
11011 ++$offset;
11013 $nGroups = $this->_getULONG($font, $offset);
11014 $offset += 4;
11015 for ($i = 0; $i < $nGroups; ++$i) {
11016 $startCharCode = $this->_getULONG($font, $offset);
11017 $offset += 4;
11018 $endCharCode = $this->_getULONG($font, $offset);
11019 $offset += 4;
11020 $startGlyphID = $this->_getULONG($font, $offset);
11021 $offset += 4;
11022 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
11023 $is32idx = floor($c / 8);
11024 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
11025 $c = $k;
11026 } else {
11027 // 32 bit format
11028 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
11029 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
11030 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
11031 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
11033 $ctg[$c] = 0;
11034 ++$startGlyphID;
11037 break;
11039 case 10: { // Format 10: Trimmed array
11040 $offset += 10; // skip reserved, length and version/language
11041 $startCharCode = $this->_getULONG($font, $offset);
11042 $offset += 4;
11043 $numChars = $this->_getULONG($font, $offset);
11044 $offset += 4;
11045 for ($k = 0; $k < $numChars; ++$k) {
11046 $c = ($k + $startCharCode);
11047 $g = $this->_getUSHORT($font, $offset);
11048 $ctg[$c] = $g;
11049 $offset += 2;
11051 break;
11053 case 12: { // Format 12: Segmented coverage
11054 $offset += 10; // skip length and version/language
11055 $nGroups = $this->_getULONG($font, $offset);
11056 $offset += 4;
11057 for ($k = 0; $k < $nGroups; ++$k) {
11058 $startCharCode = $this->_getULONG($font, $offset);
11059 $offset += 4;
11060 $endCharCode = $this->_getULONG($font, $offset);
11061 $offset += 4;
11062 $startGlyphCode = $this->_getULONG($font, $offset);
11063 $offset += 4;
11064 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
11065 $ctg[$c] = $startGlyphCode;
11066 ++$startGlyphCode;
11069 break;
11071 case 13: { // Format 13: Many-to-one range mappings
11072 // to be implemented ...
11073 break;
11075 case 14: { // Format 14: Unicode Variation Sequences
11076 // to be implemented ...
11077 break;
11082 if (!isset($ctg[0])) {
11083 $ctg[0] = 0;
11085 // get xHeight (height of x)
11086 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
11087 $yMin = $this->_getFWORD($font, $offset);
11088 $offset += 4;
11089 $yMax = $this->_getFWORD($font, $offset);
11090 $offset += 2;
11091 $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
11092 // get CapHeight (height of H)
11093 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
11094 $yMin = $this->_getFWORD($font, $offset);
11095 $offset += 4;
11096 $yMax = $this->_getFWORD($font, $offset);
11097 $offset += 2;
11098 $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
11099 // ceate widths array
11100 $cw = array();
11101 $offset = $table['hmtx']['offset'];
11102 for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
11103 $cw[$i] = round($this->_getUFWORD($font, $offset) * $urk);
11104 $offset += 4; // skip lsb
11106 if ($numberOfHMetrics < $numGlyphs) {
11107 // fill missing widths with the last value
11108 $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
11110 $fmetric['MissingWidth'] = $cw[0];
11111 $fmetric['cw'] = '';
11112 for ($cid = 0; $cid <= 65535; ++$cid) {
11113 if (isset($ctg[$cid])) {
11114 if (isset($cw[$ctg[$cid]])) {
11115 $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
11117 if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) {
11118 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]);
11119 $xMin = round($this->_getFWORD($font, $offset + 2)) * $urk;
11120 $yMin = round($this->_getFWORD($font, $offset + 4)) * $urk;
11121 $xMax = round($this->_getFWORD($font, $offset + 6)) * $urk;
11122 $yMax = round($this->_getFWORD($font, $offset + 8)) * $urk;
11123 $fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')';
11127 } // end of true type
11128 if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
11129 $fmetric['type'] == 'TrueType';
11131 // ---------- create php font file ----------
11132 $pfile = '<'.'?'.'php'."\n";
11133 $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
11134 $pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
11135 $pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
11136 $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
11137 $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
11138 if ($fmetric['MissingWidth'] > 0) {
11139 $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
11140 } else {
11141 $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
11143 $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
11144 if ($fmetric['type'] == 'Type1') {
11145 // Type 1
11146 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
11147 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
11148 $pfile .= '$size1='.$fmetric['size1'].';'."\n";
11149 $pfile .= '$size2='.$fmetric['size2'].';'."\n";
11150 } else {
11151 $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
11152 if ($fmetric['type'] == 'cidfont0') {
11153 // CID-0
11154 switch ($fonttype) {
11155 case 'CID0JP': {
11156 $pfile .= '// Japanese'."\n";
11157 $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
11158 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
11159 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
11160 break;
11162 case 'CID0KR': {
11163 $pfile .= '// Korean'."\n";
11164 $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
11165 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
11166 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
11167 break;
11169 case 'CID0CS': {
11170 $pfile .= '// Chinese Simplified'."\n";
11171 $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
11172 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
11173 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
11174 break;
11176 case 'CID0CT':
11177 default: {
11178 $pfile .= '// Chinese Traditional'."\n";
11179 $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
11180 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
11181 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
11182 break;
11185 } else {
11186 // TrueType
11187 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
11188 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
11189 $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
11190 // create CIDToGIDMap
11191 $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
11192 foreach ($ctg as $cid => $gid) {
11193 $cidtogidmap = $this->updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
11195 // store compressed CIDToGIDMap
11196 $fp = fopen($outpath.$fmetric['ctg'], 'wb');
11197 fwrite($fp, gzcompress($cidtogidmap));
11198 fclose($fp);
11201 $pfile .= '$desc=array(';
11202 $pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
11203 $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
11204 $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
11205 $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
11206 $pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
11207 $pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
11208 $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
11209 $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
11210 $pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
11211 $pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
11212 $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
11213 $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
11214 $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
11215 $pfile .= ');'."\n";
11216 if (isset($fmetric['cbbox'])) {
11217 $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
11219 $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
11220 $pfile .= '// --- EOF ---'."\n";
11221 // store file
11222 $fp = fopen($outpath.$font_name.'.php', 'w');
11223 fwrite($fp, $pfile);
11224 fclose($fp);
11225 // return TCPDF font name
11226 return $font_name;
11230 * Returns a subset of the TrueType font data without the unused glyphs.
11231 * @param $font (string) TrueType font data.
11232 * @param $subsetchars (array) Array of used characters (the glyphs to keep).
11233 * @return (string) A subset of TrueType font data without the unused glyphs.
11234 * @author Nicola Asuni
11235 * @protected
11236 * @since 5.2.000 (2010-06-02)
11238 protected function _getTrueTypeFontSubset($font, $subsetchars) {
11239 ksort($subsetchars);
11240 $offset = 0; // offset position of the font data
11241 if ($this->_getULONG($font, $offset) != 0x10000) {
11242 // sfnt version must be 0x00010000 for TrueType version 1.0.
11243 return $font;
11245 $offset += 4;
11246 // get number of tables
11247 $numTables = $this->_getUSHORT($font, $offset);
11248 $offset += 2;
11249 // skip searchRange, entrySelector and rangeShift
11250 $offset += 6;
11251 // tables array
11252 $table = array();
11253 // for each table
11254 for ($i = 0; $i < $numTables; ++$i) {
11255 // get table info
11256 $tag = substr($font, $offset, 4);
11257 $offset += 4;
11258 $table[$tag] = array();
11259 $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
11260 $offset += 4;
11261 $table[$tag]['offset'] = $this->_getULONG($font, $offset);
11262 $offset += 4;
11263 $table[$tag]['length'] = $this->_getULONG($font, $offset);
11264 $offset += 4;
11266 // check magicNumber
11267 $offset = $table['head']['offset'] + 12;
11268 if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
11269 // magicNumber must be 0x5F0F3CF5
11270 return $font;
11272 $offset += 4;
11273 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
11274 $offset = $table['head']['offset'] + 50;
11275 $short_offset = ($this->_getSHORT($font, $offset) == 0);
11276 $offset += 2;
11277 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
11278 $indexToLoc = array();
11279 $offset = $table['loca']['offset'];
11280 if ($short_offset) {
11281 // short version
11282 $tot_num_glyphs = ($table['loca']['length'] / 2); // numGlyphs + 1
11283 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
11284 $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
11285 $offset += 2;
11287 } else {
11288 // long version
11289 $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
11290 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
11291 $indexToLoc[$i] = $this->_getULONG($font, $offset);
11292 $offset += 4;
11295 // get glyphs indexes of chars from cmap table
11296 $subsetglyphs = array(); // glyph IDs on key
11297 $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
11298 $offset = $table['cmap']['offset'] + 2;
11299 $numEncodingTables = $this->_getUSHORT($font, $offset);
11300 $offset += 2;
11301 $encodingTables = array();
11302 for ($i = 0; $i < $numEncodingTables; ++$i) {
11303 $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
11304 $offset += 2;
11305 $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
11306 $offset += 2;
11307 $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
11308 $offset += 4;
11310 foreach ($encodingTables as $enctable) {
11311 // get all platforms and encodings
11312 $offset = $table['cmap']['offset'] + $enctable['offset'];
11313 $format = $this->_getUSHORT($font, $offset);
11314 $offset += 2;
11315 switch ($format) {
11316 case 0: { // Format 0: Byte encoding table
11317 $offset += 4; // skip length and version/language
11318 for ($c = 0; $c < 256; ++$c) {
11319 if (isset($subsetchars[$c])) {
11320 $g = $this->_getBYTE($font, $offset);
11321 $subsetglyphs[$g] = true;
11323 ++$offset;
11325 break;
11327 case 2: { // Format 2: High-byte mapping through table
11328 $offset += 4; // skip length and version/language
11329 $numSubHeaders = 0;
11330 for ($i = 0; $i < 256; ++$i) {
11331 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
11332 $subHeaderKeys[$i] = ($this->_getUSHORT($font, $offset) / 8);
11333 $offset += 2;
11334 if ($numSubHeaders < $subHeaderKeys[$i]) {
11335 $numSubHeaders = $subHeaderKeys[$i];
11338 // the number of subHeaders is equal to the max of subHeaderKeys + 1
11339 ++$numSubHeaders;
11340 // read subHeader structures
11341 $subHeaders = array();
11342 $numGlyphIndexArray = 0;
11343 for ($k = 0; $k < $numSubHeaders; ++$k) {
11344 $subHeaders[$k]['firstCode'] = $this->_getUSHORT($font, $offset);
11345 $offset += 2;
11346 $subHeaders[$k]['entryCount'] = $this->_getUSHORT($font, $offset);
11347 $offset += 2;
11348 $subHeaders[$k]['idDelta'] = $this->_getUSHORT($font, $offset);
11349 $offset += 2;
11350 $subHeaders[$k]['idRangeOffset'] = $this->_getUSHORT($font, $offset);
11351 $offset += 2;
11352 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
11353 $subHeaders[$k]['idRangeOffset'] /= 2;
11354 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
11356 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
11357 $glyphIndexArray[$k] = $this->_getUSHORT($font, $offset);
11358 $offset += 2;
11360 for ($i = 0; $i < 256; ++$i) {
11361 $k = $subHeaderKeys[$i];
11362 if ($k == 0) {
11363 // one byte code
11364 $c = $i;
11365 if (isset($subsetchars[$c])) {
11366 $g = $glyphIndexArray[0];
11367 $subsetglyphs[$g] = true;
11369 } else {
11370 // two bytes code
11371 $start_byte = $subHeaders[$k]['firstCode'];
11372 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
11373 for ($j = $start_byte; $j < $end_byte; ++$j) {
11374 // combine high and low bytes
11375 $c = (($i << 8) + $j);
11376 if (isset($subsetchars[$c])) {
11377 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
11378 $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
11379 if ($g < 0) {
11380 $g = 0;
11382 $subsetglyphs[$g] = true;
11387 break;
11389 case 4: { // Format 4: Segment mapping to delta values
11390 $length = $this->_getUSHORT($font, $offset);
11391 $offset += 2;
11392 $offset += 2; // skip version/language
11393 $segCount = ($this->_getUSHORT($font, $offset) / 2);
11394 $offset += 2;
11395 $offset += 6; // skip searchRange, entrySelector, rangeShift
11396 $endCount = array(); // array of end character codes for each segment
11397 for ($k = 0; $k < $segCount; ++$k) {
11398 $endCount[$k] = $this->_getUSHORT($font, $offset);
11399 $offset += 2;
11401 $offset += 2; // skip reservedPad
11402 $startCount = array(); // array of start character codes for each segment
11403 for ($k = 0; $k < $segCount; ++$k) {
11404 $startCount[$k] = $this->_getUSHORT($font, $offset);
11405 $offset += 2;
11407 $idDelta = array(); // delta for all character codes in segment
11408 for ($k = 0; $k < $segCount; ++$k) {
11409 $idDelta[$k] = $this->_getUSHORT($font, $offset);
11410 $offset += 2;
11412 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
11413 for ($k = 0; $k < $segCount; ++$k) {
11414 $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
11415 $offset += 2;
11417 $gidlen = ($length / 2) - 8 - (4 * $segCount);
11418 $glyphIdArray = array(); // glyph index array
11419 for ($k = 0; $k < $gidlen; ++$k) {
11420 $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
11421 $offset += 2;
11423 for ($k = 0; $k < $segCount; ++$k) {
11424 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
11425 if (isset($subsetchars[$c])) {
11426 if ($idRangeOffset[$k] == 0) {
11427 $g = ($idDelta[$k] + $c) % 65536;
11428 } else {
11429 $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
11430 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
11432 if ($g < 0) {
11433 $g = 0;
11435 $subsetglyphs[$g] = true;
11439 break;
11441 case 6: { // Format 6: Trimmed table mapping
11442 $offset += 4; // skip length and version/language
11443 $firstCode = $this->_getUSHORT($font, $offset);
11444 $offset += 2;
11445 $entryCount = $this->_getUSHORT($font, $offset);
11446 $offset += 2;
11447 for ($k = 0; $k < $entryCount; ++$k) {
11448 $c = ($k + $firstCode);
11449 if (isset($subsetchars[$c])) {
11450 $g = $this->_getUSHORT($font, $offset);
11451 $subsetglyphs[$g] = true;
11453 $offset += 2;
11455 break;
11457 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
11458 $offset += 10; // skip reserved, length and version/language
11459 for ($k = 0; $k < 8192; ++$k) {
11460 $is32[$k] = $this->_getBYTE($font, $offset);
11461 ++$offset;
11463 $nGroups = $this->_getULONG($font, $offset);
11464 $offset += 4;
11465 for ($i = 0; $i < $nGroups; ++$i) {
11466 $startCharCode = $this->_getULONG($font, $offset);
11467 $offset += 4;
11468 $endCharCode = $this->_getULONG($font, $offset);
11469 $offset += 4;
11470 $startGlyphID = $this->_getULONG($font, $offset);
11471 $offset += 4;
11472 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
11473 $is32idx = floor($c / 8);
11474 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
11475 $c = $k;
11476 } else {
11477 // 32 bit format
11478 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
11479 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
11480 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
11481 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
11483 if (isset($subsetchars[$c])) {
11484 $subsetglyphs[$startGlyphID] = true;
11486 ++$startGlyphID;
11489 break;
11491 case 10: { // Format 10: Trimmed array
11492 $offset += 10; // skip reserved, length and version/language
11493 $startCharCode = $this->_getULONG($font, $offset);
11494 $offset += 4;
11495 $numChars = $this->_getULONG($font, $offset);
11496 $offset += 4;
11497 for ($k = 0; $k < $numChars; ++$k) {
11498 $c = ($k + $startCharCode);
11499 if (isset($subsetchars[$c])) {
11500 $g = $this->_getUSHORT($font, $offset);
11501 $subsetglyphs[$g] = true;
11503 $offset += 2;
11505 break;
11507 case 12: { // Format 12: Segmented coverage
11508 $offset += 10; // skip length and version/language
11509 $nGroups = $this->_getULONG($font, $offset);
11510 $offset += 4;
11511 for ($k = 0; $k < $nGroups; ++$k) {
11512 $startCharCode = $this->_getULONG($font, $offset);
11513 $offset += 4;
11514 $endCharCode = $this->_getULONG($font, $offset);
11515 $offset += 4;
11516 $startGlyphCode = $this->_getULONG($font, $offset);
11517 $offset += 4;
11518 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
11519 if (isset($subsetchars[$c])) {
11520 $subsetglyphs[$startGlyphCode] = true;
11522 ++$startGlyphCode;
11525 break;
11527 case 13: { // Format 13: Many-to-one range mappings
11528 // to be implemented ...
11529 break;
11531 case 14: { // Format 14: Unicode Variation Sequences
11532 // to be implemented ...
11533 break;
11537 // include all parts of composite glyphs
11538 $new_sga = $subsetglyphs;
11539 while (!empty($new_sga)) {
11540 $sga = $new_sga;
11541 $new_sga = array();
11542 foreach ($sga as $key => $val) {
11543 if (isset($indexToLoc[$key])) {
11544 $offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
11545 $numberOfContours = $this->_getSHORT($font, $offset);
11546 $offset += 2;
11547 if ($numberOfContours < 0) { // composite glyph
11548 $offset += 8; // skip xMin, yMin, xMax, yMax
11549 do {
11550 $flags = $this->_getUSHORT($font, $offset);
11551 $offset += 2;
11552 $glyphIndex = $this->_getUSHORT($font, $offset);
11553 $offset += 2;
11554 if (!isset($subsetglyphs[$glyphIndex])) {
11555 // add missing glyphs
11556 $new_sga[$glyphIndex] = true;
11558 // skip some bytes by case
11559 if ($flags & 1) {
11560 $offset += 4;
11561 } else {
11562 $offset += 2;
11564 if ($flags & 8) {
11565 $offset += 2;
11566 } elseif ($flags & 64) {
11567 $offset += 4;
11568 } elseif ($flags & 128) {
11569 $offset += 8;
11571 } while ($flags & 32);
11575 $subsetglyphs += $new_sga;
11577 // sort glyphs by key (and remove duplicates)
11578 ksort($subsetglyphs);
11579 // build new glyf and loca tables
11580 $glyf = '';
11581 $loca = '';
11582 $offset = 0;
11583 $glyf_offset = $table['glyf']['offset'];
11584 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
11585 if (isset($subsetglyphs[$i])) {
11586 $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
11587 $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
11588 } else {
11589 $length = 0;
11591 if ($short_offset) {
11592 $loca .= pack('n', ($offset / 2));
11593 } else {
11594 $loca .= pack('N', $offset);
11596 $offset += $length;
11598 // array of table names to preserve (loca and glyf tables will be added later)
11599 // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
11600 $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
11601 // get the tables to preserve
11602 $offset = 12;
11603 foreach ($table as $tag => $val) {
11604 if (in_array($tag, $table_names)) {
11605 $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
11606 if ($tag == 'head') {
11607 // set the checkSumAdjustment to 0
11608 $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
11610 $pad = 4 - ($table[$tag]['length'] % 4);
11611 if ($pad != 4) {
11612 // the length of a table must be a multiple of four bytes
11613 $table[$tag]['length'] += $pad;
11614 $table[$tag]['data'] .= str_repeat("\x0", $pad);
11616 $table[$tag]['offset'] = $offset;
11617 $offset += $table[$tag]['length'];
11618 // check sum is not changed (so keep the following line commented)
11619 //$table[$tag]['checkSum'] = $this->_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
11620 } else {
11621 unset($table[$tag]);
11624 // add loca
11625 $table['loca']['data'] = $loca;
11626 $table['loca']['length'] = strlen($loca);
11627 $pad = 4 - ($table['loca']['length'] % 4);
11628 if ($pad != 4) {
11629 // the length of a table must be a multiple of four bytes
11630 $table['loca']['length'] += $pad;
11631 $table['loca']['data'] .= str_repeat("\x0", $pad);
11633 $table['loca']['offset'] = $offset;
11634 $table['loca']['checkSum'] = $this->_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
11635 $offset += $table['loca']['length'];
11636 // add glyf
11637 $table['glyf']['data'] = $glyf;
11638 $table['glyf']['length'] = strlen($glyf);
11639 $pad = 4 - ($table['glyf']['length'] % 4);
11640 if ($pad != 4) {
11641 // the length of a table must be a multiple of four bytes
11642 $table['glyf']['length'] += $pad;
11643 $table['glyf']['data'] .= str_repeat("\x0", $pad);
11645 $table['glyf']['offset'] = $offset;
11646 $table['glyf']['checkSum'] = $this->_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
11647 // rebuild font
11648 $font = '';
11649 $font .= pack('N', 0x10000); // sfnt version
11650 $numTables = count($table);
11651 $font .= pack('n', $numTables); // numTables
11652 $entrySelector = floor(log($numTables, 2));
11653 $searchRange = pow(2, $entrySelector) * 16;
11654 $rangeShift = ($numTables * 16) - $searchRange;
11655 $font .= pack('n', $searchRange); // searchRange
11656 $font .= pack('n', $entrySelector); // entrySelector
11657 $font .= pack('n', $rangeShift); // rangeShift
11658 $offset = ($numTables * 16);
11659 foreach ($table as $tag => $data) {
11660 $font .= $tag; // tag
11661 $font .= pack('N', $data['checkSum']); // checkSum
11662 $font .= pack('N', ($data['offset'] + $offset)); // offset
11663 $font .= pack('N', $data['length']); // length
11665 foreach ($table as $data) {
11666 $font .= $data['data'];
11668 // set checkSumAdjustment on head table
11669 $checkSumAdjustment = 0xB1B0AFBA - $this->_getTTFtableChecksum($font, strlen($font));
11670 $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
11671 return $font;
11675 * Returs the checksum of a TTF table.
11676 * @param $table (string) table to check
11677 * @param $length (int) length of table in bytes
11678 * @return int checksum
11679 * @author Nicola Asuni
11680 * @protected
11681 * @since 5.2.000 (2010-06-02)
11683 protected function _getTTFtableChecksum($table, $length) {
11684 $sum = 0;
11685 $tlen = ($length / 4);
11686 $offset = 0;
11687 for ($i = 0; $i < $tlen; ++$i) {
11688 $v = unpack('Ni', substr($table, $offset, 4));
11689 $sum += $v['i'];
11690 $offset += 4;
11692 $sum = unpack('Ni', pack('N', $sum));
11693 return $sum['i'];
11697 * Outputs font widths
11698 * @param $font (array) font data
11699 * @param $cidoffset (int) offset for CID values
11700 * @return PDF command string for font widths
11701 * @author Nicola Asuni
11702 * @protected
11703 * @since 4.4.000 (2008-12-07)
11705 protected function _putfontwidths($font, $cidoffset=0) {
11706 ksort($font['cw']);
11707 $rangeid = 0;
11708 $range = array();
11709 $prevcid = -2;
11710 $prevwidth = -1;
11711 $interval = false;
11712 // for each character
11713 foreach ($font['cw'] as $cid => $width) {
11714 $cid -= $cidoffset;
11715 if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
11716 // ignore the unused characters (font subsetting)
11717 continue;
11719 if ($width != $font['dw']) {
11720 if ($cid == ($prevcid + 1)) {
11721 // consecutive CID
11722 if ($width == $prevwidth) {
11723 if ($width == $range[$rangeid][0]) {
11724 $range[$rangeid][] = $width;
11725 } else {
11726 array_pop($range[$rangeid]);
11727 // new range
11728 $rangeid = $prevcid;
11729 $range[$rangeid] = array();
11730 $range[$rangeid][] = $prevwidth;
11731 $range[$rangeid][] = $width;
11733 $interval = true;
11734 $range[$rangeid]['interval'] = true;
11735 } else {
11736 if ($interval) {
11737 // new range
11738 $rangeid = $cid;
11739 $range[$rangeid] = array();
11740 $range[$rangeid][] = $width;
11741 } else {
11742 $range[$rangeid][] = $width;
11744 $interval = false;
11746 } else {
11747 // new range
11748 $rangeid = $cid;
11749 $range[$rangeid] = array();
11750 $range[$rangeid][] = $width;
11751 $interval = false;
11753 $prevcid = $cid;
11754 $prevwidth = $width;
11757 // optimize ranges
11758 $prevk = -1;
11759 $nextk = -1;
11760 $prevint = false;
11761 foreach ($range as $k => $ws) {
11762 $cws = count($ws);
11763 if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
11764 if (isset($range[$k]['interval'])) {
11765 unset($range[$k]['interval']);
11767 $range[$prevk] = array_merge($range[$prevk], $range[$k]);
11768 unset($range[$k]);
11769 } else {
11770 $prevk = $k;
11772 $nextk = $k + $cws;
11773 if (isset($ws['interval'])) {
11774 if ($cws > 3) {
11775 $prevint = true;
11776 } else {
11777 $prevint = false;
11779 if (isset($range[$k]['interval'])) {
11780 unset($range[$k]['interval']);
11782 --$nextk;
11783 } else {
11784 $prevint = false;
11787 // output data
11788 $w = '';
11789 foreach ($range as $k => $ws) {
11790 if (count(array_count_values($ws)) == 1) {
11791 // interval mode is more compact
11792 $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
11793 } else {
11794 // range mode
11795 $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
11798 return '/W ['.$w.' ]';
11802 * Output fonts.
11803 * @author Nicola Asuni
11804 * @protected
11806 protected function _putfonts() {
11807 $nf = $this->n;
11808 foreach ($this->diffs as $diff) {
11809 //Encodings
11810 $this->_newobj();
11811 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
11813 $mqr = $this->get_mqr();
11814 $this->set_mqr(false);
11815 foreach ($this->FontFiles as $file => $info) {
11816 // search and get font file to embedd
11817 $fontdir = $info['fontdir'];
11818 $file = strtolower($file);
11819 $fontfile = '';
11820 // search files on various directories
11821 if (($fontdir !== false) AND file_exists($fontdir.$file)) {
11822 $fontfile = $fontdir.$file;
11823 } elseif (file_exists($this->_getfontpath().$file)) {
11824 $fontfile = $this->_getfontpath().$file;
11825 } elseif (file_exists($file)) {
11826 $fontfile = $file;
11828 if (!$this->empty_string($fontfile)) {
11829 $font = file_get_contents($fontfile);
11830 $compressed = (substr($file, -2) == '.z');
11831 if ((!$compressed) AND (isset($info['length2']))) {
11832 $header = (ord($font{0}) == 128);
11833 if ($header) {
11834 // strip first binary header
11835 $font = substr($font, 6);
11837 if ($header AND (ord($font[$info['length1']]) == 128)) {
11838 // strip second binary header
11839 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
11841 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
11842 if ($compressed) {
11843 // uncompress font
11844 $font = gzuncompress($font);
11846 // merge subset characters
11847 $subsetchars = array(); // used chars
11848 foreach ($info['fontkeys'] as $fontkey) {
11849 $fontinfo = $this->getFontBuffer($fontkey);
11850 $subsetchars += $fontinfo['subsetchars'];
11852 // rebuild a font subset
11853 $font = $this->_getTrueTypeFontSubset($font, $subsetchars);
11854 // calculate new font length
11855 $info['length1'] = strlen($font);
11856 if ($compressed) {
11857 // recompress font
11858 $font = gzcompress($font);
11861 $this->_newobj();
11862 $this->FontFiles[$file]['n'] = $this->n;
11863 $stream = $this->_getrawstream($font);
11864 $out = '<< /Length '.strlen($stream);
11865 if ($compressed) {
11866 $out .= ' /Filter /FlateDecode';
11868 $out .= ' /Length1 '.$info['length1'];
11869 if (isset($info['length2'])) {
11870 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
11872 $out .= ' >>';
11873 $out .= ' stream'."\n".$stream."\n".'endstream';
11874 $out .= "\n".'endobj';
11875 $this->_out($out);
11878 $this->set_mqr($mqr);
11879 foreach ($this->fontkeys as $k) {
11880 //Font objects
11881 $font = $this->getFontBuffer($k);
11882 $type = $font['type'];
11883 $name = $font['name'];
11884 if ($type == 'core') {
11885 // standard core font
11886 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
11887 $out .= '<</Type /Font';
11888 $out .= ' /Subtype /Type1';
11889 $out .= ' /BaseFont /'.$name;
11890 $out .= ' /Name /F'.$font['i'];
11891 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
11892 $out .= ' /Encoding /WinAnsiEncoding';
11894 if ($k == 'helvetica') {
11895 // add default font for annotations
11896 $this->annotation_fonts[$k] = $font['i'];
11898 $out .= ' >>';
11899 $out .= "\n".'endobj';
11900 $this->_out($out);
11901 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
11902 // additional Type1 or TrueType font
11903 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
11904 $out .= '<</Type /Font';
11905 $out .= ' /Subtype /'.$type;
11906 $out .= ' /BaseFont /'.$name;
11907 $out .= ' /Name /F'.$font['i'];
11908 $out .= ' /FirstChar 32 /LastChar 255';
11909 $out .= ' /Widths '.($this->n + 1).' 0 R';
11910 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
11911 if ($font['enc']) {
11912 if (isset($font['diff'])) {
11913 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
11914 } else {
11915 $out .= ' /Encoding /WinAnsiEncoding';
11918 $out .= ' >>';
11919 $out .= "\n".'endobj';
11920 $this->_out($out);
11921 // Widths
11922 $this->_newobj();
11923 $s = '[';
11924 for ($i = 32; $i < 256; ++$i) {
11925 if (isset($font['cw'][$i])) {
11926 $s .= $font['cw'][$i].' ';
11927 } else {
11928 $s .= $font['dw'].' ';
11931 $s .= ']';
11932 $s .= "\n".'endobj';
11933 $this->_out($s);
11934 //Descriptor
11935 $this->_newobj();
11936 $s = '<</Type /FontDescriptor /FontName /'.$name;
11937 foreach ($font['desc'] as $fdk => $fdv) {
11938 if (is_float($fdv)) {
11939 $fdv = sprintf('%F', $fdv);
11941 $s .= ' /'.$fdk.' '.$fdv.'';
11943 if (!$this->empty_string($font['file'])) {
11944 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
11946 $s .= '>>';
11947 $s .= "\n".'endobj';
11948 $this->_out($s);
11949 } else {
11950 // additional types
11951 $mtd = '_put'.strtolower($type);
11952 if (!method_exists($this, $mtd)) {
11953 $this->Error('Unsupported font type: '.$type);
11955 $this->$mtd($font);
11961 * Adds unicode fonts.<br>
11962 * Based on PDF Reference 1.3 (section 5)
11963 * @param $font (array) font data
11964 * @protected
11965 * @author Nicola Asuni
11966 * @since 1.52.0.TC005 (2005-01-05)
11968 protected function _puttruetypeunicode($font) {
11969 $fontname = '';
11970 if ($font['subset']) {
11971 // change name for font subsetting
11972 $subtag = sprintf('%06u', $font['i']);
11973 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
11974 $fontname .= $subtag.'+';
11976 $fontname .= $font['name'];
11977 // Type0 Font
11978 // A composite font composed of other fonts, organized hierarchically
11979 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
11980 $out .= '<< /Type /Font';
11981 $out .= ' /Subtype /Type0';
11982 $out .= ' /BaseFont /'.$fontname;
11983 $out .= ' /Name /F'.$font['i'];
11984 $out .= ' /Encoding /'.$font['enc'];
11985 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
11986 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
11987 $out .= ' >>';
11988 $out .= "\n".'endobj';
11989 $this->_out($out);
11990 // ToUnicode map for Identity-H
11991 $stream = "/CIDInit /ProcSet findresource begin\n";
11992 $stream .= "12 dict begin\n";
11993 $stream .= "begincmap\n";
11994 $stream .= "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
11995 $stream .= "/CMapName /Adobe-Identity-UCS def\n";
11996 $stream .= "/CMapType 2 def\n";
11997 $stream .= "/WMode 0 def\n";
11998 $stream .= "1 begincodespacerange\n";
11999 $stream .= "<0000> <FFFF>\n";
12000 $stream .= "endcodespacerange\n";
12001 $stream .= "100 beginbfrange\n";
12002 $stream .= "<0000> <00ff> <0000>\n";
12003 $stream .= "<0100> <01ff> <0100>\n";
12004 $stream .= "<0200> <02ff> <0200>\n";
12005 $stream .= "<0300> <03ff> <0300>\n";
12006 $stream .= "<0400> <04ff> <0400>\n";
12007 $stream .= "<0500> <05ff> <0500>\n";
12008 $stream .= "<0600> <06ff> <0600>\n";
12009 $stream .= "<0700> <07ff> <0700>\n";
12010 $stream .= "<0800> <08ff> <0800>\n";
12011 $stream .= "<0900> <09ff> <0900>\n";
12012 $stream .= "<0a00> <0aff> <0a00>\n";
12013 $stream .= "<0b00> <0bff> <0b00>\n";
12014 $stream .= "<0c00> <0cff> <0c00>\n";
12015 $stream .= "<0d00> <0dff> <0d00>\n";
12016 $stream .= "<0e00> <0eff> <0e00>\n";
12017 $stream .= "<0f00> <0fff> <0f00>\n";
12018 $stream .= "<1000> <10ff> <1000>\n";
12019 $stream .= "<1100> <11ff> <1100>\n";
12020 $stream .= "<1200> <12ff> <1200>\n";
12021 $stream .= "<1300> <13ff> <1300>\n";
12022 $stream .= "<1400> <14ff> <1400>\n";
12023 $stream .= "<1500> <15ff> <1500>\n";
12024 $stream .= "<1600> <16ff> <1600>\n";
12025 $stream .= "<1700> <17ff> <1700>\n";
12026 $stream .= "<1800> <18ff> <1800>\n";
12027 $stream .= "<1900> <19ff> <1900>\n";
12028 $stream .= "<1a00> <1aff> <1a00>\n";
12029 $stream .= "<1b00> <1bff> <1b00>\n";
12030 $stream .= "<1c00> <1cff> <1c00>\n";
12031 $stream .= "<1d00> <1dff> <1d00>\n";
12032 $stream .= "<1e00> <1eff> <1e00>\n";
12033 $stream .= "<1f00> <1fff> <1f00>\n";
12034 $stream .= "<2000> <20ff> <2000>\n";
12035 $stream .= "<2100> <21ff> <2100>\n";
12036 $stream .= "<2200> <22ff> <2200>\n";
12037 $stream .= "<2300> <23ff> <2300>\n";
12038 $stream .= "<2400> <24ff> <2400>\n";
12039 $stream .= "<2500> <25ff> <2500>\n";
12040 $stream .= "<2600> <26ff> <2600>\n";
12041 $stream .= "<2700> <27ff> <2700>\n";
12042 $stream .= "<2800> <28ff> <2800>\n";
12043 $stream .= "<2900> <29ff> <2900>\n";
12044 $stream .= "<2a00> <2aff> <2a00>\n";
12045 $stream .= "<2b00> <2bff> <2b00>\n";
12046 $stream .= "<2c00> <2cff> <2c00>\n";
12047 $stream .= "<2d00> <2dff> <2d00>\n";
12048 $stream .= "<2e00> <2eff> <2e00>\n";
12049 $stream .= "<2f00> <2fff> <2f00>\n";
12050 $stream .= "<3000> <30ff> <3000>\n";
12051 $stream .= "<3100> <31ff> <3100>\n";
12052 $stream .= "<3200> <32ff> <3200>\n";
12053 $stream .= "<3300> <33ff> <3300>\n";
12054 $stream .= "<3400> <34ff> <3400>\n";
12055 $stream .= "<3500> <35ff> <3500>\n";
12056 $stream .= "<3600> <36ff> <3600>\n";
12057 $stream .= "<3700> <37ff> <3700>\n";
12058 $stream .= "<3800> <38ff> <3800>\n";
12059 $stream .= "<3900> <39ff> <3900>\n";
12060 $stream .= "<3a00> <3aff> <3a00>\n";
12061 $stream .= "<3b00> <3bff> <3b00>\n";
12062 $stream .= "<3c00> <3cff> <3c00>\n";
12063 $stream .= "<3d00> <3dff> <3d00>\n";
12064 $stream .= "<3e00> <3eff> <3e00>\n";
12065 $stream .= "<3f00> <3fff> <3f00>\n";
12066 $stream .= "<4000> <40ff> <4000>\n";
12067 $stream .= "<4100> <41ff> <4100>\n";
12068 $stream .= "<4200> <42ff> <4200>\n";
12069 $stream .= "<4300> <43ff> <4300>\n";
12070 $stream .= "<4400> <44ff> <4400>\n";
12071 $stream .= "<4500> <45ff> <4500>\n";
12072 $stream .= "<4600> <46ff> <4600>\n";
12073 $stream .= "<4700> <47ff> <4700>\n";
12074 $stream .= "<4800> <48ff> <4800>\n";
12075 $stream .= "<4900> <49ff> <4900>\n";
12076 $stream .= "<4a00> <4aff> <4a00>\n";
12077 $stream .= "<4b00> <4bff> <4b00>\n";
12078 $stream .= "<4c00> <4cff> <4c00>\n";
12079 $stream .= "<4d00> <4dff> <4d00>\n";
12080 $stream .= "<4e00> <4eff> <4e00>\n";
12081 $stream .= "<4f00> <4fff> <4f00>\n";
12082 $stream .= "<5000> <50ff> <5000>\n";
12083 $stream .= "<5100> <51ff> <5100>\n";
12084 $stream .= "<5200> <52ff> <5200>\n";
12085 $stream .= "<5300> <53ff> <5300>\n";
12086 $stream .= "<5400> <54ff> <5400>\n";
12087 $stream .= "<5500> <55ff> <5500>\n";
12088 $stream .= "<5600> <56ff> <5600>\n";
12089 $stream .= "<5700> <57ff> <5700>\n";
12090 $stream .= "<5800> <58ff> <5800>\n";
12091 $stream .= "<5900> <59ff> <5900>\n";
12092 $stream .= "<5a00> <5aff> <5a00>\n";
12093 $stream .= "<5b00> <5bff> <5b00>\n";
12094 $stream .= "<5c00> <5cff> <5c00>\n";
12095 $stream .= "<5d00> <5dff> <5d00>\n";
12096 $stream .= "<5e00> <5eff> <5e00>\n";
12097 $stream .= "<5f00> <5fff> <5f00>\n";
12098 $stream .= "<6000> <60ff> <6000>\n";
12099 $stream .= "<6100> <61ff> <6100>\n";
12100 $stream .= "<6200> <62ff> <6200>\n";
12101 $stream .= "<6300> <63ff> <6300>\n";
12102 $stream .= "endbfrange\n";
12103 $stream .= "100 beginbfrange\n";
12104 $stream .= "<6400> <64ff> <6400>\n";
12105 $stream .= "<6500> <65ff> <6500>\n";
12106 $stream .= "<6600> <66ff> <6600>\n";
12107 $stream .= "<6700> <67ff> <6700>\n";
12108 $stream .= "<6800> <68ff> <6800>\n";
12109 $stream .= "<6900> <69ff> <6900>\n";
12110 $stream .= "<6a00> <6aff> <6a00>\n";
12111 $stream .= "<6b00> <6bff> <6b00>\n";
12112 $stream .= "<6c00> <6cff> <6c00>\n";
12113 $stream .= "<6d00> <6dff> <6d00>\n";
12114 $stream .= "<6e00> <6eff> <6e00>\n";
12115 $stream .= "<6f00> <6fff> <6f00>\n";
12116 $stream .= "<7000> <70ff> <7000>\n";
12117 $stream .= "<7100> <71ff> <7100>\n";
12118 $stream .= "<7200> <72ff> <7200>\n";
12119 $stream .= "<7300> <73ff> <7300>\n";
12120 $stream .= "<7400> <74ff> <7400>\n";
12121 $stream .= "<7500> <75ff> <7500>\n";
12122 $stream .= "<7600> <76ff> <7600>\n";
12123 $stream .= "<7700> <77ff> <7700>\n";
12124 $stream .= "<7800> <78ff> <7800>\n";
12125 $stream .= "<7900> <79ff> <7900>\n";
12126 $stream .= "<7a00> <7aff> <7a00>\n";
12127 $stream .= "<7b00> <7bff> <7b00>\n";
12128 $stream .= "<7c00> <7cff> <7c00>\n";
12129 $stream .= "<7d00> <7dff> <7d00>\n";
12130 $stream .= "<7e00> <7eff> <7e00>\n";
12131 $stream .= "<7f00> <7fff> <7f00>\n";
12132 $stream .= "<8000> <80ff> <8000>\n";
12133 $stream .= "<8100> <81ff> <8100>\n";
12134 $stream .= "<8200> <82ff> <8200>\n";
12135 $stream .= "<8300> <83ff> <8300>\n";
12136 $stream .= "<8400> <84ff> <8400>\n";
12137 $stream .= "<8500> <85ff> <8500>\n";
12138 $stream .= "<8600> <86ff> <8600>\n";
12139 $stream .= "<8700> <87ff> <8700>\n";
12140 $stream .= "<8800> <88ff> <8800>\n";
12141 $stream .= "<8900> <89ff> <8900>\n";
12142 $stream .= "<8a00> <8aff> <8a00>\n";
12143 $stream .= "<8b00> <8bff> <8b00>\n";
12144 $stream .= "<8c00> <8cff> <8c00>\n";
12145 $stream .= "<8d00> <8dff> <8d00>\n";
12146 $stream .= "<8e00> <8eff> <8e00>\n";
12147 $stream .= "<8f00> <8fff> <8f00>\n";
12148 $stream .= "<9000> <90ff> <9000>\n";
12149 $stream .= "<9100> <91ff> <9100>\n";
12150 $stream .= "<9200> <92ff> <9200>\n";
12151 $stream .= "<9300> <93ff> <9300>\n";
12152 $stream .= "<9400> <94ff> <9400>\n";
12153 $stream .= "<9500> <95ff> <9500>\n";
12154 $stream .= "<9600> <96ff> <9600>\n";
12155 $stream .= "<9700> <97ff> <9700>\n";
12156 $stream .= "<9800> <98ff> <9800>\n";
12157 $stream .= "<9900> <99ff> <9900>\n";
12158 $stream .= "<9a00> <9aff> <9a00>\n";
12159 $stream .= "<9b00> <9bff> <9b00>\n";
12160 $stream .= "<9c00> <9cff> <9c00>\n";
12161 $stream .= "<9d00> <9dff> <9d00>\n";
12162 $stream .= "<9e00> <9eff> <9e00>\n";
12163 $stream .= "<9f00> <9fff> <9f00>\n";
12164 $stream .= "<a000> <a0ff> <a000>\n";
12165 $stream .= "<a100> <a1ff> <a100>\n";
12166 $stream .= "<a200> <a2ff> <a200>\n";
12167 $stream .= "<a300> <a3ff> <a300>\n";
12168 $stream .= "<a400> <a4ff> <a400>\n";
12169 $stream .= "<a500> <a5ff> <a500>\n";
12170 $stream .= "<a600> <a6ff> <a600>\n";
12171 $stream .= "<a700> <a7ff> <a700>\n";
12172 $stream .= "<a800> <a8ff> <a800>\n";
12173 $stream .= "<a900> <a9ff> <a900>\n";
12174 $stream .= "<aa00> <aaff> <aa00>\n";
12175 $stream .= "<ab00> <abff> <ab00>\n";
12176 $stream .= "<ac00> <acff> <ac00>\n";
12177 $stream .= "<ad00> <adff> <ad00>\n";
12178 $stream .= "<ae00> <aeff> <ae00>\n";
12179 $stream .= "<af00> <afff> <af00>\n";
12180 $stream .= "<b000> <b0ff> <b000>\n";
12181 $stream .= "<b100> <b1ff> <b100>\n";
12182 $stream .= "<b200> <b2ff> <b200>\n";
12183 $stream .= "<b300> <b3ff> <b300>\n";
12184 $stream .= "<b400> <b4ff> <b400>\n";
12185 $stream .= "<b500> <b5ff> <b500>\n";
12186 $stream .= "<b600> <b6ff> <b600>\n";
12187 $stream .= "<b700> <b7ff> <b700>\n";
12188 $stream .= "<b800> <b8ff> <b800>\n";
12189 $stream .= "<b900> <b9ff> <b900>\n";
12190 $stream .= "<ba00> <baff> <ba00>\n";
12191 $stream .= "<bb00> <bbff> <bb00>\n";
12192 $stream .= "<bc00> <bcff> <bc00>\n";
12193 $stream .= "<bd00> <bdff> <bd00>\n";
12194 $stream .= "<be00> <beff> <be00>\n";
12195 $stream .= "<bf00> <bfff> <bf00>\n";
12196 $stream .= "<c000> <c0ff> <c000>\n";
12197 $stream .= "<c100> <c1ff> <c100>\n";
12198 $stream .= "<c200> <c2ff> <c200>\n";
12199 $stream .= "<c300> <c3ff> <c300>\n";
12200 $stream .= "<c400> <c4ff> <c400>\n";
12201 $stream .= "<c500> <c5ff> <c500>\n";
12202 $stream .= "<c600> <c6ff> <c600>\n";
12203 $stream .= "<c700> <c7ff> <c700>\n";
12204 $stream .= "endbfrange\n";
12205 $stream .= "56 beginbfrange\n";
12206 $stream .= "<c800> <c8ff> <c800>\n";
12207 $stream .= "<c900> <c9ff> <c900>\n";
12208 $stream .= "<ca00> <caff> <ca00>\n";
12209 $stream .= "<cb00> <cbff> <cb00>\n";
12210 $stream .= "<cc00> <ccff> <cc00>\n";
12211 $stream .= "<cd00> <cdff> <cd00>\n";
12212 $stream .= "<ce00> <ceff> <ce00>\n";
12213 $stream .= "<cf00> <cfff> <cf00>\n";
12214 $stream .= "<d000> <d0ff> <d000>\n";
12215 $stream .= "<d100> <d1ff> <d100>\n";
12216 $stream .= "<d200> <d2ff> <d200>\n";
12217 $stream .= "<d300> <d3ff> <d300>\n";
12218 $stream .= "<d400> <d4ff> <d400>\n";
12219 $stream .= "<d500> <d5ff> <d500>\n";
12220 $stream .= "<d600> <d6ff> <d600>\n";
12221 $stream .= "<d700> <d7ff> <d700>\n";
12222 $stream .= "<d800> <d8ff> <d800>\n";
12223 $stream .= "<d900> <d9ff> <d900>\n";
12224 $stream .= "<da00> <daff> <da00>\n";
12225 $stream .= "<db00> <dbff> <db00>\n";
12226 $stream .= "<dc00> <dcff> <dc00>\n";
12227 $stream .= "<dd00> <ddff> <dd00>\n";
12228 $stream .= "<de00> <deff> <de00>\n";
12229 $stream .= "<df00> <dfff> <df00>\n";
12230 $stream .= "<e000> <e0ff> <e000>\n";
12231 $stream .= "<e100> <e1ff> <e100>\n";
12232 $stream .= "<e200> <e2ff> <e200>\n";
12233 $stream .= "<e300> <e3ff> <e300>\n";
12234 $stream .= "<e400> <e4ff> <e400>\n";
12235 $stream .= "<e500> <e5ff> <e500>\n";
12236 $stream .= "<e600> <e6ff> <e600>\n";
12237 $stream .= "<e700> <e7ff> <e700>\n";
12238 $stream .= "<e800> <e8ff> <e800>\n";
12239 $stream .= "<e900> <e9ff> <e900>\n";
12240 $stream .= "<ea00> <eaff> <ea00>\n";
12241 $stream .= "<eb00> <ebff> <eb00>\n";
12242 $stream .= "<ec00> <ecff> <ec00>\n";
12243 $stream .= "<ed00> <edff> <ed00>\n";
12244 $stream .= "<ee00> <eeff> <ee00>\n";
12245 $stream .= "<ef00> <efff> <ef00>\n";
12246 $stream .= "<f000> <f0ff> <f000>\n";
12247 $stream .= "<f100> <f1ff> <f100>\n";
12248 $stream .= "<f200> <f2ff> <f200>\n";
12249 $stream .= "<f300> <f3ff> <f300>\n";
12250 $stream .= "<f400> <f4ff> <f400>\n";
12251 $stream .= "<f500> <f5ff> <f500>\n";
12252 $stream .= "<f600> <f6ff> <f600>\n";
12253 $stream .= "<f700> <f7ff> <f700>\n";
12254 $stream .= "<f800> <f8ff> <f800>\n";
12255 $stream .= "<f900> <f9ff> <f900>\n";
12256 $stream .= "<fa00> <faff> <fa00>\n";
12257 $stream .= "<fb00> <fbff> <fb00>\n";
12258 $stream .= "<fc00> <fcff> <fc00>\n";
12259 $stream .= "<fd00> <fdff> <fd00>\n";
12260 $stream .= "<fe00> <feff> <fe00>\n";
12261 $stream .= "<ff00> <ffff> <ff00>\n";
12262 $stream .= "endbfrange\n";
12263 $stream .= "endcmap\n";
12264 $stream .= "CMapName currentdict /CMap defineresource pop\n";
12265 $stream .= "end\n";
12266 $stream .= "end";
12267 // ToUnicode Object
12268 $this->_newobj();
12269 $stream = ($this->compress) ? gzcompress($stream) : $stream;
12270 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
12271 $stream = $this->_getrawstream($stream);
12272 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
12273 // CIDFontType2
12274 // A CIDFont whose glyph descriptions are based on TrueType font technology
12275 $oid = $this->_newobj();
12276 $out = '<< /Type /Font';
12277 $out .= ' /Subtype /CIDFontType2';
12278 $out .= ' /BaseFont /'.$fontname;
12279 // A dictionary containing entries that define the character collection of the CIDFont.
12280 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
12281 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
12282 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
12283 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
12284 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
12285 $out .= ' /DW '.$font['dw']; // default width
12286 $out .= "\n".$this->_putfontwidths($font, 0);
12287 if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
12288 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
12290 $out .= ' >>';
12291 $out .= "\n".'endobj';
12292 $this->_out($out);
12293 // Font descriptor
12294 // A font descriptor describing the CIDFont default metrics other than its glyph widths
12295 $this->_newobj();
12296 $out = '<< /Type /FontDescriptor';
12297 $out .= ' /FontName /'.$fontname;
12298 foreach ($font['desc'] as $key => $value) {
12299 if (is_float($value)) {
12300 $value = sprintf('%F', $value);
12302 $out .= ' /'.$key.' '.$value;
12304 $fontdir = false;
12305 if (!$this->empty_string($font['file'])) {
12306 // A stream containing a TrueType font
12307 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
12308 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
12310 $out .= ' >>';
12311 $out .= "\n".'endobj';
12312 $this->_out($out);
12313 if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
12314 $this->_newobj();
12315 // Embed CIDToGIDMap
12316 // A specification of the mapping from CIDs to glyph indices
12317 // search and get CTG font file to embedd
12318 $ctgfile = strtolower($font['ctg']);
12319 // search and get ctg font file to embedd
12320 $fontfile = '';
12321 // search files on various directories
12322 if (($fontdir !== false) AND file_exists($fontdir.$ctgfile)) {
12323 $fontfile = $fontdir.$ctgfile;
12324 } elseif (file_exists($this->_getfontpath().$ctgfile)) {
12325 $fontfile = $this->_getfontpath().$ctgfile;
12326 } elseif (file_exists($ctgfile)) {
12327 $fontfile = $ctgfile;
12329 if ($this->empty_string($fontfile)) {
12330 $this->Error('Font file not found: '.$ctgfile);
12332 $stream = $this->_getrawstream(file_get_contents($fontfile));
12333 $out = '<< /Length '.strlen($stream).'';
12334 if (substr($fontfile, -2) == '.z') { // check file extension
12335 // Decompresses data encoded using the public-domain
12336 // zlib/deflate compression method, reproducing the
12337 // original text or binary data
12338 $out .= ' /Filter /FlateDecode';
12340 $out .= ' >>';
12341 $out .= ' stream'."\n".$stream."\n".'endstream';
12342 $out .= "\n".'endobj';
12343 $this->_out($out);
12348 * Output CID-0 fonts.
12349 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
12350 * @param $font (array) font data
12351 * @protected
12352 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
12353 * @since 3.2.000 (2008-06-23)
12355 protected function _putcidfont0($font) {
12356 $cidoffset = 0;
12357 if (!isset($font['cw'][1])) {
12358 $cidoffset = 31;
12360 if (isset($font['cidinfo']['uni2cid'])) {
12361 // convert unicode to cid.
12362 $uni2cid = $font['cidinfo']['uni2cid'];
12363 $cw = array();
12364 foreach ($font['cw'] as $uni => $width) {
12365 if (isset($uni2cid[$uni])) {
12366 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
12367 } elseif ($uni < 256) {
12368 $cw[$uni] = $width;
12369 } // else unknown character
12371 $font = array_merge($font, array('cw' => $cw));
12373 $name = $font['name'];
12374 $enc = $font['enc'];
12375 if ($enc) {
12376 $longname = $name.'-'.$enc;
12377 } else {
12378 $longname = $name;
12380 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
12381 $out .= '<</Type /Font';
12382 $out .= ' /Subtype /Type0';
12383 $out .= ' /BaseFont /'.$longname;
12384 $out .= ' /Name /F'.$font['i'];
12385 if ($enc) {
12386 $out .= ' /Encoding /'.$enc;
12388 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
12389 $out .= ' >>';
12390 $out .= "\n".'endobj';
12391 $this->_out($out);
12392 $oid = $this->_newobj();
12393 $out = '<</Type /Font';
12394 $out .= ' /Subtype /CIDFontType0';
12395 $out .= ' /BaseFont /'.$name;
12396 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
12397 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
12398 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
12399 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
12400 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
12401 $out .= ' /DW '.$font['dw'];
12402 $out .= "\n".$this->_putfontwidths($font, $cidoffset);
12403 $out .= ' >>';
12404 $out .= "\n".'endobj';
12405 $this->_out($out);
12406 $this->_newobj();
12407 $s = '<</Type /FontDescriptor /FontName /'.$name;
12408 foreach ($font['desc'] as $k => $v) {
12409 if ($k != 'Style') {
12410 if (is_float($v)) {
12411 $v = sprintf('%F', $v);
12413 $s .= ' /'.$k.' '.$v.'';
12416 $s .= '>>';
12417 $s .= "\n".'endobj';
12418 $this->_out($s);
12422 * Output images.
12423 * @protected
12425 protected function _putimages() {
12426 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
12427 foreach ($this->imagekeys as $file) {
12428 $info = $this->getImageBuffer($file);
12429 // set object for alternate images array
12430 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
12431 $altoid = $this->_newobj();
12432 $out = '[';
12433 foreach ($info['altimgs'] as $altimage) {
12434 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
12435 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
12436 $out .= ' /DefaultForPrinting';
12437 if ($altimage[1] === true) {
12438 $out .= ' true';
12439 } else {
12440 $out .= ' false';
12442 $out .= ' >>';
12445 $out .= ' ]';
12446 $out .= "\n".'endobj';
12447 $this->_out($out);
12449 // set image object
12450 $oid = $this->_newobj();
12451 $this->xobjects['I'.$info['i']] = array('n' => $oid);
12452 $this->setImageSubBuffer($file, 'n', $this->n);
12453 $out = '<</Type /XObject';
12454 $out .= ' /Subtype /Image';
12455 $out .= ' /Width '.$info['w'];
12456 $out .= ' /Height '.$info['h'];
12457 if (array_key_exists('masked', $info)) {
12458 $out .= ' /SMask '.($this->n - 1).' 0 R';
12460 // set color space
12461 $icc = false;
12462 if (isset($info['icc']) AND ($info['icc'] !== false)) {
12463 // ICC Colour Space
12464 $icc = true;
12465 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
12466 } elseif ($info['cs'] == 'Indexed') {
12467 // Indexed Colour Space
12468 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
12469 } else {
12470 // Device Colour Space
12471 $out .= ' /ColorSpace /'.$info['cs'];
12473 if ($info['cs'] == 'DeviceCMYK') {
12474 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
12476 $out .= ' /BitsPerComponent '.$info['bpc'];
12477 if (isset($altoid) AND ($altoid > 0)) {
12478 // reference to alternate images dictionary
12479 $out .= ' /Alternates '.$altoid.' 0 R';
12481 if (isset($info['exurl']) AND !empty($info['exurl'])) {
12482 // external stream
12483 $out .= ' /Length 0';
12484 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
12485 if (isset($info['f'])) {
12486 $out .= ' /FFilter /'.$info['f'];
12488 $out .= ' >>';
12489 $out .= ' stream'."\n".'endstream';
12490 } else {
12491 if (isset($info['f'])) {
12492 $out .= ' /Filter /'.$info['f'];
12494 if (isset($info['parms'])) {
12495 $out .= ' '.$info['parms'];
12497 if (isset($info['trns']) AND is_array($info['trns'])) {
12498 $trns = '';
12499 $count_info = count($info['trns']);
12500 for ($i=0; $i < $count_info; ++$i) {
12501 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
12503 $out .= ' /Mask ['.$trns.']';
12505 $stream = $this->_getrawstream($info['data']);
12506 $out .= ' /Length '.strlen($stream).' >>';
12507 $out .= ' stream'."\n".$stream."\n".'endstream';
12509 $out .= "\n".'endobj';
12510 $this->_out($out);
12511 if ($icc) {
12512 // ICC colour profile
12513 $this->_newobj();
12514 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
12515 $icc = $this->_getrawstream($icc);
12516 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
12517 } elseif ($info['cs'] == 'Indexed') {
12518 // colour palette
12519 $this->_newobj();
12520 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
12521 $pal = $this->_getrawstream($pal);
12522 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
12528 * Output Form XObjects Templates.
12529 * @author Nicola Asuni
12530 * @since 5.8.017 (2010-08-24)
12531 * @protected
12532 * @see startTemplate(), endTemplate(), printTemplate()
12534 protected function _putxobjects() {
12535 foreach ($this->xobjects as $key => $data) {
12536 if (isset($data['outdata'])) {
12537 $stream = trim($data['outdata']);
12538 $out = $this->_getobj($data['n'])."\n";
12539 $out .= '<<';
12540 $out .= ' /Type /XObject';
12541 $out .= ' /Subtype /Form';
12542 $out .= ' /FormType 1';
12543 if ($this->compress) {
12544 $stream = gzcompress($stream);
12545 $out .= ' /Filter /FlateDecode';
12547 $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));
12548 $out .= ' /Matrix [1 0 0 1 0 0]';
12549 $out .= ' /Resources <<';
12550 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
12551 if (!$this->pdfa_mode) {
12552 // transparency
12553 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
12554 $out .= ' /ExtGState <<';
12555 foreach ($data['extgstates'] as $k => $extgstate) {
12556 if (isset($this->extgstates[$k]['name'])) {
12557 $out .= ' /'.$this->extgstates[$k]['name'];
12558 } else {
12559 $out .= ' /GS'.$k;
12561 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
12563 $out .= ' >>';
12565 if (isset($data['gradients']) AND !empty($data['gradients'])) {
12566 $gp = '';
12567 $gs = '';
12568 foreach ($data['gradients'] as $id => $grad) {
12569 // gradient patterns
12570 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
12571 // gradient shadings
12572 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
12574 $out .= ' /Pattern <<'.$gp.' >>';
12575 $out .= ' /Shading <<'.$gs.' >>';
12578 // spot colors
12579 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
12580 $out .= ' /ColorSpace <<';
12581 foreach ($data['spot_colors'] as $name => $color) {
12582 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
12584 $out .= ' >>';
12586 // fonts
12587 if (!empty($data['fonts'])) {
12588 $out .= ' /Font <<';
12589 foreach ($data['fonts'] as $fontkey => $fontid) {
12590 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
12592 $out .= ' >>';
12594 // images or nested xobjects
12595 if (!empty($data['images']) OR !empty($data['xobjects'])) {
12596 $out .= ' /XObject <<';
12597 foreach ($data['images'] as $imgid) {
12598 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
12600 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
12601 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
12603 $out .= ' >>';
12605 $out .= ' >>'; //end resources
12606 if (isset($data['group']) AND ($data['group'] !== false)) {
12607 // set transparency group
12608 $out .= ' /Group << /Type /Group /S /Transparency';
12609 if (is_array($data['group'])) {
12610 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
12611 $out .= ' /CS /'.$data['group']['CS'];
12613 if (isset($data['group']['I'])) {
12614 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
12616 if (isset($data['group']['K'])) {
12617 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
12620 $out .= ' >>';
12622 $stream = $this->_getrawstream($stream, $data['n']);
12623 $out .= ' /Length '.strlen($stream);
12624 $out .= ' >>';
12625 $out .= ' stream'."\n".$stream."\n".'endstream';
12626 $out .= "\n".'endobj';
12627 $this->_out($out);
12633 * Output Spot Colors Resources.
12634 * @protected
12635 * @since 4.0.024 (2008-09-12)
12637 protected function _putspotcolors() {
12638 foreach ($this->spot_colors as $name => $color) {
12639 $this->_newobj();
12640 $this->spot_colors[$name]['n'] = $this->n;
12641 $out = '[/Separation /'.str_replace(' ', '#20', $name);
12642 $out .= ' /DeviceCMYK <<';
12643 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
12644 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
12645 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
12646 $out .= "\n".'endobj';
12647 $this->_out($out);
12652 * Return XObjects Dictionary.
12653 * @return string XObjects dictionary
12654 * @protected
12655 * @since 5.8.014 (2010-08-23)
12657 protected function _getxobjectdict() {
12658 $out = '';
12659 foreach ($this->xobjects as $id => $objid) {
12660 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
12662 return $out;
12666 * Output Resources Dictionary.
12667 * @protected
12669 protected function _putresourcedict() {
12670 $out = $this->_getobj(2)."\n";
12671 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
12672 $out .= ' /Font <<';
12673 foreach ($this->fontkeys as $fontkey) {
12674 $font = $this->getFontBuffer($fontkey);
12675 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
12677 $out .= ' >>';
12678 $out .= ' /XObject <<';
12679 $out .= $this->_getxobjectdict();
12680 $out .= ' >>';
12681 // layers
12682 if (!empty($this->pdflayers)) {
12683 $out .= ' /Properties <<';
12684 foreach ($this->pdflayers as $layer) {
12685 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
12687 $out .= ' >>';
12689 if (!$this->pdfa_mode) {
12690 // transparency
12691 if (isset($this->extgstates) AND !empty($this->extgstates)) {
12692 $out .= ' /ExtGState <<';
12693 foreach ($this->extgstates as $k => $extgstate) {
12694 if (isset($extgstate['name'])) {
12695 $out .= ' /'.$extgstate['name'];
12696 } else {
12697 $out .= ' /GS'.$k;
12699 $out .= ' '.$extgstate['n'].' 0 R';
12701 $out .= ' >>';
12703 if (isset($this->gradients) AND !empty($this->gradients)) {
12704 $gp = '';
12705 $gs = '';
12706 foreach ($this->gradients as $id => $grad) {
12707 // gradient patterns
12708 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
12709 // gradient shadings
12710 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
12712 $out .= ' /Pattern <<'.$gp.' >>';
12713 $out .= ' /Shading <<'.$gs.' >>';
12716 // spot colors
12717 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
12718 $out .= ' /ColorSpace <<';
12719 foreach ($this->spot_colors as $color) {
12720 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
12722 $out .= ' >>';
12724 $out .= ' >>';
12725 $out .= "\n".'endobj';
12726 $this->_out($out);
12730 * Output Resources.
12731 * @protected
12733 protected function _putresources() {
12734 $this->_putextgstates();
12735 $this->_putocg();
12736 $this->_putfonts();
12737 $this->_putimages();
12738 $this->_putspotcolors();
12739 $this->_putshaders();
12740 $this->_putxobjects();
12741 $this->_putresourcedict();
12742 $this->_putdests();
12743 $this->_putEmbeddedFiles();
12744 $this->_putannotsobjs();
12745 $this->_putjavascript();
12746 $this->_putbookmarks();
12747 $this->_putencryption();
12751 * Adds some Metadata information (Document Information Dictionary)
12752 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
12753 * @return int object id
12754 * @protected
12756 protected function _putinfo() {
12757 $oid = $this->_newobj();
12758 $out = '<<';
12759 // store current isunicode value
12760 $prev_isunicode = $this->isunicode;
12761 if ($this->docinfounicode) {
12762 $this->isunicode = true;
12764 if (!$this->empty_string($this->title)) {
12765 // The document's title.
12766 $out .= ' /Title '.$this->_textstring($this->title, $oid);
12768 if (!$this->empty_string($this->author)) {
12769 // The name of the person who created the document.
12770 $out .= ' /Author '.$this->_textstring($this->author, $oid);
12772 if (!$this->empty_string($this->subject)) {
12773 // The subject of the document.
12774 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
12776 if (!$this->empty_string($this->keywords)) {
12777 // Keywords associated with the document.
12778 $out .= ' /Keywords '.$this->_textstring($this->keywords.' TCPDF', $oid);
12780 if (!$this->empty_string($this->creator)) {
12781 // 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.
12782 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
12784 // restore previous isunicode value
12785 $this->isunicode = $prev_isunicode;
12786 // default producer
12787 $out .= ' /Producer '.$this->_textstring($this->pdfproducer, $oid);
12788 // The date and time the document was created, in human-readable form
12789 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
12790 // The date and time the document was most recently modified, in human-readable form
12791 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
12792 // A name object indicating whether the document has been modified to include trapping information
12793 $out .= ' /Trapped /False';
12794 $out .= ' >>';
12795 $out .= "\n".'endobj';
12796 $this->_out($out);
12797 return $oid;
12801 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
12802 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
12803 * @param $xmp (string) Custom XMP data.
12804 * @since 5.9.128 (2011-10-06)
12805 * @public
12807 public function setExtraXMP($xmp) {
12808 $this->custom_xmp = $xmp;
12812 * Put XMP data object and return ID.
12813 * @return (int) The object ID.
12814 * @since 5.9.121 (2011-09-28)
12815 * @protected
12817 protected function _putXMP() {
12818 $oid = $this->_newobj();
12819 // store current isunicode value
12820 $prev_isunicode = $this->isunicode;
12821 $this->isunicode = true;
12822 $prev_encrypted = $this->encrypted;
12823 $this->encrypted = false;
12824 // set XMP data
12825 $xmp = '<?xpacket begin="'.$this->unichr(0xfeff).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
12826 $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";
12827 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
12828 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
12829 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
12830 $xmp .= "\t\t\t".'<dc:title>'."\n";
12831 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
12832 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.$this->_escapeXML($this->title).'</rdf:li>'."\n";
12833 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
12834 $xmp .= "\t\t\t".'</dc:title>'."\n";
12835 $xmp .= "\t\t\t".'<dc:creator>'."\n";
12836 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
12837 $xmp .= "\t\t\t\t\t".'<rdf:li>'.$this->_escapeXML($this->author).'</rdf:li>'."\n";
12838 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
12839 $xmp .= "\t\t\t".'</dc:creator>'."\n";
12840 $xmp .= "\t\t\t".'<dc:description>'."\n";
12841 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
12842 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.$this->_escapeXML($this->subject).'</rdf:li>'."\n";
12843 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
12844 $xmp .= "\t\t\t".'</dc:description>'."\n";
12845 $xmp .= "\t\t\t".'<dc:subject>'."\n";
12846 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
12847 $xmp .= "\t\t\t\t\t".'<rdf:li>'.$this->_escapeXML($this->keywords).' TCPDF</rdf:li>'."\n";
12848 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
12849 $xmp .= "\t\t\t".'</dc:subject>'."\n";
12850 $xmp .= "\t\t".'</rdf:Description>'."\n";
12851 // convert doc creation date format
12852 $dcdate = $this->getFormattedDate($this->doc_creation_timestamp);
12853 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
12854 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
12855 $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
12856 $doccreationdate = $this->_escapeXML($doccreationdate);
12857 // convert doc modification date format
12858 $dmdate = $this->getFormattedDate($this->doc_modification_timestamp);
12859 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
12860 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
12861 $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
12862 $docmoddate = $this->_escapeXML($docmoddate);
12863 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
12864 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
12865 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
12866 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
12867 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
12868 $xmp .= "\t\t".'</rdf:Description>'."\n";
12869 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
12870 $xmp .= "\t\t\t".'<pdf:Keywords>'.$this->_escapeXML($this->keywords).' TCPDF</pdf:Keywords>'."\n";
12871 $xmp .= "\t\t\t".'<pdf:Producer>'.$this->_escapeXML($this->pdfproducer).'</pdf:Producer>'."\n";
12872 $xmp .= "\t\t".'</rdf:Description>'."\n";
12873 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
12874 $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);
12875 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
12876 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
12877 $xmp .= "\t\t".'</rdf:Description>'."\n";
12878 if ($this->pdfa_mode) {
12879 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
12880 $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
12881 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
12882 $xmp .= "\t\t".'</rdf:Description>'."\n";
12884 // XMP extension schemas
12885 $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";
12886 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
12887 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
12888 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12889 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
12890 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
12891 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
12892 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12893 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12894 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
12895 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
12896 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
12897 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
12898 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
12899 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12900 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12901 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
12902 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
12903 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
12904 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12905 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
12906 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
12907 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12908 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12909 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
12910 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
12911 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
12912 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
12913 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
12914 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12915 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12916 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
12917 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
12918 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
12919 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12920 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12921 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12922 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
12923 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
12924 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
12925 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12926 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12927 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12928 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
12929 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
12930 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
12931 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12932 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
12933 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
12934 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12935 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
12936 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
12937 $xmp .= "\t\t".'</rdf:Description>'."\n";
12938 $xmp .= "\t".'</rdf:RDF>'."\n";
12939 $xmp .= $this->custom_xmp;
12940 $xmp .= '</x:xmpmeta>'."\n";
12941 $xmp .= '<?xpacket end="w"?>';
12942 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
12943 // restore previous isunicode value
12944 $this->isunicode = $prev_isunicode;
12945 $this->encrypted = $prev_encrypted;
12946 $this->_out($out);
12947 return $oid;
12951 * Output Catalog.
12952 * @return int object id
12953 * @protected
12955 protected function _putcatalog() {
12956 // put XMP
12957 $xmpobj = $this->_putXMP();
12958 // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
12959 if ($this->pdfa_mode OR $this->force_srgb) {
12960 $iccobj = $this->_newobj();
12961 $icc = file_get_contents(dirname(__FILE__).'/sRGB.icc');
12962 $filter = '';
12963 if ($this->compress) {
12964 $filter = ' /Filter /FlateDecode';
12965 $icc = gzcompress($icc);
12967 $icc = $this->_getrawstream($icc);
12968 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
12970 // start catalog
12971 $oid = $this->_newobj();
12972 $out = '<< /Type /Catalog';
12973 $out .= ' /Version /'.$this->PDFVersion;
12974 //$out .= ' /Extensions <<>>';
12975 $out .= ' /Pages 1 0 R';
12976 //$out .= ' /PageLabels ' //...;
12977 $out .= ' /Names <<';
12978 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
12979 $out .= ' /JavaScript '.$this->n_js;
12981 if (!empty($this->efnames)) {
12982 $out .= ' /EmbeddedFiles <</Names [';
12983 foreach ($this->efnames AS $fn => $fref) {
12984 $out .= ' '.$this->_datastring($fn).' '.$fref;
12986 $out .= ' ]>>';
12988 $out .= ' >>';
12989 if (!empty($this->dests)) {
12990 $out .= ' /Dests '.($this->n_dests).' 0 R';
12992 $out .= $this->_putviewerpreferences();
12993 if (isset($this->LayoutMode) AND (!$this->empty_string($this->LayoutMode))) {
12994 $out .= ' /PageLayout /'.$this->LayoutMode;
12996 if (isset($this->PageMode) AND (!$this->empty_string($this->PageMode))) {
12997 $out .= ' /PageMode /'.$this->PageMode;
12999 if (count($this->outlines) > 0) {
13000 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
13001 $out .= ' /PageMode /UseOutlines';
13003 //$out .= ' /Threads []';
13004 if ($this->ZoomMode == 'fullpage') {
13005 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
13006 } elseif ($this->ZoomMode == 'fullwidth') {
13007 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
13008 } elseif ($this->ZoomMode == 'real') {
13009 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
13010 } elseif (!is_string($this->ZoomMode)) {
13011 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
13013 //$out .= ' /AA <<>>';
13014 //$out .= ' /URI <<>>';
13015 $out .= ' /Metadata '.$xmpobj.' 0 R';
13016 //$out .= ' /StructTreeRoot <<>>';
13017 //$out .= ' /MarkInfo <<>>';
13018 if (isset($this->l['a_meta_language'])) {
13019 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
13021 //$out .= ' /SpiderInfo <<>>';
13022 // set OutputIntent to sRGB IEC61966-2.1 if required
13023 if ($this->pdfa_mode OR $this->force_srgb) {
13024 $out .= ' /OutputIntents [<<';
13025 $out .= ' /Type /OutputIntent';
13026 $out .= ' /S /GTS_PDFA1';
13027 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
13028 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
13029 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
13030 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
13031 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
13032 $out .= ' >>]';
13034 //$out .= ' /PieceInfo <<>>';
13035 if (!empty($this->pdflayers)) {
13036 $lyrobjs = '';
13037 $lyrobjs_print = '';
13038 $lyrobjs_view = '';
13039 foreach ($this->pdflayers as $layer) {
13040 $lyrobjs .= ' '.$layer['objid'].' 0 R';
13041 if ($layer['print']) {
13042 $lyrobjs_print .= ' '.$layer['objid'].' 0 R';
13044 if ($layer['view']) {
13045 $lyrobjs_view .= ' '.$layer['objid'].' 0 R';
13048 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
13049 $out .= ' /D <<';
13050 $out .= ' /Name '.$this->_textstring('Layers', $oid);
13051 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
13052 $out .= ' /BaseState /ON';
13053 $out .= ' /ON ['.$lyrobjs_print.']';
13054 $out .= ' /OFF ['.$lyrobjs_view.']';
13055 $out .= ' /Intent /View';
13056 $out .= ' /AS [';
13057 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
13058 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
13059 $out .= ' ]';
13060 $out .= ' /Order ['.$lyrobjs.']';
13061 $out .= ' /ListMode /AllPages';
13062 //$out .= ' /RBGroups ['..']';
13063 //$out .= ' /Locked ['..']';
13064 $out .= ' >>';
13065 $out .= ' >>';
13067 // AcroForm
13068 if (!empty($this->form_obj_id) OR ($this->sign AND isset($this->signature_data['cert_type']))) {
13069 $out .= ' /AcroForm <<';
13070 $objrefs = '';
13071 if ($this->sign AND isset($this->signature_data['cert_type'])) {
13072 // set reference for signature object
13073 $objrefs .= $this->sig_obj_id.' 0 R';
13075 if (!empty($this->empty_signature_appearance)) {
13076 foreach ($this->empty_signature_appearance as $esa) {
13077 // set reference for empty signature objects
13078 $objrefs .= ' '.$esa['objid'].' 0 R';
13081 if (!empty($this->form_obj_id)) {
13082 foreach($this->form_obj_id as $objid) {
13083 $objrefs .= ' '.$objid.' 0 R';
13086 $out .= ' /Fields ['.$objrefs.']';
13087 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
13088 $out .= ' /NeedAppearances false';
13089 if ($this->sign AND isset($this->signature_data['cert_type'])) {
13090 if ($this->signature_data['cert_type'] > 0) {
13091 $out .= ' /SigFlags 3';
13092 } else {
13093 $out .= ' /SigFlags 1';
13096 //$out .= ' /CO ';
13097 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
13098 $out .= ' /DR <<';
13099 $out .= ' /Font <<';
13100 foreach ($this->annotation_fonts as $fontkey => $fontid) {
13101 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
13103 $out .= ' >> >>';
13105 $font = $this->getFontBuffer('helvetica');
13106 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
13107 $out .= ' /Q '.(($this->rtl)?'2':'0');
13108 //$out .= ' /XFA ';
13109 $out .= ' >>';
13110 // signatures
13111 if ($this->sign AND isset($this->signature_data['cert_type'])) {
13112 if ($this->signature_data['cert_type'] > 0) {
13113 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
13114 } else {
13115 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
13119 //$out .= ' /Legal <<>>';
13120 //$out .= ' /Requirements []';
13121 //$out .= ' /Collection <<>>';
13122 //$out .= ' /NeedsRendering true';
13123 $out .= ' >>';
13124 $out .= "\n".'endobj';
13125 $this->_out($out);
13126 return $oid;
13130 * Output viewer preferences.
13131 * @return string for viewer preferences
13132 * @author Nicola asuni
13133 * @since 3.1.000 (2008-06-09)
13134 * @protected
13136 protected function _putviewerpreferences() {
13137 $out = ' /ViewerPreferences <<';
13138 if ($this->rtl) {
13139 $out .= ' /Direction /R2L';
13140 } else {
13141 $out .= ' /Direction /L2R';
13143 if (isset($this->viewer_preferences['HideToolbar']) AND ($this->viewer_preferences['HideToolbar'])) {
13144 $out .= ' /HideToolbar true';
13146 if (isset($this->viewer_preferences['HideMenubar']) AND ($this->viewer_preferences['HideMenubar'])) {
13147 $out .= ' /HideMenubar true';
13149 if (isset($this->viewer_preferences['HideWindowUI']) AND ($this->viewer_preferences['HideWindowUI'])) {
13150 $out .= ' /HideWindowUI true';
13152 if (isset($this->viewer_preferences['FitWindow']) AND ($this->viewer_preferences['FitWindow'])) {
13153 $out .= ' /FitWindow true';
13155 if (isset($this->viewer_preferences['CenterWindow']) AND ($this->viewer_preferences['CenterWindow'])) {
13156 $out .= ' /CenterWindow true';
13158 if (isset($this->viewer_preferences['DisplayDocTitle']) AND ($this->viewer_preferences['DisplayDocTitle'])) {
13159 $out .= ' /DisplayDocTitle true';
13161 if (isset($this->viewer_preferences['NonFullScreenPageMode'])) {
13162 $out .= ' /NonFullScreenPageMode /'.$this->viewer_preferences['NonFullScreenPageMode'];
13164 if (isset($this->viewer_preferences['ViewArea'])) {
13165 $out .= ' /ViewArea /'.$this->viewer_preferences['ViewArea'];
13167 if (isset($this->viewer_preferences['ViewClip'])) {
13168 $out .= ' /ViewClip /'.$this->viewer_preferences['ViewClip'];
13170 if (isset($this->viewer_preferences['PrintArea'])) {
13171 $out .= ' /PrintArea /'.$this->viewer_preferences['PrintArea'];
13173 if (isset($this->viewer_preferences['PrintClip'])) {
13174 $out .= ' /PrintClip /'.$this->viewer_preferences['PrintClip'];
13176 if (isset($this->viewer_preferences['PrintScaling'])) {
13177 $out .= ' /PrintScaling /'.$this->viewer_preferences['PrintScaling'];
13179 if (isset($this->viewer_preferences['Duplex']) AND (!$this->empty_string($this->viewer_preferences['Duplex']))) {
13180 $out .= ' /Duplex /'.$this->viewer_preferences['Duplex'];
13182 if (isset($this->viewer_preferences['PickTrayByPDFSize'])) {
13183 if ($this->viewer_preferences['PickTrayByPDFSize']) {
13184 $out .= ' /PickTrayByPDFSize true';
13185 } else {
13186 $out .= ' /PickTrayByPDFSize false';
13189 if (isset($this->viewer_preferences['PrintPageRange'])) {
13190 $PrintPageRangeNum = '';
13191 foreach ($this->viewer_preferences['PrintPageRange'] as $k => $v) {
13192 $PrintPageRangeNum .= ' '.($v - 1).'';
13194 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
13196 if (isset($this->viewer_preferences['NumCopies'])) {
13197 $out .= ' /NumCopies '.intval($this->viewer_preferences['NumCopies']);
13199 $out .= ' >>';
13200 return $out;
13204 * Output PDF File Header (7.5.2).
13205 * @protected
13207 protected function _putheader() {
13208 $this->_out('%PDF-'.$this->PDFVersion);
13209 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
13213 * Output end of document (EOF).
13214 * @protected
13216 protected function _enddoc() {
13217 $this->state = 1;
13218 $this->_putheader();
13219 $this->_putpages();
13220 $this->_putresources();
13221 // empty signature fields
13222 if (!empty($this->empty_signature_appearance)) {
13223 foreach ($this->empty_signature_appearance as $key => $esa) {
13224 // widget annotation for empty signature
13225 $out = $this->_getobj($esa['objid'])."\n";
13226 $out .= '<< /Type /Annot';
13227 $out .= ' /Subtype /Widget';
13228 $out .= ' /Rect ['.$esa['rect'].']';
13229 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
13230 $out .= ' /F 4';
13231 $out .= ' /FT /Sig';
13232 $signame = sprintf('Signature_%03d', ($key + 1));
13233 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
13234 $out .= ' /Ff 0';
13235 $out .= ' >>';
13236 $out .= "\n".'endobj';
13237 $this->_out($out);
13240 // Signature
13241 if ($this->sign AND isset($this->signature_data['cert_type'])) {
13242 // widget annotation for signature
13243 $out = $this->_getobj($this->sig_obj_id)."\n";
13244 $out .= '<< /Type /Annot';
13245 $out .= ' /Subtype /Widget';
13246 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
13247 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
13248 $out .= ' /F 4';
13249 $out .= ' /FT /Sig';
13250 $out .= ' /T '.$this->_textstring('Signature_000', $this->sig_obj_id);
13251 $out .= ' /Ff 0';
13252 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
13253 $out .= ' >>';
13254 $out .= "\n".'endobj';
13255 $this->_out($out);
13256 // signature
13257 $this->_putsignature();
13259 // Info
13260 $objid_info = $this->_putinfo();
13261 // Catalog
13262 $objid_catalog = $this->_putcatalog();
13263 // Cross-ref
13264 $o = $this->bufferlen;
13265 // XREF section
13266 $this->_out('xref');
13267 $this->_out('0 '.($this->n + 1));
13268 $this->_out('0000000000 65535 f ');
13269 $freegen = ($this->n + 2);
13270 for ($i=1; $i <= $this->n; ++$i) {
13271 if (!isset($this->offsets[$i]) AND ($i > 1)) {
13272 $this->_out(sprintf('0000000000 %05d f ', $freegen));
13273 ++$freegen;
13274 } else {
13275 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
13278 // TRAILER
13279 $out = 'trailer'."\n";
13280 $out .= '<<';
13281 $out .= ' /Size '.($this->n + 1);
13282 $out .= ' /Root '.$objid_catalog.' 0 R';
13283 $out .= ' /Info '.$objid_info.' 0 R';
13284 if ($this->encrypted) {
13285 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
13287 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
13288 $out .= ' >>';
13289 $this->_out($out);
13290 $this->_out('startxref');
13291 $this->_out($o);
13292 $this->_out('%%EOF');
13293 $this->state = 3; // end-of-doc
13294 if ($this->diskcache) {
13295 // remove temporary files used for images
13296 foreach ($this->imagekeys as $key) {
13297 // remove temporary files
13298 unlink($this->images[$key]);
13300 foreach ($this->fontkeys as $key) {
13301 // remove temporary files
13302 unlink($this->fonts[$key]);
13308 * Initialize a new page.
13309 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
13310 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
13311 * @protected
13312 * @see getPageSizeFromFormat(), setPageFormat()
13314 protected function _beginpage($orientation='', $format='') {
13315 ++$this->page;
13316 $this->pageobjects[$this->page] = array();
13317 $this->setPageBuffer($this->page, '');
13318 // initialize array for graphics tranformation positions inside a page buffer
13319 $this->transfmrk[$this->page] = array();
13320 $this->state = 2;
13321 if ($this->empty_string($orientation)) {
13322 if (isset($this->CurOrientation)) {
13323 $orientation = $this->CurOrientation;
13324 } elseif ($this->fwPt > $this->fhPt) {
13325 // landscape
13326 $orientation = 'L';
13327 } else {
13328 // portrait
13329 $orientation = 'P';
13332 if ($this->empty_string($format)) {
13333 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
13334 $this->setPageOrientation($orientation);
13335 } else {
13336 $this->setPageFormat($format, $orientation);
13338 if ($this->rtl) {
13339 $this->x = $this->w - $this->rMargin;
13340 } else {
13341 $this->x = $this->lMargin;
13343 $this->y = $this->tMargin;
13344 if (isset($this->newpagegroup[$this->page])) {
13345 // start a new group
13346 $this->currpagegroup = $this->newpagegroup[$this->page];
13347 $this->pagegroups[$this->currpagegroup] = 1;
13348 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
13349 ++$this->pagegroups[$this->currpagegroup];
13354 * Mark end of page.
13355 * @protected
13357 protected function _endpage() {
13358 $this->setVisibility('all');
13359 $this->state = 1;
13363 * Begin a new object and return the object number.
13364 * @return int object number
13365 * @protected
13367 protected function _newobj() {
13368 $this->_out($this->_getobj());
13369 return $this->n;
13373 * Return the starting object string for the selected object ID.
13374 * @param $objid (int) Object ID (leave empty to get a new ID).
13375 * @return string the starting object string
13376 * @protected
13377 * @since 5.8.009 (2010-08-20)
13379 protected function _getobj($objid='') {
13380 if ($objid === '') {
13381 ++$this->n;
13382 $objid = $this->n;
13384 $this->offsets[$objid] = $this->bufferlen;
13385 $this->pageobjects[$this->page][] = $objid;
13386 return $objid.' 0 obj';
13390 * Underline text.
13391 * @param $x (int) X coordinate
13392 * @param $y (int) Y coordinate
13393 * @param $txt (string) text to underline
13394 * @protected
13396 protected function _dounderline($x, $y, $txt) {
13397 $w = $this->GetStringWidth($txt);
13398 return $this->_dounderlinew($x, $y, $w);
13402 * Underline for rectangular text area.
13403 * @param $x (int) X coordinate
13404 * @param $y (int) Y coordinate
13405 * @param $w (int) width to underline
13406 * @protected
13407 * @since 4.8.008 (2009-09-29)
13409 protected function _dounderlinew($x, $y, $w) {
13410 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
13411 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
13415 * Line through text.
13416 * @param $x (int) X coordinate
13417 * @param $y (int) Y coordinate
13418 * @param $txt (string) text to linethrough
13419 * @protected
13421 protected function _dolinethrough($x, $y, $txt) {
13422 $w = $this->GetStringWidth($txt);
13423 return $this->_dolinethroughw($x, $y, $w);
13427 * Line through for rectangular text area.
13428 * @param $x (int) X coordinate
13429 * @param $y (int) Y coordinate
13430 * @param $w (int) line length (width)
13431 * @protected
13432 * @since 4.9.008 (2009-09-29)
13434 protected function _dolinethroughw($x, $y, $w) {
13435 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
13436 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
13440 * Overline text.
13441 * @param $x (int) X coordinate
13442 * @param $y (int) Y coordinate
13443 * @param $txt (string) text to overline
13444 * @protected
13445 * @since 4.9.015 (2010-04-19)
13447 protected function _dooverline($x, $y, $txt) {
13448 $w = $this->GetStringWidth($txt);
13449 return $this->_dooverlinew($x, $y, $w);
13453 * Overline for rectangular text area.
13454 * @param $x (int) X coordinate
13455 * @param $y (int) Y coordinate
13456 * @param $w (int) width to overline
13457 * @protected
13458 * @since 4.9.015 (2010-04-19)
13460 protected function _dooverlinew($x, $y, $w) {
13461 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
13462 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
13467 * Read a 4-byte (32 bit) integer from file.
13468 * @param $f (string) file name.
13469 * @return 4-byte integer
13470 * @protected
13472 protected function _freadint($f) {
13473 $a = unpack('Ni', fread($f, 4));
13474 return $a['i'];
13478 * Add "\" before "\", "(" and ")"
13479 * @param $s (string) string to escape.
13480 * @return string escaped string.
13481 * @protected
13483 protected function _escape($s) {
13484 // the chr(13) substitution fixes the Bugs item #1421290.
13485 return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
13489 * Format a data string for meta information
13490 * @param $s (string) data string to escape.
13491 * @param $n (int) object ID
13492 * @return string escaped string.
13493 * @protected
13495 protected function _datastring($s, $n=0) {
13496 if ($n == 0) {
13497 $n = $this->n;
13499 $s = $this->_encrypt_data($n, $s);
13500 return '('. $this->_escape($s).')';
13504 * Set the document creation timestamp
13505 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
13506 * @public
13507 * @since 5.9.152 (2012-03-23)
13509 public function setDocCreationTimestamp($time) {
13510 if (is_string($time)) {
13511 $time = getTimestamp($time);
13513 $this->doc_creation_timestamp = intval($time);
13517 * Set the document modification timestamp
13518 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
13519 * @public
13520 * @since 5.9.152 (2012-03-23)
13522 public function setDocModificationTimestamp($time) {
13523 if (is_string($time)) {
13524 $time = getTimestamp($time);
13526 $this->doc_modification_timestamp = intval($time);
13530 * Returns document creation timestamp in seconds.
13531 * @return (int) Creation timestamp in seconds.
13532 * @public
13533 * @since 5.9.152 (2012-03-23)
13535 public function getDocCreationTimestamp() {
13536 return $this->doc_creation_timestamp;
13540 * Returns document modification timestamp in seconds.
13541 * @return (int) Modfication timestamp in seconds.
13542 * @public
13543 * @since 5.9.152 (2012-03-23)
13545 public function getDocModificationTimestamp() {
13546 return $this->doc_modification_timestamp;
13550 * Returns timestamp in seconds from formatted date-time.
13551 * @param $date (string) Formatted date-time.
13552 * @return int seconds.
13553 * @public
13554 * @since 5.9.152 (2012-03-23)
13556 public function getTimestamp($date) {
13557 if (($date[0] == 'D') AND ($date[1] == ':')) {
13558 // remove date prefix if present
13559 $date = substr($date, 2);
13561 return strtotime($date);
13565 * Returns a formatted date-time.
13566 * @param $time (int) Time in seconds.
13567 * @return string escaped date string.
13568 * @public
13569 * @since 5.9.152 (2012-03-23)
13571 public function getFormattedDate($time) {
13572 return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0).'\'';
13576 * Returns a formatted date for meta information
13577 * @param $n (int) Object ID.
13578 * @param $timestamp (int) Timestamp to convert.
13579 * @return string escaped date string.
13580 * @protected
13581 * @since 4.6.028 (2009-08-25)
13583 protected function _datestring($n=0, $timestamp=0) {
13584 if ((empty($timestamp)) OR ($timestamp < 0)) {
13585 $timestamp = $this->doc_creation_timestamp;
13587 return $this->_datastring('D:'.$this->getFormattedDate($timestamp), $n);
13591 * Format a text string for meta information
13592 * @param $s (string) string to escape.
13593 * @param $n (int) object ID
13594 * @return string escaped string.
13595 * @protected
13597 protected function _textstring($s, $n=0) {
13598 if ($this->isunicode) {
13599 //Convert string to UTF-16BE
13600 $s = $this->UTF8ToUTF16BE($s, true);
13602 return $this->_datastring($s, $n);
13606 * THIS METHOD IS DEPRECATED
13607 * Format a text string
13608 * @param $s (string) string to escape.
13609 * @return string escaped string.
13610 * @protected
13611 * @deprecated
13613 protected function _escapetext($s) {
13614 if ($this->isunicode) {
13615 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
13616 $s = $this->UTF8ToLatin1($s);
13617 } else {
13618 //Convert string to UTF-16BE and reverse RTL language
13619 $s = $this->utf8StrRev($s, false, $this->tmprtl);
13622 return $this->_escape($s);
13626 * Escape some special characters (&lt; &gt; &amp;) for XML output.
13627 * @param $str (string) Input string to convert.
13628 * @return converted string
13629 * @since 5.9.121 (2011-09-28)
13630 * @protected
13632 protected function _escapeXML($str) {
13633 $replaceTable = array("\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;');
13634 $str = strtr($str, $replaceTable);
13635 return $str;
13639 * get raw output stream.
13640 * @param $s (string) string to output.
13641 * @param $n (int) object reference for encryption mode
13642 * @protected
13643 * @author Nicola Asuni
13644 * @since 5.5.000 (2010-06-22)
13646 protected function _getrawstream($s, $n=0) {
13647 if ($n <= 0) {
13648 // default to current object
13649 $n = $this->n;
13651 return $this->_encrypt_data($n, $s);
13655 * Format output stream (DEPRECATED).
13656 * @param $s (string) string to output.
13657 * @param $n (int) object reference for encryption mode
13658 * @protected
13659 * @deprecated
13661 protected function _getstream($s, $n=0) {
13662 return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
13666 * Output a stream (DEPRECATED).
13667 * @param $s (string) string to output.
13668 * @param $n (int) object reference for encryption mode
13669 * @protected
13670 * @deprecated
13672 protected function _putstream($s, $n=0) {
13673 $this->_out($this->_getstream($s, $n));
13677 * Output a string to the document.
13678 * @param $s (string) string to output.
13679 * @protected
13681 protected function _out($s) {
13682 if ($this->state == 2) {
13683 if ($this->inxobj) {
13684 // we are inside an XObject template
13685 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
13686 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
13687 // puts data before page footer
13688 $pagebuff = $this->getPageBuffer($this->page);
13689 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
13690 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
13691 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
13692 // update footer position
13693 $this->footerpos[$this->page] += strlen($s."\n");
13694 } else {
13695 // set page data
13696 $this->setPageBuffer($this->page, $s."\n", true);
13698 } elseif ($this->state > 0) {
13699 // set general data
13700 $this->setBuffer($s."\n");
13705 * Converts UTF-8 strings to codepoints array.<br>
13706 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
13707 * Based on: http://www.faqs.org/rfcs/rfc3629.html
13708 * <pre>
13709 * Char. number range | UTF-8 octet sequence
13710 * (hexadecimal) | (binary)
13711 * --------------------+-----------------------------------------------
13712 * 0000 0000-0000 007F | 0xxxxxxx
13713 * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
13714 * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
13715 * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
13716 * ---------------------------------------------------------------------
13718 * ABFN notation:
13719 * ---------------------------------------------------------------------
13720 * UTF8-octets = *( UTF8-char )
13721 * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
13722 * UTF8-1 = %x00-7F
13723 * UTF8-2 = %xC2-DF UTF8-tail
13725 * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
13726 * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
13727 * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
13728 * %xF4 %x80-8F 2( UTF8-tail )
13729 * UTF8-tail = %x80-BF
13730 * ---------------------------------------------------------------------
13731 * </pre>
13732 * @param $str (string) string to process.
13733 * @return array containing codepoints (UTF-8 characters values)
13734 * @protected
13735 * @author Nicola Asuni
13736 * @since 1.53.0.TC005 (2005-01-05)
13738 protected function UTF8StringToArray($str) {
13739 // build a unique string key
13740 $strkey = md5($str);
13741 if (isset($this->cache_UTF8StringToArray[$strkey])) {
13742 // return cached value
13743 $chrarray = $this->cache_UTF8StringToArray[$strkey]['s'];
13744 if (!isset($this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']])) {
13745 if ($this->isunicode) {
13746 foreach ($chrarray as $chr) {
13747 // store this char for font subsetting
13748 $this->CurrentFont['subsetchars'][$chr] = true;
13750 // update font subsetchars
13751 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
13753 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13755 return $chrarray;
13757 // check cache size
13758 if ($this->cache_size_UTF8StringToArray >= $this->cache_maxsize_UTF8StringToArray) {
13759 // remove first element
13760 array_shift($this->cache_UTF8StringToArray);
13762 // new cache array for selected string
13763 $this->cache_UTF8StringToArray[$strkey] = array('s' => array(), 'f' => array());
13764 ++$this->cache_size_UTF8StringToArray;
13765 if (!$this->isunicode) {
13766 // split string into array of equivalent codes
13767 $strarr = array();
13768 $strlen = strlen($str);
13769 for ($i=0; $i < $strlen; ++$i) {
13770 $strarr[] = ord($str[$i]);
13772 // insert new value on cache
13773 $this->cache_UTF8StringToArray[$strkey]['s'] = $strarr;
13774 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13775 return $strarr;
13777 $unichar = -1; // last unicode char
13778 $unicode = array(); // array containing unicode values
13779 $bytes = array(); // array containing single character byte sequences
13780 $numbytes = 1; // number of octetc needed to represent the UTF-8 character
13781 $str .= ''; // force $str to be a string
13782 $length = strlen($str);
13783 for ($i = 0; $i < $length; ++$i) {
13784 $char = ord($str[$i]); // get one string character at time
13785 if (count($bytes) == 0) { // get starting octect
13786 if ($char <= 0x7F) {
13787 $unichar = $char; // use the character "as is" because is ASCII
13788 $numbytes = 1;
13789 } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
13790 $bytes[] = ($char - 0xC0) << 0x06;
13791 $numbytes = 2;
13792 } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
13793 $bytes[] = ($char - 0xE0) << 0x0C;
13794 $numbytes = 3;
13795 } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
13796 $bytes[] = ($char - 0xF0) << 0x12;
13797 $numbytes = 4;
13798 } else {
13799 // use replacement character for other invalid sequences
13800 $unichar = 0xFFFD;
13801 $bytes = array();
13802 $numbytes = 1;
13804 } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
13805 $bytes[] = $char - 0x80;
13806 if (count($bytes) == $numbytes) {
13807 // compose UTF-8 bytes to a single unicode value
13808 $char = $bytes[0];
13809 for ($j = 1; $j < $numbytes; ++$j) {
13810 $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
13812 if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
13813 /* The definition of UTF-8 prohibits encoding character numbers between
13814 U+D800 and U+DFFF, which are reserved for use with the UTF-16
13815 encoding form (as surrogate pairs) and do not directly represent
13816 characters. */
13817 $unichar = 0xFFFD; // use replacement character
13818 } else {
13819 $unichar = $char; // add char to array
13821 // reset data for next char
13822 $bytes = array();
13823 $numbytes = 1;
13825 } else {
13826 // use replacement character for other invalid sequences
13827 $unichar = 0xFFFD;
13828 $bytes = array();
13829 $numbytes = 1;
13831 if ($unichar >= 0) {
13832 // insert unicode value into array
13833 $unicode[] = $unichar;
13834 // store this char for font subsetting
13835 $this->CurrentFont['subsetchars'][$unichar] = true;
13836 $unichar = -1;
13839 // update font subsetchars
13840 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
13841 // insert new value on cache
13842 $this->cache_UTF8StringToArray[$strkey]['s'] = $unicode;
13843 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13844 return $unicode;
13848 * Converts UTF-8 strings to UTF16-BE.<br>
13849 * @param $str (string) string to process.
13850 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
13851 * @return string
13852 * @author Nicola Asuni
13853 * @since 1.53.0.TC005 (2005-01-05)
13854 * @see UTF8StringToArray(), arrUTF8ToUTF16BE()
13855 * @protected
13857 protected function UTF8ToUTF16BE($str, $setbom=false) {
13858 if (!$this->isunicode) {
13859 return $str; // string is not in unicode
13861 $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
13862 return $this->arrUTF8ToUTF16BE($unicode, $setbom);
13866 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
13867 * @param $str (string) string to process.
13868 * @return string
13869 * @author Andrew Whitehead, Nicola Asuni
13870 * @protected
13871 * @since 3.2.000 (2008-06-23)
13873 protected function UTF8ToLatin1($str) {
13874 if (!$this->isunicode) {
13875 return $str; // string is not in unicode
13877 $outstr = ''; // string to be returned
13878 $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
13879 foreach ($unicode as $char) {
13880 if ($char < 256) {
13881 $outstr .= chr($char);
13882 } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
13883 // map from UTF-8
13884 $outstr .= chr($this->unicode->uni_utf8tolatin[$char]);
13885 } elseif ($char == 0xFFFD) {
13886 // skip
13887 } else {
13888 $outstr .= '?';
13891 return $outstr;
13895 * Converts UTF-8 characters array to array of Latin1 characters<br>
13896 * @param $unicode (array) array containing UTF-8 unicode values
13897 * @return array
13898 * @author Nicola Asuni
13899 * @protected
13900 * @since 4.8.023 (2010-01-15)
13902 protected function UTF8ArrToLatin1($unicode) {
13903 if ((!$this->isunicode) OR $this->isUnicodeFont()) {
13904 return $unicode;
13906 $outarr = array(); // array to be returned
13907 foreach ($unicode as $char) {
13908 if ($char < 256) {
13909 $outarr[] = $char;
13910 } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
13911 // map from UTF-8
13912 $outarr[] = $this->unicode->uni_utf8tolatin[$char];
13913 } elseif ($char == 0xFFFD) {
13914 // skip
13915 } else {
13916 $outarr[] = 63; // '?' character
13919 return $outarr;
13923 * Converts array of UTF-8 characters to UTF16-BE string.<br>
13924 * Based on: http://www.faqs.org/rfcs/rfc2781.html
13925 * <pre>
13926 * Encoding UTF-16:
13928 * Encoding of a single character from an ISO 10646 character value to
13929 * UTF-16 proceeds as follows. Let U be the character number, no greater
13930 * than 0x10FFFF.
13932 * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
13933 * terminate.
13935 * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
13936 * U' must be less than or equal to 0xFFFFF. That is, U' can be
13937 * represented in 20 bits.
13939 * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
13940 * 0xDC00, respectively. These integers each have 10 bits free to
13941 * encode the character value, for a total of 20 bits.
13943 * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
13944 * bits of W1 and the 10 low-order bits of U' to the 10 low-order
13945 * bits of W2. Terminate.
13947 * Graphically, steps 2 through 4 look like:
13948 * U' = yyyyyyyyyyxxxxxxxxxx
13949 * W1 = 110110yyyyyyyyyy
13950 * W2 = 110111xxxxxxxxxx
13951 * </pre>
13952 * @param $unicode (array) array containing UTF-8 unicode values
13953 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
13954 * @return string
13955 * @protected
13956 * @author Nicola Asuni
13957 * @since 2.1.000 (2008-01-08)
13958 * @see UTF8ToUTF16BE()
13960 protected function arrUTF8ToUTF16BE($unicode, $setbom=false) {
13961 $outstr = ''; // string to be returned
13962 if ($setbom) {
13963 $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
13965 foreach ($unicode as $char) {
13966 if ($char == 0x200b) {
13967 // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
13968 } elseif ($char == 0xFFFD) {
13969 $outstr .= "\xFF\xFD"; // replacement character
13970 } elseif ($char < 0x10000) {
13971 $outstr .= chr($char >> 0x08);
13972 $outstr .= chr($char & 0xFF);
13973 } else {
13974 $char -= 0x10000;
13975 $w1 = 0xD800 | ($char >> 0x0a);
13976 $w2 = 0xDC00 | ($char & 0x3FF);
13977 $outstr .= chr($w1 >> 0x08);
13978 $outstr .= chr($w1 & 0xFF);
13979 $outstr .= chr($w2 >> 0x08);
13980 $outstr .= chr($w2 & 0xFF);
13983 return $outstr;
13985 // ====================================================
13988 * Set header font.
13989 * @param $font (array) font
13990 * @public
13991 * @since 1.1
13993 public function setHeaderFont($font) {
13994 $this->header_font = $font;
13998 * Get header font.
13999 * @return array()
14000 * @public
14001 * @since 4.0.012 (2008-07-24)
14003 public function getHeaderFont() {
14004 return $this->header_font;
14008 * Set footer font.
14009 * @param $font (array) font
14010 * @public
14011 * @since 1.1
14013 public function setFooterFont($font) {
14014 $this->footer_font = $font;
14018 * Get Footer font.
14019 * @return array()
14020 * @public
14021 * @since 4.0.012 (2008-07-24)
14023 public function getFooterFont() {
14024 return $this->footer_font;
14028 * Set language array.
14029 * @param $language (array)
14030 * @public
14031 * @since 1.1
14033 public function setLanguageArray($language) {
14034 $this->l = $language;
14035 if (isset($this->l['a_meta_dir'])) {
14036 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
14037 } else {
14038 $this->rtl = false;
14043 * Returns the PDF data.
14044 * @public
14046 public function getPDFData() {
14047 if ($this->state < 3) {
14048 $this->Close();
14050 return $this->buffer;
14054 * Output anchor link.
14055 * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
14056 * @param $name (string) link name
14057 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
14058 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
14059 * @param $color (array) array of RGB text color
14060 * @param $style (string) font style (U, D, B, I)
14061 * @param $firstblock (boolean) if true the string is the starting of a line.
14062 * @return the number of cells used or the remaining text if $firstline = true;
14063 * @public
14065 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
14066 if (!$this->empty_string($url) AND ($url[0] == '#') AND is_numeric($url[1])) {
14067 // convert url to internal link
14068 $lnkdata = explode(',', $url);
14069 if (isset($lnkdata[0])) {
14070 $page = intval(substr($lnkdata[0], 1));
14071 if (empty($page) OR ($page <= 0)) {
14072 $page = $this->page;
14074 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
14075 $lnky = floatval($lnkdata[1]);
14076 } else {
14077 $lnky = 0;
14079 $url = $this->AddLink();
14080 $this->SetLink($url, $lnky, $page);
14083 // store current settings
14084 $prevcolor = $this->fgcolor;
14085 $prevstyle = $this->FontStyle;
14086 if (empty($color)) {
14087 $this->SetTextColorArray($this->htmlLinkColorArray);
14088 } else {
14089 $this->SetTextColorArray($color);
14091 if ($style == -1) {
14092 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
14093 } else {
14094 $this->SetFont('', $this->FontStyle.$style);
14096 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
14097 // restore settings
14098 $this->SetFont('', $prevstyle);
14099 $this->SetTextColorArray($prevcolor);
14100 return $ret;
14104 * Returns an array (RGB or CMYK) from an html color name, or a six-digit (i.e. #3FE5AA), or three-digit (i.e. #7FF) hexadecimal color, or a javascript color array, or javascript color name.
14105 * @param $hcolor (string) HTML color.
14106 * @param $defcol (array) Color to return in case of error.
14107 * @return array RGB or CMYK color, or false in case of error.
14108 * @public
14110 public function convertHTMLColorToDec($hcolor='#FFFFFF', $defcol=array('R'=>128,'G'=>128,'B'=>128)) {
14111 $color = preg_replace('/[\s]*/', '', $hcolor); // remove extra spaces
14112 $color = strtolower($color);
14113 // check for javascript color array syntax
14114 if (strpos($color, '[') !== false) {
14115 if (preg_match('/[\[][\"\'](t|g|rgb|cmyk)[\"\'][\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\]]/', $color, $m) > 0) {
14116 $returncolor = array();
14117 switch ($m[1]) {
14118 case 'cmyk': {
14119 // RGB
14120 $returncolor['C'] = max(0, min(100, (floatval($m[2]) * 100)));
14121 $returncolor['M'] = max(0, min(100, (floatval($m[3]) * 100)));
14122 $returncolor['Y'] = max(0, min(100, (floatval($m[4]) * 100)));
14123 $returncolor['K'] = max(0, min(100, (floatval($m[5]) * 100)));
14124 break;
14126 case 'rgb': {
14127 // RGB
14128 $returncolor['R'] = max(0, min(255, (floatval($m[2]) * 255)));
14129 $returncolor['G'] = max(0, min(255, (floatval($m[3]) * 255)));
14130 $returncolor['B'] = max(0, min(255, (floatval($m[4]) * 255)));
14131 break;
14133 case 'g': {
14134 // grayscale
14135 $returncolor['G'] = max(0, min(255, (floatval($m[2]) * 255)));
14136 break;
14138 case 't':
14139 default: {
14140 // transparent (empty array)
14141 break;
14144 return $returncolor;
14146 } elseif (($dotpos = strpos($color, '.')) !== false) {
14147 // remove class parent (i.e.: color.red)
14148 $color = substr($color, ($dotpos + 1));
14149 if ($color == 'transparent') {
14150 // transparent (empty array)
14151 return array();
14154 if (strlen($color) == 0) {
14155 return $defcol;
14157 // RGB ARRAY
14158 if (substr($color, 0, 3) == 'rgb') {
14159 $codes = substr($color, 4);
14160 $codes = str_replace(')', '', $codes);
14161 $returncolor = explode(',', $codes);
14162 foreach ($returncolor as $key => $val) {
14163 if (strpos($val, '%') > 0) {
14164 // percentage
14165 $returncolor[$key] = (255 * intval($val) / 100);
14166 } else {
14167 $returncolor[$key] = intval($val);
14169 // normalize value
14170 $returncolor[$key] = max(0, min(255, $returncolor[$key]));
14172 return $returncolor;
14174 // CMYK ARRAY
14175 if (substr($color, 0, 4) == 'cmyk') {
14176 $codes = substr($color, 5);
14177 $codes = str_replace(')', '', $codes);
14178 $returncolor = explode(',', $codes);
14179 foreach ($returncolor as $key => $val) {
14180 if (strpos($val, '%') !== false) {
14181 // percentage
14182 $returncolor[$key] = (100 * intval($val) / 100);
14183 } else {
14184 $returncolor[$key] = intval($val);
14186 // normalize value
14187 $returncolor[$key] = max(0, min(100, $returncolor[$key]));
14189 return $returncolor;
14191 if ($color{0} != '#') {
14192 // COLOR NAME
14193 if (isset($this->webcolor[$color])) {
14194 // web color
14195 $color_code = $this->webcolor[$color];
14196 } else {
14197 // spot color
14198 $returncolor = $this->getSpotColor($color);
14199 if ($returncolor === false) {
14200 $returncolor = $defcol;
14202 return $returncolor;
14204 } else {
14205 $color_code = substr($color, 1);
14207 // HEXADECIMAL REPRESENTATION
14208 switch (strlen($color_code)) {
14209 case 3: {
14210 // 3-digit RGB hexadecimal representation
14211 $r = substr($color_code, 0, 1);
14212 $g = substr($color_code, 1, 1);
14213 $b = substr($color_code, 2, 1);
14214 $returncolor = array();
14215 $returncolor['R'] = max(0, min(255, hexdec($r.$r)));
14216 $returncolor['G'] = max(0, min(255, hexdec($g.$g)));
14217 $returncolor['B'] = max(0, min(255, hexdec($b.$b)));
14218 break;
14220 case 6: {
14221 // 6-digit RGB hexadecimal representation
14222 $returncolor = array();
14223 $returncolor['R'] = max(0, min(255, hexdec(substr($color_code, 0, 2))));
14224 $returncolor['G'] = max(0, min(255, hexdec(substr($color_code, 2, 2))));
14225 $returncolor['B'] = max(0, min(255, hexdec(substr($color_code, 4, 2))));
14226 break;
14228 case 8: {
14229 // 8-digit CMYK hexadecimal representation
14230 $returncolor = array();
14231 $returncolor['C'] = max(0, min(100, round(hexdec(substr($color_code, 0, 2)) / 2.55)));
14232 $returncolor['M'] = max(0, min(100, round(hexdec(substr($color_code, 2, 2)) / 2.55)));
14233 $returncolor['Y'] = max(0, min(100, round(hexdec(substr($color_code, 4, 2)) / 2.55)));
14234 $returncolor['K'] = max(0, min(100, round(hexdec(substr($color_code, 6, 2)) / 2.55)));
14235 break;
14237 default: {
14238 $returncolor = $defcol;
14239 break;
14242 return $returncolor;
14246 * Converts pixels to User's Units.
14247 * @param $px (int) pixels
14248 * @return float value in user's unit
14249 * @public
14250 * @see setImageScale(), getImageScale()
14252 public function pixelsToUnits($px) {
14253 return ($px / ($this->imgscale * $this->k));
14257 * Reverse function for htmlentities.
14258 * Convert entities in UTF-8.
14259 * @param $text_to_convert (string) Text to convert.
14260 * @return string converted text string
14261 * @public
14263 public function unhtmlentities($text_to_convert) {
14264 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
14267 // ENCRYPTION METHODS ----------------------------------
14270 * Returns a string containing random data to be used as a seed for encryption methods.
14271 * @param $seed (string) starting seed value
14272 * @return string containing random data
14273 * @author Nicola Asuni
14274 * @since 5.9.006 (2010-10-19)
14275 * @protected
14277 protected function getRandomSeed($seed='') {
14278 $seed .= microtime();
14279 if (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
14280 // this is not used on windows systems because it is very slow for a know bug
14281 $seed .= openssl_random_pseudo_bytes(512);
14282 } else {
14283 for ($i = 0; $i < 23; ++$i) {
14284 $seed .= uniqid('', true);
14287 $seed .= uniqid('', true);
14288 $seed .= rand();
14289 $seed .= getmypid();
14290 $seed .= __FILE__;
14291 $seed .= $this->bufferlen;
14292 if (isset($_SERVER['REMOTE_ADDR'])) {
14293 $seed .= $_SERVER['REMOTE_ADDR'];
14295 if (isset($_SERVER['HTTP_USER_AGENT'])) {
14296 $seed .= $_SERVER['HTTP_USER_AGENT'];
14298 if (isset($_SERVER['HTTP_ACCEPT'])) {
14299 $seed .= $_SERVER['HTTP_ACCEPT'];
14301 if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
14302 $seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
14304 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
14305 $seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
14307 if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
14308 $seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
14310 $seed .= rand();
14311 $seed .= uniqid('', true);
14312 $seed .= microtime();
14313 return $seed;
14317 * Compute encryption key depending on object number where the encrypted data is stored.
14318 * This is used for all strings and streams without crypt filter specifier.
14319 * @param $n (int) object number
14320 * @return int object key
14321 * @protected
14322 * @author Nicola Asuni
14323 * @since 2.0.000 (2008-01-02)
14325 protected function _objectkey($n) {
14326 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
14327 if ($this->encryptdata['mode'] == 2) { // AES-128
14328 // AES padding
14329 $objkey .= "\x73\x41\x6C\x54"; // sAlT
14331 $objkey = substr($this->_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
14332 $objkey = substr($objkey, 0, 16);
14333 return $objkey;
14337 * Encrypt the input string.
14338 * @param $n (int) object number
14339 * @param $s (string) data string to encrypt
14340 * @return encrypted string
14341 * @protected
14342 * @author Nicola Asuni
14343 * @since 5.0.005 (2010-05-11)
14345 protected function _encrypt_data($n, $s) {
14346 if (!$this->encrypted) {
14347 return $s;
14349 switch ($this->encryptdata['mode']) {
14350 case 0: // RC4-40
14351 case 1: { // RC4-128
14352 $s = $this->_RC4($this->_objectkey($n), $s);
14353 break;
14355 case 2: { // AES-128
14356 $s = $this->_AES($this->_objectkey($n), $s);
14357 break;
14359 case 3: { // AES-256
14360 $s = $this->_AES($this->encryptdata['key'], $s);
14361 break;
14364 return $s;
14368 * Put encryption on PDF document.
14369 * @protected
14370 * @author Nicola Asuni
14371 * @since 2.0.000 (2008-01-02)
14373 protected function _putencryption() {
14374 if (!$this->encrypted) {
14375 return;
14377 $this->encryptdata['objid'] = $this->_newobj();
14378 $out = '<<';
14379 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
14380 $this->encryptdata['Filter'] = 'Standard';
14382 $out .= ' /Filter /'.$this->encryptdata['Filter'];
14383 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
14384 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
14386 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
14387 $this->encryptdata['V'] = 1;
14389 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
14390 $out .= ' /V '.$this->encryptdata['V'];
14391 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
14392 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
14393 $out .= ' /Length '.$this->encryptdata['Length'];
14394 } else {
14395 $out .= ' /Length 40';
14397 if ($this->encryptdata['V'] >= 4) {
14398 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
14399 $this->encryptdata['StmF'] = 'Identity';
14401 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
14402 // The name of the crypt filter that shall be used when decrypting all strings in the document.
14403 $this->encryptdata['StrF'] = 'Identity';
14405 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
14406 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
14407 $out .= ' /CF <<';
14408 $out .= ' /'.$this->encryptdata['StmF'].' <<';
14409 $out .= ' /Type /CryptFilter';
14410 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
14411 // The method used
14412 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
14413 if ($this->encryptdata['pubkey']) {
14414 $out .= ' /Recipients [';
14415 foreach ($this->encryptdata['Recipients'] as $rec) {
14416 $out .= ' <'.$rec.'>';
14418 $out .= ' ]';
14419 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
14420 $out .= ' /EncryptMetadata false';
14421 } else {
14422 $out .= ' /EncryptMetadata true';
14425 } else {
14426 $out .= ' /CFM /None';
14428 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
14429 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
14430 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
14431 } else {
14432 $out .= ' /AuthEvent /DocOpen';
14434 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
14435 // The bit length of the encryption key.
14436 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
14438 $out .= ' >> >>';
14440 // The name of the crypt filter that shall be used by default when decrypting streams.
14441 $out .= ' /StmF /'.$this->encryptdata['StmF'];
14442 // The name of the crypt filter that shall be used when decrypting all strings in the document.
14443 $out .= ' /StrF /'.$this->encryptdata['StrF'];
14444 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
14445 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
14446 $out .= ' /EFF /'.$this->encryptdata[''];
14449 // Additional encryption dictionary entries for the standard security handler
14450 if ($this->encryptdata['pubkey']) {
14451 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
14452 $out .= ' /Recipients [';
14453 foreach ($this->encryptdata['Recipients'] as $rec) {
14454 $out .= ' <'.$rec.'>';
14456 $out .= ' ]';
14458 } else {
14459 $out .= ' /R';
14460 if ($this->encryptdata['V'] == 5) { // AES-256
14461 $out .= ' 5';
14462 $out .= ' /OE ('.$this->_escape($this->encryptdata['OE']).')';
14463 $out .= ' /UE ('.$this->_escape($this->encryptdata['UE']).')';
14464 $out .= ' /Perms ('.$this->_escape($this->encryptdata['perms']).')';
14465 } elseif ($this->encryptdata['V'] == 4) { // AES-128
14466 $out .= ' 4';
14467 } elseif ($this->encryptdata['V'] < 2) { // RC-40
14468 $out .= ' 2';
14469 } else { // RC-128
14470 $out .= ' 3';
14472 $out .= ' /O ('.$this->_escape($this->encryptdata['O']).')';
14473 $out .= ' /U ('.$this->_escape($this->encryptdata['U']).')';
14474 $out .= ' /P '.$this->encryptdata['P'];
14475 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
14476 $out .= ' /EncryptMetadata false';
14477 } else {
14478 $out .= ' /EncryptMetadata true';
14481 $out .= ' >>';
14482 $out .= "\n".'endobj';
14483 $this->_out($out);
14487 * Returns the input text encrypted using RC4 algorithm and the specified key.
14488 * RC4 is the standard encryption algorithm used in PDF format
14489 * @param $key (string) encryption key
14490 * @param $text (String) input text to be encrypted
14491 * @return String encrypted text
14492 * @protected
14493 * @since 2.0.000 (2008-01-02)
14494 * @author Klemen Vodopivec, Nicola Asuni
14496 protected function _RC4($key, $text) {
14497 if (function_exists('mcrypt_decrypt') AND ($out = @mcrypt_decrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
14498 // try to use mcrypt function if exist
14499 return $out;
14501 if ($this->last_enc_key != $key) {
14502 $k = str_repeat($key, ((256 / strlen($key)) + 1));
14503 $rc4 = range(0, 255);
14504 $j = 0;
14505 for ($i = 0; $i < 256; ++$i) {
14506 $t = $rc4[$i];
14507 $j = ($j + $t + ord($k[$i])) % 256;
14508 $rc4[$i] = $rc4[$j];
14509 $rc4[$j] = $t;
14511 $this->last_enc_key = $key;
14512 $this->last_enc_key_c = $rc4;
14513 } else {
14514 $rc4 = $this->last_enc_key_c;
14516 $len = strlen($text);
14517 $a = 0;
14518 $b = 0;
14519 $out = '';
14520 for ($i = 0; $i < $len; ++$i) {
14521 $a = ($a + 1) % 256;
14522 $t = $rc4[$a];
14523 $b = ($b + $t) % 256;
14524 $rc4[$a] = $rc4[$b];
14525 $rc4[$b] = $t;
14526 $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
14527 $out .= chr(ord($text[$i]) ^ $k);
14529 return $out;
14533 * Returns the input text exrypted using AES algorithm and the specified key.
14534 * This method requires mcrypt.
14535 * @param $key (string) encryption key
14536 * @param $text (String) input text to be encrypted
14537 * @return String encrypted text
14538 * @protected
14539 * @author Nicola Asuni
14540 * @since 5.0.005 (2010-05-11)
14542 protected function _AES($key, $text) {
14543 // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
14544 $padding = 16 - (strlen($text) % 16);
14545 $text .= str_repeat(chr($padding), $padding);
14546 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
14547 $text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
14548 $text = $iv.$text;
14549 return $text;
14553 * Encrypts a string using MD5 and returns it's value as a binary string.
14554 * @param $str (string) input string
14555 * @return String MD5 encrypted binary string
14556 * @protected
14557 * @since 2.0.000 (2008-01-02)
14558 * @author Klemen Vodopivec
14560 protected function _md5_16($str) {
14561 return pack('H*', md5($str));
14565 * Compute U value (used for encryption)
14566 * @return string U value
14567 * @protected
14568 * @since 2.0.000 (2008-01-02)
14569 * @author Nicola Asuni
14571 protected function _Uvalue() {
14572 if ($this->encryptdata['mode'] == 0) { // RC4-40
14573 return $this->_RC4($this->encryptdata['key'], $this->enc_padding);
14574 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
14575 $tmp = $this->_md5_16($this->enc_padding.$this->encryptdata['fileid']);
14576 $enc = $this->_RC4($this->encryptdata['key'], $tmp);
14577 $len = strlen($tmp);
14578 for ($i = 1; $i <= 19; ++$i) {
14579 $ek = '';
14580 for ($j = 0; $j < $len; ++$j) {
14581 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
14583 $enc = $this->_RC4($ek, $enc);
14585 $enc .= str_repeat("\x00", 16);
14586 return substr($enc, 0, 32);
14587 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
14588 $seed = $this->_md5_16($this->getRandomSeed());
14589 // User Validation Salt
14590 $this->encryptdata['UVS'] = substr($seed, 0, 8);
14591 // User Key Salt
14592 $this->encryptdata['UKS'] = substr($seed, 8, 16);
14593 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
14598 * Compute UE value (used for encryption)
14599 * @return string UE value
14600 * @protected
14601 * @since 5.9.006 (2010-10-19)
14602 * @author Nicola Asuni
14604 protected function _UEvalue() {
14605 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
14606 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
14607 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
14611 * Compute O value (used for encryption)
14612 * @return string O value
14613 * @protected
14614 * @since 2.0.000 (2008-01-02)
14615 * @author Nicola Asuni
14617 protected function _Ovalue() {
14618 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
14619 $tmp = $this->_md5_16($this->encryptdata['owner_password']);
14620 if ($this->encryptdata['mode'] > 0) {
14621 for ($i = 0; $i < 50; ++$i) {
14622 $tmp = $this->_md5_16($tmp);
14625 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
14626 $enc = $this->_RC4($owner_key, $this->encryptdata['user_password']);
14627 if ($this->encryptdata['mode'] > 0) {
14628 $len = strlen($owner_key);
14629 for ($i = 1; $i <= 19; ++$i) {
14630 $ek = '';
14631 for ($j = 0; $j < $len; ++$j) {
14632 $ek .= chr(ord($owner_key[$j]) ^ $i);
14634 $enc = $this->_RC4($ek, $enc);
14637 return $enc;
14638 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
14639 $seed = $this->_md5_16($this->getRandomSeed());
14640 // Owner Validation Salt
14641 $this->encryptdata['OVS'] = substr($seed, 0, 8);
14642 // Owner Key Salt
14643 $this->encryptdata['OKS'] = substr($seed, 8, 16);
14644 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
14649 * Compute OE value (used for encryption)
14650 * @return string OE value
14651 * @protected
14652 * @since 5.9.006 (2010-10-19)
14653 * @author Nicola Asuni
14655 protected function _OEvalue() {
14656 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
14657 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
14658 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
14662 * Convert password for AES-256 encryption mode
14663 * @param $password (string) password
14664 * @return string password
14665 * @protected
14666 * @since 5.9.006 (2010-10-19)
14667 * @author Nicola Asuni
14669 protected function _fixAES256Password($password) {
14670 $psw = ''; // password to be returned
14671 $psw_array = $this->utf8Bidi($this->UTF8StringToArray($password), $password, $this->rtl);
14672 foreach ($psw_array as $c) {
14673 $psw .= $this->unichr($c);
14675 return substr($psw, 0, 127);
14679 * Compute encryption key
14680 * @protected
14681 * @since 2.0.000 (2008-01-02)
14682 * @author Nicola Asuni
14684 protected function _generateencryptionkey() {
14685 $keybytelen = ($this->encryptdata['Length'] / 8);
14686 if (!$this->encryptdata['pubkey']) { // standard mode
14687 if ($this->encryptdata['mode'] == 3) { // AES-256
14688 // generate 256 bit random key
14689 $this->encryptdata['key'] = substr(hash('sha256', $this->getRandomSeed(), true), 0, $keybytelen);
14690 // truncate passwords
14691 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
14692 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
14693 // Compute U value
14694 $this->encryptdata['U'] = $this->_Uvalue();
14695 // Compute UE value
14696 $this->encryptdata['UE'] = $this->_UEvalue();
14697 // Compute O value
14698 $this->encryptdata['O'] = $this->_Ovalue();
14699 // Compute OE value
14700 $this->encryptdata['OE'] = $this->_OEvalue();
14701 // Compute P value
14702 $this->encryptdata['P'] = $this->encryptdata['protection'];
14703 // Computing the encryption dictionary's Perms (permissions) value
14704 $perms = $this->getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
14705 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
14706 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
14707 $perms .= 'F';
14708 } else {
14709 $perms .= 'T';
14711 $perms .= 'adb'; // bytes 9-11
14712 $perms .= 'nick'; // bytes 12-15
14713 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
14714 $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
14715 } else { // RC4-40, RC4-128, AES-128
14716 // Pad passwords
14717 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].$this->enc_padding, 0, 32);
14718 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].$this->enc_padding, 0, 32);
14719 // Compute O value
14720 $this->encryptdata['O'] = $this->_Ovalue();
14721 // get default permissions (reverse byte order)
14722 $permissions = $this->getEncPermissionsString($this->encryptdata['protection']);
14723 // Compute encryption key
14724 $tmp = $this->_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
14725 if ($this->encryptdata['mode'] > 0) {
14726 for ($i = 0; $i < 50; ++$i) {
14727 $tmp = $this->_md5_16(substr($tmp, 0, $keybytelen));
14730 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
14731 // Compute U value
14732 $this->encryptdata['U'] = $this->_Uvalue();
14733 // Compute P value
14734 $this->encryptdata['P'] = $this->encryptdata['protection'];
14736 } else { // Public-Key mode
14737 // random 20-byte seed
14738 $seed = sha1($this->getRandomSeed(), true);
14739 $recipient_bytes = '';
14740 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
14741 // for each public certificate
14742 if (isset($pubkey['p'])) {
14743 $pkprotection = $this->getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
14744 } else {
14745 $pkprotection = $this->encryptdata['protection'];
14747 // get default permissions (reverse byte order)
14748 $pkpermissions = $this->getEncPermissionsString($pkprotection);
14749 // envelope data
14750 $envelope = $seed.$pkpermissions;
14751 // write the envelope data to a temporary file
14752 $tempkeyfile = tempnam(K_PATH_CACHE, 'tmpkey_');
14753 $f = fopen($tempkeyfile, 'wb');
14754 if (!$f) {
14755 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
14757 $envelope_length = strlen($envelope);
14758 fwrite($f, $envelope, $envelope_length);
14759 fclose($f);
14760 $tempencfile = tempnam(K_PATH_CACHE, 'tmpenc_');
14761 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
14762 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
14764 unlink($tempkeyfile);
14765 // read encryption signature
14766 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
14767 unlink($tempencfile);
14768 // extract signature
14769 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
14770 $tmparr = explode("\n\n", $signature);
14771 $signature = trim($tmparr[1]);
14772 unset($tmparr);
14773 // decode signature
14774 $signature = base64_decode($signature);
14775 // convert signature to hex
14776 $hexsignature = current(unpack('H*', $signature));
14777 // store signature on recipients array
14778 $this->encryptdata['Recipients'][] = $hexsignature;
14779 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
14780 $recipient_bytes .= $signature;
14782 // calculate encryption key
14783 if ($this->encryptdata['mode'] == 3) { // AES-256
14784 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
14785 } else { // RC4-40, RC4-128, AES-128
14786 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
14792 * Return the premission code used on encryption (P value).
14793 * @param $permissions (Array) the set of permissions (specify the ones you want to block).
14794 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
14795 * @protected
14796 * @since 5.0.005 (2010-05-12)
14797 * @author Nicola Asuni
14799 protected function getUserPermissionCode($permissions, $mode=0) {
14800 $options = array(
14801 'owner' => 2, // bit 2 -- inverted logic: cleared by default
14802 'print' => 4, // bit 3
14803 'modify' => 8, // bit 4
14804 'copy' => 16, // bit 5
14805 'annot-forms' => 32, // bit 6
14806 'fill-forms' => 256, // bit 9
14807 'extract' => 512, // bit 10
14808 'assemble' => 1024,// bit 11
14809 'print-high' => 2048 // bit 12
14811 $protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
14812 foreach ($permissions as $permission) {
14813 if (!isset($options[$permission])) {
14814 $this->Error('Incorrect permission: '.$permission);
14816 if (($mode > 0) OR ($options[$permission] <= 32)) {
14817 // set only valid permissions
14818 if ($options[$permission] == 2) {
14819 // the logic for bit 2 is inverted (cleared by default)
14820 $protection += $options[$permission];
14821 } else {
14822 $protection -= $options[$permission];
14826 return $protection;
14830 * Set document protection
14831 * Remark: the protection against modification is for people who have the full Acrobat product.
14832 * 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.
14833 * 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.
14834 * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
14835 * @param $user_pass (String) user password. Empty by default.
14836 * @param $owner_pass (String) owner password. If not specified, a random value is used.
14837 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
14838 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../tcpdf.crt', 'p' => array('print')))
14839 * @public
14840 * @since 2.0.000 (2008-01-02)
14841 * @author Nicola Asuni
14843 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) {
14844 if ($this->pdfa_mode) {
14845 // encryption is not allowed in PDF/A mode
14846 return;
14848 $this->encryptdata['protection'] = $this->getUserPermissionCode($permissions, $mode);
14849 if (($pubkeys !== null) AND (is_array($pubkeys))) {
14850 // public-key mode
14851 $this->encryptdata['pubkeys'] = $pubkeys;
14852 if ($mode == 0) {
14853 // public-Key Security requires at least 128 bit
14854 $mode = 1;
14856 if (!function_exists('openssl_pkcs7_encrypt')) {
14857 $this->Error('Public-Key Security requires openssl library.');
14859 // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
14860 $this->encryptdata['pubkey'] = true;
14861 $this->encryptdata['Filter'] = 'Adobe.PubSec';
14862 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
14863 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
14864 } else {
14865 // standard mode (password mode)
14866 $this->encryptdata['pubkey'] = false;
14867 $this->encryptdata['Filter'] = 'Standard';
14868 $this->encryptdata['StmF'] = 'StdCF';
14869 $this->encryptdata['StrF'] = 'StdCF';
14871 if ($mode > 1) { // AES
14872 if (!extension_loaded('mcrypt')) {
14873 $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
14875 if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
14876 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
14878 if (($mode == 3) AND !function_exists('hash')) {
14879 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
14880 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
14883 if ($owner_pass === null) {
14884 $owner_pass = md5($this->getRandomSeed());
14886 $this->encryptdata['user_password'] = $user_pass;
14887 $this->encryptdata['owner_password'] = $owner_pass;
14888 $this->encryptdata['mode'] = $mode;
14889 switch ($mode) {
14890 case 0: { // RC4 40 bit
14891 $this->encryptdata['V'] = 1;
14892 $this->encryptdata['Length'] = 40;
14893 $this->encryptdata['CF']['CFM'] = 'V2';
14894 break;
14896 case 1: { // RC4 128 bit
14897 $this->encryptdata['V'] = 2;
14898 $this->encryptdata['Length'] = 128;
14899 $this->encryptdata['CF']['CFM'] = 'V2';
14900 if ($this->encryptdata['pubkey']) {
14901 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
14902 $this->encryptdata['Recipients'] = array();
14904 break;
14906 case 2: { // AES 128 bit
14907 $this->encryptdata['V'] = 4;
14908 $this->encryptdata['Length'] = 128;
14909 $this->encryptdata['CF']['CFM'] = 'AESV2';
14910 $this->encryptdata['CF']['Length'] = 128;
14911 if ($this->encryptdata['pubkey']) {
14912 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
14913 $this->encryptdata['Recipients'] = array();
14915 break;
14917 case 3: { // AES 256 bit
14918 $this->encryptdata['V'] = 5;
14919 $this->encryptdata['Length'] = 256;
14920 $this->encryptdata['CF']['CFM'] = 'AESV3';
14921 $this->encryptdata['CF']['Length'] = 256;
14922 if ($this->encryptdata['pubkey']) {
14923 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
14924 $this->encryptdata['Recipients'] = array();
14926 break;
14929 $this->encrypted = true;
14930 $this->encryptdata['fileid'] = $this->convertHexStringToString($this->file_id);
14931 $this->_generateencryptionkey();
14935 * Convert hexadecimal string to string
14936 * @param $bs (string) byte-string to convert
14937 * @return String
14938 * @protected
14939 * @since 5.0.005 (2010-05-12)
14940 * @author Nicola Asuni
14942 protected function convertHexStringToString($bs) {
14943 $string = ''; // string to be returned
14944 $bslength = strlen($bs);
14945 if (($bslength % 2) != 0) {
14946 // padding
14947 $bs .= '0';
14948 ++$bslength;
14950 for ($i = 0; $i < $bslength; $i += 2) {
14951 $string .= chr(hexdec($bs[$i].$bs[($i + 1)]));
14953 return $string;
14957 * Convert string to hexadecimal string (byte string)
14958 * @param $s (string) string to convert
14959 * @return byte string
14960 * @protected
14961 * @since 5.0.010 (2010-05-17)
14962 * @author Nicola Asuni
14964 protected function convertStringToHexString($s) {
14965 $bs = '';
14966 $chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
14967 foreach ($chars as $c) {
14968 $bs .= sprintf('%02s', dechex(ord($c)));
14970 return $bs;
14974 * Convert encryption P value to a string of bytes, low-order byte first.
14975 * @param $protection (string) 32bit encryption permission value (P value)
14976 * @return String
14977 * @protected
14978 * @since 5.0.005 (2010-05-12)
14979 * @author Nicola Asuni
14981 protected function getEncPermissionsString($protection) {
14982 $binprot = sprintf('%032b', $protection);
14983 $str = chr(bindec(substr($binprot, 24, 8)));
14984 $str .= chr(bindec(substr($binprot, 16, 8)));
14985 $str .= chr(bindec(substr($binprot, 8, 8)));
14986 $str .= chr(bindec(substr($binprot, 0, 8)));
14987 return $str;
14990 // END OF ENCRYPTION FUNCTIONS -------------------------
14992 // START TRANSFORMATIONS SECTION -----------------------
14995 * Starts a 2D tranformation saving current graphic state.
14996 * This function must be called before scaling, mirroring, translation, rotation and skewing.
14997 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
14998 * @public
14999 * @since 2.1.000 (2008-01-07)
15000 * @see StartTransform(), StopTransform()
15002 public function StartTransform() {
15003 if ($this->state != 2) {
15004 return;
15006 $this->_out('q');
15007 if ($this->inxobj) {
15008 // we are inside an XObject template
15009 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
15010 } else {
15011 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
15013 ++$this->transfmatrix_key;
15014 $this->transfmatrix[$this->transfmatrix_key] = array();
15018 * Stops a 2D tranformation restoring previous graphic state.
15019 * This function must be called after scaling, mirroring, translation, rotation and skewing.
15020 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
15021 * @public
15022 * @since 2.1.000 (2008-01-07)
15023 * @see StartTransform(), StopTransform()
15025 public function StopTransform() {
15026 if ($this->state != 2) {
15027 return;
15029 $this->_out('Q');
15030 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
15031 array_pop($this->transfmatrix[$this->transfmatrix_key]);
15032 --$this->transfmatrix_key;
15034 if ($this->inxobj) {
15035 // we are inside an XObject template
15036 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
15037 } else {
15038 array_pop($this->transfmrk[$this->page]);
15042 * Horizontal Scaling.
15043 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
15044 * @param $x (int) abscissa of the scaling center. Default is current x position
15045 * @param $y (int) ordinate of the scaling center. Default is current y position
15046 * @public
15047 * @since 2.1.000 (2008-01-07)
15048 * @see StartTransform(), StopTransform()
15050 public function ScaleX($s_x, $x='', $y='') {
15051 $this->Scale($s_x, 100, $x, $y);
15055 * Vertical Scaling.
15056 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
15057 * @param $x (int) abscissa of the scaling center. Default is current x position
15058 * @param $y (int) ordinate of the scaling center. Default is current y position
15059 * @public
15060 * @since 2.1.000 (2008-01-07)
15061 * @see StartTransform(), StopTransform()
15063 public function ScaleY($s_y, $x='', $y='') {
15064 $this->Scale(100, $s_y, $x, $y);
15068 * Vertical and horizontal proportional Scaling.
15069 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
15070 * @param $x (int) abscissa of the scaling center. Default is current x position
15071 * @param $y (int) ordinate of the scaling center. Default is current y position
15072 * @public
15073 * @since 2.1.000 (2008-01-07)
15074 * @see StartTransform(), StopTransform()
15076 public function ScaleXY($s, $x='', $y='') {
15077 $this->Scale($s, $s, $x, $y);
15081 * Vertical and horizontal non-proportional Scaling.
15082 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
15083 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
15084 * @param $x (int) abscissa of the scaling center. Default is current x position
15085 * @param $y (int) ordinate of the scaling center. Default is current y position
15086 * @public
15087 * @since 2.1.000 (2008-01-07)
15088 * @see StartTransform(), StopTransform()
15090 public function Scale($s_x, $s_y, $x='', $y='') {
15091 if ($x === '') {
15092 $x = $this->x;
15094 if ($y === '') {
15095 $y = $this->y;
15097 if (($s_x == 0) OR ($s_y == 0)) {
15098 $this->Error('Please do not use values equal to zero for scaling');
15100 $y = ($this->h - $y) * $this->k;
15101 $x *= $this->k;
15102 //calculate elements of transformation matrix
15103 $s_x /= 100;
15104 $s_y /= 100;
15105 $tm = array();
15106 $tm[0] = $s_x;
15107 $tm[1] = 0;
15108 $tm[2] = 0;
15109 $tm[3] = $s_y;
15110 $tm[4] = $x * (1 - $s_x);
15111 $tm[5] = $y * (1 - $s_y);
15112 //scale the coordinate system
15113 $this->Transform($tm);
15117 * Horizontal Mirroring.
15118 * @param $x (int) abscissa of the point. Default is current x position
15119 * @public
15120 * @since 2.1.000 (2008-01-07)
15121 * @see StartTransform(), StopTransform()
15123 public function MirrorH($x='') {
15124 $this->Scale(-100, 100, $x);
15128 * Verical Mirroring.
15129 * @param $y (int) ordinate of the point. Default is current y position
15130 * @public
15131 * @since 2.1.000 (2008-01-07)
15132 * @see StartTransform(), StopTransform()
15134 public function MirrorV($y='') {
15135 $this->Scale(100, -100, '', $y);
15139 * Point reflection mirroring.
15140 * @param $x (int) abscissa of the point. Default is current x position
15141 * @param $y (int) ordinate of the point. Default is current y position
15142 * @public
15143 * @since 2.1.000 (2008-01-07)
15144 * @see StartTransform(), StopTransform()
15146 public function MirrorP($x='',$y='') {
15147 $this->Scale(-100, -100, $x, $y);
15151 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
15152 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
15153 * @param $x (int) abscissa of the point. Default is current x position
15154 * @param $y (int) ordinate of the point. Default is current y position
15155 * @public
15156 * @since 2.1.000 (2008-01-07)
15157 * @see StartTransform(), StopTransform()
15159 public function MirrorL($angle=0, $x='',$y='') {
15160 $this->Scale(-100, 100, $x, $y);
15161 $this->Rotate(-2*($angle-90), $x, $y);
15165 * Translate graphic object horizontally.
15166 * @param $t_x (int) movement to the right (or left for RTL)
15167 * @public
15168 * @since 2.1.000 (2008-01-07)
15169 * @see StartTransform(), StopTransform()
15171 public function TranslateX($t_x) {
15172 $this->Translate($t_x, 0);
15176 * Translate graphic object vertically.
15177 * @param $t_y (int) movement to the bottom
15178 * @public
15179 * @since 2.1.000 (2008-01-07)
15180 * @see StartTransform(), StopTransform()
15182 public function TranslateY($t_y) {
15183 $this->Translate(0, $t_y);
15187 * Translate graphic object horizontally and vertically.
15188 * @param $t_x (int) movement to the right
15189 * @param $t_y (int) movement to the bottom
15190 * @public
15191 * @since 2.1.000 (2008-01-07)
15192 * @see StartTransform(), StopTransform()
15194 public function Translate($t_x, $t_y) {
15195 //calculate elements of transformation matrix
15196 $tm = array();
15197 $tm[0] = 1;
15198 $tm[1] = 0;
15199 $tm[2] = 0;
15200 $tm[3] = 1;
15201 $tm[4] = $t_x * $this->k;
15202 $tm[5] = -$t_y * $this->k;
15203 //translate the coordinate system
15204 $this->Transform($tm);
15208 * Rotate object.
15209 * @param $angle (float) angle in degrees for counter-clockwise rotation
15210 * @param $x (int) abscissa of the rotation center. Default is current x position
15211 * @param $y (int) ordinate of the rotation center. Default is current y position
15212 * @public
15213 * @since 2.1.000 (2008-01-07)
15214 * @see StartTransform(), StopTransform()
15216 public function Rotate($angle, $x='', $y='') {
15217 if ($x === '') {
15218 $x = $this->x;
15220 if ($y === '') {
15221 $y = $this->y;
15223 $y = ($this->h - $y) * $this->k;
15224 $x *= $this->k;
15225 //calculate elements of transformation matrix
15226 $tm = array();
15227 $tm[0] = cos(deg2rad($angle));
15228 $tm[1] = sin(deg2rad($angle));
15229 $tm[2] = -$tm[1];
15230 $tm[3] = $tm[0];
15231 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
15232 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
15233 //rotate the coordinate system around ($x,$y)
15234 $this->Transform($tm);
15238 * Skew horizontally.
15239 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
15240 * @param $x (int) abscissa of the skewing center. default is current x position
15241 * @param $y (int) ordinate of the skewing center. default is current y position
15242 * @public
15243 * @since 2.1.000 (2008-01-07)
15244 * @see StartTransform(), StopTransform()
15246 public function SkewX($angle_x, $x='', $y='') {
15247 $this->Skew($angle_x, 0, $x, $y);
15251 * Skew vertically.
15252 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
15253 * @param $x (int) abscissa of the skewing center. default is current x position
15254 * @param $y (int) ordinate of the skewing center. default is current y position
15255 * @public
15256 * @since 2.1.000 (2008-01-07)
15257 * @see StartTransform(), StopTransform()
15259 public function SkewY($angle_y, $x='', $y='') {
15260 $this->Skew(0, $angle_y, $x, $y);
15264 * Skew.
15265 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
15266 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
15267 * @param $x (int) abscissa of the skewing center. default is current x position
15268 * @param $y (int) ordinate of the skewing center. default is current y position
15269 * @public
15270 * @since 2.1.000 (2008-01-07)
15271 * @see StartTransform(), StopTransform()
15273 public function Skew($angle_x, $angle_y, $x='', $y='') {
15274 if ($x === '') {
15275 $x = $this->x;
15277 if ($y === '') {
15278 $y = $this->y;
15280 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
15281 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
15283 $x *= $this->k;
15284 $y = ($this->h - $y) * $this->k;
15285 //calculate elements of transformation matrix
15286 $tm = array();
15287 $tm[0] = 1;
15288 $tm[1] = tan(deg2rad($angle_y));
15289 $tm[2] = tan(deg2rad($angle_x));
15290 $tm[3] = 1;
15291 $tm[4] = -$tm[2] * $y;
15292 $tm[5] = -$tm[1] * $x;
15293 //skew the coordinate system
15294 $this->Transform($tm);
15298 * Apply graphic transformations.
15299 * @param $tm (array) transformation matrix
15300 * @protected
15301 * @since 2.1.000 (2008-01-07)
15302 * @see StartTransform(), StopTransform()
15304 protected function Transform($tm) {
15305 if ($this->state != 2) {
15306 return;
15308 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
15309 // add tranformation matrix
15310 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
15311 // update transformation mark
15312 if ($this->inxobj) {
15313 // we are inside an XObject template
15314 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
15315 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
15316 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
15318 } elseif (end($this->transfmrk[$this->page]) !== false) {
15319 $key = key($this->transfmrk[$this->page]);
15320 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
15324 // END TRANSFORMATIONS SECTION -------------------------
15326 // START GRAPHIC FUNCTIONS SECTION ---------------------
15327 // The following section is based on the code provided by David Hernandez Sanz
15330 * 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.
15331 * @param $width (float) The width.
15332 * @public
15333 * @since 1.0
15334 * @see Line(), Rect(), Cell(), MultiCell()
15336 public function SetLineWidth($width) {
15337 //Set line width
15338 $this->LineWidth = $width;
15339 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
15340 if ($this->state == 2) {
15341 $this->_out($this->linestyleWidth);
15346 * Returns the current the line width.
15347 * @return int Line width
15348 * @public
15349 * @since 2.1.000 (2008-01-07)
15350 * @see Line(), SetLineWidth()
15352 public function GetLineWidth() {
15353 return $this->LineWidth;
15357 * Set line style.
15358 * @param $style (array) Line style. Array with keys among the following:
15359 * <ul>
15360 * <li>width (float): Width of the line in user units.</li>
15361 * <li>cap (string): Type of cap to put on the line. Possible values are:
15362 * butt, round, square. The difference between "square" and "butt" is that
15363 * "square" projects a flat end past the end of the line.</li>
15364 * <li>join (string): Type of join. Possible values are: miter, round,
15365 * bevel.</li>
15366 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
15367 * series of length values, which are the lengths of the on and off dashes.
15368 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
15369 * 1 off, 2 on, 1 off, ...</li>
15370 * <li>phase (integer): Modifier on the dash pattern which is used to shift
15371 * the point at which the pattern starts.</li>
15372 * <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>
15373 * </ul>
15374 * @param $ret (boolean) if true do not send the command.
15375 * @return string the PDF command
15376 * @public
15377 * @since 2.1.000 (2008-01-08)
15379 public function SetLineStyle($style, $ret=false) {
15380 $s = ''; // string to be returned
15381 if (!is_array($style)) {
15382 return;
15384 if (isset($style['width'])) {
15385 $this->LineWidth = $style['width'];
15386 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
15387 $s .= $this->linestyleWidth.' ';
15389 if (isset($style['cap'])) {
15390 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
15391 if (isset($ca[$style['cap']])) {
15392 $this->linestyleCap = $ca[$style['cap']].' J';
15393 $s .= $this->linestyleCap.' ';
15396 if (isset($style['join'])) {
15397 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
15398 if (isset($ja[$style['join']])) {
15399 $this->linestyleJoin = $ja[$style['join']].' j';
15400 $s .= $this->linestyleJoin.' ';
15403 if (isset($style['dash'])) {
15404 $dash_string = '';
15405 if ($style['dash']) {
15406 if (preg_match('/^.+,/', $style['dash']) > 0) {
15407 $tab = explode(',', $style['dash']);
15408 } else {
15409 $tab = array($style['dash']);
15411 $dash_string = '';
15412 foreach ($tab as $i => $v) {
15413 if ($i) {
15414 $dash_string .= ' ';
15416 $dash_string .= sprintf('%F', $v);
15419 if (!isset($style['phase']) OR !$style['dash']) {
15420 $style['phase'] = 0;
15422 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
15423 $s .= $this->linestyleDash.' ';
15425 if (isset($style['color'])) {
15426 $s .= $this->SetDrawColorArray($style['color'], true).' ';
15428 if (!$ret AND ($this->state == 2)) {
15429 $this->_out($s);
15431 return $s;
15435 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
15436 * @param $x (float) Abscissa of point.
15437 * @param $y (float) Ordinate of point.
15438 * @protected
15439 * @since 2.1.000 (2008-01-08)
15441 protected function _outPoint($x, $y) {
15442 if ($this->state == 2) {
15443 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
15448 * Append a straight line segment from the current point to the point (x, y).
15449 * The new current point shall be (x, y).
15450 * @param $x (float) Abscissa of end point.
15451 * @param $y (float) Ordinate of end point.
15452 * @protected
15453 * @since 2.1.000 (2008-01-08)
15455 protected function _outLine($x, $y) {
15456 if ($this->state == 2) {
15457 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
15462 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
15463 * @param $x (float) Abscissa of upper-left corner.
15464 * @param $y (float) Ordinate of upper-left corner.
15465 * @param $w (float) Width.
15466 * @param $h (float) Height.
15467 * @param $op (string) options
15468 * @protected
15469 * @since 2.1.000 (2008-01-08)
15471 protected function _outRect($x, $y, $w, $h, $op) {
15472 if ($this->state == 2) {
15473 $this->_out(sprintf('%F %F %F %F re %s', $x * $this->k, ($this->h - $y) * $this->k, $w * $this->k, -$h * $this->k, $op));
15478 * Append a cubic Bézier 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 Bézier control points.
15479 * The new current point shall be (x3, y3).
15480 * @param $x1 (float) Abscissa of control point 1.
15481 * @param $y1 (float) Ordinate of control point 1.
15482 * @param $x2 (float) Abscissa of control point 2.
15483 * @param $y2 (float) Ordinate of control point 2.
15484 * @param $x3 (float) Abscissa of end point.
15485 * @param $y3 (float) Ordinate of end point.
15486 * @protected
15487 * @since 2.1.000 (2008-01-08)
15489 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
15490 if ($this->state == 2) {
15491 $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));
15496 * Append a cubic Bézier 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 Bézier control points.
15497 * The new current point shall be (x3, y3).
15498 * @param $x2 (float) Abscissa of control point 2.
15499 * @param $y2 (float) Ordinate of control point 2.
15500 * @param $x3 (float) Abscissa of end point.
15501 * @param $y3 (float) Ordinate of end point.
15502 * @protected
15503 * @since 4.9.019 (2010-04-26)
15505 protected function _outCurveV($x2, $y2, $x3, $y3) {
15506 if ($this->state == 2) {
15507 $this->_out(sprintf('%F %F %F %F v', $x2 * $this->k, ($this->h - $y2) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
15512 * Append a cubic Bézier 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 Bézier control points.
15513 * The new current point shall be (x3, y3).
15514 * @param $x1 (float) Abscissa of control point 1.
15515 * @param $y1 (float) Ordinate of control point 1.
15516 * @param $x3 (float) Abscissa of end point.
15517 * @param $y3 (float) Ordinate of end point.
15518 * @protected
15519 * @since 2.1.000 (2008-01-08)
15521 protected function _outCurveY($x1, $y1, $x3, $y3) {
15522 if ($this->state == 2) {
15523 $this->_out(sprintf('%F %F %F %F y', $x1 * $this->k, ($this->h - $y1) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
15528 * Draws a line between two points.
15529 * @param $x1 (float) Abscissa of first point.
15530 * @param $y1 (float) Ordinate of first point.
15531 * @param $x2 (float) Abscissa of second point.
15532 * @param $y2 (float) Ordinate of second point.
15533 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
15534 * @public
15535 * @since 1.0
15536 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
15538 public function Line($x1, $y1, $x2, $y2, $style=array()) {
15539 if ($this->state != 2) {
15540 return;
15542 if (is_array($style)) {
15543 $this->SetLineStyle($style);
15545 $this->_outPoint($x1, $y1);
15546 $this->_outLine($x2, $y2);
15547 $this->_out('S');
15551 * Draws a rectangle.
15552 * @param $x (float) Abscissa of upper-left corner.
15553 * @param $y (float) Ordinate of upper-left corner.
15554 * @param $w (float) Width.
15555 * @param $h (float) Height.
15556 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15557 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
15558 * <ul>
15559 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
15560 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
15561 * </ul>
15562 * If a key is not present or is null, not draws the border. Default value: default line style (empty array).
15563 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15564 * @public
15565 * @since 1.0
15566 * @see SetLineStyle()
15568 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
15569 if ($this->state != 2) {
15570 return;
15572 if (!(false === strpos($style, 'F')) AND !empty($fill_color)) {
15573 $this->SetFillColorArray($fill_color);
15575 $op = $this->getPathPaintOperator($style);
15576 if ((!$border_style) OR (isset($border_style['all']))) {
15577 if (isset($border_style['all']) AND $border_style['all']) {
15578 $this->SetLineStyle($border_style['all']);
15579 $border_style = array();
15582 $this->_outRect($x, $y, $w, $h, $op);
15583 if ($border_style) {
15584 $border_style2 = array();
15585 foreach ($border_style as $line => $value) {
15586 $length = strlen($line);
15587 for ($i = 0; $i < $length; ++$i) {
15588 $border_style2[$line[$i]] = $value;
15591 $border_style = $border_style2;
15592 if (isset($border_style['L']) AND $border_style['L']) {
15593 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
15595 if (isset($border_style['T']) AND $border_style['T']) {
15596 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
15598 if (isset($border_style['R']) AND $border_style['R']) {
15599 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
15601 if (isset($border_style['B']) AND $border_style['B']) {
15602 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
15608 * Draws a Bezier curve.
15609 * The Bezier curve is a tangent to the line between the control points at
15610 * either end of the curve.
15611 * @param $x0 (float) Abscissa of start point.
15612 * @param $y0 (float) Ordinate of start point.
15613 * @param $x1 (float) Abscissa of control point 1.
15614 * @param $y1 (float) Ordinate of control point 1.
15615 * @param $x2 (float) Abscissa of control point 2.
15616 * @param $y2 (float) Ordinate of control point 2.
15617 * @param $x3 (float) Abscissa of end point.
15618 * @param $y3 (float) Ordinate of end point.
15619 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15620 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
15621 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15622 * @public
15623 * @see SetLineStyle()
15624 * @since 2.1.000 (2008-01-08)
15626 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
15627 if ($this->state != 2) {
15628 return;
15630 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15631 $this->SetFillColorArray($fill_color);
15633 $op = $this->getPathPaintOperator($style);
15634 if ($line_style) {
15635 $this->SetLineStyle($line_style);
15637 $this->_outPoint($x0, $y0);
15638 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
15639 $this->_out($op);
15643 * Draws a poly-Bezier curve.
15644 * Each Bezier curve segment is a tangent to the line between the control points at
15645 * either end of the curve.
15646 * @param $x0 (float) Abscissa of start point.
15647 * @param $y0 (float) Ordinate of start point.
15648 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
15649 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15650 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
15651 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15652 * @public
15653 * @see SetLineStyle()
15654 * @since 3.0008 (2008-05-12)
15656 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
15657 if ($this->state != 2) {
15658 return;
15660 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15661 $this->SetFillColorArray($fill_color);
15663 $op = $this->getPathPaintOperator($style);
15664 if ($op == 'f') {
15665 $line_style = array();
15667 if ($line_style) {
15668 $this->SetLineStyle($line_style);
15670 $this->_outPoint($x0, $y0);
15671 foreach ($segments as $segment) {
15672 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
15673 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
15675 $this->_out($op);
15679 * Draws an ellipse.
15680 * An ellipse is formed from n Bezier curves.
15681 * @param $x0 (float) Abscissa of center point.
15682 * @param $y0 (float) Ordinate of center point.
15683 * @param $rx (float) Horizontal radius.
15684 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
15685 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
15686 * @param $astart: (float) Angle start of draw line. Default value: 0.
15687 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
15688 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15689 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
15690 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15691 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
15692 * @author Nicola Asuni
15693 * @public
15694 * @since 2.1.000 (2008-01-08)
15696 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
15697 if ($this->state != 2) {
15698 return;
15700 if ($this->empty_string($ry) OR ($ry == 0)) {
15701 $ry = $rx;
15703 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15704 $this->SetFillColorArray($fill_color);
15706 $op = $this->getPathPaintOperator($style);
15707 if ($op == 'f') {
15708 $line_style = array();
15710 if ($line_style) {
15711 $this->SetLineStyle($line_style);
15713 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
15714 $this->_out($op);
15718 * Append an elliptical arc to the current path.
15719 * An ellipse is formed from n Bezier curves.
15720 * @param $xc (float) Abscissa of center point.
15721 * @param $yc (float) Ordinate of center point.
15722 * @param $rx (float) Horizontal radius.
15723 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
15724 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
15725 * @param $angs: (float) Angle start of draw line. Default value: 0.
15726 * @param $angf: (float) Angle finish of draw line. Default value: 360.
15727 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
15728 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
15729 * @param $startpoint (boolean) if true output a starting point.
15730 * @param $ccw (boolean) if true draws in counter-clockwise.
15731 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
15732 * @return array bounding box coordinates (x min, y min, x max, y max)
15733 * @author Nicola Asuni
15734 * @protected
15735 * @since 4.9.019 (2010-04-26)
15737 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
15738 $k = $this->k;
15739 if ($nc < 2) {
15740 $nc = 2;
15742 $xmin = 2147483647;
15743 $ymin = 2147483647;
15744 $xmax = 0;
15745 $ymax = 0;
15746 if ($pie) {
15747 // center of the arc
15748 $this->_outPoint($xc, $yc);
15750 $xang = deg2rad((float) $xang);
15751 $angs = deg2rad((float) $angs);
15752 $angf = deg2rad((float) $angf);
15753 if ($svg) {
15754 $as = $angs;
15755 $af = $angf;
15756 } else {
15757 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
15758 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
15760 if ($as < 0) {
15761 $as += (2 * M_PI);
15763 if ($af < 0) {
15764 $af += (2 * M_PI);
15766 if ($ccw AND ($as > $af)) {
15767 // reverse rotation
15768 $as -= (2 * M_PI);
15769 } elseif (!$ccw AND ($as < $af)) {
15770 // reverse rotation
15771 $af -= (2 * M_PI);
15773 $total_angle = ($af - $as);
15774 if ($nc < 2) {
15775 $nc = 2;
15777 // total arcs to draw
15778 $nc *= (2 * abs($total_angle) / M_PI);
15779 $nc = round($nc) + 1;
15780 // angle of each arc
15781 $arcang = ($total_angle / $nc);
15782 // center point in PDF coordinates
15783 $x0 = $xc;
15784 $y0 = ($this->h - $yc);
15785 // starting angle
15786 $ang = $as;
15787 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
15788 $cos_xang = cos($xang);
15789 $sin_xang = sin($xang);
15790 $cos_ang = cos($ang);
15791 $sin_ang = sin($ang);
15792 // first arc point
15793 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
15794 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
15795 // first Bezier control point
15796 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
15797 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
15798 if ($pie) {
15799 // line from center to arc starting point
15800 $this->_outLine($px1, $this->h - $py1);
15801 } elseif ($startpoint) {
15802 // arc starting point
15803 $this->_outPoint($px1, $this->h - $py1);
15805 // draw arcs
15806 for ($i = 1; $i <= $nc; ++$i) {
15807 // starting angle
15808 $ang = $as + ($i * $arcang);
15809 if ($i == $nc) {
15810 $ang = $af;
15812 $cos_ang = cos($ang);
15813 $sin_ang = sin($ang);
15814 // second arc point
15815 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
15816 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
15817 // second Bezier control point
15818 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
15819 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
15820 // draw arc
15821 $cx1 = ($px1 + $qx1);
15822 $cy1 = ($this->h - ($py1 + $qy1));
15823 $cx2 = ($px2 - $qx2);
15824 $cy2 = ($this->h - ($py2 - $qy2));
15825 $cx3 = $px2;
15826 $cy3 = ($this->h - $py2);
15827 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
15828 // get bounding box coordinates
15829 $xmin = min($xmin, $cx1, $cx2, $cx3);
15830 $ymin = min($ymin, $cy1, $cy2, $cy3);
15831 $xmax = max($xmax, $cx1, $cx2, $cx3);
15832 $ymax = max($ymax, $cy1, $cy2, $cy3);
15833 // move to next point
15834 $px1 = $px2;
15835 $py1 = $py2;
15836 $qx1 = $qx2;
15837 $qy1 = $qy2;
15839 if ($pie) {
15840 $this->_outLine($xc, $yc);
15841 // get bounding box coordinates
15842 $xmin = min($xmin, $xc);
15843 $ymin = min($ymin, $yc);
15844 $xmax = max($xmax, $xc);
15845 $ymax = max($ymax, $yc);
15847 return array($xmin, $ymin, $xmax, $ymax);
15851 * Draws a circle.
15852 * A circle is formed from n Bezier curves.
15853 * @param $x0 (float) Abscissa of center point.
15854 * @param $y0 (float) Ordinate of center point.
15855 * @param $r (float) Radius.
15856 * @param $angstr: (float) Angle start of draw line. Default value: 0.
15857 * @param $angend: (float) Angle finish of draw line. Default value: 360.
15858 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15859 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
15860 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
15861 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
15862 * @public
15863 * @since 2.1.000 (2008-01-08)
15865 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
15866 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
15870 * Draws a polygonal line
15871 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
15872 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15873 * @param $line_style (array) Line style of polygon. Array with keys among the following:
15874 * <ul>
15875 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
15876 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
15877 * </ul>
15878 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
15879 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15880 * @since 4.8.003 (2009-09-15)
15881 * @public
15883 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
15884 $this->Polygon($p, $style, $line_style, $fill_color, false);
15888 * Draws a polygon.
15889 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
15890 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15891 * @param $line_style (array) Line style of polygon. Array with keys among the following:
15892 * <ul>
15893 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
15894 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
15895 * </ul>
15896 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
15897 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
15898 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
15899 * @public
15900 * @since 2.1.000 (2008-01-08)
15902 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
15903 if ($this->state != 2) {
15904 return;
15906 $nc = count($p); // number of coordinates
15907 $np = $nc / 2; // number of points
15908 if ($closed) {
15909 // close polygon by adding the first 2 points at the end (one line)
15910 for ($i = 0; $i < 4; ++$i) {
15911 $p[$nc + $i] = $p[$i];
15913 // copy style for the last added line
15914 if (isset($line_style[0])) {
15915 $line_style[$np] = $line_style[0];
15917 $nc += 4;
15919 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15920 $this->SetFillColorArray($fill_color);
15922 $op = $this->getPathPaintOperator($style);
15923 if ($op == 'f') {
15924 $line_style = array();
15926 $draw = true;
15927 if ($line_style) {
15928 if (isset($line_style['all'])) {
15929 $this->SetLineStyle($line_style['all']);
15930 } else {
15931 $draw = false;
15932 if ($op == 'B') {
15933 // draw fill
15934 $op = 'f';
15935 $this->_outPoint($p[0], $p[1]);
15936 for ($i = 2; $i < $nc; $i = $i + 2) {
15937 $this->_outLine($p[$i], $p[$i + 1]);
15939 $this->_out($op);
15941 // draw outline
15942 $this->_outPoint($p[0], $p[1]);
15943 for ($i = 2; $i < $nc; $i = $i + 2) {
15944 $line_num = ($i / 2) - 1;
15945 if (isset($line_style[$line_num])) {
15946 if ($line_style[$line_num] != 0) {
15947 if (is_array($line_style[$line_num])) {
15948 $this->_out('S');
15949 $this->SetLineStyle($line_style[$line_num]);
15950 $this->_outPoint($p[$i - 2], $p[$i - 1]);
15951 $this->_outLine($p[$i], $p[$i + 1]);
15952 $this->_out('S');
15953 $this->_outPoint($p[$i], $p[$i + 1]);
15954 } else {
15955 $this->_outLine($p[$i], $p[$i + 1]);
15958 } else {
15959 $this->_outLine($p[$i], $p[$i + 1]);
15962 $this->_out($op);
15965 if ($draw) {
15966 $this->_outPoint($p[0], $p[1]);
15967 for ($i = 2; $i < $nc; $i = $i + 2) {
15968 $this->_outLine($p[$i], $p[$i + 1]);
15970 $this->_out($op);
15975 * Draws a regular polygon.
15976 * @param $x0 (float) Abscissa of center point.
15977 * @param $y0 (float) Ordinate of center point.
15978 * @param $r: (float) Radius of inscribed circle.
15979 * @param $ns (integer) Number of sides.
15980 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
15981 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
15982 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15983 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
15984 * <ul>
15985 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
15986 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
15987 * </ul>
15988 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
15989 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
15990 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
15991 * <ul>
15992 * <li>D or empty string: Draw (default).</li>
15993 * <li>F: Fill.</li>
15994 * <li>DF or FD: Draw and fill.</li>
15995 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
15996 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
15997 * </ul>
15998 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
15999 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
16000 * @public
16001 * @since 2.1.000 (2008-01-08)
16003 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()) {
16004 if (3 > $ns) {
16005 $ns = 3;
16007 if ($draw_circle) {
16008 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
16010 $p = array();
16011 for ($i = 0; $i < $ns; ++$i) {
16012 $a = $angle + ($i * 360 / $ns);
16013 $a_rad = deg2rad((float) $a);
16014 $p[] = $x0 + ($r * sin($a_rad));
16015 $p[] = $y0 + ($r * cos($a_rad));
16017 $this->Polygon($p, $style, $line_style, $fill_color);
16021 * Draws a star polygon
16022 * @param $x0 (float) Abscissa of center point.
16023 * @param $y0 (float) Ordinate of center point.
16024 * @param $r (float) Radius of inscribed circle.
16025 * @param $nv (integer) Number of vertices.
16026 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
16027 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
16028 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
16029 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
16030 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
16031 * <ul>
16032 * <li>all: Line style of all sides. Array like for
16033 * SetLineStyle().</li>
16034 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
16035 * </ul>
16036 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
16037 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
16038 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
16039 * <ul>
16040 * <li>D or empty string: Draw (default).</li>
16041 * <li>F: Fill.</li>
16042 * <li>DF or FD: Draw and fill.</li>
16043 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
16044 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
16045 * </ul>
16046 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
16047 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
16048 * @public
16049 * @since 2.1.000 (2008-01-08)
16051 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()) {
16052 if ($nv < 2) {
16053 $nv = 2;
16055 if ($draw_circle) {
16056 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
16058 $p2 = array();
16059 $visited = array();
16060 for ($i = 0; $i < $nv; ++$i) {
16061 $a = $angle + ($i * 360 / $nv);
16062 $a_rad = deg2rad((float) $a);
16063 $p2[] = $x0 + ($r * sin($a_rad));
16064 $p2[] = $y0 + ($r * cos($a_rad));
16065 $visited[] = false;
16067 $p = array();
16068 $i = 0;
16069 do {
16070 $p[] = $p2[$i * 2];
16071 $p[] = $p2[($i * 2) + 1];
16072 $visited[$i] = true;
16073 $i += $ng;
16074 $i %= $nv;
16075 } while (!$visited[$i]);
16076 $this->Polygon($p, $style, $line_style, $fill_color);
16080 * Draws a rounded rectangle.
16081 * @param $x (float) Abscissa of upper-left corner.
16082 * @param $y (float) Ordinate of upper-left corner.
16083 * @param $w (float) Width.
16084 * @param $h (float) Height.
16085 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
16086 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
16087 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
16088 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
16089 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
16090 * @public
16091 * @since 2.1.000 (2008-01-08)
16093 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
16094 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
16098 * Draws a rounded rectangle.
16099 * @param $x (float) Abscissa of upper-left corner.
16100 * @param $y (float) Ordinate of upper-left corner.
16101 * @param $w (float) Width.
16102 * @param $h (float) Height.
16103 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
16104 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
16105 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
16106 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
16107 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
16108 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
16109 * @public
16110 * @since 4.9.019 (2010-04-22)
16112 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
16113 if ($this->state != 2) {
16114 return;
16116 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
16117 // Not rounded
16118 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
16119 return;
16121 // Rounded
16122 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
16123 $this->SetFillColorArray($fill_color);
16125 $op = $this->getPathPaintOperator($style);
16126 if ($op == 'f') {
16127 $border_style = array();
16129 if ($border_style) {
16130 $this->SetLineStyle($border_style);
16132 $MyArc = 4 / 3 * (sqrt(2) - 1);
16133 $this->_outPoint($x + $rx, $y);
16134 $xc = $x + $w - $rx;
16135 $yc = $y + $ry;
16136 $this->_outLine($xc, $y);
16137 if ($round_corner[0]) {
16138 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
16139 } else {
16140 $this->_outLine($x + $w, $y);
16142 $xc = $x + $w - $rx;
16143 $yc = $y + $h - $ry;
16144 $this->_outLine($x + $w, $yc);
16145 if ($round_corner[1]) {
16146 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
16147 } else {
16148 $this->_outLine($x + $w, $y + $h);
16150 $xc = $x + $rx;
16151 $yc = $y + $h - $ry;
16152 $this->_outLine($xc, $y + $h);
16153 if ($round_corner[2]) {
16154 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
16155 } else {
16156 $this->_outLine($x, $y + $h);
16158 $xc = $x + $rx;
16159 $yc = $y + $ry;
16160 $this->_outLine($x, $yc);
16161 if ($round_corner[3]) {
16162 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
16163 } else {
16164 $this->_outLine($x, $y);
16165 $this->_outLine($x + $rx, $y);
16167 $this->_out($op);
16171 * Draws a grahic arrow.
16172 * @param $x0 (float) Abscissa of first point.
16173 * @param $y0 (float) Ordinate of first point.
16174 * @param $x1 (float) Abscissa of second point.
16175 * @param $y1 (float) Ordinate of second point.
16176 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
16177 * @param $arm_size (float) length of arrowhead arms
16178 * @param $arm_angle (int) angle between an arm and the shaft
16179 * @author Piotr Galecki, Nicola Asuni, Andy Meier
16180 * @since 4.6.018 (2009-07-10)
16182 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
16183 // getting arrow direction angle
16184 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
16185 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
16186 if ($dir_angle < 0) {
16187 $dir_angle += (2 * M_PI);
16189 $arm_angle = deg2rad($arm_angle);
16190 $sx1 = $x1;
16191 $sy1 = $y1;
16192 if ($head_style > 0) {
16193 // calculate the stopping point for the arrow shaft
16194 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
16195 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
16197 // main arrow line / shaft
16198 $this->Line($x0, $y0, $sx1, $sy1);
16199 // left arrowhead arm tip
16200 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
16201 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
16202 // right arrowhead arm tip
16203 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
16204 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
16205 $mode = 'D';
16206 $style = array();
16207 switch ($head_style) {
16208 case 0: {
16209 // draw only arrowhead arms
16210 $mode = 'D';
16211 $style = array(1, 1, 0);
16212 break;
16214 case 1: {
16215 // draw closed arrowhead, but no fill
16216 $mode = 'D';
16217 break;
16219 case 2: {
16220 // closed and filled arrowhead
16221 $mode = 'DF';
16222 break;
16224 case 3: {
16225 // filled arrowhead
16226 $mode = 'F';
16227 break;
16230 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
16233 // END GRAPHIC FUNCTIONS SECTION -----------------------
16235 // BIDIRECTIONAL TEXT SECTION --------------------------
16238 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
16239 * @param $str (string) string to manipulate.
16240 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
16241 * @param $forcertl (bool) if true forces RTL text direction
16242 * @return string
16243 * @protected
16244 * @author Nicola Asuni
16245 * @since 2.1.000 (2008-01-08)
16247 protected function utf8StrRev($str, $setbom=false, $forcertl=false) {
16248 return $this->utf8StrArrRev($this->UTF8StringToArray($str), $str, $setbom, $forcertl);
16252 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
16253 * @param $arr (array) array of unicode values.
16254 * @param $str (string) string to manipulate (or empty value).
16255 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
16256 * @param $forcertl (bool) if true forces RTL text direction
16257 * @return string
16258 * @protected
16259 * @author Nicola Asuni
16260 * @since 4.9.000 (2010-03-27)
16262 protected function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false) {
16263 return $this->arrUTF8ToUTF16BE($this->utf8Bidi($arr, $str, $forcertl), $setbom);
16267 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
16268 * @param $ta (array) array of characters composing the string.
16269 * @param $str (string) string to process
16270 * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
16271 * @return array of unicode chars
16272 * @author Nicola Asuni
16273 * @protected
16274 * @since 2.4.000 (2008-03-06)
16276 protected function utf8Bidi($ta, $str='', $forcertl=false) {
16277 // paragraph embedding level
16278 $pel = 0;
16279 // max level
16280 $maxlevel = 0;
16281 if ($this->empty_string($str)) {
16282 // create string from array
16283 $str = $this->UTF8ArrSubString($ta);
16285 // check if string contains arabic text
16286 if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $str)) {
16287 $arabic = true;
16288 } else {
16289 $arabic = false;
16291 // check if string contains RTL text
16292 if (!($forcertl OR $arabic OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $str))) {
16293 return $ta;
16296 // get number of chars
16297 $numchars = count($ta);
16299 if ($forcertl == 'R') {
16300 $pel = 1;
16301 } elseif ($forcertl == 'L') {
16302 $pel = 0;
16303 } else {
16304 // P2. In each paragraph, find the first character of type L, AL, or R.
16305 // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
16306 for ($i=0; $i < $numchars; ++$i) {
16307 $type = $this->unicode->uni_type[$ta[$i]];
16308 if ($type == 'L') {
16309 $pel = 0;
16310 break;
16311 } elseif (($type == 'AL') OR ($type == 'R')) {
16312 $pel = 1;
16313 break;
16318 // Current Embedding Level
16319 $cel = $pel;
16320 // directional override status
16321 $dos = 'N';
16322 $remember = array();
16323 // start-of-level-run
16324 $sor = $pel % 2 ? 'R' : 'L';
16325 $eor = $sor;
16327 // Array of characters data
16328 $chardata = Array();
16330 // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
16331 // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
16332 for ($i=0; $i < $numchars; ++$i) {
16333 if ($ta[$i] == $this->unicode->uni_RLE) {
16334 // X2. With each RLE, compute the least greater odd embedding level.
16335 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
16336 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
16337 $next_level = $cel + ($cel % 2) + 1;
16338 if ($next_level < 62) {
16339 $remember[] = array('num' => $this->unicode->uni_RLE, 'cel' => $cel, 'dos' => $dos);
16340 $cel = $next_level;
16341 $dos = 'N';
16342 $sor = $eor;
16343 $eor = $cel % 2 ? 'R' : 'L';
16345 } elseif ($ta[$i] == $this->unicode->uni_LRE) {
16346 // X3. With each LRE, compute the least greater even embedding level.
16347 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
16348 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
16349 $next_level = $cel + 2 - ($cel % 2);
16350 if ( $next_level < 62 ) {
16351 $remember[] = array('num' => $this->unicode->uni_LRE, 'cel' => $cel, 'dos' => $dos);
16352 $cel = $next_level;
16353 $dos = 'N';
16354 $sor = $eor;
16355 $eor = $cel % 2 ? 'R' : 'L';
16357 } elseif ($ta[$i] == $this->unicode->uni_RLO) {
16358 // X4. With each RLO, compute the least greater odd embedding level.
16359 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
16360 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
16361 $next_level = $cel + ($cel % 2) + 1;
16362 if ($next_level < 62) {
16363 $remember[] = array('num' => $this->unicode->uni_RLO, 'cel' => $cel, 'dos' => $dos);
16364 $cel = $next_level;
16365 $dos = 'R';
16366 $sor = $eor;
16367 $eor = $cel % 2 ? 'R' : 'L';
16369 } elseif ($ta[$i] == $this->unicode->uni_LRO) {
16370 // X5. With each LRO, compute the least greater even embedding level.
16371 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
16372 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
16373 $next_level = $cel + 2 - ($cel % 2);
16374 if ( $next_level < 62 ) {
16375 $remember[] = array('num' => $this->unicode->uni_LRO, 'cel' => $cel, 'dos' => $dos);
16376 $cel = $next_level;
16377 $dos = 'L';
16378 $sor = $eor;
16379 $eor = $cel % 2 ? 'R' : 'L';
16381 } elseif ($ta[$i] == $this->unicode->uni_PDF) {
16382 // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
16383 if (count($remember)) {
16384 $last = count($remember ) - 1;
16385 if (($remember[$last]['num'] == $this->unicode->uni_RLE) OR
16386 ($remember[$last]['num'] == $this->unicode->uni_LRE) OR
16387 ($remember[$last]['num'] == $this->unicode->uni_RLO) OR
16388 ($remember[$last]['num'] == $this->unicode->uni_LRO)) {
16389 $match = array_pop($remember);
16390 $cel = $match['cel'];
16391 $dos = $match['dos'];
16392 $sor = $eor;
16393 $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
16396 } elseif (($ta[$i] != $this->unicode->uni_RLE) AND
16397 ($ta[$i] != $this->unicode->uni_LRE) AND
16398 ($ta[$i] != $this->unicode->uni_RLO) AND
16399 ($ta[$i] != $this->unicode->uni_LRO) AND
16400 ($ta[$i] != $this->unicode->uni_PDF)) {
16401 // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
16402 // a. Set the level of the current character to the current embedding level.
16403 // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
16404 if ($dos != 'N') {
16405 $chardir = $dos;
16406 } else {
16407 if (isset($this->unicode->uni_type[$ta[$i]])) {
16408 $chardir = $this->unicode->uni_type[$ta[$i]];
16409 } else {
16410 $chardir = 'L';
16413 // stores string characters and other information
16414 $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
16416 } // end for each char
16418 // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
16419 // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
16420 // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
16422 // 3.3.3 Resolving Weak Types
16423 // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
16424 // Nonspacing marks are now resolved based on the previous characters.
16425 $numchars = count($chardata);
16427 // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
16428 $prevlevel = -1; // track level changes
16429 $levcount = 0; // counts consecutive chars at the same level
16430 for ($i=0; $i < $numchars; ++$i) {
16431 if ($chardata[$i]['type'] == 'NSM') {
16432 if ($levcount) {
16433 $chardata[$i]['type'] = $chardata[$i]['sor'];
16434 } elseif ($i > 0) {
16435 $chardata[$i]['type'] = $chardata[($i-1)]['type'];
16438 if ($chardata[$i]['level'] != $prevlevel) {
16439 $levcount = 0;
16440 } else {
16441 ++$levcount;
16443 $prevlevel = $chardata[$i]['level'];
16446 // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
16447 $prevlevel = -1;
16448 $levcount = 0;
16449 for ($i=0; $i < $numchars; ++$i) {
16450 if ($chardata[$i]['char'] == 'EN') {
16451 for ($j=$levcount; $j >= 0; $j--) {
16452 if ($chardata[$j]['type'] == 'AL') {
16453 $chardata[$i]['type'] = 'AN';
16454 } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
16455 break;
16459 if ($chardata[$i]['level'] != $prevlevel) {
16460 $levcount = 0;
16461 } else {
16462 ++$levcount;
16464 $prevlevel = $chardata[$i]['level'];
16467 // W3. Change all ALs to R.
16468 for ($i=0; $i < $numchars; ++$i) {
16469 if ($chardata[$i]['type'] == 'AL') {
16470 $chardata[$i]['type'] = 'R';
16474 // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
16475 $prevlevel = -1;
16476 $levcount = 0;
16477 for ($i=0; $i < $numchars; ++$i) {
16478 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16479 if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
16480 $chardata[$i]['type'] = 'EN';
16481 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
16482 $chardata[$i]['type'] = 'EN';
16483 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
16484 $chardata[$i]['type'] = 'AN';
16487 if ($chardata[$i]['level'] != $prevlevel) {
16488 $levcount = 0;
16489 } else {
16490 ++$levcount;
16492 $prevlevel = $chardata[$i]['level'];
16495 // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
16496 $prevlevel = -1;
16497 $levcount = 0;
16498 for ($i=0; $i < $numchars; ++$i) {
16499 if ($chardata[$i]['type'] == 'ET') {
16500 if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
16501 $chardata[$i]['type'] = 'EN';
16502 } else {
16503 $j = $i+1;
16504 while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
16505 if ($chardata[$j]['type'] == 'EN') {
16506 $chardata[$i]['type'] = 'EN';
16507 break;
16508 } elseif ($chardata[$j]['type'] != 'ET') {
16509 break;
16511 ++$j;
16515 if ($chardata[$i]['level'] != $prevlevel) {
16516 $levcount = 0;
16517 } else {
16518 ++$levcount;
16520 $prevlevel = $chardata[$i]['level'];
16523 // W6. Otherwise, separators and terminators change to Other Neutral.
16524 $prevlevel = -1;
16525 $levcount = 0;
16526 for ($i=0; $i < $numchars; ++$i) {
16527 if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
16528 $chardata[$i]['type'] = 'ON';
16530 if ($chardata[$i]['level'] != $prevlevel) {
16531 $levcount = 0;
16532 } else {
16533 ++$levcount;
16535 $prevlevel = $chardata[$i]['level'];
16538 //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
16539 $prevlevel = -1;
16540 $levcount = 0;
16541 for ($i=0; $i < $numchars; ++$i) {
16542 if ($chardata[$i]['char'] == 'EN') {
16543 for ($j=$levcount; $j >= 0; $j--) {
16544 if ($chardata[$j]['type'] == 'L') {
16545 $chardata[$i]['type'] = 'L';
16546 } elseif ($chardata[$j]['type'] == 'R') {
16547 break;
16551 if ($chardata[$i]['level'] != $prevlevel) {
16552 $levcount = 0;
16553 } else {
16554 ++$levcount;
16556 $prevlevel = $chardata[$i]['level'];
16559 // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
16560 $prevlevel = -1;
16561 $levcount = 0;
16562 for ($i=0; $i < $numchars; ++$i) {
16563 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16564 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
16565 $chardata[$i]['type'] = 'L';
16566 } elseif (($chardata[$i]['type'] == 'N') AND
16567 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
16568 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
16569 $chardata[$i]['type'] = 'R';
16570 } elseif ($chardata[$i]['type'] == 'N') {
16571 // N2. Any remaining neutrals take the embedding direction
16572 $chardata[$i]['type'] = $chardata[$i]['sor'];
16574 } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16575 // first char
16576 if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
16577 $chardata[$i]['type'] = 'L';
16578 } elseif (($chardata[$i]['type'] == 'N') AND
16579 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
16580 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
16581 $chardata[$i]['type'] = 'R';
16582 } elseif ($chardata[$i]['type'] == 'N') {
16583 // N2. Any remaining neutrals take the embedding direction
16584 $chardata[$i]['type'] = $chardata[$i]['sor'];
16586 } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
16587 //last char
16588 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
16589 $chardata[$i]['type'] = 'L';
16590 } elseif (($chardata[$i]['type'] == 'N') AND
16591 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
16592 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
16593 $chardata[$i]['type'] = 'R';
16594 } elseif ($chardata[$i]['type'] == 'N') {
16595 // N2. Any remaining neutrals take the embedding direction
16596 $chardata[$i]['type'] = $chardata[$i]['sor'];
16598 } elseif ($chardata[$i]['type'] == 'N') {
16599 // N2. Any remaining neutrals take the embedding direction
16600 $chardata[$i]['type'] = $chardata[$i]['sor'];
16602 if ($chardata[$i]['level'] != $prevlevel) {
16603 $levcount = 0;
16604 } else {
16605 ++$levcount;
16607 $prevlevel = $chardata[$i]['level'];
16610 // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
16611 // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
16612 for ($i=0; $i < $numchars; ++$i) {
16613 $odd = $chardata[$i]['level'] % 2;
16614 if ($odd) {
16615 if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
16616 $chardata[$i]['level'] += 1;
16618 } else {
16619 if ($chardata[$i]['type'] == 'R') {
16620 $chardata[$i]['level'] += 1;
16621 } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
16622 $chardata[$i]['level'] += 2;
16625 $maxlevel = max($chardata[$i]['level'],$maxlevel);
16628 // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
16629 // 1. Segment separators,
16630 // 2. Paragraph separators,
16631 // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
16632 // 4. Any sequence of white space characters at the end of the line.
16633 for ($i=0; $i < $numchars; ++$i) {
16634 if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
16635 $chardata[$i]['level'] = $pel;
16636 } elseif ($chardata[$i]['type'] == 'WS') {
16637 $j = $i+1;
16638 while ($j < $numchars) {
16639 if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
16640 (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
16641 $chardata[$i]['level'] = $pel;
16642 break;
16643 } elseif ($chardata[$j]['type'] != 'WS') {
16644 break;
16646 ++$j;
16651 // Arabic Shaping
16652 // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
16653 if ($arabic) {
16654 $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
16655 $alfletter = array(1570,1571,1573,1575);
16656 $chardata2 = $chardata;
16657 $laaletter = false;
16658 $charAL = array();
16659 $x = 0;
16660 for ($i=0; $i < $numchars; ++$i) {
16661 if (($this->unicode->uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
16662 $charAL[$x] = $chardata[$i];
16663 $charAL[$x]['i'] = $i;
16664 $chardata[$i]['x'] = $x;
16665 ++$x;
16668 $numAL = $x;
16669 for ($i=0; $i < $numchars; ++$i) {
16670 $thischar = $chardata[$i];
16671 if ($i > 0) {
16672 $prevchar = $chardata[($i-1)];
16673 } else {
16674 $prevchar = false;
16676 if (($i+1) < $numchars) {
16677 $nextchar = $chardata[($i+1)];
16678 } else {
16679 $nextchar = false;
16681 if ($this->unicode->uni_type[$thischar['char']] == 'AL') {
16682 $x = $thischar['x'];
16683 if ($x > 0) {
16684 $prevchar = $charAL[($x-1)];
16685 } else {
16686 $prevchar = false;
16688 if (($x+1) < $numAL) {
16689 $nextchar = $charAL[($x+1)];
16690 } else {
16691 $nextchar = false;
16693 // if laa letter
16694 if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
16695 $arabicarr = $this->unicode->uni_laa_array;
16696 $laaletter = true;
16697 if ($x > 1) {
16698 $prevchar = $charAL[($x-2)];
16699 } else {
16700 $prevchar = false;
16702 } else {
16703 $arabicarr = $this->unicode->uni_arabicsubst;
16704 $laaletter = false;
16706 if (($prevchar !== false) AND ($nextchar !== false) AND
16707 (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
16708 (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
16709 ($prevchar['type'] == $thischar['type']) AND
16710 ($nextchar['type'] == $thischar['type']) AND
16711 ($nextchar['char'] != 1567)) {
16712 if (in_array($prevchar['char'], $endedletter)) {
16713 if (isset($arabicarr[$thischar['char']][2])) {
16714 // initial
16715 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
16717 } else {
16718 if (isset($arabicarr[$thischar['char']][3])) {
16719 // medial
16720 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
16723 } elseif (($nextchar !== false) AND
16724 (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
16725 ($nextchar['type'] == $thischar['type']) AND
16726 ($nextchar['char'] != 1567)) {
16727 if (isset($arabicarr[$chardata[$i]['char']][2])) {
16728 // initial
16729 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
16731 } elseif ((($prevchar !== false) AND
16732 (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
16733 ($prevchar['type'] == $thischar['type'])) OR
16734 (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
16735 // final
16736 if (($i > 1) AND ($thischar['char'] == 1607) AND
16737 ($chardata[$i-1]['char'] == 1604) AND
16738 ($chardata[$i-2]['char'] == 1604)) {
16739 //Allah Word
16740 // mark characters to delete with false
16741 $chardata2[$i-2]['char'] = false;
16742 $chardata2[$i-1]['char'] = false;
16743 $chardata2[$i]['char'] = 65010;
16744 } else {
16745 if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
16746 if (isset($arabicarr[$thischar['char']][0])) {
16747 // isolated
16748 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
16750 } else {
16751 if (isset($arabicarr[$thischar['char']][1])) {
16752 // final
16753 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
16757 } elseif (isset($arabicarr[$thischar['char']][0])) {
16758 // isolated
16759 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
16761 // if laa letter
16762 if ($laaletter) {
16763 // mark characters to delete with false
16764 $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
16766 } // end if AL (Arabic Letter)
16767 } // end for each char
16769 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
16770 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
16772 for ($i = 0; $i < ($numchars-1); ++$i) {
16773 if (($chardata2[$i]['char'] == 1617) AND (isset($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])]))) {
16774 // check if the subtitution font is defined on current font
16775 if (isset($this->CurrentFont['cw'][($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])])])) {
16776 $chardata2[$i]['char'] = false;
16777 $chardata2[$i+1]['char'] = $this->unicode->uni_diacritics[($chardata2[$i+1]['char'])];
16781 // remove marked characters
16782 foreach ($chardata2 as $key => $value) {
16783 if ($value['char'] === false) {
16784 unset($chardata2[$key]);
16787 $chardata = array_values($chardata2);
16788 $numchars = count($chardata);
16789 unset($chardata2);
16790 unset($arabicarr);
16791 unset($laaletter);
16792 unset($charAL);
16795 // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
16796 for ($j=$maxlevel; $j > 0; $j--) {
16797 $ordarray = Array();
16798 $revarr = Array();
16799 $onlevel = false;
16800 for ($i=0; $i < $numchars; ++$i) {
16801 if ($chardata[$i]['level'] >= $j) {
16802 $onlevel = true;
16803 if (isset($this->unicode->uni_mirror[$chardata[$i]['char']])) {
16804 // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
16805 $chardata[$i]['char'] = $this->unicode->uni_mirror[$chardata[$i]['char']];
16807 $revarr[] = $chardata[$i];
16808 } else {
16809 if ($onlevel) {
16810 $revarr = array_reverse($revarr);
16811 $ordarray = array_merge($ordarray, $revarr);
16812 $revarr = Array();
16813 $onlevel = false;
16815 $ordarray[] = $chardata[$i];
16818 if ($onlevel) {
16819 $revarr = array_reverse($revarr);
16820 $ordarray = array_merge($ordarray, $revarr);
16822 $chardata = $ordarray;
16825 $ordarray = array();
16826 for ($i=0; $i < $numchars; ++$i) {
16827 $ordarray[] = $chardata[$i]['char'];
16828 // store char values for subsetting
16829 $this->CurrentFont['subsetchars'][$chardata[$i]['char']] = true;
16831 // update font subsetchars
16832 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
16833 return $ordarray;
16836 // END OF BIDIRECTIONAL TEXT SECTION -------------------
16839 * Encode a name object.
16840 * @param $name (string) Name object to encode.
16841 * @return (string) Encoded name object.
16842 * @protected
16843 * @author Nicola Asuni
16844 * @since 5.9.097 (2011-06-23)
16846 protected function encodeNameObject($name) {
16847 $escname = '';
16848 $length = strlen($name);
16849 for ($i = 0; $i < $length; ++$i) {
16850 $chr = $name[$i];
16851 if (preg_match('/[0-9a-zA-Z]/', $chr) == 1) {
16852 $escname .= $chr;
16853 } else {
16854 $escname .= sprintf('#%02X', ord($chr));
16857 return $escname;
16861 * Add a Named Destination.
16862 * NOTE: destination names are unique, so only last entry will be saved.
16863 * @param $name (string) Destination name.
16864 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
16865 * @param $page (int) Target page number (leave empty for current page).
16866 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
16867 * @return (string) Stripped named destination identifier or false in case of error.
16868 * @public
16869 * @author Christian Deligant, Nicola Asuni
16870 * @since 5.9.097 (2011-06-23)
16872 public function setDestination($name, $y=-1, $page='', $x=-1) {
16873 // remove unsupported characters
16874 $name = $this->encodeNameObject($name);
16875 if ($this->empty_string($name)) {
16876 return false;
16878 if ($y == -1) {
16879 $y = $this->GetY();
16880 } elseif ($y < 0) {
16881 $y = 0;
16882 } elseif ($y > $this->h) {
16883 $y = $this->h;
16885 if ($x == -1) {
16886 $x = $this->GetX();
16887 } elseif ($x < 0) {
16888 $x = 0;
16889 } elseif ($x > $this->w) {
16890 $x = $this->w;
16892 if (empty($page)) {
16893 $page = $this->PageNo();
16894 if (empty($page)) {
16895 return;
16898 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page);
16899 return $name;
16903 * Return the Named Destination array.
16904 * @return (array) Named Destination array.
16905 * @public
16906 * @author Nicola Asuni
16907 * @since 5.9.097 (2011-06-23)
16909 public function getDestination() {
16910 return $this->dests;
16914 * Insert Named Destinations.
16915 * @protected
16916 * @author Johannes Güntert, Nicola Asuni
16917 * @since 5.9.098 (2011-06-23)
16919 protected function _putdests() {
16920 if (empty($this->dests)) {
16921 return;
16923 $this->n_dests = $this->_newobj();
16924 $out = ' <<';
16925 foreach($this->dests as $name => $o) {
16926 $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)));
16928 $out .= ' >>';
16929 $out .= "\n".'endobj';
16930 $this->_out($out);
16934 * Adds a bookmark - alias for Bookmark().
16935 * @param $txt (string) Bookmark description.
16936 * @param $level (int) Bookmark level (minimum value is 0).
16937 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
16938 * @param $page (int) Target page number (leave empty for current page).
16939 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
16940 * @param $color (array) RGB color array (values from 0 to 255).
16941 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
16942 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
16943 * @public
16945 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
16946 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
16950 * Adds a bookmark.
16951 * @param $txt (string) Bookmark description.
16952 * @param $level (int) Bookmark level (minimum value is 0).
16953 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
16954 * @param $page (int) Target page number (leave empty for current page).
16955 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
16956 * @param $color (array) RGB color array (values from 0 to 255).
16957 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
16958 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
16959 * @public
16960 * @since 2.1.002 (2008-02-12)
16962 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
16963 if ($level < 0) {
16964 $level = 0;
16966 if (isset($this->outlines[0])) {
16967 $lastoutline = end($this->outlines);
16968 $maxlevel = $lastoutline['l'] + 1;
16969 } else {
16970 $maxlevel = 0;
16972 if ($level > $maxlevel) {
16973 $level = $maxlevel;
16975 if ($y == -1) {
16976 $y = $this->GetY();
16977 } elseif ($y < 0) {
16978 $y = 0;
16979 } elseif ($y > $this->h) {
16980 $y = $this->h;
16982 if ($x == -1) {
16983 $x = $this->GetX();
16984 } elseif ($x < 0) {
16985 $x = 0;
16986 } elseif ($x > $this->w) {
16987 $x = $this->w;
16989 if (empty($page)) {
16990 $page = $this->PageNo();
16991 if (empty($page)) {
16992 return;
16995 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
16999 * Sort bookmarks for page and key.
17000 * @protected
17001 * @since 5.9.119 (2011-09-19)
17003 protected function sortBookmarks() {
17004 // get sorting columns
17005 $outline_p = array();
17006 $outline_y = array();
17007 foreach ($this->outlines as $key => $row) {
17008 $outline_p[$key] = $row['p'];
17009 $outline_k[$key] = $key;
17011 // sort outlines by page and original position
17012 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
17016 * Create a bookmark PDF string.
17017 * @protected
17018 * @author Olivier Plathey, Nicola Asuni
17019 * @since 2.1.002 (2008-02-12)
17021 protected function _putbookmarks() {
17022 $nb = count($this->outlines);
17023 if ($nb == 0) {
17024 return;
17026 // sort bookmarks
17027 $this->sortBookmarks();
17028 $lru = array();
17029 $level = 0;
17030 foreach ($this->outlines as $i => $o) {
17031 if ($o['l'] > 0) {
17032 $parent = $lru[($o['l'] - 1)];
17033 //Set parent and last pointers
17034 $this->outlines[$i]['parent'] = $parent;
17035 $this->outlines[$parent]['last'] = $i;
17036 if ($o['l'] > $level) {
17037 //Level increasing: set first pointer
17038 $this->outlines[$parent]['first'] = $i;
17040 } else {
17041 $this->outlines[$i]['parent'] = $nb;
17043 if (($o['l'] <= $level) AND ($i > 0)) {
17044 //Set prev and next pointers
17045 $prev = $lru[$o['l']];
17046 $this->outlines[$prev]['next'] = $i;
17047 $this->outlines[$i]['prev'] = $prev;
17049 $lru[$o['l']] = $i;
17050 $level = $o['l'];
17052 //Outline items
17053 $n = $this->n + 1;
17054 $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';
17055 foreach ($this->outlines as $i => $o) {
17056 $oid = $this->_newobj();
17057 // covert HTML title to string
17058 $title = preg_replace($nltags, "\n", $o['t']);
17059 $title = preg_replace("/[\r]+/si", '', $title);
17060 $title = preg_replace("/[\n]+/si", "\n", $title);
17061 $title = strip_tags($title);
17062 $title = $this->stringTrim($title);
17063 $out = '<</Title '.$this->_textstring($title, $oid);
17064 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
17065 if (isset($o['prev'])) {
17066 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
17068 if (isset($o['next'])) {
17069 $out .= ' /Next '.($n + $o['next']).' 0 R';
17071 if (isset($o['first'])) {
17072 $out .= ' /First '.($n + $o['first']).' 0 R';
17074 if (isset($o['last'])) {
17075 $out .= ' /Last '.($n + $o['last']).' 0 R';
17077 if (isset($o['u']) AND !empty($o['u'])) {
17078 // link
17079 if (is_string($o['u'])) {
17080 if ($o['u'][0] == '#') {
17081 // internal destination
17082 $out .= ' /Dest /'.$this->encodeNameObject(substr($o['u'], 1));
17083 } elseif ($o['u'][0] == '%') {
17084 // embedded PDF file
17085 $filename = basename(substr($o['u'], 1));
17086 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
17087 } elseif ($o['u'][0] == '*') {
17088 // embedded generic file
17089 $filename = basename(substr($o['u'], 1));
17090 $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});';
17091 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
17092 } else {
17093 // external URI link
17094 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
17096 } elseif (isset($this->links[$o['u']])) {
17097 // internal link ID
17098 $l = $this->links[$o['u']];
17099 if (isset($this->page_obj_id[($l[0])])) {
17100 $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
17103 } elseif (isset($this->page_obj_id[($o['p'])])) {
17104 // link to a page
17105 $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)));
17107 // set font style
17108 $style = 0;
17109 if (!empty($o['s'])) {
17110 // bold
17111 if (strpos($o['s'], 'B') !== false) {
17112 $style |= 2;
17114 // oblique
17115 if (strpos($o['s'], 'I') !== false) {
17116 $style |= 1;
17119 $out .= sprintf(' /F %d', $style);
17120 // set bookmark color
17121 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
17122 $color = array_values($o['c']);
17123 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
17124 } else {
17125 // black
17126 $out .= ' /C [0.0 0.0 0.0]';
17128 $out .= ' /Count 0'; // normally closed item
17129 $out .= ' >>';
17130 $out .= "\n".'endobj';
17131 $this->_out($out);
17133 //Outline root
17134 $this->OutlineRoot = $this->_newobj();
17135 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
17138 // --- JAVASCRIPT ------------------------------------------------------
17141 * Adds a javascript
17142 * @param $script (string) Javascript code
17143 * @public
17144 * @author Johannes Güntert, Nicola Asuni
17145 * @since 2.1.002 (2008-02-12)
17147 public function IncludeJS($script) {
17148 $this->javascript .= $script;
17152 * Adds a javascript object and return object ID
17153 * @param $script (string) Javascript code
17154 * @param $onload (boolean) if true executes this object when opening the document
17155 * @return int internal object ID
17156 * @public
17157 * @author Nicola Asuni
17158 * @since 4.8.000 (2009-09-07)
17160 public function addJavascriptObject($script, $onload=false) {
17161 if ($this->pdfa_mode) {
17162 // javascript is not allowed in PDF/A mode
17163 return false;
17165 ++$this->n;
17166 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
17167 return $this->n;
17171 * Create a javascript PDF string.
17172 * @protected
17173 * @author Johannes Güntert, Nicola Asuni
17174 * @since 2.1.002 (2008-02-12)
17176 protected function _putjavascript() {
17177 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
17178 return;
17180 if (strpos($this->javascript, 'this.addField') > 0) {
17181 if (!$this->ur['enabled']) {
17182 //$this->setUserRights();
17184 // the following two lines are used to avoid form fields duplication after saving
17185 // The addField method only works when releasing user rights (UR3)
17186 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
17187 $jsb = "getField('tcpdfdocsaved').value='saved';";
17188 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
17190 // name tree for javascript
17191 $this->n_js = '<< /Names [';
17192 if (!empty($this->javascript)) {
17193 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
17195 if (!empty($this->js_objects)) {
17196 foreach ($this->js_objects as $key => $val) {
17197 if ($val['onload']) {
17198 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
17202 $this->n_js .= ' ] >>';
17203 // default Javascript object
17204 if (!empty($this->javascript)) {
17205 $obj_id = $this->_newobj();
17206 $out = '<< /S /JavaScript';
17207 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
17208 $out .= ' >>';
17209 $out .= "\n".'endobj';
17210 $this->_out($out);
17212 // additional Javascript objects
17213 if (!empty($this->js_objects)) {
17214 foreach ($this->js_objects as $key => $val) {
17215 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
17216 $this->_out($out);
17222 * Convert color to javascript color.
17223 * @param $color (string) color name or "#RRGGBB"
17224 * @protected
17225 * @author Denis Van Nuffelen, Nicola Asuni
17226 * @since 2.1.002 (2008-02-12)
17228 protected function _JScolor($color) {
17229 static $aColors = array('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
17230 if (substr($color,0,1) == '#') {
17231 return sprintf("['RGB',%F,%F,%F]", hexdec(substr($color,1,2))/255, hexdec(substr($color,3,2))/255, hexdec(substr($color,5,2))/255);
17233 if (!in_array($color,$aColors)) {
17234 $this->Error('Invalid color: '.$color);
17236 return 'color.'.$color;
17240 * Adds a javascript form field.
17241 * @param $type (string) field type
17242 * @param $name (string) field name
17243 * @param $x (int) horizontal position
17244 * @param $y (int) vertical position
17245 * @param $w (int) width
17246 * @param $h (int) height
17247 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17248 * @protected
17249 * @author Denis Van Nuffelen, Nicola Asuni
17250 * @since 2.1.002 (2008-02-12)
17252 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
17253 if ($this->rtl) {
17254 $x = $x - $w;
17256 // the followind avoid fields duplication after saving the document
17257 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
17258 $k = $this->k;
17259 $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";
17260 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
17261 while (list($key, $val) = each($prop)) {
17262 if (strcmp(substr($key, -5), 'Color') == 0) {
17263 $val = $this->_JScolor($val);
17264 } else {
17265 $val = "'".$val."'";
17267 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
17269 if ($this->rtl) {
17270 $this->x -= $w;
17271 } else {
17272 $this->x += $w;
17274 $this->javascript .= '}';
17277 // --- FORM FIELDS -----------------------------------------------------
17280 * Convert JavaScript form fields properties array to Annotation Properties array.
17281 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17282 * @return array of annotation properties
17283 * @protected
17284 * @author Nicola Asuni
17285 * @since 4.8.000 (2009-09-06)
17287 protected function getAnnotOptFromJSProp($prop) {
17288 if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
17289 // the annotation options area lready defined
17290 return $prop['aopt'];
17292 $opt = array(); // value to be returned
17293 // alignment: Controls how the text is laid out within the text field.
17294 if (isset($prop['alignment'])) {
17295 switch ($prop['alignment']) {
17296 case 'left': {
17297 $opt['q'] = 0;
17298 break;
17300 case 'center': {
17301 $opt['q'] = 1;
17302 break;
17304 case 'right': {
17305 $opt['q'] = 2;
17306 break;
17308 default: {
17309 $opt['q'] = ($this->rtl)?2:0;
17310 break;
17314 // lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
17315 if (isset($prop['lineWidth'])) {
17316 $linewidth = intval($prop['lineWidth']);
17317 } else {
17318 $linewidth = 1;
17320 // borderStyle: The border style for a field.
17321 if (isset($prop['borderStyle'])) {
17322 switch ($prop['borderStyle']) {
17323 case 'border.d':
17324 case 'dashed': {
17325 $opt['border'] = array(0, 0, $linewidth, array(3, 2));
17326 $opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
17327 break;
17329 case 'border.b':
17330 case 'beveled': {
17331 $opt['border'] = array(0, 0, $linewidth);
17332 $opt['bs'] = array('w'=>$linewidth, 's'=>'B');
17333 break;
17335 case 'border.i':
17336 case 'inset': {
17337 $opt['border'] = array(0, 0, $linewidth);
17338 $opt['bs'] = array('w'=>$linewidth, 's'=>'I');
17339 break;
17341 case 'border.u':
17342 case 'underline': {
17343 $opt['border'] = array(0, 0, $linewidth);
17344 $opt['bs'] = array('w'=>$linewidth, 's'=>'U');
17345 break;
17347 case 'border.s':
17348 case 'solid': {
17349 $opt['border'] = array(0, 0, $linewidth);
17350 $opt['bs'] = array('w'=>$linewidth, 's'=>'S');
17351 break;
17353 default: {
17354 break;
17358 if (isset($prop['border']) AND is_array($prop['border'])) {
17359 $opt['border'] = $prop['border'];
17361 if (!isset($opt['mk'])) {
17362 $opt['mk'] = array();
17364 if (!isset($opt['mk']['if'])) {
17365 $opt['mk']['if'] = array();
17367 $opt['mk']['if']['a'] = array(0.5, 0.5);
17368 // buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
17369 if (isset($prop['buttonAlignX'])) {
17370 $opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
17372 // buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
17373 if (isset($prop['buttonAlignY'])) {
17374 $opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
17376 // buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
17377 if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
17378 $opt['mk']['if']['fb'] = true;
17380 // buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
17381 if (isset($prop['buttonScaleHow'])) {
17382 switch ($prop['buttonScaleHow']) {
17383 case 'scaleHow.proportional': {
17384 $opt['mk']['if']['s'] = 'P';
17385 break;
17387 case 'scaleHow.anamorphic': {
17388 $opt['mk']['if']['s'] = 'A';
17389 break;
17393 // buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
17394 if (isset($prop['buttonScaleWhen'])) {
17395 switch ($prop['buttonScaleWhen']) {
17396 case 'scaleWhen.always': {
17397 $opt['mk']['if']['sw'] = 'A';
17398 break;
17400 case 'scaleWhen.never': {
17401 $opt['mk']['if']['sw'] = 'N';
17402 break;
17404 case 'scaleWhen.tooBig': {
17405 $opt['mk']['if']['sw'] = 'B';
17406 break;
17408 case 'scaleWhen.tooSmall': {
17409 $opt['mk']['if']['sw'] = 'S';
17410 break;
17414 // buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
17415 if (isset($prop['buttonPosition'])) {
17416 switch ($prop['buttonPosition']) {
17417 case 0:
17418 case 'position.textOnly': {
17419 $opt['mk']['tp'] = 0;
17420 break;
17422 case 1:
17423 case 'position.iconOnly': {
17424 $opt['mk']['tp'] = 1;
17425 break;
17427 case 2:
17428 case 'position.iconTextV': {
17429 $opt['mk']['tp'] = 2;
17430 break;
17432 case 3:
17433 case 'position.textIconV': {
17434 $opt['mk']['tp'] = 3;
17435 break;
17437 case 4:
17438 case 'position.iconTextH': {
17439 $opt['mk']['tp'] = 4;
17440 break;
17442 case 5:
17443 case 'position.textIconH': {
17444 $opt['mk']['tp'] = 5;
17445 break;
17447 case 6:
17448 case 'position.overlay': {
17449 $opt['mk']['tp'] = 6;
17450 break;
17454 // fillColor: Specifies the background color for a field.
17455 if (isset($prop['fillColor'])) {
17456 if (is_array($prop['fillColor'])) {
17457 $opt['mk']['bg'] = $prop['fillColor'];
17458 } else {
17459 $opt['mk']['bg'] = $this->convertHTMLColorToDec($prop['fillColor']);
17462 // strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
17463 if (isset($prop['strokeColor'])) {
17464 if (is_array($prop['strokeColor'])) {
17465 $opt['mk']['bc'] = $prop['strokeColor'];
17466 } else {
17467 $opt['mk']['bc'] = $this->convertHTMLColorToDec($prop['strokeColor']);
17470 // rotation: The rotation of a widget in counterclockwise increments.
17471 if (isset($prop['rotation'])) {
17472 $opt['mk']['r'] = $prop['rotation'];
17474 // charLimit: Limits the number of characters that a user can type into a text field.
17475 if (isset($prop['charLimit'])) {
17476 $opt['maxlen'] = intval($prop['charLimit']);
17478 if (!isset($ff)) {
17479 $ff = 0; // default value
17481 // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
17482 if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
17483 $ff += 1 << 0;
17485 // required: Specifies whether a field requires a value.
17486 if (isset($prop['required']) AND ($prop['required'] == 'true')) {
17487 $ff += 1 << 1;
17489 // multiline: Controls how text is wrapped within the field.
17490 if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
17491 $ff += 1 << 12;
17493 // password: Specifies whether the field should display asterisks when data is entered in the field.
17494 if (isset($prop['password']) AND ($prop['password'] == 'true')) {
17495 $ff += 1 << 13;
17497 // NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
17498 if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
17499 $ff += 1 << 14;
17501 // Radio: If set, the field is a set of radio buttons.
17502 if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
17503 $ff += 1 << 15;
17505 // Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
17506 if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
17507 $ff += 1 << 16;
17509 // Combo: If set, the field is a combo box; if clear, the field is a list box.
17510 if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
17511 $ff += 1 << 17;
17513 // editable: Controls whether a combo box is editable.
17514 if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
17515 $ff += 1 << 18;
17517 // Sort: If set, the field's option items shall be sorted alphabetically.
17518 if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
17519 $ff += 1 << 19;
17521 // fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
17522 if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
17523 $ff += 1 << 20;
17525 // multipleSelection: If true, indicates that a list box allows a multiple selection of items.
17526 if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
17527 $ff += 1 << 21;
17529 // doNotSpellCheck: If true, spell checking is not performed on this editable text field.
17530 if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
17531 $ff += 1 << 22;
17533 // doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
17534 if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
17535 $ff += 1 << 23;
17537 // comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
17538 if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
17539 $ff += 1 << 24;
17541 // radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
17542 if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
17543 $ff += 1 << 25;
17545 // richText: If true, the field allows rich text formatting.
17546 if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
17547 $ff += 1 << 25;
17549 // commitOnSelChange: Controls whether a field value is committed after a selection change.
17550 if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
17551 $ff += 1 << 26;
17553 $opt['ff'] = $ff;
17554 // defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
17555 if (isset($prop['defaultValue'])) {
17556 $opt['dv'] = $prop['defaultValue'];
17558 $f = 4; // default value for annotation flags
17559 // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
17560 if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
17561 $f += 1 << 6;
17563 // display: Controls whether the field is hidden or visible on screen and in print.
17564 if (isset($prop['display'])) {
17565 if ($prop['display'] == 'display.visible') {
17567 } elseif ($prop['display'] == 'display.hidden') {
17568 $f += 1 << 1;
17569 } elseif ($prop['display'] == 'display.noPrint') {
17570 $f -= 1 << 2;
17571 } elseif ($prop['display'] == 'display.noView') {
17572 $f += 1 << 5;
17575 $opt['f'] = $f;
17576 // currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
17577 if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
17578 $opt['i'] = $prop['currentValueIndices'];
17580 // value: The value of the field data that the user has entered.
17581 if (isset($prop['value'])) {
17582 if (is_array($prop['value'])) {
17583 $opt['opt'] = array();
17584 foreach ($prop['value'] AS $key => $optval) {
17585 // exportValues: An array of strings representing the export values for the field.
17586 if (isset($prop['exportValues'][$key])) {
17587 $opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
17588 } else {
17589 $opt['opt'][$key] = $prop['value'][$key];
17592 } else {
17593 $opt['v'] = $prop['value'];
17596 // richValue: This property specifies the text contents and formatting of a rich text field.
17597 if (isset($prop['richValue'])) {
17598 $opt['rv'] = $prop['richValue'];
17600 // submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
17601 if (isset($prop['submitName'])) {
17602 $opt['tm'] = $prop['submitName'];
17604 // name: Fully qualified field name.
17605 if (isset($prop['name'])) {
17606 $opt['t'] = $prop['name'];
17608 // userName: The user name (short description string) of the field.
17609 if (isset($prop['userName'])) {
17610 $opt['tu'] = $prop['userName'];
17612 // highlight: Defines how a button reacts when a user clicks it.
17613 if (isset($prop['highlight'])) {
17614 switch ($prop['highlight']) {
17615 case 'none':
17616 case 'highlight.n': {
17617 $opt['h'] = 'N';
17618 break;
17620 case 'invert':
17621 case 'highlight.i': {
17622 $opt['h'] = 'i';
17623 break;
17625 case 'push':
17626 case 'highlight.p': {
17627 $opt['h'] = 'P';
17628 break;
17630 case 'outline':
17631 case 'highlight.o': {
17632 $opt['h'] = 'O';
17633 break;
17637 // Unsupported options:
17638 // - calcOrderIndex: Changes the calculation order of fields in the document.
17639 // - delay: Delays the redrawing of a field's appearance.
17640 // - defaultStyle: This property defines the default style attributes for the form field.
17641 // - style: Allows the user to set the glyph style of a check box or radio button.
17642 // - textColor, textFont, textSize
17643 return $opt;
17647 * Set default properties for form fields.
17648 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17649 * @public
17650 * @author Nicola Asuni
17651 * @since 4.8.000 (2009-09-06)
17653 public function setFormDefaultProp($prop=array()) {
17654 $this->default_form_prop = $prop;
17658 * Return the default properties for form fields.
17659 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17660 * @public
17661 * @author Nicola Asuni
17662 * @since 4.8.000 (2009-09-06)
17664 public function getFormDefaultProp() {
17665 return $this->default_form_prop;
17669 * Creates a text field
17670 * @param $name (string) field name
17671 * @param $w (float) Width of the rectangle
17672 * @param $h (float) Height of the rectangle
17673 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17674 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17675 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17676 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17677 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17678 * @public
17679 * @author Nicola Asuni
17680 * @since 4.8.000 (2009-09-07)
17682 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17683 if ($x === '') {
17684 $x = $this->x;
17686 if ($y === '') {
17687 $y = $this->y;
17689 // check page for no-write regions and adapt page margins if necessary
17690 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17691 if ($js) {
17692 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
17693 return;
17695 // get default style
17696 $prop = array_merge($this->getFormDefaultProp(), $prop);
17697 // get annotation data
17698 $popt = $this->getAnnotOptFromJSProp($prop);
17699 // set default appearance stream
17700 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17701 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17702 $popt['da'] = $fontstyle;
17703 // build appearance stream
17704 $popt['ap'] = array();
17705 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17706 $text = '';
17707 if (isset($prop['value']) AND !empty($prop['value'])) {
17708 $text = $prop['value'];
17709 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
17710 $text = $opt['v'];
17712 $tmpid = $this->startTemplate($w, $h, false);
17713 $align = '';
17714 if (isset($popt['q'])) {
17715 switch ($popt['q']) {
17716 case 0: {
17717 $align = 'L';
17718 break;
17720 case 1: {
17721 $align = 'C';
17722 break;
17724 case 2: {
17725 $align = 'R';
17726 break;
17728 default: {
17729 $align = '';
17730 break;
17734 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
17735 $this->endTemplate();
17736 --$this->n;
17737 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17738 unset($this->xobjects[$tmpid]);
17739 $popt['ap']['n'] .= 'Q EMC';
17740 // merge options
17741 $opt = array_merge($popt, $opt);
17742 // remove some conflicting options
17743 unset($opt['bs']);
17744 // set remaining annotation data
17745 $opt['Subtype'] = 'Widget';
17746 $opt['ft'] = 'Tx';
17747 $opt['t'] = $name;
17748 // Additional annotation's parameters (check _putannotsobj() method):
17749 //$opt['f']
17750 //$opt['as']
17751 //$opt['bs']
17752 //$opt['be']
17753 //$opt['c']
17754 //$opt['border']
17755 //$opt['h']
17756 //$opt['mk'];
17757 //$opt['mk']['r']
17758 //$opt['mk']['bc'];
17759 //$opt['mk']['bg'];
17760 unset($opt['mk']['ca']);
17761 unset($opt['mk']['rc']);
17762 unset($opt['mk']['ac']);
17763 unset($opt['mk']['i']);
17764 unset($opt['mk']['ri']);
17765 unset($opt['mk']['ix']);
17766 unset($opt['mk']['if']);
17767 //$opt['mk']['if']['sw'];
17768 //$opt['mk']['if']['s'];
17769 //$opt['mk']['if']['a'];
17770 //$opt['mk']['if']['fb'];
17771 unset($opt['mk']['tp']);
17772 //$opt['tu']
17773 //$opt['tm']
17774 //$opt['ff']
17775 //$opt['v']
17776 //$opt['dv']
17777 //$opt['a']
17778 //$opt['aa']
17779 //$opt['q']
17780 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17781 if ($this->rtl) {
17782 $this->x -= $w;
17783 } else {
17784 $this->x += $w;
17789 * Creates a RadioButton field.
17790 * @param $name (string) Field name.
17791 * @param $w (int) Width of the radio button.
17792 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17793 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
17794 * @param $onvalue (string) Value to be returned if selected.
17795 * @param $checked (boolean) Define the initial state.
17796 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17797 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17798 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
17799 * @public
17800 * @author Nicola Asuni
17801 * @since 4.8.000 (2009-09-07)
17803 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
17804 if ($x === '') {
17805 $x = $this->x;
17807 if ($y === '') {
17808 $y = $this->y;
17810 // check page for no-write regions and adapt page margins if necessary
17811 list($x, $y) = $this->checkPageRegions($w, $x, $y);
17812 if ($js) {
17813 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
17814 return;
17816 if ($this->empty_string($onvalue)) {
17817 $onvalue = 'On';
17819 if ($checked) {
17820 $defval = $onvalue;
17821 } else {
17822 $defval = 'Off';
17824 // set font
17825 $font = 'zapfdingbats';
17826 if ($this->pdfa_mode) {
17827 // all fonts must be embedded
17828 $font = 'pdfa'.$font;
17830 $this->AddFont($font);
17831 $tmpfont = $this->getFontBuffer($font);
17832 // set data for parent group
17833 if (!isset($this->radiobutton_groups[$this->page])) {
17834 $this->radiobutton_groups[$this->page] = array();
17836 if (!isset($this->radiobutton_groups[$this->page][$name])) {
17837 $this->radiobutton_groups[$this->page][$name] = array();
17838 ++$this->n;
17839 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
17840 $this->radio_groups[] = $this->n;
17842 $kid = ($this->n + 1);
17843 // save object ID to be added on Kids entry on parent object
17844 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
17845 // get default style
17846 $prop = array_merge($this->getFormDefaultProp(), $prop);
17847 $prop['NoToggleToOff'] = 'true';
17848 $prop['Radio'] = 'true';
17849 $prop['borderStyle'] = 'inset';
17850 // get annotation data
17851 $popt = $this->getAnnotOptFromJSProp($prop);
17852 // set additional default options
17853 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
17854 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
17855 $popt['da'] = $fontstyle;
17856 // build appearance stream
17857 $popt['ap'] = array();
17858 $popt['ap']['n'] = array();
17859 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
17860 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
17861 $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);
17862 $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);
17863 if (!isset($popt['mk'])) {
17864 $popt['mk'] = array();
17866 $popt['mk']['ca'] = '(l)';
17867 // merge options
17868 $opt = array_merge($popt, $opt);
17869 // set remaining annotation data
17870 $opt['Subtype'] = 'Widget';
17871 $opt['ft'] = 'Btn';
17872 if ($checked) {
17873 $opt['v'] = array('/'.$onvalue);
17874 $opt['as'] = $onvalue;
17875 } else {
17876 $opt['as'] = 'Off';
17878 // store readonly flag
17879 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
17880 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
17882 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
17883 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
17884 if ($this->rtl) {
17885 $this->x -= $w;
17886 } else {
17887 $this->x += $w;
17892 * Creates a List-box field
17893 * @param $name (string) field name
17894 * @param $w (int) width
17895 * @param $h (int) height
17896 * @param $values (array) array containing the list of values.
17897 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17898 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17899 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17900 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17901 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17902 * @public
17903 * @author Nicola Asuni
17904 * @since 4.8.000 (2009-09-07)
17906 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17907 if ($x === '') {
17908 $x = $this->x;
17910 if ($y === '') {
17911 $y = $this->y;
17913 // check page for no-write regions and adapt page margins if necessary
17914 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17915 if ($js) {
17916 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
17917 $s = '';
17918 foreach ($values as $value) {
17919 if (is_array($value)) {
17920 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
17921 } else {
17922 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
17925 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
17926 return;
17928 // get default style
17929 $prop = array_merge($this->getFormDefaultProp(), $prop);
17930 // get annotation data
17931 $popt = $this->getAnnotOptFromJSProp($prop);
17932 // set additional default values
17933 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17934 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17935 $popt['da'] = $fontstyle;
17936 // build appearance stream
17937 $popt['ap'] = array();
17938 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17939 $text = '';
17940 foreach($values as $item) {
17941 if (is_array($item)) {
17942 $text .= $item[1]."\n";
17943 } else {
17944 $text .= $item."\n";
17947 $tmpid = $this->startTemplate($w, $h, false);
17948 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
17949 $this->endTemplate();
17950 --$this->n;
17951 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17952 unset($this->xobjects[$tmpid]);
17953 $popt['ap']['n'] .= 'Q EMC';
17954 // merge options
17955 $opt = array_merge($popt, $opt);
17956 // set remaining annotation data
17957 $opt['Subtype'] = 'Widget';
17958 $opt['ft'] = 'Ch';
17959 $opt['t'] = $name;
17960 $opt['opt'] = $values;
17961 unset($opt['mk']['ca']);
17962 unset($opt['mk']['rc']);
17963 unset($opt['mk']['ac']);
17964 unset($opt['mk']['i']);
17965 unset($opt['mk']['ri']);
17966 unset($opt['mk']['ix']);
17967 unset($opt['mk']['if']);
17968 unset($opt['mk']['tp']);
17969 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17970 if ($this->rtl) {
17971 $this->x -= $w;
17972 } else {
17973 $this->x += $w;
17978 * Creates a Combo-box field
17979 * @param $name (string) field name
17980 * @param $w (int) width
17981 * @param $h (int) height
17982 * @param $values (array) array containing the list of values.
17983 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17984 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17985 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17986 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17987 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17988 * @public
17989 * @author Nicola Asuni
17990 * @since 4.8.000 (2009-09-07)
17992 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17993 if ($x === '') {
17994 $x = $this->x;
17996 if ($y === '') {
17997 $y = $this->y;
17999 // check page for no-write regions and adapt page margins if necessary
18000 list($x, $y) = $this->checkPageRegions($h, $x, $y);
18001 if ($js) {
18002 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
18003 $s = '';
18004 foreach ($values as $value) {
18005 if (is_array($value)) {
18006 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
18007 } else {
18008 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
18011 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
18012 return;
18014 // get default style
18015 $prop = array_merge($this->getFormDefaultProp(), $prop);
18016 $prop['Combo'] = true;
18017 // get annotation data
18018 $popt = $this->getAnnotOptFromJSProp($prop);
18019 // set additional default options
18020 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
18021 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
18022 $popt['da'] = $fontstyle;
18023 // build appearance stream
18024 $popt['ap'] = array();
18025 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
18026 $text = '';
18027 foreach($values as $item) {
18028 if (is_array($item)) {
18029 $text .= $item[1]."\n";
18030 } else {
18031 $text .= $item."\n";
18034 $tmpid = $this->startTemplate($w, $h, false);
18035 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
18036 $this->endTemplate();
18037 --$this->n;
18038 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
18039 unset($this->xobjects[$tmpid]);
18040 $popt['ap']['n'] .= 'Q EMC';
18041 // merge options
18042 $opt = array_merge($popt, $opt);
18043 // set remaining annotation data
18044 $opt['Subtype'] = 'Widget';
18045 $opt['ft'] = 'Ch';
18046 $opt['t'] = $name;
18047 $opt['opt'] = $values;
18048 unset($opt['mk']['ca']);
18049 unset($opt['mk']['rc']);
18050 unset($opt['mk']['ac']);
18051 unset($opt['mk']['i']);
18052 unset($opt['mk']['ri']);
18053 unset($opt['mk']['ix']);
18054 unset($opt['mk']['if']);
18055 unset($opt['mk']['tp']);
18056 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
18057 if ($this->rtl) {
18058 $this->x -= $w;
18059 } else {
18060 $this->x += $w;
18065 * Creates a CheckBox field
18066 * @param $name (string) field name
18067 * @param $w (int) width
18068 * @param $checked (boolean) define the initial state.
18069 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
18070 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
18071 * @param $onvalue (string) value to be returned if selected.
18072 * @param $x (float) Abscissa of the upper-left corner of the rectangle
18073 * @param $y (float) Ordinate of the upper-left corner of the rectangle
18074 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
18075 * @public
18076 * @author Nicola Asuni
18077 * @since 4.8.000 (2009-09-07)
18079 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
18080 if ($x === '') {
18081 $x = $this->x;
18083 if ($y === '') {
18084 $y = $this->y;
18086 // check page for no-write regions and adapt page margins if necessary
18087 list($x, $y) = $this->checkPageRegions($w, $x, $y);
18088 if ($js) {
18089 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
18090 return;
18092 if (!isset($prop['value'])) {
18093 $prop['value'] = array('Yes');
18095 // get default style
18096 $prop = array_merge($this->getFormDefaultProp(), $prop);
18097 $prop['borderStyle'] = 'inset';
18098 // get annotation data
18099 $popt = $this->getAnnotOptFromJSProp($prop);
18100 // set additional default options
18101 $font = 'zapfdingbats';
18102 if ($this->pdfa_mode) {
18103 // all fonts must be embedded
18104 $font = 'pdfa'.$font;
18106 $this->AddFont($font);
18107 $tmpfont = $this->getFontBuffer($font);
18108 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
18109 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
18110 $popt['da'] = $fontstyle;
18111 // build appearance stream
18112 $popt['ap'] = array();
18113 $popt['ap']['n'] = array();
18114 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
18115 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
18116 $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);
18117 $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);
18118 // merge options
18119 $opt = array_merge($popt, $opt);
18120 // set remaining annotation data
18121 $opt['Subtype'] = 'Widget';
18122 $opt['ft'] = 'Btn';
18123 $opt['t'] = $name;
18124 if ($this->empty_string($onvalue)) {
18125 $onvalue = 'Yes';
18127 $opt['opt'] = array($onvalue);
18128 if ($checked) {
18129 $opt['v'] = array('/Yes');
18130 $opt['as'] = 'Yes';
18131 } else {
18132 $opt['v'] = array('/Off');
18133 $opt['as'] = 'Off';
18135 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
18136 if ($this->rtl) {
18137 $this->x -= $w;
18138 } else {
18139 $this->x += $w;
18144 * Creates a button field
18145 * @param $name (string) field name
18146 * @param $w (int) width
18147 * @param $h (int) height
18148 * @param $caption (string) caption.
18149 * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
18150 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
18151 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
18152 * @param $x (float) Abscissa of the upper-left corner of the rectangle
18153 * @param $y (float) Ordinate of the upper-left corner of the rectangle
18154 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
18155 * @public
18156 * @author Nicola Asuni
18157 * @since 4.8.000 (2009-09-07)
18159 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
18160 if ($x === '') {
18161 $x = $this->x;
18163 if ($y === '') {
18164 $y = $this->y;
18166 // check page for no-write regions and adapt page margins if necessary
18167 list($x, $y) = $this->checkPageRegions($h, $x, $y);
18168 if ($js) {
18169 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
18170 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
18171 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
18172 $this->javascript .= 'f'.$name.".highlight='push';\n";
18173 $this->javascript .= 'f'.$name.".print=false;\n";
18174 return;
18176 // get default style
18177 $prop = array_merge($this->getFormDefaultProp(), $prop);
18178 $prop['Pushbutton'] = 'true';
18179 $prop['highlight'] = 'push';
18180 $prop['display'] = 'display.noPrint';
18181 // get annotation data
18182 $popt = $this->getAnnotOptFromJSProp($prop);
18183 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
18184 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
18185 $popt['da'] = $fontstyle;
18186 // build appearance stream
18187 $popt['ap'] = array();
18188 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
18189 $tmpid = $this->startTemplate($w, $h, false);
18190 $bw = (2 / $this->k); // border width
18191 $border = array(
18192 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
18193 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
18194 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
18195 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
18196 $this->SetFillColor(204);
18197 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
18198 $this->endTemplate();
18199 --$this->n;
18200 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
18201 unset($this->xobjects[$tmpid]);
18202 $popt['ap']['n'] .= 'Q EMC';
18203 // set additional default options
18204 if (!isset($popt['mk'])) {
18205 $popt['mk'] = array();
18207 $ann_obj_id = ($this->n + 1);
18208 if (!empty($action) AND !is_array($action)) {
18209 $ann_obj_id = ($this->n + 2);
18211 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
18212 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
18213 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
18214 // merge options
18215 $opt = array_merge($popt, $opt);
18216 // set remaining annotation data
18217 $opt['Subtype'] = 'Widget';
18218 $opt['ft'] = 'Btn';
18219 $opt['t'] = $caption;
18220 $opt['v'] = $name;
18221 if (!empty($action)) {
18222 if (is_array($action)) {
18223 // form action options as on section 12.7.5 of PDF32000_2008.
18224 $opt['aa'] = '/D <<';
18225 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
18226 foreach ($action AS $key => $val) {
18227 if (($key == 'S') AND in_array($val, $bmode)) {
18228 $opt['aa'] .= ' /S /'.$val;
18229 } elseif (($key == 'F') AND (!empty($val))) {
18230 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
18231 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
18232 $opt['aa'] .= ' /Fields [';
18233 foreach ($val AS $field) {
18234 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
18236 $opt['aa'] .= ']';
18237 } elseif (($key == 'Flags')) {
18238 $ff = 0;
18239 if (is_array($val)) {
18240 foreach ($val AS $flag) {
18241 switch ($flag) {
18242 case 'Include/Exclude': {
18243 $ff += 1 << 0;
18244 break;
18246 case 'IncludeNoValueFields': {
18247 $ff += 1 << 1;
18248 break;
18250 case 'ExportFormat': {
18251 $ff += 1 << 2;
18252 break;
18254 case 'GetMethod': {
18255 $ff += 1 << 3;
18256 break;
18258 case 'SubmitCoordinates': {
18259 $ff += 1 << 4;
18260 break;
18262 case 'XFDF': {
18263 $ff += 1 << 5;
18264 break;
18266 case 'IncludeAppendSaves': {
18267 $ff += 1 << 6;
18268 break;
18270 case 'IncludeAnnotations': {
18271 $ff += 1 << 7;
18272 break;
18274 case 'SubmitPDF': {
18275 $ff += 1 << 8;
18276 break;
18278 case 'CanonicalFormat': {
18279 $ff += 1 << 9;
18280 break;
18282 case 'ExclNonUserAnnots': {
18283 $ff += 1 << 10;
18284 break;
18286 case 'ExclFKey': {
18287 $ff += 1 << 11;
18288 break;
18290 case 'EmbedForm': {
18291 $ff += 1 << 13;
18292 break;
18296 } else {
18297 $ff = intval($val);
18299 $opt['aa'] .= ' /Flags '.$ff;
18302 $opt['aa'] .= ' >>';
18303 } else {
18304 // Javascript action or raw action command
18305 $js_obj_id = $this->addJavascriptObject($action);
18306 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
18309 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
18310 if ($this->rtl) {
18311 $this->x -= $w;
18312 } else {
18313 $this->x += $w;
18317 // --- END FORMS FIELDS ------------------------------------------------
18320 * Add certification signature (DocMDP or UR3)
18321 * You can set only one signature type
18322 * @protected
18323 * @author Nicola Asuni
18324 * @since 4.6.008 (2009-05-07)
18326 protected function _putsignature() {
18327 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
18328 return;
18330 $sigobjid = ($this->sig_obj_id + 1);
18331 $out = $this->_getobj($sigobjid)."\n";
18332 $out .= '<< /Type /Sig';
18333 $out .= ' /Filter /Adobe.PPKLite';
18334 $out .= ' /SubFilter /adbe.pkcs7.detached';
18335 $out .= ' '.$this->byterange_string;
18336 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
18337 $out .= ' /Reference ['; // array of signature reference dictionaries
18338 $out .= ' << /Type /SigRef';
18339 if ($this->signature_data['cert_type'] > 0) {
18340 $out .= ' /TransformMethod /DocMDP';
18341 $out .= ' /TransformParams <<';
18342 $out .= ' /Type /TransformParams';
18343 $out .= ' /P '.$this->signature_data['cert_type'];
18344 $out .= ' /V /1.2';
18345 } else {
18346 $out .= ' /TransformMethod /UR3';
18347 $out .= ' /TransformParams <<';
18348 $out .= ' /Type /TransformParams';
18349 $out .= ' /V /2.2';
18350 if (!$this->empty_string($this->ur['document'])) {
18351 $out .= ' /Document['.$this->ur['document'].']';
18353 if (!$this->empty_string($this->ur['form'])) {
18354 $out .= ' /Form['.$this->ur['form'].']';
18356 if (!$this->empty_string($this->ur['signature'])) {
18357 $out .= ' /Signature['.$this->ur['signature'].']';
18359 if (!$this->empty_string($this->ur['annots'])) {
18360 $out .= ' /Annots['.$this->ur['annots'].']';
18362 if (!$this->empty_string($this->ur['ef'])) {
18363 $out .= ' /EF['.$this->ur['ef'].']';
18365 if (!$this->empty_string($this->ur['formex'])) {
18366 $out .= ' /FormEX['.$this->ur['formex'].']';
18369 $out .= ' >>'; // close TransformParams
18370 // optional digest data (values must be calculated and replaced later)
18371 //$out .= ' /Data ********** 0 R';
18372 //$out .= ' /DigestMethod/MD5';
18373 //$out .= ' /DigestLocation[********** 34]';
18374 //$out .= ' /DigestValue<********************************>';
18375 $out .= ' >>';
18376 $out .= ' ]'; // end of reference
18377 if (isset($this->signature_data['info']['Name']) AND !$this->empty_string($this->signature_data['info']['Name'])) {
18378 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
18380 if (isset($this->signature_data['info']['Location']) AND !$this->empty_string($this->signature_data['info']['Location'])) {
18381 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
18383 if (isset($this->signature_data['info']['Reason']) AND !$this->empty_string($this->signature_data['info']['Reason'])) {
18384 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
18386 if (isset($this->signature_data['info']['ContactInfo']) AND !$this->empty_string($this->signature_data['info']['ContactInfo'])) {
18387 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
18389 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
18390 $out .= ' >>';
18391 $out .= "\n".'endobj';
18392 $this->_out($out);
18396 * Set User's Rights for PDF Reader
18397 * WARNING: This is experimental and currently do not work.
18398 * Check the PDF Reference 8.7.1 Transform Methods,
18399 * Table 8.105 Entries in the UR transform parameters dictionary
18400 * @param $enable (boolean) if true enable user's rights on PDF reader
18401 * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
18402 * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
18403 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
18404 * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
18405 * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
18406 Names specifying additional embedded-files-related usage rights for the document.
18407 * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
18408 * @public
18409 * @author Nicola Asuni
18410 * @since 2.9.000 (2008-03-26)
18412 public function setUserRights(
18413 $enable=true,
18414 $document='/FullSave',
18415 $annots='/Create/Delete/Modify/Copy/Import/Export',
18416 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
18417 $signature='/Modify',
18418 $ef='/Create/Delete/Modify/Import',
18419 $formex='') {
18420 $this->ur['enabled'] = $enable;
18421 $this->ur['document'] = $document;
18422 $this->ur['annots'] = $annots;
18423 $this->ur['form'] = $form;
18424 $this->ur['signature'] = $signature;
18425 $this->ur['ef'] = $ef;
18426 $this->ur['formex'] = $formex;
18427 if (!$this->sign) {
18428 $this->setSignature('', '', '', '', 0, array());
18433 * Enable document signature (requires the OpenSSL Library).
18434 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
18435 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
18436 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
18437 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
18438 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
18439 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
18440 * @param $private_key_password (string) password
18441 * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
18442 * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
18443 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
18444 * @public
18445 * @author Nicola Asuni
18446 * @since 4.6.005 (2009-04-24)
18448 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
18449 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
18450 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
18451 // to convert pfx certificate to pem: openssl
18452 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
18453 $this->sign = true;
18454 ++$this->n;
18455 $this->sig_obj_id = $this->n; // signature widget
18456 ++$this->n; // signature object ($this->sig_obj_id + 1)
18457 $this->signature_data = array();
18458 if (strlen($signing_cert) == 0) {
18459 $signing_cert = 'file://'.dirname(__FILE__).'/tcpdf.crt';
18460 $private_key_password = 'tcpdfdemo';
18462 if (strlen($private_key) == 0) {
18463 $private_key = $signing_cert;
18465 $this->signature_data['signcert'] = $signing_cert;
18466 $this->signature_data['privkey'] = $private_key;
18467 $this->signature_data['password'] = $private_key_password;
18468 $this->signature_data['extracerts'] = $extracerts;
18469 $this->signature_data['cert_type'] = $cert_type;
18470 $this->signature_data['info'] = $info;
18474 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
18475 * @param $x (float) Abscissa of the upper-left corner.
18476 * @param $y (float) Ordinate of the upper-left corner.
18477 * @param $w (float) Width of the signature area.
18478 * @param $h (float) Height of the signature area.
18479 * @param $page (int) option page number (if < 0 the current page is used).
18480 * @public
18481 * @author Nicola Asuni
18482 * @since 5.3.011 (2010-06-17)
18484 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
18485 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page);
18489 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
18490 * @param $x (float) Abscissa of the upper-left corner.
18491 * @param $y (float) Ordinate of the upper-left corner.
18492 * @param $w (float) Width of the signature area.
18493 * @param $h (float) Height of the signature area.
18494 * @param $page (int) option page number (if < 0 the current page is used).
18495 * @public
18496 * @author Nicola Asuni
18497 * @since 5.9.101 (2011-07-06)
18499 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
18500 ++$this->n;
18501 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page);
18505 * Get the array that defines the signature appearance (page and rectangle coordinates).
18506 * @param $x (float) Abscissa of the upper-left corner.
18507 * @param $y (float) Ordinate of the upper-left corner.
18508 * @param $w (float) Width of the signature area.
18509 * @param $h (float) Height of the signature area.
18510 * @param $page (int) option page number (if < 0 the current page is used).
18511 * @return (array) Array defining page and rectangle coordinates of signature appearance.
18512 * @protected
18513 * @author Nicola Asuni
18514 * @since 5.9.101 (2011-07-06)
18516 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1) {
18517 $sigapp = array();
18518 if (($page < 1) OR ($page > $this->numpages)) {
18519 $sigapp['page'] = $this->page;
18520 } else {
18521 $sigapp['page'] = intval($page);
18523 $a = $x * $this->k;
18524 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
18525 $c = $w * $this->k;
18526 $d = $h * $this->k;
18527 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
18528 return $sigapp;
18532 * Create a new page group.
18533 * NOTE: call this function before calling AddPage()
18534 * @param $page (int) starting group page (leave empty for next page).
18535 * @public
18536 * @since 3.0.000 (2008-03-27)
18538 public function startPageGroup($page='') {
18539 if (empty($page)) {
18540 $page = $this->page + 1;
18542 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
18546 * This method is DEPRECATED and doesn't have any effect.
18547 * Please remove any reference to this method.
18548 * @param $s (string) Empty parameter.
18549 * @deprecated deprecated since version 5.9.089 (2011-06-13)
18550 * @public
18552 public function AliasNbPages($s='') {}
18555 * This method is DEPRECATED and doesn't have any effect.
18556 * Please remove any reference to this method.
18557 * @param $s (string) Empty parameter.
18558 * @deprecated deprecated since version 5.9.089 (2011-06-13)
18559 * @public
18561 public function AliasNumPage($s='') {}
18564 * Set the starting page number.
18565 * @param $num (int) Starting page number.
18566 * @since 5.9.093 (2011-06-16)
18567 * @public
18569 public function setStartingPageNumber($num=1) {
18570 $this->starting_page_number = max(0, intval($num));
18574 * Returns the string alias used right align page numbers.
18575 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
18576 * @return string
18577 * @since 5.9.099 (2011-06-27)
18578 * @public
18580 public function getAliasRightShift() {
18581 // calculate aproximatively the ratio between widths of aliases and replacements.
18582 $ref = '{'.$this->alias_right_shift.'}{'.$this->alias_tot_pages.'}{'.$this->alias_num_page.'}';
18583 $rep = str_repeat(' ', $this->GetNumChars($ref));
18584 $wdiff = max(1, ($this->GetStringWidth($ref) / $this->GetStringWidth($rep)));
18585 $sdiff = sprintf('%F', $wdiff);
18586 $alias = $this->alias_right_shift.$sdiff.'}';
18587 if ($this->isUnicodeFont()) {
18588 $alias = '{'.$alias;
18590 return $alias;
18594 * Returns the string alias used for the total number of pages.
18595 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18596 * This alias will be replaced by the total number of pages in the document.
18597 * @return string
18598 * @since 4.0.018 (2008-08-08)
18599 * @public
18601 public function getAliasNbPages() {
18602 if ($this->isUnicodeFont()) {
18603 return '{'.$this->alias_tot_pages.'}';
18605 return $this->alias_tot_pages;
18609 * Returns the string alias used for the page number.
18610 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18611 * This alias will be replaced by the page number.
18612 * @return string
18613 * @since 4.5.000 (2009-01-02)
18614 * @public
18616 public function getAliasNumPage() {
18617 if ($this->isUnicodeFont()) {
18618 return '{'.$this->alias_num_page.'}';
18620 return $this->alias_num_page;
18624 * Return the alias for the total number of pages in the current page group.
18625 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18626 * This alias will be replaced by the total number of pages in this group.
18627 * @return alias of the current page group
18628 * @public
18629 * @since 3.0.000 (2008-03-27)
18631 public function getPageGroupAlias() {
18632 if ($this->isUnicodeFont()) {
18633 return '{'.$this->alias_group_tot_pages.'}';
18635 return $this->alias_group_tot_pages;
18639 * Return the alias for the page number on the current page group.
18640 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18641 * This alias will be replaced by the page number (relative to the belonging group).
18642 * @return alias of the current page group
18643 * @public
18644 * @since 4.5.000 (2009-01-02)
18646 public function getPageNumGroupAlias() {
18647 if ($this->isUnicodeFont()) {
18648 return '{'.$this->alias_group_num_page.'}';
18650 return $this->alias_group_num_page;
18654 * Return the current page in the group.
18655 * @return current page in the group
18656 * @public
18657 * @since 3.0.000 (2008-03-27)
18659 public function getGroupPageNo() {
18660 return $this->pagegroups[$this->currpagegroup];
18664 * Returns the current group page number formatted as a string.
18665 * @public
18666 * @since 4.3.003 (2008-11-18)
18667 * @see PaneNo(), formatPageNumber()
18669 public function getGroupPageNoFormatted() {
18670 return $this->formatPageNumber($this->getGroupPageNo());
18674 * Format the page numbers.
18675 * This method can be overriden for custom formats.
18676 * @param $num (int) page number
18677 * @protected
18678 * @since 4.2.005 (2008-11-06)
18680 protected function formatPageNumber($num) {
18681 return number_format((float)$num, 0, '', '.');
18685 * Format the page numbers on the Table Of Content.
18686 * This method can be overriden for custom formats.
18687 * @param $num (int) page number
18688 * @protected
18689 * @since 4.5.001 (2009-01-04)
18690 * @see addTOC(), addHTMLTOC()
18692 protected function formatTOCPageNumber($num) {
18693 return number_format((float)$num, 0, '', '.');
18697 * Returns the current page number formatted as a string.
18698 * @public
18699 * @since 4.2.005 (2008-11-06)
18700 * @see PaneNo(), formatPageNumber()
18702 public function PageNoFormatted() {
18703 return $this->formatPageNumber($this->PageNo());
18707 * Put pdf layers.
18708 * @protected
18709 * @since 3.0.000 (2008-03-27)
18711 protected function _putocg() {
18712 if (empty($this->pdflayers)) {
18713 return;
18715 foreach ($this->pdflayers as $key => $layer) {
18716 $this->pdflayers[$key]['objid'] = $this->_newobj();
18717 $out = '<< /Type /OCG';
18718 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
18719 $out .= ' /Usage <<';
18720 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
18721 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
18722 $out .= ' >> >>';
18723 $out .= "\n".'endobj';
18724 $this->_out($out);
18729 * Start a new pdf layer.
18730 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
18731 * @param $print (boolean) Set to true to print this layer.
18732 * @param $view (boolean) Set to true to view this layer.
18733 * @public
18734 * @since 5.9.102 (2011-07-13)
18736 public function startLayer($name='', $print=true, $view=true) {
18737 if ($this->state != 2) {
18738 return;
18740 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
18741 if (empty($name)) {
18742 $name = $layer;
18743 } else {
18744 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
18746 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view);
18747 $this->openMarkedContent = true;
18748 $this->_out('/OC /'.$layer.' BDC');
18752 * End the current PDF layer.
18753 * @public
18754 * @since 5.9.102 (2011-07-13)
18756 public function endLayer() {
18757 if ($this->state != 2) {
18758 return;
18760 if ($this->openMarkedContent) {
18761 // close existing open marked-content layer
18762 $this->_out('EMC');
18763 $this->openMarkedContent = false;
18768 * Set the visibility of the successive elements.
18769 * This can be useful, for instance, to put a background
18770 * image or color that will show on screen but won't print.
18771 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
18772 * @public
18773 * @since 3.0.000 (2008-03-27)
18775 public function setVisibility($v) {
18776 if ($this->state != 2) {
18777 return;
18779 $this->endLayer();
18780 switch($v) {
18781 case 'print': {
18782 $this->startLayer('Print', true, false);
18783 break;
18785 case 'view':
18786 case 'screen': {
18787 $this->startLayer('View', false, true);
18788 break;
18790 case 'all': {
18791 $this->_out('');
18792 break;
18794 default: {
18795 $this->Error('Incorrect visibility: '.$v);
18796 break;
18802 * Add transparency parameters to the current extgstate
18803 * @param $parms (array) parameters
18804 * @return the number of extgstates
18805 * @protected
18806 * @since 3.0.000 (2008-03-27)
18808 protected function addExtGState($parms) {
18809 if ($this->pdfa_mode) {
18810 // transparencies are not allowed in PDF/A mode
18811 return;
18813 // check if this ExtGState already exist
18814 foreach ($this->extgstates as $i => $ext) {
18815 if ($ext['parms'] == $parms) {
18816 if ($this->inxobj) {
18817 // we are inside an XObject template
18818 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
18820 // return reference to existing ExtGState
18821 return $i;
18824 $n = (count($this->extgstates) + 1);
18825 $this->extgstates[$n] = array('parms' => $parms);
18826 if ($this->inxobj) {
18827 // we are inside an XObject template
18828 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
18830 return $n;
18834 * Add an extgstate
18835 * @param $gs (array) extgstate
18836 * @protected
18837 * @since 3.0.000 (2008-03-27)
18839 protected function setExtGState($gs) {
18840 if ($this->pdfa_mode OR ($this->state != 2)) {
18841 // transparency is not allowed in PDF/A mode
18842 return;
18844 $this->_out(sprintf('/GS%d gs', $gs));
18848 * Put extgstates for object transparency
18849 * @protected
18850 * @since 3.0.000 (2008-03-27)
18852 protected function _putextgstates() {
18853 foreach ($this->extgstates as $i => $ext) {
18854 $this->extgstates[$i]['n'] = $this->_newobj();
18855 $out = '<< /Type /ExtGState';
18856 foreach ($ext['parms'] as $k => $v) {
18857 if (is_float($v)) {
18858 $v = sprintf('%F', $v);
18859 } elseif ($v === true) {
18860 $v = 'true';
18861 } elseif ($v === false) {
18862 $v = 'false';
18864 $out .= ' /'.$k.' '.$v;
18866 $out .= ' >>';
18867 $out .= "\n".'endobj';
18868 $this->_out($out);
18873 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
18874 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18875 * @param $stroking (boolean) If true apply overprint for stroking operations.
18876 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
18877 * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
18878 * @public
18879 * @since 5.9.152 (2012-03-23)
18881 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
18882 if ($this->state != 2) {
18883 return;
18885 $stroking = $stroking ? true : false;
18886 if ($this->empty_string($nonstroking)) {
18887 // default value if not set
18888 $nonstroking = $stroking;
18889 } else {
18890 $nonstroking = $nonstroking ? true : false;
18892 if (($mode != 0) AND ($mode != 1)) {
18893 $mode = 0;
18895 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
18896 $gs = $this->addExtGState($this->overprint);
18897 $this->setExtGState($gs);
18901 * Get the overprint mode array (OP, op, OPM).
18902 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18903 * @return array.
18904 * @public
18905 * @since 5.9.152 (2012-03-23)
18907 public function getOverprint() {
18908 return $this->overprint;
18912 * Set alpha for stroking (CA) and non-stroking (ca) operations.
18913 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
18914 * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
18915 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
18916 * @param $ais (boolean)
18917 * @public
18918 * @since 3.0.000 (2008-03-27)
18920 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
18921 if ($this->pdfa_mode) {
18922 // transparency is not allowed in PDF/A mode
18923 return;
18925 $stroking = floatval($stroking);
18926 if ($this->empty_string($nonstroking)) {
18927 // default value if not set
18928 $nonstroking = $stroking;
18929 } else {
18930 $nonstroking = floatval($nonstroking);
18932 if ($bm[0] == '/') {
18933 // remove trailing slash
18934 $bm = substr($bm, 1);
18936 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
18937 $bm = 'Normal';
18939 $ais = $ais ? true : false;
18940 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
18941 $gs = $this->addExtGState($this->alpha);
18942 $this->setExtGState($gs);
18946 * Get the alpha mode array (CA, ca, BM, AIS).
18947 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18948 * @return array.
18949 * @public
18950 * @since 5.9.152 (2012-03-23)
18952 public function getAlpha() {
18953 return $this->alpha;
18957 * Set the default JPEG compression quality (1-100)
18958 * @param $quality (int) JPEG quality, integer between 1 and 100
18959 * @public
18960 * @since 3.0.000 (2008-03-27)
18962 public function setJPEGQuality($quality) {
18963 if (($quality < 1) OR ($quality > 100)) {
18964 $quality = 75;
18966 $this->jpeg_quality = intval($quality);
18970 * Set the default number of columns in a row for HTML tables.
18971 * @param $cols (int) number of columns
18972 * @public
18973 * @since 3.0.014 (2008-06-04)
18975 public function setDefaultTableColumns($cols=4) {
18976 $this->default_table_columns = intval($cols);
18980 * Set the height of the cell (line height) respect the font height.
18981 * @param $h (int) cell proportion respect font height (typical value = 1.25).
18982 * @public
18983 * @since 3.0.014 (2008-06-04)
18985 public function setCellHeightRatio($h) {
18986 $this->cell_height_ratio = $h;
18990 * return the height of cell repect font height.
18991 * @public
18992 * @since 4.0.012 (2008-07-24)
18994 public function getCellHeightRatio() {
18995 return $this->cell_height_ratio;
18999 * Set the PDF version (check PDF reference for valid values).
19000 * @param $version (string) PDF document version.
19001 * @public
19002 * @since 3.1.000 (2008-06-09)
19004 public function setPDFVersion($version='1.7') {
19005 if ($this->pdfa_mode) {
19006 // PDF/A mode
19007 $this->PDFVersion = '1.4';
19008 } else {
19009 $this->PDFVersion = $version;
19014 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
19015 * (see Section 8.1 of PDF reference, "Viewer Preferences").
19016 * <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>
19017 * @param $preferences (array) array of options.
19018 * @author Nicola Asuni
19019 * @public
19020 * @since 3.1.000 (2008-06-09)
19022 public function setViewerPreferences($preferences) {
19023 $this->viewer_preferences = $preferences;
19027 * Paints color transition registration bars
19028 * @param $x (float) abscissa of the top left corner of the rectangle.
19029 * @param $y (float) ordinate of the top left corner of the rectangle.
19030 * @param $w (float) width of the rectangle.
19031 * @param $h (float) height of the rectangle.
19032 * @param $transition (boolean) if true prints tcolor transitions to white.
19033 * @param $vertical (boolean) if true prints bar vertically.
19034 * @param $colors (string) colors to print, one letter per color separated by comma (for example 'A,W,R,G,B,C,M,Y,K'): A=black, W=white, R=red, G=green, B=blue, C=cyan, M=magenta, Y=yellow, K=black.
19035 * @author Nicola Asuni
19036 * @since 4.9.000 (2010-03-26)
19037 * @public
19039 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
19040 $bars = explode(',', $colors);
19041 $numbars = count($bars); // number of bars to print
19042 // set bar measures
19043 if ($vertical) {
19044 $coords = array(0, 0, 0, 1);
19045 $wb = $w / $numbars; // bar width
19046 $hb = $h; // bar height
19047 $xd = $wb; // delta x
19048 $yd = 0; // delta y
19049 } else {
19050 $coords = array(1, 0, 0, 0);
19051 $wb = $w; // bar width
19052 $hb = $h / $numbars; // bar height
19053 $xd = 0; // delta x
19054 $yd = $hb; // delta y
19056 $xb = $x;
19057 $yb = $y;
19058 foreach ($bars as $col) {
19059 switch ($col) {
19060 // set transition colors
19061 case 'A': { // BLACK
19062 $col_a = array(255);
19063 $col_b = array(0);
19064 break;
19066 case 'W': { // WHITE
19067 $col_a = array(0);
19068 $col_b = array(255);
19069 break;
19071 case 'R': { // R
19072 $col_a = array(255,255,255);
19073 $col_b = array(255,0,0);
19074 break;
19076 case 'G': { // G
19077 $col_a = array(255,255,255);
19078 $col_b = array(0,255,0);
19079 break;
19081 case 'B': { // B
19082 $col_a = array(255,255,255);
19083 $col_b = array(0,0,255);
19084 break;
19086 case 'C': { // C
19087 $col_a = array(0,0,0,0);
19088 $col_b = array(100,0,0,0);
19089 break;
19091 case 'M': { // M
19092 $col_a = array(0,0,0,0);
19093 $col_b = array(0,100,0,0);
19094 break;
19096 case 'Y': { // Y
19097 $col_a = array(0,0,0,0);
19098 $col_b = array(0,0,100,0);
19099 break;
19101 case 'K': { // K
19102 $col_a = array(0,0,0,0);
19103 $col_b = array(0,0,0,100);
19104 break;
19106 default: { // GRAY
19107 $col_a = array(255);
19108 $col_b = array(0);
19109 break;
19112 if ($transition) {
19113 // color gradient
19114 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
19115 } else {
19116 // color rectangle
19117 $this->SetFillColorArray($col_b);
19118 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
19120 $xb += $xd;
19121 $yb += $yd;
19126 * Paints crop marks.
19127 * @param $x (float) abscissa of the crop mark center.
19128 * @param $y (float) ordinate of the crop mark center.
19129 * @param $w (float) width of the crop mark.
19130 * @param $h (float) height of the crop mark.
19131 * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
19132 * @param $color (array) crop mark color (default black).
19133 * @author Nicola Asuni
19134 * @since 4.9.000 (2010-03-26)
19135 * @public
19137 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(0,0,0)) {
19138 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
19139 $type = strtoupper($type);
19140 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
19141 // split type in single components
19142 $type = str_replace('-', ',', $type);
19143 $type = str_replace('TL', 'T,L', $type);
19144 $type = str_replace('TR', 'T,R', $type);
19145 $type = str_replace('BL', 'F,L', $type);
19146 $type = str_replace('BR', 'F,R', $type);
19147 $type = str_replace('A', 'T,L', $type);
19148 $type = str_replace('B', 'T,R', $type);
19149 $type = str_replace('T,RO', 'BO', $type);
19150 $type = str_replace('C', 'F,L', $type);
19151 $type = str_replace('D', 'F,R', $type);
19152 $crops = explode(',', strtoupper($type));
19153 // remove duplicates
19154 $crops = array_unique($crops);
19155 $dw = ($w / 4); // horizontal space to leave before the intersection point
19156 $dh = ($h / 4); // vertical space to leave before the intersection point
19157 foreach ($crops as $crop) {
19158 switch ($crop) {
19159 case 'T':
19160 case 'TOP': {
19161 $x1 = $x;
19162 $y1 = ($y - $h);
19163 $x2 = $x;
19164 $y2 = ($y - $dh);
19165 break;
19167 case 'F':
19168 case 'BOTTOM': {
19169 $x1 = $x;
19170 $y1 = ($y + $dh);
19171 $x2 = $x;
19172 $y2 = ($y + $h);
19173 break;
19175 case 'L':
19176 case 'LEFT': {
19177 $x1 = ($x - $w);
19178 $y1 = $y;
19179 $x2 = ($x - $dw);
19180 $y2 = $y;
19181 break;
19183 case 'R':
19184 case 'RIGHT': {
19185 $x1 = ($x + $dw);
19186 $y1 = $y;
19187 $x2 = ($x + $w);
19188 $y2 = $y;
19189 break;
19192 $this->Line($x1, $y1, $x2, $y2);
19197 * Paints a registration mark
19198 * @param $x (float) abscissa of the registration mark center.
19199 * @param $y (float) ordinate of the registration mark center.
19200 * @param $r (float) radius of the crop mark.
19201 * @param $double (boolean) if true print two concentric crop marks.
19202 * @param $cola (array) crop mark color (default black).
19203 * @param $colb (array) second crop mark color.
19204 * @author Nicola Asuni
19205 * @since 4.9.000 (2010-03-26)
19206 * @public
19208 public function registrationMark($x, $y, $r, $double=false, $cola=array(0,0,0), $colb=array(255,255,255)) {
19209 $line_style = array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
19210 $this->SetFillColorArray($cola);
19211 $this->PieSector($x, $y, $r, 90, 180, 'F');
19212 $this->PieSector($x, $y, $r, 270, 360, 'F');
19213 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
19214 if ($double) {
19215 $r2 = $r * 0.5;
19216 $this->SetFillColorArray($colb);
19217 $this->PieSector($x, $y, $r2, 90, 180, 'F');
19218 $this->PieSector($x, $y, $r2, 270, 360, 'F');
19219 $this->SetFillColorArray($cola);
19220 $this->PieSector($x, $y, $r2, 0, 90, 'F');
19221 $this->PieSector($x, $y, $r2, 180, 270, 'F');
19222 $this->Circle($x, $y, $r2, 0, 360, 'C', $line_style, array(), 8);
19227 * Paints a linear colour gradient.
19228 * @param $x (float) abscissa of the top left corner of the rectangle.
19229 * @param $y (float) ordinate of the top left corner of the rectangle.
19230 * @param $w (float) width of the rectangle.
19231 * @param $h (float) height of the rectangle.
19232 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
19233 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
19234 * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
19235 * @author Andreas Würmser, Nicola Asuni
19236 * @since 3.1.000 (2008-06-09)
19237 * @public
19239 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
19240 $this->Clip($x, $y, $w, $h);
19241 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
19245 * Paints a radial colour gradient.
19246 * @param $x (float) abscissa of the top left corner of the rectangle.
19247 * @param $y (float) ordinate of the top left corner of the rectangle.
19248 * @param $w (float) width of the rectangle.
19249 * @param $h (float) height of the rectangle.
19250 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
19251 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
19252 * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
19253 * @author Andreas Würmser, Nicola Asuni
19254 * @since 3.1.000 (2008-06-09)
19255 * @public
19257 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
19258 $this->Clip($x, $y, $w, $h);
19259 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
19263 * Paints a coons patch mesh.
19264 * @param $x (float) abscissa of the top left corner of the rectangle.
19265 * @param $y (float) ordinate of the top left corner of the rectangle.
19266 * @param $w (float) width of the rectangle.
19267 * @param $h (float) height of the rectangle.
19268 * @param $col1 (array) first color (lower left corner) (RGB components).
19269 * @param $col2 (array) second color (lower right corner) (RGB components).
19270 * @param $col3 (array) third color (upper right corner) (RGB components).
19271 * @param $col4 (array) fourth color (upper left corner) (RGB components).
19272 * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
19273 * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
19274 * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
19275 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
19276 * @author Andreas Würmser, Nicola Asuni
19277 * @since 3.1.000 (2008-06-09)
19278 * @public
19280 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) {
19281 if ($this->pdfa_mode OR ($this->state != 2)) {
19282 return;
19284 $this->Clip($x, $y, $w, $h);
19285 $n = count($this->gradients) + 1;
19286 $this->gradients[$n] = array();
19287 $this->gradients[$n]['type'] = 6; //coons patch mesh
19288 $this->gradients[$n]['coords'] = array();
19289 $this->gradients[$n]['antialias'] = $antialias;
19290 $this->gradients[$n]['colors'] = array();
19291 $this->gradients[$n]['transparency'] = false;
19292 //check the coords array if it is the simple array or the multi patch array
19293 if (!isset($coords[0]['f'])) {
19294 //simple array -> convert to multi patch array
19295 if (!isset($col1[1])) {
19296 $col1[1] = $col1[2] = $col1[0];
19298 if (!isset($col2[1])) {
19299 $col2[1] = $col2[2] = $col2[0];
19301 if (!isset($col3[1])) {
19302 $col3[1] = $col3[2] = $col3[0];
19304 if (!isset($col4[1])) {
19305 $col4[1] = $col4[2] = $col4[0];
19307 $patch_array[0]['f'] = 0;
19308 $patch_array[0]['points'] = $coords;
19309 $patch_array[0]['colors'][0]['r'] = $col1[0];
19310 $patch_array[0]['colors'][0]['g'] = $col1[1];
19311 $patch_array[0]['colors'][0]['b'] = $col1[2];
19312 $patch_array[0]['colors'][1]['r'] = $col2[0];
19313 $patch_array[0]['colors'][1]['g'] = $col2[1];
19314 $patch_array[0]['colors'][1]['b'] = $col2[2];
19315 $patch_array[0]['colors'][2]['r'] = $col3[0];
19316 $patch_array[0]['colors'][2]['g'] = $col3[1];
19317 $patch_array[0]['colors'][2]['b'] = $col3[2];
19318 $patch_array[0]['colors'][3]['r'] = $col4[0];
19319 $patch_array[0]['colors'][3]['g'] = $col4[1];
19320 $patch_array[0]['colors'][3]['b'] = $col4[2];
19321 } else {
19322 //multi patch array
19323 $patch_array = $coords;
19325 $bpcd = 65535; //16 bits per coordinate
19326 //build the data stream
19327 $this->gradients[$n]['stream'] = '';
19328 $count_patch = count($patch_array);
19329 for ($i=0; $i < $count_patch; ++$i) {
19330 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
19331 $count_points = count($patch_array[$i]['points']);
19332 for ($j=0; $j < $count_points; ++$j) {
19333 //each point as 16 bit
19334 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
19335 if ($patch_array[$i]['points'][$j] < 0) {
19336 $patch_array[$i]['points'][$j] = 0;
19338 if ($patch_array[$i]['points'][$j] > $bpcd) {
19339 $patch_array[$i]['points'][$j] = $bpcd;
19341 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
19342 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
19344 $count_cols = count($patch_array[$i]['colors']);
19345 for ($j=0; $j < $count_cols; ++$j) {
19346 //each color component as 8 bit
19347 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
19348 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
19349 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
19352 //paint the gradient
19353 $this->_out('/Sh'.$n.' sh');
19354 //restore previous Graphic State
19355 $this->_out('Q');
19356 if ($this->inxobj) {
19357 // we are inside an XObject template
19358 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
19363 * Set a rectangular clipping area.
19364 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
19365 * @param $y (float) ordinate of the top left corner of the rectangle.
19366 * @param $w (float) width of the rectangle.
19367 * @param $h (float) height of the rectangle.
19368 * @author Andreas Würmser, Nicola Asuni
19369 * @since 3.1.000 (2008-06-09)
19370 * @protected
19372 protected function Clip($x, $y, $w, $h) {
19373 if ($this->state != 2) {
19374 return;
19376 if ($this->rtl) {
19377 $x = $this->w - $x - $w;
19379 //save current Graphic State
19380 $s = 'q';
19381 //set clipping area
19382 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
19383 //set up transformation matrix for gradient
19384 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
19385 $this->_out($s);
19389 * Output gradient.
19390 * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
19391 * @param $coords (array) array of coordinates.
19392 * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
19393 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
19394 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
19395 * @author Nicola Asuni
19396 * @since 3.1.000 (2008-06-09)
19397 * @public
19399 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
19400 if ($this->pdfa_mode OR ($this->state != 2)) {
19401 return;
19403 $n = count($this->gradients) + 1;
19404 $this->gradients[$n] = array();
19405 $this->gradients[$n]['type'] = $type;
19406 $this->gradients[$n]['coords'] = $coords;
19407 $this->gradients[$n]['antialias'] = $antialias;
19408 $this->gradients[$n]['colors'] = array();
19409 $this->gradients[$n]['transparency'] = false;
19410 // color space
19411 $numcolspace = count($stops[0]['color']);
19412 $bcolor = array_values($background);
19413 switch($numcolspace) {
19414 case 4: { // CMYK
19415 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
19416 if (!empty($background)) {
19417 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
19419 break;
19421 case 3: { // RGB
19422 $this->gradients[$n]['colspace'] = 'DeviceRGB';
19423 if (!empty($background)) {
19424 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
19426 break;
19428 case 1: { // Gray scale
19429 $this->gradients[$n]['colspace'] = 'DeviceGray';
19430 if (!empty($background)) {
19431 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
19433 break;
19436 $num_stops = count($stops);
19437 $last_stop_id = $num_stops - 1;
19438 foreach ($stops as $key => $stop) {
19439 $this->gradients[$n]['colors'][$key] = array();
19440 // offset represents a location along the gradient vector
19441 if (isset($stop['offset'])) {
19442 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
19443 } else {
19444 if ($key == 0) {
19445 $this->gradients[$n]['colors'][$key]['offset'] = 0;
19446 } elseif ($key == $last_stop_id) {
19447 $this->gradients[$n]['colors'][$key]['offset'] = 1;
19448 } else {
19449 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
19450 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
19453 if (isset($stop['opacity'])) {
19454 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
19455 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
19456 $this->gradients[$n]['transparency'] = true;
19458 } else {
19459 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
19461 // exponent for the exponential interpolation function
19462 if (isset($stop['exponent'])) {
19463 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
19464 } else {
19465 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
19467 // set colors
19468 $color = array_values($stop['color']);
19469 switch($numcolspace) {
19470 case 4: { // CMYK
19471 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
19472 break;
19474 case 3: { // RGB
19475 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
19476 break;
19478 case 1: { // Gray scale
19479 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
19480 break;
19484 if ($this->gradients[$n]['transparency']) {
19485 // paint luminosity gradient
19486 $this->_out('/TGS'.$n.' gs');
19488 //paint the gradient
19489 $this->_out('/Sh'.$n.' sh');
19490 //restore previous Graphic State
19491 $this->_out('Q');
19492 if ($this->inxobj) {
19493 // we are inside an XObject template
19494 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
19499 * Output gradient shaders.
19500 * @author Nicola Asuni
19501 * @since 3.1.000 (2008-06-09)
19502 * @protected
19504 function _putshaders() {
19505 if ($this->pdfa_mode) {
19506 return;
19508 $idt = count($this->gradients); //index for transparency gradients
19509 foreach ($this->gradients as $id => $grad) {
19510 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
19511 $fc = $this->_newobj();
19512 $out = '<<';
19513 $out .= ' /FunctionType 3';
19514 $out .= ' /Domain [0 1]';
19515 $functions = '';
19516 $bounds = '';
19517 $encode = '';
19518 $i = 1;
19519 $num_cols = count($grad['colors']);
19520 $lastcols = $num_cols - 1;
19521 for ($i = 1; $i < $num_cols; ++$i) {
19522 $functions .= ($fc + $i).' 0 R ';
19523 if ($i < $lastcols) {
19524 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
19526 $encode .= '0 1 ';
19528 $out .= ' /Functions ['.trim($functions).']';
19529 $out .= ' /Bounds ['.trim($bounds).']';
19530 $out .= ' /Encode ['.trim($encode).']';
19531 $out .= ' >>';
19532 $out .= "\n".'endobj';
19533 $this->_out($out);
19534 for ($i = 1; $i < $num_cols; ++$i) {
19535 $this->_newobj();
19536 $out = '<<';
19537 $out .= ' /FunctionType 2';
19538 $out .= ' /Domain [0 1]';
19539 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
19540 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
19541 $out .= ' /N '.$grad['colors'][$i]['exponent'];
19542 $out .= ' >>';
19543 $out .= "\n".'endobj';
19544 $this->_out($out);
19546 // set transparency fuctions
19547 if ($grad['transparency']) {
19548 $ft = $this->_newobj();
19549 $out = '<<';
19550 $out .= ' /FunctionType 3';
19551 $out .= ' /Domain [0 1]';
19552 $functions = '';
19553 $i = 1;
19554 $num_cols = count($grad['colors']);
19555 for ($i = 1; $i < $num_cols; ++$i) {
19556 $functions .= ($ft + $i).' 0 R ';
19558 $out .= ' /Functions ['.trim($functions).']';
19559 $out .= ' /Bounds ['.trim($bounds).']';
19560 $out .= ' /Encode ['.trim($encode).']';
19561 $out .= ' >>';
19562 $out .= "\n".'endobj';
19563 $this->_out($out);
19564 for ($i = 1; $i < $num_cols; ++$i) {
19565 $this->_newobj();
19566 $out = '<<';
19567 $out .= ' /FunctionType 2';
19568 $out .= ' /Domain [0 1]';
19569 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
19570 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
19571 $out .= ' /N '.$grad['colors'][$i]['exponent'];
19572 $out .= ' >>';
19573 $out .= "\n".'endobj';
19574 $this->_out($out);
19578 // set shading object
19579 $this->_newobj();
19580 $out = '<< /ShadingType '.$grad['type'];
19581 if (isset($grad['colspace'])) {
19582 $out .= ' /ColorSpace /'.$grad['colspace'];
19583 } else {
19584 $out .= ' /ColorSpace /DeviceRGB';
19586 if (isset($grad['background']) AND !empty($grad['background'])) {
19587 $out .= ' /Background ['.$grad['background'].']';
19589 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
19590 $out .= ' /AntiAlias true';
19592 if ($grad['type'] == 2) {
19593 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
19594 $out .= ' /Domain [0 1]';
19595 $out .= ' /Function '.$fc.' 0 R';
19596 $out .= ' /Extend [true true]';
19597 $out .= ' >>';
19598 } elseif ($grad['type'] == 3) {
19599 //x0, y0, r0, x1, y1, r1
19600 //at this this time radius of inner circle is 0
19601 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
19602 $out .= ' /Domain [0 1]';
19603 $out .= ' /Function '.$fc.' 0 R';
19604 $out .= ' /Extend [true true]';
19605 $out .= ' >>';
19606 } elseif ($grad['type'] == 6) {
19607 $out .= ' /BitsPerCoordinate 16';
19608 $out .= ' /BitsPerComponent 8';
19609 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
19610 $out .= ' /BitsPerFlag 8';
19611 $stream = $this->_getrawstream($grad['stream']);
19612 $out .= ' /Length '.strlen($stream);
19613 $out .= ' >>';
19614 $out .= ' stream'."\n".$stream."\n".'endstream';
19616 $out .= "\n".'endobj';
19617 $this->_out($out);
19618 if ($grad['transparency']) {
19619 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
19620 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
19622 $this->gradients[$id]['id'] = $this->n;
19623 // set pattern object
19624 $this->_newobj();
19625 $out = '<< /Type /Pattern /PatternType 2';
19626 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
19627 $out .= ' >>';
19628 $out .= "\n".'endobj';
19629 $this->_out($out);
19630 $this->gradients[$id]['pattern'] = $this->n;
19631 // set shading and pattern for transparency mask
19632 if ($grad['transparency']) {
19633 // luminosity pattern
19634 $idgs = $id + $idt;
19635 $this->_newobj();
19636 $this->_out($shading_transparency);
19637 $this->gradients[$idgs]['id'] = $this->n;
19638 $this->_newobj();
19639 $out = '<< /Type /Pattern /PatternType 2';
19640 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
19641 $out .= ' >>';
19642 $out .= "\n".'endobj';
19643 $this->_out($out);
19644 $this->gradients[$idgs]['pattern'] = $this->n;
19645 // luminosity XObject
19646 $oid = $this->_newobj();
19647 $this->xobjects['LX'.$oid] = array('n' => $oid);
19648 $filter = '';
19649 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
19650 if ($this->compress) {
19651 $filter = ' /Filter /FlateDecode';
19652 $stream = gzcompress($stream);
19654 $stream = $this->_getrawstream($stream);
19655 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
19656 $out .= ' /Length '.strlen($stream);
19657 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
19658 $out .= ' /BBox [0 0 '.$rect.']';
19659 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
19660 $out .= ' /Resources <<';
19661 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
19662 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
19663 $out .= ' >>';
19664 $out .= ' >> ';
19665 $out .= ' stream'."\n".$stream."\n".'endstream';
19666 $out .= "\n".'endobj';
19667 $this->_out($out);
19668 // SMask
19669 $this->_newobj();
19670 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
19671 $this->_out($out);
19672 // ExtGState
19673 $this->_newobj();
19674 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
19675 $this->_out($out);
19676 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
19682 * Draw the sector of a circle.
19683 * It can be used for instance to render pie charts.
19684 * @param $xc (float) abscissa of the center.
19685 * @param $yc (float) ordinate of the center.
19686 * @param $r (float) radius.
19687 * @param $a (float) start angle (in degrees).
19688 * @param $b (float) end angle (in degrees).
19689 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
19690 * @param $cw: (float) indicates whether to go clockwise (default: true).
19691 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
19692 * @author Maxime Delorme, Nicola Asuni
19693 * @since 3.1.000 (2008-06-09)
19694 * @public
19696 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
19697 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
19701 * Draw the sector of an ellipse.
19702 * It can be used for instance to render pie charts.
19703 * @param $xc (float) abscissa of the center.
19704 * @param $yc (float) ordinate of the center.
19705 * @param $rx (float) the x-axis radius.
19706 * @param $ry (float) the y-axis radius.
19707 * @param $a (float) start angle (in degrees).
19708 * @param $b (float) end angle (in degrees).
19709 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
19710 * @param $cw: (float) indicates whether to go clockwise.
19711 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
19712 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
19713 * @author Maxime Delorme, Nicola Asuni
19714 * @since 3.1.000 (2008-06-09)
19715 * @public
19717 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
19718 if ($this->state != 2) {
19719 return;
19721 if ($this->rtl) {
19722 $xc = ($this->w - $xc);
19724 $op = $this->getPathPaintOperator($style);
19725 if ($op == 'f') {
19726 $line_style = array();
19728 if ($cw) {
19729 $d = $b;
19730 $b = (360 - $a + $o);
19731 $a = (360 - $d + $o);
19732 } else {
19733 $b += $o;
19734 $a += $o;
19736 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
19737 $this->_out($op);
19741 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
19742 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
19743 * Only vector drawing is supported, not text or bitmap.
19744 * 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).
19745 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
19746 * @param $x (float) Abscissa of the upper-left corner.
19747 * @param $y (float) Ordinate of the upper-left corner.
19748 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
19749 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
19750 * @param $link (mixed) URL or identifier returned by AddLink().
19751 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
19752 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
19753 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
19754 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
19755 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
19756 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
19757 * @author Valentin Schmidt, Nicola Asuni
19758 * @since 3.1.000 (2008-06-09)
19759 * @public
19761 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
19762 if ($this->state != 2) {
19763 return;
19765 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
19766 // convert EPS to raster image using GD or ImageMagick libraries
19767 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
19769 if ($x === '') {
19770 $x = $this->x;
19772 if ($y === '') {
19773 $y = $this->y;
19775 // check page for no-write regions and adapt page margins if necessary
19776 list($x, $y) = $this->checkPageRegions($h, $x, $y);
19777 $k = $this->k;
19778 if ($file{0} === '@') { // image from string
19779 $data = substr($file, 1);
19780 } else { // EPS/AI file
19781 $data = file_get_contents($file);
19783 if ($data === false) {
19784 $this->Error('EPS file not found: '.$file);
19786 $regs = array();
19787 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
19788 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
19789 if (count($regs) > 1) {
19790 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
19791 if (strpos($version_str, 'Adobe Illustrator') !== false) {
19792 $versexp = explode(' ', $version_str);
19793 $version = (float)array_pop($versexp);
19794 if ($version >= 9) {
19795 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
19799 // strip binary bytes in front of PS-header
19800 $start = strpos($data, '%!PS-Adobe');
19801 if ($start > 0) {
19802 $data = substr($data, $start);
19804 // find BoundingBox params
19805 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
19806 if (count($regs) > 1) {
19807 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
19808 } else {
19809 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
19811 $start = strpos($data, '%%EndSetup');
19812 if ($start === false) {
19813 $start = strpos($data, '%%EndProlog');
19815 if ($start === false) {
19816 $start = strpos($data, '%%BoundingBox');
19818 $data = substr($data, $start);
19819 $end = strpos($data, '%%PageTrailer');
19820 if ($end===false) {
19821 $end = strpos($data, 'showpage');
19823 if ($end) {
19824 $data = substr($data, 0, $end);
19826 // calculate image width and height on document
19827 if (($w <= 0) AND ($h <= 0)) {
19828 $w = ($x2 - $x1) / $k;
19829 $h = ($y2 - $y1) / $k;
19830 } elseif ($w <= 0) {
19831 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
19832 } elseif ($h <= 0) {
19833 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
19835 // fit the image on available space
19836 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
19837 if ($this->rasterize_vector_images) {
19838 // convert EPS to raster image using GD or ImageMagick libraries
19839 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
19841 // set scaling factors
19842 $scale_x = $w / (($x2 - $x1) / $k);
19843 $scale_y = $h / (($y2 - $y1) / $k);
19844 // set alignment
19845 $this->img_rb_y = $y + $h;
19846 // set alignment
19847 if ($this->rtl) {
19848 if ($palign == 'L') {
19849 $ximg = $this->lMargin;
19850 } elseif ($palign == 'C') {
19851 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19852 } elseif ($palign == 'R') {
19853 $ximg = $this->w - $this->rMargin - $w;
19854 } else {
19855 $ximg = $x - $w;
19857 $this->img_rb_x = $ximg;
19858 } else {
19859 if ($palign == 'L') {
19860 $ximg = $this->lMargin;
19861 } elseif ($palign == 'C') {
19862 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19863 } elseif ($palign == 'R') {
19864 $ximg = $this->w - $this->rMargin - $w;
19865 } else {
19866 $ximg = $x;
19868 $this->img_rb_x = $ximg + $w;
19870 if ($useBoundingBox) {
19871 $dx = $ximg * $k - $x1;
19872 $dy = $y * $k - $y1;
19873 } else {
19874 $dx = $ximg * $k;
19875 $dy = $y * $k;
19877 // save the current graphic state
19878 $this->_out('q'.$this->epsmarker);
19879 // translate
19880 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
19881 // scale
19882 if (isset($scale_x)) {
19883 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
19885 // handle pc/unix/mac line endings
19886 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
19887 $u=0;
19888 $cnt = count($lines);
19889 for ($i=0; $i < $cnt; ++$i) {
19890 $line = $lines[$i];
19891 if (($line == '') OR ($line{0} == '%')) {
19892 continue;
19894 $len = strlen($line);
19895 // check for spot color names
19896 $color_name = '';
19897 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
19898 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
19899 // extract spot color name
19900 $color_name = $matches[0];
19901 // remove color name from string
19902 $line = str_replace(' '.$color_name, '', $line);
19903 // remove pharentesis from color name
19904 $color_name = substr($color_name, 1, -1);
19907 $chunks = explode(' ', $line);
19908 $cmd = trim(array_pop($chunks));
19909 // RGB
19910 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
19911 $b = array_pop($chunks);
19912 $g = array_pop($chunks);
19913 $r = array_pop($chunks);
19914 $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!
19915 continue;
19917 $skip = false;
19918 if ($fixoutvals) {
19919 // check for values outside the bounding box
19920 switch ($cmd) {
19921 case 'm':
19922 case 'l':
19923 case 'L': {
19924 // skip values outside bounding box
19925 foreach ($chunks as $key => $val) {
19926 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
19927 $skip = true;
19928 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
19929 $skip = true;
19935 switch ($cmd) {
19936 case 'm':
19937 case 'l':
19938 case 'v':
19939 case 'y':
19940 case 'c':
19941 case 'k':
19942 case 'K':
19943 case 'g':
19944 case 'G':
19945 case 's':
19946 case 'S':
19947 case 'J':
19948 case 'j':
19949 case 'w':
19950 case 'M':
19951 case 'd':
19952 case 'n': {
19953 if ($skip) {
19954 break;
19956 $this->_out($line);
19957 break;
19959 case 'x': {// custom fill color
19960 if (empty($color_name)) {
19961 // CMYK color
19962 list($col_c, $col_m, $col_y, $col_k) = $chunks;
19963 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
19964 } else {
19965 // Spot Color (CMYK + tint)
19966 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
19967 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
19968 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
19969 $this->_out($color_cmd);
19971 break;
19973 case 'X': { // custom stroke color
19974 if (empty($color_name)) {
19975 // CMYK color
19976 list($col_c, $col_m, $col_y, $col_k) = $chunks;
19977 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
19978 } else {
19979 // Spot Color (CMYK + tint)
19980 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
19981 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
19982 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
19983 $this->_out($color_cmd);
19985 break;
19987 case 'Y':
19988 case 'N':
19989 case 'V':
19990 case 'L':
19991 case 'C': {
19992 if ($skip) {
19993 break;
19995 $line[($len - 1)] = strtolower($cmd);
19996 $this->_out($line);
19997 break;
19999 case 'b':
20000 case 'B': {
20001 $this->_out($cmd . '*');
20002 break;
20004 case 'f':
20005 case 'F': {
20006 if ($u > 0) {
20007 $isU = false;
20008 $max = min(($i + 5), $cnt);
20009 for ($j = ($i + 1); $j < $max; ++$j) {
20010 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
20012 if ($isU) {
20013 $this->_out('f*');
20015 } else {
20016 $this->_out('f*');
20018 break;
20020 case '*u': {
20021 ++$u;
20022 break;
20024 case '*U': {
20025 --$u;
20026 break;
20030 // restore previous graphic state
20031 $this->_out($this->epsmarker.'Q');
20032 if (!empty($border)) {
20033 $bx = $this->x;
20034 $by = $this->y;
20035 $this->x = $ximg;
20036 if ($this->rtl) {
20037 $this->x += $w;
20039 $this->y = $y;
20040 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
20041 $this->x = $bx;
20042 $this->y = $by;
20044 if ($link) {
20045 $this->Link($ximg, $y, $w, $h, $link, 0);
20047 // set pointer to align the next text/objects
20048 switch($align) {
20049 case 'T':{
20050 $this->y = $y;
20051 $this->x = $this->img_rb_x;
20052 break;
20054 case 'M':{
20055 $this->y = $y + round($h/2);
20056 $this->x = $this->img_rb_x;
20057 break;
20059 case 'B':{
20060 $this->y = $this->img_rb_y;
20061 $this->x = $this->img_rb_x;
20062 break;
20064 case 'N':{
20065 $this->SetY($this->img_rb_y);
20066 break;
20068 default:{
20069 break;
20072 $this->endlinex = $this->img_rb_x;
20076 * Set document barcode.
20077 * @param $bc (string) barcode
20078 * @public
20080 public function setBarcode($bc='') {
20081 $this->barcode = $bc;
20085 * Get current barcode.
20086 * @return string
20087 * @public
20088 * @since 4.0.012 (2008-07-24)
20090 public function getBarcode() {
20091 return $this->barcode;
20095 * Print a Linear Barcode.
20096 * @param $code (string) code to print
20097 * @param $type (string) type of barcode (see barcodes.php for supported formats).
20098 * @param $x (int) x position in user units (empty string = current x position)
20099 * @param $y (int) y position in user units (empty string = current y position)
20100 * @param $w (int) width in user units (empty string = remaining page width)
20101 * @param $h (int) height in user units (empty string = remaining page height)
20102 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
20103 * @param $style (array) array of options:<ul>
20104 * <li>boolean $style['border'] if true prints a border</li>
20105 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
20106 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
20107 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
20108 * <li>array $style['fgcolor'] color array for bars and text</li>
20109 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
20110 * <li>boolean $style['text'] if true prints text below the barcode</li>
20111 * <li>string $style['label'] override default label</li>
20112 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
20113 * <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>
20114 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
20115 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
20116 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
20117 * <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>
20118 * <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>
20119 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
20120 * @author Nicola Asuni
20121 * @since 3.1.000 (2008-06-09)
20122 * @public
20124 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
20125 if ($this->empty_string(trim($code))) {
20126 return;
20128 require_once(dirname(__FILE__).'/barcodes.php');
20129 // save current graphic settings
20130 $gvars = $this->getGraphicVars();
20131 // create new barcode object
20132 $barcodeobj = new TCPDFBarcode($code, $type);
20133 $arrcode = $barcodeobj->getBarcodeArray();
20134 if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] == 0)) {
20135 $this->Error('Error in 1D barcode string');
20137 // set default values
20138 if (!isset($style['position'])) {
20139 $style['position'] = '';
20140 } elseif ($style['position'] == 'S') {
20141 // keep this for backward compatibility
20142 $style['position'] = '';
20143 $style['stretch'] = true;
20145 if (!isset($style['fitwidth'])) {
20146 if (!isset($style['stretch'])) {
20147 $style['fitwidth'] = true;
20148 } else {
20149 $style['fitwidth'] = false;
20152 if ($style['fitwidth']) {
20153 // disable stretch
20154 $style['stretch'] = false;
20156 if (!isset($style['stretch'])) {
20157 if (($w === '') OR ($w <= 0)) {
20158 $style['stretch'] = false;
20159 } else {
20160 $style['stretch'] = true;
20163 if (!isset($style['fgcolor'])) {
20164 $style['fgcolor'] = array(0,0,0); // default black
20166 if (!isset($style['bgcolor'])) {
20167 $style['bgcolor'] = false; // default transparent
20169 if (!isset($style['border'])) {
20170 $style['border'] = false;
20172 $fontsize = 0;
20173 if (!isset($style['text'])) {
20174 $style['text'] = false;
20176 if ($style['text'] AND isset($style['font'])) {
20177 if (isset($style['fontsize'])) {
20178 $fontsize = $style['fontsize'];
20180 $this->SetFont($style['font'], '', $fontsize);
20182 if (!isset($style['stretchtext'])) {
20183 $style['stretchtext'] = 4;
20185 if ($x === '') {
20186 $x = $this->x;
20188 if ($y === '') {
20189 $y = $this->y;
20191 // check page for no-write regions and adapt page margins if necessary
20192 list($x, $y) = $this->checkPageRegions($h, $x, $y);
20193 if (($w === '') OR ($w <= 0)) {
20194 if ($this->rtl) {
20195 $w = $x - $this->lMargin;
20196 } else {
20197 $w = $this->w - $this->rMargin - $x;
20200 // padding
20201 if (!isset($style['padding'])) {
20202 $padding = 0;
20203 } elseif ($style['padding'] === 'auto') {
20204 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
20205 } else {
20206 $padding = floatval($style['padding']);
20208 // horizontal padding
20209 if (!isset($style['hpadding'])) {
20210 $hpadding = $padding;
20211 } elseif ($style['hpadding'] === 'auto') {
20212 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
20213 } else {
20214 $hpadding = floatval($style['hpadding']);
20216 // vertical padding
20217 if (!isset($style['vpadding'])) {
20218 $vpadding = $padding;
20219 } elseif ($style['vpadding'] === 'auto') {
20220 $vpadding = ($hpadding / 2);
20221 } else {
20222 $vpadding = floatval($style['vpadding']);
20224 // calculate xres (single bar width)
20225 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
20226 if ($style['stretch']) {
20227 $xres = $max_xres;
20228 } else {
20229 if ($this->empty_string($xres)) {
20230 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
20232 if ($xres > $max_xres) {
20233 // correct xres to fit on $w
20234 $xres = $max_xres;
20236 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
20237 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
20238 $hpadding = 10 * $xres;
20239 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
20240 $vpadding = ($hpadding / 2);
20244 if ($style['fitwidth']) {
20245 $wold = $w;
20246 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
20247 if (isset($style['cellfitalign'])) {
20248 switch ($style['cellfitalign']) {
20249 case 'L': {
20250 if ($this->rtl) {
20251 $x -= ($wold - $w);
20253 break;
20255 case 'R': {
20256 if (!$this->rtl) {
20257 $x += ($wold - $w);
20259 break;
20261 case 'C': {
20262 if ($this->rtl) {
20263 $x -= (($wold - $w) / 2);
20264 } else {
20265 $x += (($wold - $w) / 2);
20267 break;
20269 default : {
20270 break;
20275 $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
20276 // height
20277 if (($h === '') OR ($h <= 0)) {
20278 // set default height
20279 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
20281 $barh = $h - $text_height - (2 * $vpadding);
20282 if ($barh <=0) {
20283 // try to reduce font or padding to fit barcode on available height
20284 if ($text_height > $h) {
20285 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
20286 $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
20287 $this->SetFont($style['font'], '', $fontsize);
20289 if ($vpadding > 0) {
20290 $vpadding = (($h - $text_height) / 4);
20292 $barh = $h - $text_height - (2 * $vpadding);
20294 // fit the barcode on available space
20295 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
20296 // set alignment
20297 $this->img_rb_y = $y + $h;
20298 // set alignment
20299 if ($this->rtl) {
20300 if ($style['position'] == 'L') {
20301 $xpos = $this->lMargin;
20302 } elseif ($style['position'] == 'C') {
20303 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20304 } elseif ($style['position'] == 'R') {
20305 $xpos = $this->w - $this->rMargin - $w;
20306 } else {
20307 $xpos = $x - $w;
20309 $this->img_rb_x = $xpos;
20310 } else {
20311 if ($style['position'] == 'L') {
20312 $xpos = $this->lMargin;
20313 } elseif ($style['position'] == 'C') {
20314 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20315 } elseif ($style['position'] == 'R') {
20316 $xpos = $this->w - $this->rMargin - $w;
20317 } else {
20318 $xpos = $x;
20320 $this->img_rb_x = $xpos + $w;
20322 $xpos_rect = $xpos;
20323 if (!isset($style['align'])) {
20324 $style['align'] = 'C';
20326 switch ($style['align']) {
20327 case 'L': {
20328 $xpos = $xpos_rect + $hpadding;
20329 break;
20331 case 'R': {
20332 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
20333 break;
20335 case 'C':
20336 default : {
20337 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
20338 break;
20341 $xpos_text = $xpos;
20342 // barcode is always printed in LTR direction
20343 $tempRTL = $this->rtl;
20344 $this->rtl = false;
20345 // print background color
20346 if ($style['bgcolor']) {
20347 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
20348 } elseif ($style['border']) {
20349 $this->Rect($xpos_rect, $y, $w, $h, 'D');
20351 // set foreground color
20352 $this->SetDrawColorArray($style['fgcolor']);
20353 $this->SetTextColorArray($style['fgcolor']);
20354 // print bars
20355 foreach ($arrcode['bcode'] as $k => $v) {
20356 $bw = ($v['w'] * $xres);
20357 if ($v['t']) {
20358 // draw a vertical bar
20359 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
20360 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
20362 $xpos += $bw;
20364 // print text
20365 if ($style['text']) {
20366 if (isset($style['label']) AND !$this->empty_string($style['label'])) {
20367 $label = $style['label'];
20368 } else {
20369 $label = $code;
20371 $txtwidth = ($arrcode['maxw'] * $xres);
20372 if ($this->GetStringWidth($label) > $txtwidth) {
20373 $style['stretchtext'] = 2;
20375 // print text
20376 $this->x = $xpos_text;
20377 $this->y = $y + $vpadding + $barh;
20378 $cellpadding = $this->cell_padding;
20379 $this->SetCellPadding(0);
20380 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
20381 $this->cell_padding = $cellpadding;
20383 // restore original direction
20384 $this->rtl = $tempRTL;
20385 // restore previous settings
20386 $this->setGraphicVars($gvars);
20387 // set pointer to align the next text/objects
20388 switch($align) {
20389 case 'T':{
20390 $this->y = $y;
20391 $this->x = $this->img_rb_x;
20392 break;
20394 case 'M':{
20395 $this->y = $y + round($h / 2);
20396 $this->x = $this->img_rb_x;
20397 break;
20399 case 'B':{
20400 $this->y = $this->img_rb_y;
20401 $this->x = $this->img_rb_x;
20402 break;
20404 case 'N':{
20405 $this->SetY($this->img_rb_y);
20406 break;
20408 default:{
20409 break;
20412 $this->endlinex = $this->img_rb_x;
20416 * This function is DEPRECATED, please use the new write1DBarcode() function.
20417 * @param $x (int) x position in user units
20418 * @param $y (int) y position in user units
20419 * @param $w (int) width in user units
20420 * @param $h (int) height position in user units
20421 * @param $type (string) type of barcode
20422 * @param $style (string) barcode style
20423 * @param $font (string) font for text
20424 * @param $xres (int) x resolution
20425 * @param $code (string) code to print
20426 * @deprecated deprecated since version 3.1.000 (2008-06-10)
20427 * @public
20428 * @see write1DBarcode()
20430 public function writeBarcode($x, $y, $w, $h, $type, $style, $font, $xres, $code) {
20431 // convert old settings for the new write1DBarcode() function.
20432 $xres = 1 / $xres;
20433 $newstyle = array(
20434 'position' => '',
20435 'align' => '',
20436 'stretch' => false,
20437 'fitwidth' => false,
20438 'cellfitalign' => '',
20439 'border' => false,
20440 'padding' => 0,
20441 'fgcolor' => array(0,0,0),
20442 'bgcolor' => false,
20443 'text' => true,
20444 'font' => $font,
20445 'fontsize' => 8,
20446 'stretchtext' => 4
20448 if ($style & 1) {
20449 $newstyle['border'] = true;
20451 if ($style & 2) {
20452 $newstyle['bgcolor'] = false;
20454 if ($style & 4) {
20455 $newstyle['position'] = 'C';
20456 } elseif ($style & 8) {
20457 $newstyle['position'] = 'L';
20458 } elseif ($style & 16) {
20459 $newstyle['position'] = 'R';
20461 if ($style & 128) {
20462 $newstyle['text'] = true;
20464 if ($style & 256) {
20465 $newstyle['stretchtext'] = 4;
20467 $this->write1DBarcode($code, $type, $x, $y, $w, $h, $xres, $newstyle, '');
20471 * Print 2D Barcode.
20472 * @param $code (string) code to print
20473 * @param $type (string) type of barcode (see 2dbarcodes.php for supported formats).
20474 * @param $x (int) x position in user units
20475 * @param $y (int) y position in user units
20476 * @param $w (int) width in user units
20477 * @param $h (int) height in user units
20478 * @param $style (array) array of options:<ul>
20479 * <li>boolean $style['border'] if true prints a border around the barcode</li>
20480 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
20481 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
20482 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
20483 * <li>int $style['module_width'] width of a single module in points</li>
20484 * <li>int $style['module_height'] height of a single module in points</li>
20485 * <li>array $style['fgcolor'] color array for bars and text</li>
20486 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
20487 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
20488 * <li>$style['module_height'] height of a single module in points</li></ul>
20489 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
20490 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
20491 * @author Nicola Asuni
20492 * @since 4.5.037 (2009-04-07)
20493 * @public
20495 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
20496 if ($this->empty_string(trim($code))) {
20497 return;
20499 require_once(dirname(__FILE__).'/2dbarcodes.php');
20500 // save current graphic settings
20501 $gvars = $this->getGraphicVars();
20502 // create new barcode object
20503 $barcodeobj = new TCPDF2DBarcode($code, $type);
20504 $arrcode = $barcodeobj->getBarcodeArray();
20505 if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
20506 $this->Error('Error in 2D barcode string');
20508 // set default values
20509 if (!isset($style['position'])) {
20510 $style['position'] = '';
20512 if (!isset($style['fgcolor'])) {
20513 $style['fgcolor'] = array(0,0,0); // default black
20515 if (!isset($style['bgcolor'])) {
20516 $style['bgcolor'] = false; // default transparent
20518 if (!isset($style['border'])) {
20519 $style['border'] = false;
20521 // padding
20522 if (!isset($style['padding'])) {
20523 $style['padding'] = 0;
20524 } elseif ($style['padding'] === 'auto') {
20525 $style['padding'] = 4;
20527 if (!isset($style['hpadding'])) {
20528 $style['hpadding'] = $style['padding'];
20529 } elseif ($style['hpadding'] === 'auto') {
20530 $style['hpadding'] = 4;
20532 if (!isset($style['vpadding'])) {
20533 $style['vpadding'] = $style['padding'];
20534 } elseif ($style['vpadding'] === 'auto') {
20535 $style['vpadding'] = 4;
20537 $hpad = (2 * $style['hpadding']);
20538 $vpad = (2 * $style['vpadding']);
20539 // cell (module) dimension
20540 if (!isset($style['module_width'])) {
20541 $style['module_width'] = 1; // width of a single module in points
20543 if (!isset($style['module_height'])) {
20544 $style['module_height'] = 1; // height of a single module in points
20546 if ($x === '') {
20547 $x = $this->x;
20549 if ($y === '') {
20550 $y = $this->y;
20552 // check page for no-write regions and adapt page margins if necessary
20553 list($x, $y) = $this->checkPageRegions($h, $x, $y);
20554 // number of barcode columns and rows
20555 $rows = $arrcode['num_rows'];
20556 $cols = $arrcode['num_cols'];
20557 // module width and height
20558 $mw = $style['module_width'];
20559 $mh = $style['module_height'];
20560 if (($mw == 0) OR ($mh == 0)) {
20561 $this->Error('Error in 2D barcode string');
20563 // get max dimensions
20564 if ($this->rtl) {
20565 $maxw = $x - $this->lMargin;
20566 } else {
20567 $maxw = $this->w - $this->rMargin - $x;
20569 $maxh = ($this->h - $this->tMargin - $this->bMargin);
20570 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
20571 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
20572 if (!$distort) {
20573 if (($maxw * $ratioHW) > $maxh) {
20574 $maxw = $maxh * $ratioWH;
20576 if (($maxh * $ratioWH) > $maxw) {
20577 $maxh = $maxw * $ratioHW;
20580 // set maximum dimesions
20581 if ($w > $maxw) {
20582 $w = $maxw;
20584 if ($h > $maxh) {
20585 $h = $maxh;
20587 // set dimensions
20588 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
20589 $w = ($cols + $hpad) * ($mw / $this->k);
20590 $h = ($rows + $vpad) * ($mh / $this->k);
20591 } elseif (($w === '') OR ($w <= 0)) {
20592 $w = $h * $ratioWH;
20593 } elseif (($h === '') OR ($h <= 0)) {
20594 $h = $w * $ratioHW;
20596 // barcode size (excluding padding)
20597 $bw = ($w * $cols) / ($cols + $hpad);
20598 $bh = ($h * $rows) / ($rows + $vpad);
20599 // dimension of single barcode cell unit
20600 $cw = $bw / $cols;
20601 $ch = $bh / $rows;
20602 if (!$distort) {
20603 if (($cw / $ch) > ($mw / $mh)) {
20604 // correct horizontal distortion
20605 $cw = $ch * $mw / $mh;
20606 $bw = $cw * $cols;
20607 $style['hpadding'] = ($w - $bw) / (2 * $cw);
20608 } else {
20609 // correct vertical distortion
20610 $ch = $cw * $mh / $mw;
20611 $bh = $ch * $rows;
20612 $style['vpadding'] = ($h - $bh) / (2 * $ch);
20615 // fit the barcode on available space
20616 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
20617 // set alignment
20618 $this->img_rb_y = $y + $h;
20619 // set alignment
20620 if ($this->rtl) {
20621 if ($style['position'] == 'L') {
20622 $xpos = $this->lMargin;
20623 } elseif ($style['position'] == 'C') {
20624 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20625 } elseif ($style['position'] == 'R') {
20626 $xpos = $this->w - $this->rMargin - $w;
20627 } else {
20628 $xpos = $x - $w;
20630 $this->img_rb_x = $xpos;
20631 } else {
20632 if ($style['position'] == 'L') {
20633 $xpos = $this->lMargin;
20634 } elseif ($style['position'] == 'C') {
20635 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20636 } elseif ($style['position'] == 'R') {
20637 $xpos = $this->w - $this->rMargin - $w;
20638 } else {
20639 $xpos = $x;
20641 $this->img_rb_x = $xpos + $w;
20643 $xstart = $xpos + ($style['hpadding'] * $cw);
20644 $ystart = $y + ($style['vpadding'] * $ch);
20645 // barcode is always printed in LTR direction
20646 $tempRTL = $this->rtl;
20647 $this->rtl = false;
20648 // print background color
20649 if ($style['bgcolor']) {
20650 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
20651 } elseif ($style['border']) {
20652 $this->Rect($xpos, $y, $w, $h, 'D');
20654 // set foreground color
20655 $this->SetDrawColorArray($style['fgcolor']);
20656 // print barcode cells
20657 // for each row
20658 for ($r = 0; $r < $rows; ++$r) {
20659 $xr = $xstart;
20660 // for each column
20661 for ($c = 0; $c < $cols; ++$c) {
20662 if ($arrcode['bcode'][$r][$c] == 1) {
20663 // draw a single barcode cell
20664 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
20666 $xr += $cw;
20668 $ystart += $ch;
20670 // restore original direction
20671 $this->rtl = $tempRTL;
20672 // restore previous settings
20673 $this->setGraphicVars($gvars);
20674 // set pointer to align the next text/objects
20675 switch($align) {
20676 case 'T':{
20677 $this->y = $y;
20678 $this->x = $this->img_rb_x;
20679 break;
20681 case 'M':{
20682 $this->y = $y + round($h/2);
20683 $this->x = $this->img_rb_x;
20684 break;
20686 case 'B':{
20687 $this->y = $this->img_rb_y;
20688 $this->x = $this->img_rb_x;
20689 break;
20691 case 'N':{
20692 $this->SetY($this->img_rb_y);
20693 break;
20695 default:{
20696 break;
20699 $this->endlinex = $this->img_rb_x;
20703 * Returns an array containing current margins:
20704 * <ul>
20705 <li>$ret['left'] = left margin</li>
20706 <li>$ret['right'] = right margin</li>
20707 <li>$ret['top'] = top margin</li>
20708 <li>$ret['bottom'] = bottom margin</li>
20709 <li>$ret['header'] = header margin</li>
20710 <li>$ret['footer'] = footer margin</li>
20711 <li>$ret['cell'] = cell padding array</li>
20712 <li>$ret['padding_left'] = cell left padding</li>
20713 <li>$ret['padding_top'] = cell top padding</li>
20714 <li>$ret['padding_right'] = cell right padding</li>
20715 <li>$ret['padding_bottom'] = cell bottom padding</li>
20716 * </ul>
20717 * @return array containing all margins measures
20718 * @public
20719 * @since 3.2.000 (2008-06-23)
20721 public function getMargins() {
20722 $ret = array(
20723 'left' => $this->lMargin,
20724 'right' => $this->rMargin,
20725 'top' => $this->tMargin,
20726 'bottom' => $this->bMargin,
20727 'header' => $this->header_margin,
20728 'footer' => $this->footer_margin,
20729 'cell' => $this->cell_padding,
20730 'padding_left' => $this->cell_padding['L'],
20731 'padding_top' => $this->cell_padding['T'],
20732 'padding_right' => $this->cell_padding['R'],
20733 'padding_bottom' => $this->cell_padding['B']
20735 return $ret;
20739 * Returns an array containing original margins:
20740 * <ul>
20741 <li>$ret['left'] = left margin</li>
20742 <li>$ret['right'] = right margin</li>
20743 * </ul>
20744 * @return array containing all margins measures
20745 * @public
20746 * @since 4.0.012 (2008-07-24)
20748 public function getOriginalMargins() {
20749 $ret = array(
20750 'left' => $this->original_lMargin,
20751 'right' => $this->original_rMargin
20753 return $ret;
20757 * Returns the current font size.
20758 * @return current font size
20759 * @public
20760 * @since 3.2.000 (2008-06-23)
20762 public function getFontSize() {
20763 return $this->FontSize;
20767 * Returns the current font size in points unit.
20768 * @return current font size in points unit
20769 * @public
20770 * @since 3.2.000 (2008-06-23)
20772 public function getFontSizePt() {
20773 return $this->FontSizePt;
20777 * Returns the current font family name.
20778 * @return string current font family name
20779 * @public
20780 * @since 4.3.008 (2008-12-05)
20782 public function getFontFamily() {
20783 return $this->FontFamily;
20787 * Returns the current font style.
20788 * @return string current font style
20789 * @public
20790 * @since 4.3.008 (2008-12-05)
20792 public function getFontStyle() {
20793 return $this->FontStyle;
20797 * Cleanup HTML code (requires HTML Tidy library).
20798 * @param $html (string) htmlcode to fix
20799 * @param $default_css (string) CSS commands to add
20800 * @param $tagvs (array) parameters for setHtmlVSpace method
20801 * @param $tidy_options (array) options for tidy_parse_string function
20802 * @return string XHTML code cleaned up
20803 * @author Nicola Asuni
20804 * @public
20805 * @since 5.9.017 (2010-11-16)
20806 * @see setHtmlVSpace()
20808 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
20809 // configure parameters for HTML Tidy
20810 if ($tidy_options === '') {
20811 $tidy_options = array (
20812 'clean' => 1,
20813 'drop-empty-paras' => 0,
20814 'drop-proprietary-attributes' => 1,
20815 'fix-backslash' => 1,
20816 'hide-comments' => 1,
20817 'join-styles' => 1,
20818 'lower-literals' => 1,
20819 'merge-divs' => 1,
20820 'merge-spans' => 1,
20821 'output-xhtml' => 1,
20822 'word-2000' => 1,
20823 'wrap' => 0,
20824 'output-bom' => 0,
20825 //'char-encoding' => 'utf8',
20826 //'input-encoding' => 'utf8',
20827 //'output-encoding' => 'utf8'
20830 // clean up the HTML code
20831 $tidy = tidy_parse_string($html, $tidy_options);
20832 // fix the HTML
20833 $tidy->cleanRepair();
20834 // get the CSS part
20835 $tidy_head = tidy_get_head($tidy);
20836 $css = $tidy_head->value;
20837 $css = preg_replace('/<style([^>]+)>/ims', '<style>', $css);
20838 $css = preg_replace('/<\/style>(.*)<style>/ims', "\n", $css);
20839 $css = str_replace('/*<![CDATA[*/', '', $css);
20840 $css = str_replace('/*]]>*/', '', $css);
20841 preg_match('/<style>(.*)<\/style>/ims', $css, $matches);
20842 if (isset($matches[1])) {
20843 $css = strtolower($matches[1]);
20844 } else {
20845 $css = '';
20847 // include default css
20848 $css = '<style>'.$default_css.$css.'</style>';
20849 // get the body part
20850 $tidy_body = tidy_get_body($tidy);
20851 $html = $tidy_body->value;
20852 // fix some self-closing tags
20853 $html = str_replace('<br>', '<br />', $html);
20854 // remove some empty tag blocks
20855 $html = preg_replace('/<div([^\>]*)><\/div>/', '', $html);
20856 $html = preg_replace('/<p([^\>]*)><\/p>/', '', $html);
20857 if ($tagvs !== '') {
20858 // set vertical space for some XHTML tags
20859 $this->setHtmlVSpace($tagvs);
20861 // return the cleaned XHTML code + CSS
20862 return $css.$html;
20866 * Extracts the CSS properties from a CSS string.
20867 * @param $cssdata (string) string containing CSS definitions.
20868 * @return An array where the keys are the CSS selectors and the values are the CSS properties.
20869 * @author Nicola Asuni
20870 * @since 5.1.000 (2010-05-25)
20871 * @protected
20873 protected function extractCSSproperties($cssdata) {
20874 if (empty($cssdata)) {
20875 return array();
20877 // remove comments
20878 $cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
20879 // remove newlines and multiple spaces
20880 $cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
20881 // remove some spaces
20882 $cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
20883 // remove empty blocks
20884 $cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
20885 // replace media type parenthesis
20886 $cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $cssdata);
20887 $cssdata = preg_replace('/\}\}/si', '}§', $cssdata);
20888 // trim string
20889 $cssdata = trim($cssdata);
20890 // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
20891 $cssblocks = array();
20892 $matches = array();
20893 if (preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $cssdata, $matches) > 0) {
20894 foreach ($matches[1] as $key => $type) {
20895 $cssblocks[$type] = $matches[2][$key];
20897 // remove media blocks
20898 $cssdata = preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $cssdata);
20900 // keep 'all' and 'print' media, other media types are discarded
20901 if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
20902 $cssdata .= $cssblocks['all'];
20904 if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
20905 $cssdata .= $cssblocks['print'];
20907 // reset css blocks array
20908 $cssblocks = array();
20909 $matches = array();
20910 // explode css data string into array
20911 if (substr($cssdata, -1) == '}') {
20912 // remove last parethesis
20913 $cssdata = substr($cssdata, 0, -1);
20915 $matches = explode('}', $cssdata);
20916 foreach ($matches as $key => $block) {
20917 // index 0 contains the CSS selector, index 1 contains CSS properties
20918 $cssblocks[$key] = explode('{', $block);
20919 if (!isset($cssblocks[$key][1])) {
20920 // remove empty definitions
20921 unset($cssblocks[$key]);
20924 // split groups of selectors (comma-separated list of selectors)
20925 foreach ($cssblocks as $key => $block) {
20926 if (strpos($block[0], ',') > 0) {
20927 $selectors = explode(',', $block[0]);
20928 foreach ($selectors as $sel) {
20929 $cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
20931 unset($cssblocks[$key]);
20934 // covert array to selector => properties
20935 $cssdata = array();
20936 foreach ($cssblocks as $block) {
20937 $selector = $block[0];
20938 // calculate selector's specificity
20939 $matches = array();
20940 $a = 0; // the declaration is not from is a 'style' attribute
20941 $b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
20942 $c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
20943 $c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
20944 $d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9]+/', ' '.$selector, $matches)); // number of element names
20945 $d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
20946 $specificity = $a.$b.$c.$d;
20947 // add specificity to the beginning of the selector
20948 $cssdata[$specificity.' '.$selector] = $block[1];
20950 // sort selectors alphabetically to account for specificity
20951 ksort($cssdata, SORT_STRING);
20952 // return array
20953 return $cssdata;
20957 * Returns true if the CSS selector is valid for the selected HTML tag
20958 * @param $dom (array) array of HTML tags and properties
20959 * @param $key (int) key of the current HTML tag
20960 * @param $selector (string) CSS selector string
20961 * @return true if the selector is valid, false otherwise
20962 * @protected
20963 * @since 5.1.000 (2010-05-25)
20965 protected function isValidCSSSelectorForTag($dom, $key, $selector) {
20966 $valid = false; // value to be returned
20967 $tag = $dom[$key]['value'];
20968 $class = array();
20969 if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
20970 $class = explode(' ', strtolower($dom[$key]['attribute']['class']));
20972 $id = '';
20973 if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
20974 $id = strtolower($dom[$key]['attribute']['id']);
20976 $selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
20977 $matches = array();
20978 if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
20979 $parentop = array_pop($matches[1]);
20980 $operator = $parentop[0];
20981 $offset = $parentop[1];
20982 $lasttag = array_pop($matches[2]);
20983 $lasttag = strtolower(trim($lasttag[0]));
20984 if (($lasttag == '*') OR ($lasttag == $tag)) {
20985 // the last element on selector is our tag or 'any tag'
20986 $attrib = array_pop($matches[3]);
20987 $attrib = strtolower(trim($attrib[0]));
20988 if (!empty($attrib)) {
20989 // check if matches class, id, attribute, pseudo-class or pseudo-element
20990 switch ($attrib{0}) {
20991 case '.': { // class
20992 if (in_array(substr($attrib, 1), $class)) {
20993 $valid = true;
20995 break;
20997 case '#': { // ID
20998 if (substr($attrib, 1) == $id) {
20999 $valid = true;
21001 break;
21003 case '[': { // attribute
21004 $attrmatch = array();
21005 if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
21006 $att = strtolower($attrmatch[1]);
21007 $val = $attrmatch[3];
21008 if (isset($dom[$key]['attribute'][$att])) {
21009 switch ($attrmatch[2]) {
21010 case '=': {
21011 if ($dom[$key]['attribute'][$att] == $val) {
21012 $valid = true;
21014 break;
21016 case '~=': {
21017 if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
21018 $valid = true;
21020 break;
21022 case '^=': {
21023 if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
21024 $valid = true;
21026 break;
21028 case '$=': {
21029 if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
21030 $valid = true;
21032 break;
21034 case '*=': {
21035 if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
21036 $valid = true;
21038 break;
21040 case '|=': {
21041 if ($dom[$key]['attribute'][$att] == $val) {
21042 $valid = true;
21043 } elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
21044 $valid = true;
21046 break;
21048 default: {
21049 $valid = true;
21054 break;
21056 case ':': { // pseudo-class or pseudo-element
21057 if ($attrib{1} == ':') { // pseudo-element
21058 // pseudo-elements are not supported!
21059 // (::first-line, ::first-letter, ::before, ::after)
21060 } else { // pseudo-class
21061 // pseudo-classes are not supported!
21062 // (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
21064 break;
21066 } // end of switch
21067 } else {
21068 $valid = true;
21070 if ($valid AND ($offset > 0)) {
21071 $valid = false;
21072 // check remaining selector part
21073 $selector = substr($selector, 0, $offset);
21074 switch ($operator) {
21075 case ' ': { // descendant of an element
21076 while ($dom[$key]['parent'] > 0) {
21077 if ($this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
21078 $valid = true;
21079 break;
21080 } else {
21081 $key = $dom[$key]['parent'];
21084 break;
21086 case '>': { // child of an element
21087 $valid = $this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
21088 break;
21090 case '+': { // immediately preceded by an element
21091 for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
21092 if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
21093 $valid = $this->isValidCSSSelectorForTag($dom, $i, $selector);
21094 break;
21097 break;
21099 case '~': { // preceded by an element
21100 for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
21101 if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
21102 if ($this->isValidCSSSelectorForTag($dom, $i, $selector)) {
21103 break;
21107 break;
21113 return $valid;
21117 * Returns the styles array that apply for the selected HTML tag.
21118 * @param $dom (array) array of HTML tags and properties
21119 * @param $key (int) key of the current HTML tag
21120 * @param $css (array) array of CSS properties
21121 * @return array containing CSS properties
21122 * @protected
21123 * @since 5.1.000 (2010-05-25)
21125 protected function getCSSdataArray($dom, $key, $css) {
21126 $cssarray = array(); // style to be returned
21127 // get parent CSS selectors
21128 $selectors = array();
21129 if (isset($dom[($dom[$key]['parent'])]['csssel'])) {
21130 $selectors = $dom[($dom[$key]['parent'])]['csssel'];
21132 // get all styles that apply
21133 foreach($css as $selector => $style) {
21134 $pos = strpos($selector, ' ');
21135 // get specificity
21136 $specificity = substr($selector, 0, $pos);
21137 // remove specificity
21138 $selector = substr($selector, $pos);
21139 // check if this selector apply to current tag
21140 if ($this->isValidCSSSelectorForTag($dom, $key, $selector)) {
21141 if (!in_array($selector, $selectors)) {
21142 // add style if not already added on parent selector
21143 $cssarray[] = array('k' => $selector, 's' => $specificity, 'c' => $style);
21144 $selectors[] = $selector;
21148 if (isset($dom[$key]['attribute']['style'])) {
21149 // attach inline style (latest properties have high priority)
21150 $cssarray[] = array('k' => '', 's' => '1000', 'c' => $dom[$key]['attribute']['style']);
21152 // order the css array to account for specificity
21153 $cssordered = array();
21154 foreach ($cssarray as $key => $val) {
21155 $skey = sprintf('%04d', $key);
21156 $cssordered[$val['s'].'_'.$skey] = $val;
21158 // sort selectors alphabetically to account for specificity
21159 ksort($cssordered, SORT_STRING);
21160 return array($selectors, $cssordered);
21164 * Compact CSS data array into single string.
21165 * @param $css (array) array of CSS properties
21166 * @return string containing merged CSS properties
21167 * @protected
21168 * @since 5.9.070 (2011-04-19)
21170 protected function getTagStyleFromCSSarray($css) {
21171 $tagstyle = ''; // value to be returned
21172 foreach ($css as $style) {
21173 // split single css commands
21174 $csscmds = explode(';', $style['c']);
21175 foreach ($csscmds as $cmd) {
21176 if (!empty($cmd)) {
21177 $pos = strpos($cmd, ':');
21178 if ($pos !== false) {
21179 $cmd = substr($cmd, 0, ($pos + 1));
21180 if (strpos($tagstyle, $cmd) !== false) {
21181 // remove duplicate commands (last commands have high priority)
21182 $tagstyle = preg_replace('/'.$cmd.'[^;]+/i', '', $tagstyle);
21187 $tagstyle .= ';'.$style['c'];
21189 // remove multiple semicolons
21190 $tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
21191 return $tagstyle;
21195 * Returns the border width from CSS property
21196 * @param $width (string) border width
21197 * @return int with in user units
21198 * @protected
21199 * @since 5.7.000 (2010-08-02)
21201 protected function getCSSBorderWidth($width) {
21202 if ($width == 'thin') {
21203 $width = (2 / $this->k);
21204 } elseif ($width == 'medium') {
21205 $width = (4 / $this->k);
21206 } elseif ($width == 'thick') {
21207 $width = (6 / $this->k);
21208 } else {
21209 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
21211 return $width;
21215 * Returns the border dash style from CSS property
21216 * @param $style (string) border style to convert
21217 * @return int sash style (return -1 in case of none or hidden border)
21218 * @protected
21219 * @since 5.7.000 (2010-08-02)
21221 protected function getCSSBorderDashStyle($style) {
21222 switch (strtolower($style)) {
21223 case 'none':
21224 case 'hidden': {
21225 $dash = -1;
21226 break;
21228 case 'dotted': {
21229 $dash = 1;
21230 break;
21232 case 'dashed': {
21233 $dash = 3;
21234 break;
21236 case 'double':
21237 case 'groove':
21238 case 'ridge':
21239 case 'inset':
21240 case 'outset':
21241 case 'solid':
21242 default: {
21243 $dash = 0;
21244 break;
21247 return $dash;
21251 * Returns the border style array from CSS border properties
21252 * @param $cssborder (string) border properties
21253 * @return array containing border properties
21254 * @protected
21255 * @since 5.7.000 (2010-08-02)
21257 protected function getCSSBorderStyle($cssborder) {
21258 $bprop = preg_split('/[\s]+/', trim($cssborder));
21259 $border = array(); // value to be returned
21260 switch (count($bprop)) {
21261 case 3: {
21262 $width = $bprop[0];
21263 $style = $bprop[1];
21264 $color = $bprop[2];
21265 break;
21267 case 2: {
21268 $width = 'medium';
21269 $style = $bprop[0];
21270 $color = $bprop[1];
21271 break;
21273 case 1: {
21274 $width = 'medium';
21275 $style = $bprop[0];
21276 $color = 'black';
21277 break;
21279 default: {
21280 $width = 'medium';
21281 $style = 'solid';
21282 $color = 'black';
21283 break;
21286 if ($style == 'none') {
21287 return array();
21289 $border['cap'] = 'square';
21290 $border['join'] = 'miter';
21291 $border['dash'] = $this->getCSSBorderDashStyle($style);
21292 if ($border['dash'] < 0) {
21293 return array();
21295 $border['width'] = $this->getCSSBorderWidth($width);
21296 $border['color'] = $this->convertHTMLColorToDec($color);
21297 return $border;
21301 * Get the internal Cell padding from CSS attribute.
21302 * @param $csspadding (string) padding properties
21303 * @param $width (float) width of the containing element
21304 * @return array of cell paddings
21305 * @public
21306 * @since 5.9.000 (2010-10-04)
21308 public function getCSSPadding($csspadding, $width=0) {
21309 $padding = preg_split('/[\s]+/', trim($csspadding));
21310 $cell_padding = array(); // value to be returned
21311 switch (count($padding)) {
21312 case 4: {
21313 $cell_padding['T'] = $padding[0];
21314 $cell_padding['R'] = $padding[1];
21315 $cell_padding['B'] = $padding[2];
21316 $cell_padding['L'] = $padding[3];
21317 break;
21319 case 3: {
21320 $cell_padding['T'] = $padding[0];
21321 $cell_padding['R'] = $padding[1];
21322 $cell_padding['B'] = $padding[2];
21323 $cell_padding['L'] = $padding[1];
21324 break;
21326 case 2: {
21327 $cell_padding['T'] = $padding[0];
21328 $cell_padding['R'] = $padding[1];
21329 $cell_padding['B'] = $padding[0];
21330 $cell_padding['L'] = $padding[1];
21331 break;
21333 case 1: {
21334 $cell_padding['T'] = $padding[0];
21335 $cell_padding['R'] = $padding[0];
21336 $cell_padding['B'] = $padding[0];
21337 $cell_padding['L'] = $padding[0];
21338 break;
21340 default: {
21341 return $this->cell_padding;
21344 if ($width == 0) {
21345 $width = $this->w - $this->lMargin - $this->rMargin;
21347 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
21348 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
21349 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
21350 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
21351 return $cell_padding;
21355 * Get the internal Cell margin from CSS attribute.
21356 * @param $cssmargin (string) margin properties
21357 * @param $width (float) width of the containing element
21358 * @return array of cell margins
21359 * @public
21360 * @since 5.9.000 (2010-10-04)
21362 public function getCSSMargin($cssmargin, $width=0) {
21363 $margin = preg_split('/[\s]+/', trim($cssmargin));
21364 $cell_margin = array(); // value to be returned
21365 switch (count($margin)) {
21366 case 4: {
21367 $cell_margin['T'] = $margin[0];
21368 $cell_margin['R'] = $margin[1];
21369 $cell_margin['B'] = $margin[2];
21370 $cell_margin['L'] = $margin[3];
21371 break;
21373 case 3: {
21374 $cell_margin['T'] = $margin[0];
21375 $cell_margin['R'] = $margin[1];
21376 $cell_margin['B'] = $margin[2];
21377 $cell_margin['L'] = $margin[1];
21378 break;
21380 case 2: {
21381 $cell_margin['T'] = $margin[0];
21382 $cell_margin['R'] = $margin[1];
21383 $cell_margin['B'] = $margin[0];
21384 $cell_margin['L'] = $margin[1];
21385 break;
21387 case 1: {
21388 $cell_margin['T'] = $margin[0];
21389 $cell_margin['R'] = $margin[0];
21390 $cell_margin['B'] = $margin[0];
21391 $cell_margin['L'] = $margin[0];
21392 break;
21394 default: {
21395 return $this->cell_margin;
21398 if ($width == 0) {
21399 $width = $this->w - $this->lMargin - $this->rMargin;
21401 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
21402 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
21403 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
21404 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
21405 return $cell_margin;
21409 * Get the border-spacing from CSS attribute.
21410 * @param $cssbspace (string) border-spacing CSS properties
21411 * @param $width (float) width of the containing element
21412 * @return array of border spacings
21413 * @public
21414 * @since 5.9.010 (2010-10-27)
21416 public function getCSSBorderMargin($cssbspace, $width=0) {
21417 $space = preg_split('/[\s]+/', trim($cssbspace));
21418 $border_spacing = array(); // value to be returned
21419 switch (count($space)) {
21420 case 2: {
21421 $border_spacing['H'] = $space[0];
21422 $border_spacing['V'] = $space[1];
21423 break;
21425 case 1: {
21426 $border_spacing['H'] = $space[0];
21427 $border_spacing['V'] = $space[0];
21428 break;
21430 default: {
21431 return array('H' => 0, 'V' => 0);
21434 if ($width == 0) {
21435 $width = $this->w - $this->lMargin - $this->rMargin;
21437 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
21438 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
21439 return $border_spacing;
21443 * Returns the letter-spacing value from CSS value
21444 * @param $spacing (string) letter-spacing value
21445 * @param $parent (float) font spacing (tracking) value of the parent element
21446 * @return float quantity to increases or decreases the space between characters in a text.
21447 * @protected
21448 * @since 5.9.000 (2010-10-02)
21450 protected function getCSSFontSpacing($spacing, $parent=0) {
21451 $val = 0; // value to be returned
21452 $spacing = trim($spacing);
21453 switch ($spacing) {
21454 case 'normal': {
21455 $val = 0;
21456 break;
21458 case 'inherit': {
21459 if ($parent == 'normal') {
21460 $val = 0;
21461 } else {
21462 $val = $parent;
21464 break;
21466 default: {
21467 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
21470 return $val;
21474 * Returns the percentage of font stretching from CSS value
21475 * @param $stretch (string) stretch mode
21476 * @param $parent (float) stretch value of the parent element
21477 * @return float font stretching percentage
21478 * @protected
21479 * @since 5.9.000 (2010-10-02)
21481 protected function getCSSFontStretching($stretch, $parent=100) {
21482 $val = 100; // value to be returned
21483 $stretch = trim($stretch);
21484 switch ($stretch) {
21485 case 'ultra-condensed': {
21486 $val = 40;
21487 break;
21489 case 'extra-condensed': {
21490 $val = 55;
21491 break;
21493 case 'condensed': {
21494 $val = 70;
21495 break;
21497 case 'semi-condensed': {
21498 $val = 85;
21499 break;
21501 case 'normal': {
21502 $val = 100;
21503 break;
21505 case 'semi-expanded': {
21506 $val = 115;
21507 break;
21509 case 'expanded': {
21510 $val = 130;
21511 break;
21513 case 'extra-expanded': {
21514 $val = 145;
21515 break;
21517 case 'ultra-expanded': {
21518 $val = 160;
21519 break;
21521 case 'wider': {
21522 $val = $parent + 10;
21523 break;
21525 case 'narrower': {
21526 $val = $parent - 10;
21527 break;
21529 case 'inherit': {
21530 if ($parent == 'normal') {
21531 $val = 100;
21532 } else {
21533 $val = $parent;
21535 break;
21537 default: {
21538 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
21541 return $val;
21545 * Returns the HTML DOM array.
21546 * @param $html (string) html code
21547 * @return array
21548 * @protected
21549 * @since 3.2.000 (2008-06-20)
21551 protected function getHtmlDomArray($html) {
21552 // array of CSS styles ( selector => properties).
21553 $css = array();
21554 // get CSS array defined at previous call
21555 $matches = array();
21556 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
21557 if (isset($matches[1][0])) {
21558 $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
21560 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
21562 // extract external CSS files
21563 $matches = array();
21564 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
21565 foreach ($matches[1] as $key => $link) {
21566 $type = array();
21567 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
21568 $type = array();
21569 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
21570 // get 'all' and 'print' media, other media types are discarded
21571 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
21572 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
21573 $type = array();
21574 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
21575 // read CSS data file
21576 $cssdata = file_get_contents(trim($type[1]));
21577 $css = array_merge($css, $this->extractCSSproperties($cssdata));
21583 // extract style tags
21584 $matches = array();
21585 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
21586 foreach ($matches[1] as $key => $media) {
21587 $type = array();
21588 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
21589 // get 'all' and 'print' media, other media types are discarded
21590 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
21591 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
21592 $cssdata = $matches[2][$key];
21593 $css = array_merge($css, $this->extractCSSproperties($cssdata));
21597 // create a special tag to contain the CSS array (used for table content)
21598 $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
21599 // remove head and style blocks
21600 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
21601 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
21602 // define block tags
21603 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
21604 // define self-closing tags
21605 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
21606 // remove all unsupported tags (the line below lists all supported tags)
21607 $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>');
21608 //replace some blank characters
21609 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
21610 $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);
21611 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
21612 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
21613 $html = strtr($html, $repTable);
21614 $offset = 0;
21615 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
21616 $html_a = substr($html, 0, $offset);
21617 $html_b = substr($html, $offset, ($pos - $offset + 6));
21618 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
21619 // preserve newlines on <pre> tag
21620 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
21622 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
21623 // preserve spaces on <pre> tag
21624 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
21626 $html = $html_a.$html_b.substr($html, $pos + 6);
21627 $offset = strlen($html_a.$html_b);
21629 $offset = 0;
21630 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
21631 $html_a = substr($html, 0, $offset);
21632 $html_b = substr($html, $offset, ($pos - $offset + 11));
21633 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
21634 // preserve newlines on <textarea> tag
21635 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
21636 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
21638 $html = $html_a.$html_b.substr($html, $pos + 11);
21639 $offset = strlen($html_a.$html_b);
21641 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
21642 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
21643 $offset = 0;
21644 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
21645 $html_a = substr($html, 0, $offset);
21646 $html_b = substr($html, $offset, ($pos - $offset + 9));
21647 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
21648 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
21649 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
21651 $html = $html_a.$html_b.substr($html, $pos + 9);
21652 $offset = strlen($html_a.$html_b);
21654 if (preg_match("'</select'si", $html)) {
21655 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
21656 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
21658 $html = str_replace("\n", ' ', $html);
21659 // restore textarea newlines
21660 $html = str_replace('<TBR>', "\n", $html);
21661 // remove extra spaces from code
21662 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
21663 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
21664 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
21665 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
21666 $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);
21667 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
21668 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
21669 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
21670 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
21671 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
21672 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
21673 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
21674 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
21675 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
21676 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
21677 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
21678 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
21679 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
21680 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
21681 // trim string
21682 $html = $this->stringTrim($html);
21683 // fix first image tag alignment
21684 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
21685 // pattern for generic tag
21686 $tagpattern = '/(<[^>]+>)/';
21687 // explodes the string
21688 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
21689 // count elements
21690 $maxel = count($a);
21691 $elkey = 0;
21692 $key = 0;
21693 // create an array of elements
21694 $dom = array();
21695 $dom[$key] = array();
21696 // set inheritable properties fot the first void element
21697 // 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
21698 $dom[$key]['tag'] = false;
21699 $dom[$key]['block'] = false;
21700 $dom[$key]['value'] = '';
21701 $dom[$key]['parent'] = 0;
21702 $dom[$key]['hide'] = false;
21703 $dom[$key]['fontname'] = $this->FontFamily;
21704 $dom[$key]['fontstyle'] = $this->FontStyle;
21705 $dom[$key]['fontsize'] = $this->FontSizePt;
21706 $dom[$key]['font-stretch'] = $this->font_stretching;
21707 $dom[$key]['letter-spacing'] = $this->font_spacing;
21708 $dom[$key]['stroke'] = $this->textstrokewidth;
21709 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
21710 $dom[$key]['clip'] = ($this->textrendermode > 3);
21711 $dom[$key]['line-height'] = $this->cell_height_ratio;
21712 $dom[$key]['bgcolor'] = false;
21713 $dom[$key]['fgcolor'] = $this->fgcolor; // color
21714 $dom[$key]['strokecolor'] = $this->strokecolor;
21715 $dom[$key]['align'] = '';
21716 $dom[$key]['listtype'] = '';
21717 $dom[$key]['text-indent'] = 0;
21718 $dom[$key]['border'] = array();
21719 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
21720 $thead = false; // true when we are inside the THEAD tag
21721 ++$key;
21722 $level = array();
21723 array_push($level, 0); // root
21724 while ($elkey < $maxel) {
21725 $dom[$key] = array();
21726 $element = $a[$elkey];
21727 $dom[$key]['elkey'] = $elkey;
21728 if (preg_match($tagpattern, $element)) {
21729 // html tag
21730 $element = substr($element, 1, -1);
21731 // get tag name
21732 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
21733 $tagname = strtolower($tag[1]);
21734 // check if we are inside a table header
21735 if ($tagname == 'thead') {
21736 if ($element{0} == '/') {
21737 $thead = false;
21738 } else {
21739 $thead = true;
21741 ++$elkey;
21742 continue;
21744 $dom[$key]['tag'] = true;
21745 $dom[$key]['value'] = $tagname;
21746 if (in_array($dom[$key]['value'], $blocktags)) {
21747 $dom[$key]['block'] = true;
21748 } else {
21749 $dom[$key]['block'] = false;
21751 if ($element{0} == '/') {
21752 // *** closing html tag
21753 $dom[$key]['opening'] = false;
21754 $dom[$key]['parent'] = end($level);
21755 array_pop($level);
21756 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
21757 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
21758 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
21759 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
21760 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
21761 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
21762 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
21763 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
21764 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
21765 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
21766 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
21767 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
21768 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
21769 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
21770 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
21771 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
21772 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
21774 // set the number of columns in table tag
21775 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
21776 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
21778 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
21779 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
21780 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
21781 $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
21783 $key = $i;
21784 // mark nested tables
21785 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
21786 // remove thead sections from nested tables
21787 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
21788 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
21790 // store header rows on a new table
21791 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
21792 if ($this->empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
21793 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
21795 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
21796 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
21798 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
21799 $dom[($dom[$key]['parent'])]['attribute'] = array();
21801 // header elements must be always contained in a single page
21802 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
21804 if (($dom[$key]['value'] == 'table') AND (!$this->empty_string($dom[($dom[$key]['parent'])]['thead']))) {
21805 // remove the nobr attributes from the table header
21806 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
21807 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
21809 } else {
21810 // *** opening or self-closing html tag
21811 $dom[$key]['opening'] = true;
21812 $dom[$key]['parent'] = end($level);
21813 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
21814 // self-closing tag
21815 $dom[$key]['self'] = true;
21816 } else {
21817 // opening tag
21818 array_push($level, $key);
21819 $dom[$key]['self'] = false;
21821 // copy some values from parent
21822 $parentkey = 0;
21823 if ($key > 0) {
21824 $parentkey = $dom[$key]['parent'];
21825 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
21826 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
21827 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
21828 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
21829 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
21830 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
21831 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
21832 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
21833 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
21834 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
21835 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
21836 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
21837 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
21838 $dom[$key]['align'] = $dom[$parentkey]['align'];
21839 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
21840 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
21841 $dom[$key]['border'] = array();
21842 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
21844 // get attributes
21845 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
21846 $dom[$key]['attribute'] = array(); // reset attribute array
21847 while (list($id, $name) = each($attr_array[1])) {
21848 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
21850 if (!empty($css)) {
21851 // merge CSS style to current style
21852 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = $this->getCSSdataArray($dom, $key, $css);
21853 $dom[$key]['attribute']['style'] = $this->getTagStyleFromCSSarray($dom[$key]['cssdata']);
21855 // split style attributes
21856 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
21857 // get style attributes
21858 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
21859 $dom[$key]['style'] = array(); // reset style attribute array
21860 while (list($id, $name) = each($style_array[1])) {
21861 // in case of duplicate attribute the last replace the previous
21862 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
21864 // --- get some style attributes ---
21865 // text direction
21866 if (isset($dom[$key]['style']['direction'])) {
21867 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
21869 // display
21870 if (isset($dom[$key]['style']['display'])) {
21871 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
21873 // font family
21874 if (isset($dom[$key]['style']['font-family'])) {
21875 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
21877 // list-style-type
21878 if (isset($dom[$key]['style']['list-style-type'])) {
21879 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
21880 if ($dom[$key]['listtype'] == 'inherit') {
21881 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
21884 // text-indent
21885 if (isset($dom[$key]['style']['text-indent'])) {
21886 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
21887 if ($dom[$key]['text-indent'] == 'inherit') {
21888 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
21891 // font size
21892 if (isset($dom[$key]['style']['font-size'])) {
21893 $fsize = trim($dom[$key]['style']['font-size']);
21894 switch ($fsize) {
21895 // absolute-size
21896 case 'xx-small': {
21897 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 4;
21898 break;
21900 case 'x-small': {
21901 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 3;
21902 break;
21904 case 'small': {
21905 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 2;
21906 break;
21908 case 'medium': {
21909 $dom[$key]['fontsize'] = $dom[0]['fontsize'];
21910 break;
21912 case 'large': {
21913 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 2;
21914 break;
21916 case 'x-large': {
21917 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 4;
21918 break;
21920 case 'xx-large': {
21921 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 6;
21922 break;
21924 // relative-size
21925 case 'smaller': {
21926 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] - 3;
21927 break;
21929 case 'larger': {
21930 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] + 3;
21931 break;
21933 default: {
21934 $dom[$key]['fontsize'] = $this->getHTMLUnitToUnits($fsize, $dom[$parentkey]['fontsize'], 'pt', true);
21938 // font-stretch
21939 if (isset($dom[$key]['style']['font-stretch'])) {
21940 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
21942 // letter-spacing
21943 if (isset($dom[$key]['style']['letter-spacing'])) {
21944 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
21946 // line-height
21947 if (isset($dom[$key]['style']['line-height'])) {
21948 $lineheight = trim($dom[$key]['style']['line-height']);
21949 switch ($lineheight) {
21950 // A normal line height. This is default
21951 case 'normal': {
21952 $dom[$key]['line-height'] = $dom[0]['line-height'];
21953 break;
21955 default: {
21956 if (is_numeric($lineheight)) {
21957 $lineheight = $lineheight * 100;
21959 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
21963 // font style
21964 if (isset($dom[$key]['style']['font-weight'])) {
21965 if (strtolower($dom[$key]['style']['font-weight']{0}) == 'n') {
21966 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
21967 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
21969 } elseif (strtolower($dom[$key]['style']['font-weight']{0}) == 'b') {
21970 $dom[$key]['fontstyle'] .= 'B';
21973 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
21974 $dom[$key]['fontstyle'] .= 'I';
21976 // font color
21977 if (isset($dom[$key]['style']['color']) AND (!$this->empty_string($dom[$key]['style']['color']))) {
21978 $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['color']);
21979 } elseif ($dom[$key]['value'] == 'a') {
21980 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
21982 // background color
21983 if (isset($dom[$key]['style']['background-color']) AND (!$this->empty_string($dom[$key]['style']['background-color']))) {
21984 $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['background-color']);
21986 // text-decoration
21987 if (isset($dom[$key]['style']['text-decoration'])) {
21988 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
21989 foreach ($decors as $dec) {
21990 $dec = trim($dec);
21991 if (!$this->empty_string($dec)) {
21992 if ($dec{0} == 'u') {
21993 // underline
21994 $dom[$key]['fontstyle'] .= 'U';
21995 } elseif ($dec{0} == 'l') {
21996 // line-trough
21997 $dom[$key]['fontstyle'] .= 'D';
21998 } elseif ($dec{0} == 'o') {
21999 // overline
22000 $dom[$key]['fontstyle'] .= 'O';
22004 } elseif ($dom[$key]['value'] == 'a') {
22005 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
22007 // check for width attribute
22008 if (isset($dom[$key]['style']['width'])) {
22009 $dom[$key]['width'] = $dom[$key]['style']['width'];
22011 // check for height attribute
22012 if (isset($dom[$key]['style']['height'])) {
22013 $dom[$key]['height'] = $dom[$key]['style']['height'];
22015 // check for text alignment
22016 if (isset($dom[$key]['style']['text-align'])) {
22017 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
22019 // check for CSS border properties
22020 if (isset($dom[$key]['style']['border'])) {
22021 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
22022 if (!empty($borderstyle)) {
22023 $dom[$key]['border']['LTRB'] = $borderstyle;
22026 if (isset($dom[$key]['style']['border-color'])) {
22027 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
22028 if (isset($brd_colors[3])) {
22029 $dom[$key]['border']['L']['color'] = $this->convertHTMLColorToDec($brd_colors[3]);
22031 if (isset($brd_colors[1])) {
22032 $dom[$key]['border']['R']['color'] = $this->convertHTMLColorToDec($brd_colors[1]);
22034 if (isset($brd_colors[0])) {
22035 $dom[$key]['border']['T']['color'] = $this->convertHTMLColorToDec($brd_colors[0]);
22037 if (isset($brd_colors[2])) {
22038 $dom[$key]['border']['B']['color'] = $this->convertHTMLColorToDec($brd_colors[2]);
22041 if (isset($dom[$key]['style']['border-width'])) {
22042 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
22043 if (isset($brd_widths[3])) {
22044 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
22046 if (isset($brd_widths[1])) {
22047 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
22049 if (isset($brd_widths[0])) {
22050 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
22052 if (isset($brd_widths[2])) {
22053 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
22056 if (isset($dom[$key]['style']['border-style'])) {
22057 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
22058 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
22059 $dom[$key]['border']['L']['cap'] = 'square';
22060 $dom[$key]['border']['L']['join'] = 'miter';
22061 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
22062 if ($dom[$key]['border']['L']['dash'] < 0) {
22063 $dom[$key]['border']['L'] = array();
22066 if (isset($brd_styles[1])) {
22067 $dom[$key]['border']['R']['cap'] = 'square';
22068 $dom[$key]['border']['R']['join'] = 'miter';
22069 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
22070 if ($dom[$key]['border']['R']['dash'] < 0) {
22071 $dom[$key]['border']['R'] = array();
22074 if (isset($brd_styles[0])) {
22075 $dom[$key]['border']['T']['cap'] = 'square';
22076 $dom[$key]['border']['T']['join'] = 'miter';
22077 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
22078 if ($dom[$key]['border']['T']['dash'] < 0) {
22079 $dom[$key]['border']['T'] = array();
22082 if (isset($brd_styles[2])) {
22083 $dom[$key]['border']['B']['cap'] = 'square';
22084 $dom[$key]['border']['B']['join'] = 'miter';
22085 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
22086 if ($dom[$key]['border']['B']['dash'] < 0) {
22087 $dom[$key]['border']['B'] = array();
22091 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
22092 foreach ($cellside as $bsk => $bsv) {
22093 if (isset($dom[$key]['style']['border-'.$bsv])) {
22094 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
22095 if (!empty($borderstyle)) {
22096 $dom[$key]['border'][$bsk] = $borderstyle;
22099 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
22100 $dom[$key]['border'][$bsk]['color'] = $this->convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color']);
22102 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
22103 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
22105 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
22106 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
22107 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
22108 $dom[$key]['border'][$bsk] = array();
22112 // check for CSS padding properties
22113 if (isset($dom[$key]['style']['padding'])) {
22114 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
22115 } else {
22116 $dom[$key]['padding'] = $this->cell_padding;
22118 foreach ($cellside as $psk => $psv) {
22119 if (isset($dom[$key]['style']['padding-'.$psv])) {
22120 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
22123 // check for CSS margin properties
22124 if (isset($dom[$key]['style']['margin'])) {
22125 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
22126 } else {
22127 $dom[$key]['margin'] = $this->cell_margin;
22129 foreach ($cellside as $psk => $psv) {
22130 if (isset($dom[$key]['style']['margin-'.$psv])) {
22131 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
22134 // check for CSS border-spacing properties
22135 if (isset($dom[$key]['style']['border-spacing'])) {
22136 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
22138 // page-break-inside
22139 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
22140 $dom[$key]['attribute']['nobr'] = 'true';
22142 // page-break-before
22143 if (isset($dom[$key]['style']['page-break-before'])) {
22144 if ($dom[$key]['style']['page-break-before'] == 'always') {
22145 $dom[$key]['attribute']['pagebreak'] = 'true';
22146 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
22147 $dom[$key]['attribute']['pagebreak'] = 'left';
22148 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
22149 $dom[$key]['attribute']['pagebreak'] = 'right';
22152 // page-break-after
22153 if (isset($dom[$key]['style']['page-break-after'])) {
22154 if ($dom[$key]['style']['page-break-after'] == 'always') {
22155 $dom[$key]['attribute']['pagebreakafter'] = 'true';
22156 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
22157 $dom[$key]['attribute']['pagebreakafter'] = 'left';
22158 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
22159 $dom[$key]['attribute']['pagebreakafter'] = 'right';
22163 if (isset($dom[$key]['attribute']['display'])) {
22164 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
22166 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
22167 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
22168 if (!empty($borderstyle)) {
22169 $dom[$key]['border']['LTRB'] = $borderstyle;
22172 // check for font tag
22173 if ($dom[$key]['value'] == 'font') {
22174 // font family
22175 if (isset($dom[$key]['attribute']['face'])) {
22176 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
22178 // font size
22179 if (isset($dom[$key]['attribute']['size'])) {
22180 if ($key > 0) {
22181 if ($dom[$key]['attribute']['size']{0} == '+') {
22182 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
22183 } elseif ($dom[$key]['attribute']['size']{0} == '-') {
22184 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
22185 } else {
22186 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
22188 } else {
22189 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
22193 // force natural alignment for lists
22194 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
22195 AND (!isset($dom[$key]['align']) OR $this->empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
22196 if ($this->rtl) {
22197 $dom[$key]['align'] = 'R';
22198 } else {
22199 $dom[$key]['align'] = 'L';
22202 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
22203 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
22204 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
22207 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
22208 $dom[$key]['fontstyle'] .= 'B';
22210 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
22211 $dom[$key]['fontstyle'] .= 'I';
22213 if ($dom[$key]['value'] == 'u') {
22214 $dom[$key]['fontstyle'] .= 'U';
22216 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
22217 $dom[$key]['fontstyle'] .= 'D';
22219 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
22220 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
22222 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
22223 $dom[$key]['fontname'] = $this->default_monospaced_font;
22225 if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
22226 // headings h1, h2, h3, h4, h5, h6
22227 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
22228 $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
22229 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
22231 if (!isset($dom[$key]['style']['font-weight'])) {
22232 $dom[$key]['fontstyle'] .= 'B';
22235 if (($dom[$key]['value'] == 'table')) {
22236 $dom[$key]['rows'] = 0; // number of rows
22237 $dom[$key]['trids'] = array(); // IDs of TR elements
22238 $dom[$key]['thead'] = ''; // table header rows
22240 if (($dom[$key]['value'] == 'tr')) {
22241 $dom[$key]['cols'] = 0;
22242 if ($thead) {
22243 $dom[$key]['thead'] = true;
22244 // rows on thead block are printed as a separate table
22245 } else {
22246 $dom[$key]['thead'] = false;
22247 // store the number of rows on table element
22248 ++$dom[($dom[$key]['parent'])]['rows'];
22249 // store the TR elements IDs on table element
22250 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
22253 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
22254 if (isset($dom[$key]['attribute']['colspan'])) {
22255 $colspan = intval($dom[$key]['attribute']['colspan']);
22256 } else {
22257 $colspan = 1;
22259 $dom[$key]['attribute']['colspan'] = $colspan;
22260 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
22262 // text direction
22263 if (isset($dom[$key]['attribute']['dir'])) {
22264 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
22266 // set foreground color attribute
22267 if (isset($dom[$key]['attribute']['color']) AND (!$this->empty_string($dom[$key]['attribute']['color']))) {
22268 $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['color']);
22269 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
22270 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
22272 // set background color attribute
22273 if (isset($dom[$key]['attribute']['bgcolor']) AND (!$this->empty_string($dom[$key]['attribute']['bgcolor']))) {
22274 $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['bgcolor']);
22276 // set stroke color attribute
22277 if (isset($dom[$key]['attribute']['strokecolor']) AND (!$this->empty_string($dom[$key]['attribute']['strokecolor']))) {
22278 $dom[$key]['strokecolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['strokecolor']);
22280 // check for width attribute
22281 if (isset($dom[$key]['attribute']['width'])) {
22282 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
22284 // check for height attribute
22285 if (isset($dom[$key]['attribute']['height'])) {
22286 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
22288 // check for text alignment
22289 if (isset($dom[$key]['attribute']['align']) AND (!$this->empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
22290 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
22292 // check for text rendering mode (the following attributes do not exist in HTML)
22293 if (isset($dom[$key]['attribute']['stroke'])) {
22294 // font stroke width
22295 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
22297 if (isset($dom[$key]['attribute']['fill'])) {
22298 // font fill
22299 if ($dom[$key]['attribute']['fill'] == 'true') {
22300 $dom[$key]['fill'] = true;
22301 } else {
22302 $dom[$key]['fill'] = false;
22305 if (isset($dom[$key]['attribute']['clip'])) {
22306 // clipping mode
22307 if ($dom[$key]['attribute']['clip'] == 'true') {
22308 $dom[$key]['clip'] = true;
22309 } else {
22310 $dom[$key]['clip'] = false;
22313 } // end opening tag
22314 } else {
22315 // text
22316 $dom[$key]['tag'] = false;
22317 $dom[$key]['block'] = false;
22318 //$element = str_replace('&nbsp;', $this->unichr(160), $element);
22319 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
22320 $dom[$key]['parent'] = end($level);
22321 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
22323 ++$elkey;
22324 ++$key;
22326 return $dom;
22330 * Returns the string used to find spaces
22331 * @return string
22332 * @protected
22333 * @author Nicola Asuni
22334 * @since 4.8.024 (2010-01-15)
22336 protected function getSpaceString() {
22337 $spacestr = chr(32);
22338 if ($this->isUnicodeFont()) {
22339 $spacestr = chr(0).chr(32);
22341 return $spacestr;
22345 * Prints a cell (rectangular area) with optional borders, background color and html text string.
22346 * 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 />
22347 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
22348 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
22349 * 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
22350 * NOTE: all the HTML attributes must be enclosed in double-quote.
22351 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
22352 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
22353 * @param $x (float) upper-left corner X coordinate
22354 * @param $y (float) upper-left corner Y coordinate
22355 * @param $html (string) html text to print. Default value: empty string.
22356 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22357 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
22358 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
22359 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
22360 * @param $reseth (boolean) if true reset the last cell height (default true).
22361 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22362 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
22363 * @see Multicell(), writeHTML()
22364 * @public
22366 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
22367 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
22371 * Allows to preserve some HTML formatting (limited support).<br />
22372 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
22373 * 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
22374 * NOTE: all the HTML attributes must be enclosed in double-quote.
22375 * @param $html (string) text to display
22376 * @param $ln (boolean) if true add a new line after text (default = true)
22377 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
22378 * @param $reseth (boolean) if true reset the last cell height (default false).
22379 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
22380 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22381 * @public
22383 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
22384 $gvars = $this->getGraphicVars();
22385 // store current values
22386 $prev_cell_margin = $this->cell_margin;
22387 $prev_cell_padding = $this->cell_padding;
22388 $prevPage = $this->page;
22389 $prevlMargin = $this->lMargin;
22390 $prevrMargin = $this->rMargin;
22391 $curfontname = $this->FontFamily;
22392 $curfontstyle = $this->FontStyle;
22393 $curfontsize = $this->FontSizePt;
22394 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
22395 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
22396 $curfontstretcing = $this->font_stretching;
22397 $curfonttracking = $this->font_spacing;
22398 $this->newline = true;
22399 $newline = true;
22400 $startlinepage = $this->page;
22401 $minstartliney = $this->y;
22402 $maxbottomliney = 0;
22403 $startlinex = $this->x;
22404 $startliney = $this->y;
22405 $yshift = 0;
22406 $loop = 0;
22407 $curpos = 0;
22408 $this_method_vars = array();
22409 $undo = false;
22410 $fontaligned = false;
22411 $reverse_dir = false; // true when the text direction is reversed
22412 $this->premode = false;
22413 if ($this->inxobj) {
22414 // we are inside an XObject template
22415 $pask = count($this->xobjects[$this->xobjid]['annotations']);
22416 } elseif (isset($this->PageAnnots[$this->page])) {
22417 $pask = count($this->PageAnnots[$this->page]);
22418 } else {
22419 $pask = 0;
22421 if ($this->inxobj) {
22422 // we are inside an XObject template
22423 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
22424 } elseif (!$this->InFooter) {
22425 if (isset($this->footerlen[$this->page])) {
22426 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
22427 } else {
22428 $this->footerpos[$this->page] = $this->pagelen[$this->page];
22430 $startlinepos = $this->footerpos[$this->page];
22431 } else {
22432 // we are inside the footer
22433 $startlinepos = $this->pagelen[$this->page];
22435 $lalign = $align;
22436 $plalign = $align;
22437 if ($this->rtl) {
22438 $w = $this->x - $this->lMargin;
22439 } else {
22440 $w = $this->w - $this->rMargin - $this->x;
22442 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
22443 if ($cell) {
22444 if ($this->rtl) {
22445 $this->x -= $this->cell_padding['R'];
22446 $this->lMargin += $this->cell_padding['R'];
22447 } else {
22448 $this->x += $this->cell_padding['L'];
22449 $this->rMargin += $this->cell_padding['L'];
22452 if ($this->customlistindent >= 0) {
22453 $this->listindent = $this->customlistindent;
22454 } else {
22455 $this->listindent = $this->GetStringWidth('000000');
22457 $this->listindentlevel = 0;
22458 // save previous states
22459 $prev_cell_height_ratio = $this->cell_height_ratio;
22460 $prev_listnum = $this->listnum;
22461 $prev_listordered = $this->listordered;
22462 $prev_listcount = $this->listcount;
22463 $prev_lispacer = $this->lispacer;
22464 $this->listnum = 0;
22465 $this->listordered = array();
22466 $this->listcount = array();
22467 $this->lispacer = '';
22468 if (($this->empty_string($this->lasth)) OR ($reseth)) {
22469 // reset row height
22470 $this->resetLastH();
22472 $dom = $this->getHtmlDomArray($html);
22473 $maxel = count($dom);
22474 $key = 0;
22475 $hidden_node_key = -1;
22476 while ($key < $maxel) {
22477 if ($dom[$key]['tag']) {
22478 if ($dom[$key]['opening']) {
22479 if (($hidden_node_key <= 0) AND $dom[$key]['hide']) {
22480 // store the node key
22481 $hidden_node_key = $key;
22483 } elseif (($hidden_node_key > 0) AND ($dom[$key]['parent'] == $hidden_node_key)) {
22484 // we have reached the closing tag of the hidden node
22485 $hidden_node_key = 0;
22488 if ($hidden_node_key >= 0) {
22489 // skip this node
22490 ++$key;
22491 if ($hidden_node_key == 0) {
22492 // reset hidden mode
22493 $hidden_node_key = -1;
22495 continue;
22497 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
22498 // check for pagebreak
22499 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
22500 // add a page (or trig AcceptPageBreak() for multicolumn mode)
22501 $this->checkPageBreak($this->PageBreakTrigger + 1);
22502 $this->htmlvspace = ($this->PageBreakTrigger + 1);
22504 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
22505 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
22506 // add a page (or trig AcceptPageBreak() for multicolumn mode)
22507 $this->checkPageBreak($this->PageBreakTrigger + 1);
22508 $this->htmlvspace = ($this->PageBreakTrigger + 1);
22511 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
22512 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
22513 $dom[$key]['attribute']['nobr'] = false;
22514 } else {
22515 // store current object
22516 $this->startTransaction();
22517 // save this method vars
22518 $this_method_vars['html'] = $html;
22519 $this_method_vars['ln'] = $ln;
22520 $this_method_vars['fill'] = $fill;
22521 $this_method_vars['reseth'] = $reseth;
22522 $this_method_vars['cell'] = $cell;
22523 $this_method_vars['align'] = $align;
22524 $this_method_vars['gvars'] = $gvars;
22525 $this_method_vars['prevPage'] = $prevPage;
22526 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
22527 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
22528 $this_method_vars['prevlMargin'] = $prevlMargin;
22529 $this_method_vars['prevrMargin'] = $prevrMargin;
22530 $this_method_vars['curfontname'] = $curfontname;
22531 $this_method_vars['curfontstyle'] = $curfontstyle;
22532 $this_method_vars['curfontsize'] = $curfontsize;
22533 $this_method_vars['curfontascent'] = $curfontascent;
22534 $this_method_vars['curfontdescent'] = $curfontdescent;
22535 $this_method_vars['curfontstretcing'] = $curfontstretcing;
22536 $this_method_vars['curfonttracking'] = $curfonttracking;
22537 $this_method_vars['minstartliney'] = $minstartliney;
22538 $this_method_vars['maxbottomliney'] = $maxbottomliney;
22539 $this_method_vars['yshift'] = $yshift;
22540 $this_method_vars['startlinepage'] = $startlinepage;
22541 $this_method_vars['startlinepos'] = $startlinepos;
22542 $this_method_vars['startlinex'] = $startlinex;
22543 $this_method_vars['startliney'] = $startliney;
22544 $this_method_vars['newline'] = $newline;
22545 $this_method_vars['loop'] = $loop;
22546 $this_method_vars['curpos'] = $curpos;
22547 $this_method_vars['pask'] = $pask;
22548 $this_method_vars['lalign'] = $lalign;
22549 $this_method_vars['plalign'] = $plalign;
22550 $this_method_vars['w'] = $w;
22551 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
22552 $this_method_vars['prev_listnum'] = $prev_listnum;
22553 $this_method_vars['prev_listordered'] = $prev_listordered;
22554 $this_method_vars['prev_listcount'] = $prev_listcount;
22555 $this_method_vars['prev_lispacer'] = $prev_lispacer;
22556 $this_method_vars['fontaligned'] = $fontaligned;
22557 $this_method_vars['key'] = $key;
22558 $this_method_vars['dom'] = $dom;
22561 // print THEAD block
22562 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
22563 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !$this->empty_string($dom[$dom[$key]['parent']]['thead'])) {
22564 $this->inthead = true;
22565 // print table header (thead)
22566 $this->writeHTML($this->thead, false, false, false, false, '');
22567 // check if we are on a new page or on a new column
22568 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
22569 // we are on a new page or on a new column and the total object height is less than the available vertical space.
22570 // restore previous object
22571 $this->rollbackTransaction(true);
22572 // restore previous values
22573 foreach ($this_method_vars as $vkey => $vval) {
22574 $$vkey = $vval;
22576 // disable table header
22577 $tmp_thead = $this->thead;
22578 $this->thead = '';
22579 // add a page (or trig AcceptPageBreak() for multicolumn mode)
22580 $pre_y = $this->y;
22581 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
22582 // fix for multicolumn mode
22583 $startliney = $this->y;
22585 $this->start_transaction_page = $this->page;
22586 $this->start_transaction_y = $this->y;
22587 // restore table header
22588 $this->thead = $tmp_thead;
22589 // fix table border properties
22590 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
22591 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
22592 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
22593 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
22594 } else {
22595 $tmp_cellspacing = 0;
22597 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
22598 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
22599 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
22600 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
22601 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
22602 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
22603 // print table header (thead)
22604 $this->writeHTML($this->thead, false, false, false, false, '');
22607 // move $key index forward to skip THEAD block
22608 while ( ($key < $maxel) AND (!(
22609 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
22610 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
22611 ++$key;
22614 if ($dom[$key]['tag'] OR ($key == 0)) {
22615 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
22616 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
22618 // vertically align image in line
22619 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
22620 // get image height
22621 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
22622 $autolinebreak = false;
22623 if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
22624 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
22625 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
22626 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
22627 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
22628 // add automatic line break
22629 $autolinebreak = true;
22630 $this->Ln('', $cell);
22631 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
22632 // go back to evaluate this line break
22633 --$key;
22637 if (!$autolinebreak) {
22638 if ($this->inPageBody()) {
22639 $pre_y = $this->y;
22640 // check for page break
22641 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
22642 // fix for multicolumn mode
22643 $startliney = $this->y;
22646 if ($this->page > $startlinepage) {
22647 // fix line splitted over two pages
22648 if (isset($this->footerlen[$startlinepage])) {
22649 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22651 // line to be moved one page forward
22652 $pagebuff = $this->getPageBuffer($startlinepage);
22653 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
22654 $tstart = substr($pagebuff, 0, $startlinepos);
22655 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
22656 // remove line from previous page
22657 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
22658 $pagebuff = $this->getPageBuffer($this->page);
22659 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
22660 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
22661 // add line start to current page
22662 $yshift = ($minstartliney - $this->y);
22663 if ($fontaligned) {
22664 $yshift += ($curfontsize / $this->k);
22666 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
22667 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
22668 // shift the annotations and links
22669 if (isset($this->PageAnnots[$this->page])) {
22670 $next_pask = count($this->PageAnnots[$this->page]);
22671 } else {
22672 $next_pask = 0;
22674 if (isset($this->PageAnnots[$startlinepage])) {
22675 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
22676 if ($pak >= $pask) {
22677 $this->PageAnnots[$this->page][] = $pac;
22678 unset($this->PageAnnots[$startlinepage][$pak]);
22679 $npak = count($this->PageAnnots[$this->page]) - 1;
22680 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
22684 $pask = $next_pask;
22685 $startlinepos = $this->cntmrk[$this->page];
22686 $startlinepage = $this->page;
22687 $startliney = $this->y;
22688 $this->newline = false;
22690 $this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
22691 $minstartliney = min($this->y, $minstartliney);
22692 $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
22694 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
22695 // account for different font size
22696 $pfontname = $curfontname;
22697 $pfontstyle = $curfontstyle;
22698 $pfontsize = $curfontsize;
22699 $fontname = isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname;
22700 $fontstyle = isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle;
22701 $fontsize = isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize;
22702 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
22703 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
22704 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
22705 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
22706 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
22707 if (($key < ($maxel - 1)) AND (
22708 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
22709 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
22710 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize) AND ($fontsize >= 0) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
22711 )) {
22712 if ($this->page > $startlinepage) {
22713 // fix lines splitted over two pages
22714 if (isset($this->footerlen[$startlinepage])) {
22715 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22717 // line to be moved one page forward
22718 $pagebuff = $this->getPageBuffer($startlinepage);
22719 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
22720 $tstart = substr($pagebuff, 0, $startlinepos);
22721 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
22722 // remove line start from previous page
22723 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
22724 $pagebuff = $this->getPageBuffer($this->page);
22725 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
22726 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
22727 // add line start to current page
22728 $yshift = ($minstartliney - $this->y);
22729 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
22730 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
22731 // shift the annotations and links
22732 if (isset($this->PageAnnots[$this->page])) {
22733 $next_pask = count($this->PageAnnots[$this->page]);
22734 } else {
22735 $next_pask = 0;
22737 if (isset($this->PageAnnots[$startlinepage])) {
22738 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
22739 if ($pak >= $pask) {
22740 $this->PageAnnots[$this->page][] = $pac;
22741 unset($this->PageAnnots[$startlinepage][$pak]);
22742 $npak = count($this->PageAnnots[$this->page]) - 1;
22743 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
22747 $pask = $next_pask;
22748 $startlinepos = $this->cntmrk[$this->page];
22749 $startlinepage = $this->page;
22750 $startliney = $this->y;
22752 if (!isset($dom[$key]['line-height'])) {
22753 $dom[$key]['line-height'] = $this->cell_height_ratio;
22755 if (!$dom[$key]['block']) {
22756 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']))) {
22757 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
22759 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
22760 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
22761 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)))) {
22762 $minstartliney = min($this->y, $line_align_data[1]);
22763 $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $line_align_data[2]);
22764 } else {
22765 $minstartliney = min($this->y, $minstartliney);
22766 $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
22768 $line_align_data = $current_line_align_data;
22771 $this->cell_height_ratio = $dom[$key]['line-height'];
22772 $fontaligned = true;
22774 $this->SetFont($fontname, $fontstyle, $fontsize);
22775 // reset row height
22776 $this->resetLastH();
22777 $curfontname = $fontname;
22778 $curfontstyle = $fontstyle;
22779 $curfontsize = $fontsize;
22780 $curfontascent = $fontascent;
22781 $curfontdescent = $fontdescent;
22784 // set text rendering mode
22785 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
22786 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
22787 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
22788 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
22789 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
22790 $this->setFontStretching($dom[$key]['font-stretch']);
22792 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
22793 $this->setFontSpacing($dom[$key]['letter-spacing']);
22795 if (($plalign == 'J') AND $dom[$key]['block']) {
22796 $plalign = '';
22798 // get current position on page buffer
22799 $curpos = $this->pagelen[$startlinepage];
22800 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
22801 $this->SetFillColorArray($dom[$key]['bgcolor']);
22802 $wfill = true;
22803 } else {
22804 $wfill = $fill | false;
22806 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
22807 $this->SetTextColorArray($dom[$key]['fgcolor']);
22809 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
22810 $this->SetDrawColorArray($dom[$key]['strokecolor']);
22812 if (isset($dom[$key]['align'])) {
22813 $lalign = $dom[$key]['align'];
22815 if ($this->empty_string($lalign)) {
22816 $lalign = $align;
22819 // align lines
22820 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
22821 $newline = true;
22822 $fontaligned = false;
22823 // we are at the beginning of a new line
22824 if (isset($startlinex)) {
22825 $yshift = ($minstartliney - $startliney);
22826 if (($yshift > 0) OR ($this->page > $startlinepage)) {
22827 $yshift = 0;
22829 $t_x = 0;
22830 // the last line must be shifted to be aligned as requested
22831 $linew = abs($this->endlinex - $startlinex);
22832 if ($this->inxobj) {
22833 // we are inside an XObject template
22834 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
22835 if (isset($opentagpos)) {
22836 $midpos = $opentagpos;
22837 } else {
22838 $midpos = 0;
22840 if ($midpos > 0) {
22841 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
22842 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
22843 } else {
22844 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
22845 $pend = '';
22847 } else {
22848 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
22849 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
22850 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22851 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
22852 } elseif (isset($opentagpos)) {
22853 $midpos = $opentagpos;
22854 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
22855 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22856 $midpos = $this->footerpos[$startlinepage];
22857 } else {
22858 $midpos = 0;
22860 if ($midpos > 0) {
22861 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
22862 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
22863 } else {
22864 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
22865 $pend = '';
22868 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
22869 // calculate shifting amount
22870 $tw = $w;
22871 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
22872 $tw += $this->cell_padding['R'];
22874 if ($this->lMargin != $prevlMargin) {
22875 $tw += ($prevlMargin - $this->lMargin);
22877 if ($this->rMargin != $prevrMargin) {
22878 $tw += ($prevrMargin - $this->rMargin);
22880 $one_space_width = $this->GetStringWidth(chr(32));
22881 $no = 0; // number of spaces on a line contained on a single block
22882 if ($this->isRTLTextDir()) { // RTL
22883 // remove left space if exist
22884 $pos1 = $this->revstrpos($pmid, '[(');
22885 if ($pos1 > 0) {
22886 $pos1 = intval($pos1);
22887 if ($this->isUnicodeFont()) {
22888 $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
22889 $spacelen = 2;
22890 } else {
22891 $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
22892 $spacelen = 1;
22894 if ($pos1 == $pos2) {
22895 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
22896 if (substr($pmid, $pos1, 4) == '[()]') {
22897 $linew -= $one_space_width;
22898 } elseif ($pos1 == strpos($pmid, '[(')) {
22899 $no = 1;
22903 } else { // LTR
22904 // remove right space if exist
22905 $pos1 = $this->revstrpos($pmid, ')]');
22906 if ($pos1 > 0) {
22907 $pos1 = intval($pos1);
22908 if ($this->isUnicodeFont()) {
22909 $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
22910 $spacelen = 2;
22911 } else {
22912 $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
22913 $spacelen = 1;
22915 if ($pos1 == $pos2) {
22916 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
22917 $linew -= $one_space_width;
22921 $mdiff = ($tw - $linew);
22922 if ($plalign == 'C') {
22923 if ($this->rtl) {
22924 $t_x = -($mdiff / 2);
22925 } else {
22926 $t_x = ($mdiff / 2);
22928 } elseif ($plalign == 'R') {
22929 // right alignment on LTR document
22930 $t_x = $mdiff;
22931 } elseif ($plalign == 'L') {
22932 // left alignment on RTL document
22933 $t_x = -$mdiff;
22934 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
22935 // Justification
22936 if ($this->isRTLTextDir()) {
22937 // align text on the left
22938 $t_x = -$mdiff;
22940 $ns = 0; // number of spaces
22941 $pmidtemp = $pmid;
22942 // escape special characters
22943 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
22944 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
22945 // search spaces
22946 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
22947 $spacestr = $this->getSpaceString();
22948 $maxkk = count($lnstring[1]) - 1;
22949 for ($kk=0; $kk <= $maxkk; ++$kk) {
22950 // restore special characters
22951 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
22952 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
22953 // store number of spaces on the strings
22954 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
22955 // count total spaces on line
22956 $ns += $lnstring[2][$kk];
22957 $lnstring[3][$kk] = $ns;
22959 if ($ns == 0) {
22960 $ns = 1;
22962 // calculate additional space to add to each existing space
22963 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
22964 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
22965 if ($this->font_spacing != 0) {
22966 // fixed spacing mode
22967 $osw = -1000 * $this->font_spacing / $this->FontSize;
22968 $spacewidthu += $osw;
22970 $nsmax = $ns;
22971 $ns = 0;
22972 reset($lnstring);
22973 $offset = 0;
22974 $strcount = 0;
22975 $prev_epsposbeg = 0;
22976 $textpos = 0;
22977 if ($this->isRTLTextDir()) {
22978 $textpos = $this->wPt;
22980 global $spacew;
22981 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
22982 // check if we are inside a string section '[( ... )]'
22983 $stroffset = strpos($pmid, '[(', $offset);
22984 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
22985 // set offset to the end of string section
22986 $offset = strpos($pmid, ')]', $stroffset);
22987 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
22988 $offset = strpos($pmid, ')]', ($offset + 1));
22990 if ($offset === false) {
22991 $this->Error('HTML Justification: malformed PDF code.');
22993 continue;
22995 if ($this->isRTLTextDir()) {
22996 $spacew = ($spacewidth * ($nsmax - $ns));
22997 } else {
22998 $spacew = ($spacewidth * $ns);
23000 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
23001 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
23002 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
23003 if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
23004 OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
23005 // shift EPS images
23006 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
23007 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
23008 $pmid_b = substr($pmid, 0, $epsposbeg);
23009 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
23010 $pmid_e = substr($pmid, $epsposend);
23011 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
23012 $offset = $epsposend;
23013 continue;
23016 $prev_epsposbeg = $epsposbeg;
23017 $currentxpos = 0;
23018 // shift blocks of code
23019 switch ($strpiece[2][0]) {
23020 case 'Td':
23021 case 'cm':
23022 case 'm':
23023 case 'l': {
23024 // get current X position
23025 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
23026 $currentxpos = $xmatches[1];
23027 $textpos = $currentxpos;
23028 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
23029 $ns = $lnstring[3][$strcount];
23030 if ($this->isRTLTextDir()) {
23031 $spacew = ($spacewidth * ($nsmax - $ns));
23033 ++$strcount;
23035 // justify block
23036 $pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
23037 create_function('$matches', 'global $spacew;
23038 $newx = sprintf("%F",(floatval($matches[1]) + $spacew));
23039 return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
23040 break;
23042 case 're': {
23043 // justify block
23044 if (!$this->empty_string($this->lispacer)) {
23045 $this->lispacer = '';
23046 continue;
23048 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
23049 $currentxpos = $xmatches[1];
23050 global $x_diff, $w_diff;
23051 $x_diff = 0;
23052 $w_diff = 0;
23053 if ($this->isRTLTextDir()) { // RTL
23054 if ($currentxpos < $textpos) {
23055 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
23056 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
23057 } else {
23058 if ($strcount > 0) {
23059 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
23060 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
23063 } else { // LTR
23064 if ($currentxpos > $textpos) {
23065 if ($strcount > 0) {
23066 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
23068 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
23069 } else {
23070 if ($strcount > 1) {
23071 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
23073 if ($strcount > 0) {
23074 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
23078 $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
23079 create_function('$matches', 'global $x_diff, $w_diff;
23080 $newx = sprintf("%F",(floatval($matches[1]) + $x_diff));
23081 $neww = sprintf("%F",(floatval($matches[3]) + $w_diff));
23082 return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
23083 break;
23085 case 'c': {
23086 // get current X position
23087 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);
23088 $currentxpos = $xmatches[1];
23089 // justify block
23090 $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x',
23091 create_function('$matches', 'global $spacew;
23092 $newx1 = sprintf("%F",(floatval($matches[1]) + $spacew));
23093 $newx2 = sprintf("%F",(floatval($matches[3]) + $spacew));
23094 $newx3 = sprintf("%F",(floatval($matches[5]) + $spacew));
23095 return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
23096 break;
23099 // shift the annotations and links
23100 $cxpos = ($currentxpos / $this->k);
23101 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
23102 if ($this->inxobj) {
23103 // we are inside an XObject template
23104 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
23105 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
23106 if ($cxpos > $lmpos) {
23107 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
23108 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
23109 } else {
23110 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
23112 break;
23115 } elseif (isset($this->PageAnnots[$this->page])) {
23116 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
23117 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
23118 if ($cxpos > $lmpos) {
23119 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
23120 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
23121 } else {
23122 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
23124 break;
23128 } // end of while
23129 // remove markers
23130 $pmid = str_replace('x*#!#*x', '', $pmid);
23131 if ($this->isUnicodeFont()) {
23132 // multibyte characters
23133 $spacew = $spacewidthu;
23134 if ($this->font_stretching != 100) {
23135 // word spacing is affected by stretching
23136 $spacew /= ($this->font_stretching / 100);
23138 $pmidtemp = $pmid;
23139 // escape special characters
23140 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
23141 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
23142 $pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
23143 create_function('$matches', 'global $spacew;
23144 $matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
23145 $matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
23146 return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
23147 if ($this->inxobj) {
23148 // we are inside an XObject template
23149 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
23150 } else {
23151 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
23153 $endlinepos = strlen($pstart."\n".$pmid."\n");
23154 } else {
23155 // non-unicode (single-byte characters)
23156 if ($this->font_stretching != 100) {
23157 // word spacing (Tw) is affected by stretching
23158 $spacewidth /= ($this->font_stretching / 100);
23160 $rs = sprintf('%F Tw', $spacewidth);
23161 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
23162 if ($this->inxobj) {
23163 // we are inside an XObject template
23164 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
23165 } else {
23166 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
23168 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
23171 } // end of J
23172 } // end if $startlinex
23173 if (($t_x != 0) OR ($yshift < 0)) {
23174 // shift the line
23175 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
23176 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
23177 $endlinepos = strlen($pstart);
23178 if ($this->inxobj) {
23179 // we are inside an XObject template
23180 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
23181 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
23182 if ($pak >= $pask) {
23183 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
23184 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
23187 } else {
23188 $this->setPageBuffer($startlinepage, $pstart.$pend);
23189 // shift the annotations and links
23190 if (isset($this->PageAnnots[$this->page])) {
23191 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
23192 if ($pak >= $pask) {
23193 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
23194 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
23199 $this->y -= $yshift;
23202 $pbrk = $this->checkPageBreak($this->lasth);
23203 $this->newline = false;
23204 $startlinex = $this->x;
23205 $startliney = $this->y;
23206 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
23207 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
23208 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
23209 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
23210 } else {
23211 $minstartliney = $startliney;
23212 $maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
23214 $startlinepage = $this->page;
23215 if (isset($endlinepos) AND (!$pbrk)) {
23216 $startlinepos = $endlinepos;
23217 } else {
23218 if ($this->inxobj) {
23219 // we are inside an XObject template
23220 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
23221 } elseif (!$this->InFooter) {
23222 if (isset($this->footerlen[$this->page])) {
23223 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
23224 } else {
23225 $this->footerpos[$this->page] = $this->pagelen[$this->page];
23227 $startlinepos = $this->footerpos[$this->page];
23228 } else {
23229 $startlinepos = $this->pagelen[$this->page];
23232 unset($endlinepos);
23233 $plalign = $lalign;
23234 if (isset($this->PageAnnots[$this->page])) {
23235 $pask = count($this->PageAnnots[$this->page]);
23236 } else {
23237 $pask = 0;
23239 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
23240 AND (isset($this->emptypagemrk[$this->page]))
23241 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
23242 $this->SetFont($fontname, $fontstyle, $fontsize);
23243 if ($wfill) {
23244 $this->SetFillColorArray($this->bgcolor);
23247 } // end newline
23248 if (isset($opentagpos)) {
23249 unset($opentagpos);
23251 if ($dom[$key]['tag']) {
23252 if ($dom[$key]['opening']) {
23253 // get text indentation (if any)
23254 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
23255 $this->textindent = $dom[$key]['text-indent'];
23256 $this->newline = true;
23258 // table
23259 if ($dom[$key]['value'] == 'table') {
23260 // available page width
23261 if ($this->rtl) {
23262 $wtmp = $this->x - $this->lMargin;
23263 } else {
23264 $wtmp = $this->w - $this->rMargin - $this->x;
23266 // get cell spacing
23267 if (isset($dom[$key]['attribute']['cellspacing'])) {
23268 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
23269 $cellspacing = array('H' => $clsp, 'V' => $clsp);
23270 } elseif (isset($dom[$key]['border-spacing'])) {
23271 $cellspacing = $dom[$key]['border-spacing'];
23272 } else {
23273 $cellspacing = array('H' => 0, 'V' => 0);
23275 // table width
23276 if (isset($dom[$key]['width'])) {
23277 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
23278 } else {
23279 $table_width = $wtmp;
23281 $table_width -= (2 * $cellspacing['H']);
23282 if (!$this->inthead) {
23283 $this->y += $cellspacing['V'];
23285 if ($this->rtl) {
23286 $cellspacingx = -$cellspacing['H'];
23287 } else {
23288 $cellspacingx = $cellspacing['H'];
23290 // total table width without cellspaces
23291 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
23292 // minimum column width
23293 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
23294 // array of custom column widths
23295 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
23297 // table row
23298 if ($dom[$key]['value'] == 'tr') {
23299 // reset column counter
23300 $colid = 0;
23302 // table cell
23303 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
23304 $trid = $dom[$key]['parent'];
23305 $table_el = $dom[$trid]['parent'];
23306 if (!isset($dom[$table_el]['cols'])) {
23307 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
23309 // store border info
23310 $tdborder = 0;
23311 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
23312 $tdborder = $dom[$key]['border'];
23314 $colspan = $dom[$key]['attribute']['colspan'];
23315 $old_cell_padding = $this->cell_padding;
23316 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
23317 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
23318 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
23319 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
23320 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
23321 } else {
23322 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
23324 $this->cell_padding = $current_cell_padding;
23325 if (isset($dom[$key]['height'])) {
23326 // minimum cell height
23327 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
23328 } else {
23329 $cellh = 0;
23331 if (isset($dom[$key]['content'])) {
23332 $cell_content = stripslashes($dom[$key]['content']);
23333 } else {
23334 $cell_content = '&nbsp;';
23336 $tagtype = $dom[$key]['value'];
23337 $parentid = $key;
23338 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
23339 // move $key index forward
23340 ++$key;
23342 if (!isset($dom[$trid]['startpage'])) {
23343 $dom[$trid]['startpage'] = $this->page;
23344 } else {
23345 $this->setPage($dom[$trid]['startpage']);
23347 if (!isset($dom[$trid]['startcolumn'])) {
23348 $dom[$trid]['startcolumn'] = $this->current_column;
23349 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
23350 $tmpx = $this->x;
23351 $this->selectColumn($dom[$trid]['startcolumn']);
23352 $this->x = $tmpx;
23354 if (!isset($dom[$trid]['starty'])) {
23355 $dom[$trid]['starty'] = $this->y;
23356 } else {
23357 $this->y = $dom[$trid]['starty'];
23359 if (!isset($dom[$trid]['startx'])) {
23360 $dom[$trid]['startx'] = $this->x;
23361 $this->x += $cellspacingx;
23362 } else {
23363 $this->x += ($cellspacingx / 2);
23365 if (isset($dom[$parentid]['attribute']['rowspan'])) {
23366 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
23367 } else {
23368 $rowspan = 1;
23370 // skip row-spanned cells started on the previous rows
23371 if (isset($dom[$table_el]['rowspans'])) {
23372 $rsk = 0;
23373 $rskmax = count($dom[$table_el]['rowspans']);
23374 while ($rsk < $rskmax) {
23375 $trwsp = $dom[$table_el]['rowspans'][$rsk];
23376 $rsstartx = $trwsp['startx'];
23377 $rsendx = $trwsp['endx'];
23378 // account for margin changes
23379 if ($trwsp['startpage'] < $this->page) {
23380 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
23381 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
23382 $rsstartx -= $dl;
23383 $rsendx -= $dl;
23384 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
23385 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
23386 $rsstartx += $dl;
23387 $rsendx += $dl;
23390 if (($trwsp['rowspan'] > 0)
23391 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
23392 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
23393 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
23394 // set the starting X position of the current cell
23395 $this->x = $rsendx + $cellspacingx;
23396 // increment column indicator
23397 $colid += $trwsp['colspan'];
23398 if (($trwsp['rowspan'] == 1)
23399 AND (isset($dom[$trid]['endy']))
23400 AND (isset($dom[$trid]['endpage']))
23401 AND (isset($dom[$trid]['endcolumn']))
23402 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
23403 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
23404 // set ending Y position for row
23405 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
23406 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
23408 $rsk = 0;
23409 } else {
23410 ++$rsk;
23414 if (isset($dom[$parentid]['width'])) {
23415 // user specified width
23416 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
23417 $tmpcw = ($cellw / $colspan);
23418 for ($i = 0; $i < $colspan; ++$i) {
23419 $table_colwidths[($colid + $i)] = $tmpcw;
23421 } else {
23422 // inherit column width
23423 $cellw = 0;
23424 for ($i = 0; $i < $colspan; ++$i) {
23425 $cellw += $table_colwidths[($colid + $i)];
23428 $cellw += (($colspan - 1) * $cellspacing['H']);
23429 // increment column indicator
23430 $colid += $colspan;
23431 // add rowspan information to table element
23432 if ($rowspan > 1) {
23433 $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));
23435 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
23436 if ($rowspan > 1) {
23437 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
23439 // push background colors
23440 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
23441 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
23443 // store border info
23444 if (isset($tdborder) AND !empty($tdborder)) {
23445 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
23447 $prevLastH = $this->lasth;
23448 // store some info for multicolumn mode
23449 if ($this->rtl) {
23450 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
23451 } else {
23452 $this->colxshift['x'] = $this->x - $this->lMargin;
23454 $this->colxshift['s'] = $cellspacing;
23455 $this->colxshift['p'] = $current_cell_padding;
23456 // ****** write the cell content ******
23457 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
23458 // restore some values
23459 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
23460 $this->lasth = $prevLastH;
23461 $this->cell_padding = $old_cell_padding;
23462 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
23463 // update the end of row position
23464 if ($rowspan <= 1) {
23465 if (isset($dom[$trid]['endy'])) {
23466 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
23467 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
23468 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
23469 $dom[$trid]['endy'] = $this->y;
23471 } else {
23472 $dom[$trid]['endy'] = $this->y;
23474 if (isset($dom[$trid]['endpage'])) {
23475 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
23476 } else {
23477 $dom[$trid]['endpage'] = $this->page;
23479 if (isset($dom[$trid]['endcolumn'])) {
23480 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
23481 } else {
23482 $dom[$trid]['endcolumn'] = $this->current_column;
23484 } else {
23485 // account for row-spanned cells
23486 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
23487 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
23488 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
23489 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
23491 if (isset($dom[$table_el]['rowspans'])) {
23492 // update endy and endpage on rowspanned cells
23493 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
23494 if ($trwsp['rowspan'] > 0) {
23495 if (isset($dom[$trid]['endpage'])) {
23496 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
23497 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
23498 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
23499 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
23500 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
23501 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
23502 } else {
23503 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
23509 $this->x += ($cellspacingx / 2);
23510 } else {
23511 // opening tag (or self-closing tag)
23512 if (!isset($opentagpos)) {
23513 if ($this->inxobj) {
23514 // we are inside an XObject template
23515 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
23516 } elseif (!$this->InFooter) {
23517 if (isset($this->footerlen[$this->page])) {
23518 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
23519 } else {
23520 $this->footerpos[$this->page] = $this->pagelen[$this->page];
23522 $opentagpos = $this->footerpos[$this->page];
23525 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
23527 } else { // closing tag
23528 $prev_numpages = $this->numpages;
23529 $old_bordermrk = $this->bordermrk[$this->page];
23530 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
23531 if ($this->bordermrk[$this->page] > $old_bordermrk) {
23532 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
23534 if ($prev_numpages > $this->numpages) {
23535 $startlinepage = $this->page;
23538 } elseif (strlen($dom[$key]['value']) > 0) {
23539 // print list-item
23540 if (!$this->empty_string($this->lispacer) AND ($this->lispacer != '^')) {
23541 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
23542 $this->resetLastH();
23543 $minstartliney = $this->y;
23544 $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
23545 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
23546 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
23547 $this->resetLastH();
23548 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
23549 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
23550 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
23551 $this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
23552 $minstartliney = min($this->y, $minstartliney);
23553 $maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
23556 // text
23557 $this->htmlvspace = 0;
23558 if ((!$this->premode) AND $this->isRTLTextDir()) {
23559 // reverse spaces order
23560 $lsp = ''; // left spaces
23561 $rsp = ''; // right spaces
23562 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
23563 $lsp = $matches[1];
23565 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
23566 $rsp = $matches[1];
23568 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
23570 if ($newline) {
23571 if (!$this->premode) {
23572 $prelen = strlen($dom[$key]['value']);
23573 if ($this->isRTLTextDir()) {
23574 // right trim except non-breaking space
23575 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
23576 } else {
23577 // left trim except non-breaking space
23578 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
23580 $postlen = strlen($dom[$key]['value']);
23581 if (($postlen == 0) AND ($prelen > 0)) {
23582 $dom[$key]['trimmed_space'] = true;
23585 $newline = false;
23586 $firstblock = true;
23587 } else {
23588 $firstblock = false;
23589 // replace empty multiple spaces string with a single space
23590 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
23592 $strrest = '';
23593 if ($this->rtl) {
23594 $this->x -= $this->textindent;
23595 } else {
23596 $this->x += $this->textindent;
23598 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
23599 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
23600 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
23601 // HTML <a> Link
23602 $hrefcolor = '';
23603 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
23604 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
23606 $hrefstyle = -1;
23607 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
23608 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
23610 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
23611 } else {
23612 $wadj = 0; // space to leave for block continuity
23613 if ($this->rtl) {
23614 $cwa = ($this->x - $this->lMargin);
23615 } else {
23616 $cwa = ($this->w - $this->rMargin - $this->x);
23618 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
23619 // check the next text blocks for continuity
23620 $nkey = ($key + 1);
23621 $write_block = true;
23622 $same_textdir = true;
23623 $tmp_fontname = $this->FontFamily;
23624 $tmp_fontstyle = $this->FontStyle;
23625 $tmp_fontsize = $this->FontSizePt;
23626 while ($write_block AND isset($dom[$nkey])) {
23627 if ($dom[$nkey]['tag']) {
23628 if ($dom[$nkey]['block']) {
23629 // end of block
23630 $write_block = false;
23632 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
23633 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
23634 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
23635 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
23636 } else {
23637 $nextstr = preg_split('/'.$this->re_space['p'].'+/'.$this->re_space['m'], $dom[$nkey]['value']);
23638 if (isset($nextstr[0]) AND $same_textdir) {
23639 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
23640 if (isset($nextstr[1])) {
23641 $write_block = false;
23645 ++$nkey;
23648 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
23649 $wadj = 0;
23650 $nextstr = preg_split('/'.$this->re_space['p'].'/'.$this->re_space['m'], $dom[$key]['value']);
23651 $numblks = count($nextstr);
23652 if ($numblks > 1) {
23653 // try to split on blank spaces
23654 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
23655 } else {
23656 // set the entire block on new line
23657 $wadj = $this->GetStringWidth($nextstr[0]);
23660 // check for reversed text direction
23661 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
23662 // LTR text on RTL direction or RTL text on LTR direction
23663 $reverse_dir = true;
23664 $this->rtl = !$this->rtl;
23665 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
23666 if ($this->rtl) {
23667 $this->x += $revshift;
23668 } else {
23669 $this->x -= $revshift;
23671 $xws = $this->x;
23673 // ****** write only until the end of the line and get the rest ******
23674 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
23675 // restore default direction
23676 if ($reverse_dir AND ($wadj == 0)) {
23677 $this->x = $xws;
23678 $this->rtl = !$this->rtl;
23679 $reverse_dir = false;
23683 $this->textindent = 0;
23684 if (strlen($strrest) > 0) {
23685 // store the remaining string on the previous $key position
23686 $this->newline = true;
23687 if ($strrest == $dom[$key]['value']) {
23688 // used to avoid infinite loop
23689 ++$loop;
23690 } else {
23691 $loop = 0;
23693 $dom[$key]['value'] = $strrest;
23694 if ($cell) {
23695 if ($this->rtl) {
23696 $this->x -= $this->cell_padding['R'];
23697 } else {
23698 $this->x += $this->cell_padding['L'];
23701 if ($loop < 3) {
23702 --$key;
23704 } else {
23705 $loop = 0;
23706 // add the positive font spacing of the last character (if any)
23707 if ($this->font_spacing > 0) {
23708 if ($this->rtl) {
23709 $this->x -= $this->font_spacing;
23710 } else {
23711 $this->x += $this->font_spacing;
23716 ++$key;
23717 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')) {
23718 // check if we are on a new page or on a new column
23719 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
23720 // we are on a new page or on a new column and the total object height is less than the available vertical space.
23721 // restore previous object
23722 $this->rollbackTransaction(true);
23723 // restore previous values
23724 foreach ($this_method_vars as $vkey => $vval) {
23725 $$vkey = $vval;
23727 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23728 $pre_y = $this->y;
23729 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
23730 $startliney = $this->y;
23732 $undo = true; // avoid infinite loop
23733 } else {
23734 $undo = false;
23737 } // end for each $key
23738 // align the last line
23739 if (isset($startlinex)) {
23740 $yshift = ($minstartliney - $startliney);
23741 if (($yshift > 0) OR ($this->page > $startlinepage)) {
23742 $yshift = 0;
23744 $t_x = 0;
23745 // the last line must be shifted to be aligned as requested
23746 $linew = abs($this->endlinex - $startlinex);
23747 if ($this->inxobj) {
23748 // we are inside an XObject template
23749 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
23750 if (isset($opentagpos)) {
23751 $midpos = $opentagpos;
23752 } else {
23753 $midpos = 0;
23755 if ($midpos > 0) {
23756 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
23757 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
23758 } else {
23759 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
23760 $pend = '';
23762 } else {
23763 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
23764 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
23765 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
23766 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
23767 } elseif (isset($opentagpos)) {
23768 $midpos = $opentagpos;
23769 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
23770 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
23771 $midpos = $this->footerpos[$startlinepage];
23772 } else {
23773 $midpos = 0;
23775 if ($midpos > 0) {
23776 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
23777 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
23778 } else {
23779 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
23780 $pend = '';
23783 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
23784 // calculate shifting amount
23785 $tw = $w;
23786 if ($this->lMargin != $prevlMargin) {
23787 $tw += ($prevlMargin - $this->lMargin);
23789 if ($this->rMargin != $prevrMargin) {
23790 $tw += ($prevrMargin - $this->rMargin);
23792 $one_space_width = $this->GetStringWidth(chr(32));
23793 $no = 0; // number of spaces on a line contained on a single block
23794 if ($this->isRTLTextDir()) { // RTL
23795 // remove left space if exist
23796 $pos1 = $this->revstrpos($pmid, '[(');
23797 if ($pos1 > 0) {
23798 $pos1 = intval($pos1);
23799 if ($this->isUnicodeFont()) {
23800 $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
23801 $spacelen = 2;
23802 } else {
23803 $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
23804 $spacelen = 1;
23806 if ($pos1 == $pos2) {
23807 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
23808 if (substr($pmid, $pos1, 4) == '[()]') {
23809 $linew -= $one_space_width;
23810 } elseif ($pos1 == strpos($pmid, '[(')) {
23811 $no = 1;
23815 } else { // LTR
23816 // remove right space if exist
23817 $pos1 = $this->revstrpos($pmid, ')]');
23818 if ($pos1 > 0) {
23819 $pos1 = intval($pos1);
23820 if ($this->isUnicodeFont()) {
23821 $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
23822 $spacelen = 2;
23823 } else {
23824 $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
23825 $spacelen = 1;
23827 if ($pos1 == $pos2) {
23828 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
23829 $linew -= $one_space_width;
23833 $mdiff = ($tw - $linew);
23834 if ($plalign == 'C') {
23835 if ($this->rtl) {
23836 $t_x = -($mdiff / 2);
23837 } else {
23838 $t_x = ($mdiff / 2);
23840 } elseif ($plalign == 'R') {
23841 // right alignment on LTR document
23842 $t_x = $mdiff;
23843 } elseif ($plalign == 'L') {
23844 // left alignment on RTL document
23845 $t_x = -$mdiff;
23847 } // end if startlinex
23848 if (($t_x != 0) OR ($yshift < 0)) {
23849 // shift the line
23850 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
23851 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
23852 $endlinepos = strlen($pstart);
23853 if ($this->inxobj) {
23854 // we are inside an XObject template
23855 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
23856 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
23857 if ($pak >= $pask) {
23858 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
23859 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
23862 } else {
23863 $this->setPageBuffer($startlinepage, $pstart.$pend);
23864 // shift the annotations and links
23865 if (isset($this->PageAnnots[$this->page])) {
23866 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
23867 if ($pak >= $pask) {
23868 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
23869 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
23874 $this->y -= $yshift;
23875 $yshift = 0;
23878 // restore previous values
23879 $this->setGraphicVars($gvars);
23880 if ($this->num_columns > 1) {
23881 $this->selectColumn();
23882 } elseif ($this->page > $prevPage) {
23883 $this->lMargin = $this->pagedim[$this->page]['olm'];
23884 $this->rMargin = $this->pagedim[$this->page]['orm'];
23886 // restore previous list state
23887 $this->cell_height_ratio = $prev_cell_height_ratio;
23888 $this->listnum = $prev_listnum;
23889 $this->listordered = $prev_listordered;
23890 $this->listcount = $prev_listcount;
23891 $this->lispacer = $prev_lispacer;
23892 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
23893 $this->Ln($this->lasth);
23894 if ($this->y < $maxbottomliney) {
23895 $this->y = $maxbottomliney;
23898 unset($dom);
23902 * Process opening tags.
23903 * @param $dom (array) html dom array
23904 * @param $key (int) current element id
23905 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
23906 * @return $dom array
23907 * @protected
23909 protected function openHTMLTagHandler($dom, $key, $cell) {
23910 $tag = $dom[$key];
23911 $parent = $dom[($dom[$key]['parent'])];
23912 $firsttag = ($key == 1);
23913 // check for text direction attribute
23914 if (isset($tag['dir'])) {
23915 $this->setTempRTL($tag['dir']);
23916 } else {
23917 $this->tmprtl = false;
23919 if ($tag['block']) {
23920 $hbz = 0; // distance from y to line bottom
23921 $hb = 0; // vertical space between block tags
23922 // calculate vertical space for block tags
23923 if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
23924 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
23925 } elseif (isset($tag['fontsize'])) {
23926 $cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
23927 } else {
23928 $cur_h = $this->FontSize * $this->cell_height_ratio;
23930 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
23931 $n = $this->tagvspaces[$tag['value']][0]['n'];
23932 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
23933 $n = 0.6;
23934 } else {
23935 $n = 1;
23937 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br')))) {
23938 $hb = 0;
23939 } else {
23940 $hb = ($n * $cur_h);
23942 if (($this->htmlvspace <= 0) AND ($n > 0)) {
23943 if (isset($parent['fontsize'])) {
23944 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
23945 } else {
23946 $hbz = $this->FontSize * $this->cell_height_ratio;
23949 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
23950 // fix vertical space after table
23951 $hbz = 0;
23954 // Opening tag
23955 switch($tag['value']) {
23956 case 'table': {
23957 $cp = 0;
23958 $cs = 0;
23959 $dom[$key]['rowspans'] = array();
23960 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
23961 $this->htmlvspace = 0;
23962 // set table header
23963 if (!$this->empty_string($dom[$key]['thead'])) {
23964 // set table header
23965 $this->thead = $dom[$key]['thead'];
23966 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
23967 $this->theadMargins = array();
23968 $this->theadMargins['cell_padding'] = $this->cell_padding;
23969 $this->theadMargins['lmargin'] = $this->lMargin;
23970 $this->theadMargins['rmargin'] = $this->rMargin;
23971 $this->theadMargins['page'] = $this->page;
23972 $this->theadMargins['cell'] = $cell;
23976 // store current margins and page
23977 $dom[$key]['old_cell_padding'] = $this->cell_padding;
23978 if (isset($tag['attribute']['cellpadding'])) {
23979 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
23980 $this->SetCellPadding($pad);
23981 } elseif (isset($tag['padding'])) {
23982 $this->cell_padding = $tag['padding'];
23984 if (isset($tag['attribute']['cellspacing'])) {
23985 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
23986 } elseif (isset($tag['border-spacing'])) {
23987 $cs = $tag['border-spacing']['V'];
23989 $prev_y = $this->y;
23990 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
23991 $this->inthead = true;
23992 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23993 $this->checkPageBreak($this->PageBreakTrigger + 1);
23995 break;
23997 case 'tr': {
23998 // array of columns positions
23999 $dom[$key]['cellpos'] = array();
24000 break;
24002 case 'hr': {
24003 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
24004 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
24005 } else {
24006 $hrHeight = $this->GetLineWidth();
24008 $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
24009 $x = $this->GetX();
24010 $y = $this->GetY();
24011 $wtmp = $this->w - $this->lMargin - $this->rMargin;
24012 if ($cell) {
24013 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
24015 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
24016 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
24017 } else {
24018 $hrWidth = $wtmp;
24020 $prevlinewidth = $this->GetLineWidth();
24021 $this->SetLineWidth($hrHeight);
24022 $this->Line($x, $y, $x + $hrWidth, $y);
24023 $this->SetLineWidth($prevlinewidth);
24024 $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
24025 break;
24027 case 'a': {
24028 if (array_key_exists('href', $tag['attribute'])) {
24029 $this->HREF['url'] = $tag['attribute']['href'];
24031 break;
24033 case 'img': {
24034 if (isset($tag['attribute']['src'])) {
24035 if ($tag['attribute']['src']{0} === '@') {
24036 // data stream
24037 $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
24038 $type = '';
24039 } else {
24040 // check for images without protocol
24041 if (preg_match('%^/{2}%', $tag['attribute']['src'])) {
24042 $tag['attribute']['src'] = 'http:'.$tag['attribute']['src'];
24044 // replace relative path with real server path
24045 if (($tag['attribute']['src'][0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24046 $findroot = strpos($tag['attribute']['src'], $_SERVER['DOCUMENT_ROOT']);
24047 if (($findroot === false) OR ($findroot > 1)) {
24048 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24049 $tag['attribute']['src'] = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$tag['attribute']['src'];
24050 } else {
24051 $tag['attribute']['src'] = $_SERVER['DOCUMENT_ROOT'].$tag['attribute']['src'];
24055 $tag['attribute']['src'] = htmlspecialchars_decode(urldecode($tag['attribute']['src']));
24056 $type = $this->getImageFileType($tag['attribute']['src']);
24057 $testscrtype = @parse_url($tag['attribute']['src']);
24058 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24059 // convert URL to server path
24060 $tag['attribute']['src'] = str_replace(K_PATH_URL, K_PATH_MAIN, $tag['attribute']['src']);
24063 if (!isset($tag['width'])) {
24064 $tag['width'] = 0;
24066 if (!isset($tag['height'])) {
24067 $tag['height'] = 0;
24069 //if (!isset($tag['attribute']['align'])) {
24070 // the only alignment supported is "bottom"
24071 // further development is required for other modes.
24072 $tag['attribute']['align'] = 'bottom';
24074 switch($tag['attribute']['align']) {
24075 case 'top': {
24076 $align = 'T';
24077 break;
24079 case 'middle': {
24080 $align = 'M';
24081 break;
24083 case 'bottom': {
24084 $align = 'B';
24085 break;
24087 default: {
24088 $align = 'B';
24089 break;
24092 $prevy = $this->y;
24093 $xpos = $this->x;
24094 $imglink = '';
24095 if (isset($this->HREF['url']) AND !$this->empty_string($this->HREF['url'])) {
24096 $imglink = $this->HREF['url'];
24097 if ($imglink{0} == '#') {
24098 // convert url to internal link
24099 $lnkdata = explode(',', $imglink);
24100 if (isset($lnkdata[0])) {
24101 $page = intval(substr($lnkdata[0], 1));
24102 if (empty($page) OR ($page <= 0)) {
24103 $page = $this->page;
24105 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
24106 $lnky = floatval($lnkdata[1]);
24107 } else {
24108 $lnky = 0;
24110 $imglink = $this->AddLink();
24111 $this->SetLink($imglink, $lnky, $page);
24115 $border = 0;
24116 if (isset($tag['border']) AND !empty($tag['border'])) {
24117 // currently only support 1 (frame) or a combination of 'LTRB'
24118 $border = $tag['border'];
24120 $iw = '';
24121 if (isset($tag['width'])) {
24122 $iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
24124 $ih = '';
24125 if (isset($tag['height'])) {
24126 $ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
24128 if (($type == 'eps') OR ($type == 'ai')) {
24129 $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
24130 } elseif ($type == 'svg') {
24131 $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
24132 } else {
24133 $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
24135 switch($align) {
24136 case 'T': {
24137 $this->y = $prevy;
24138 break;
24140 case 'M': {
24141 $this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
24142 break;
24144 case 'B': {
24145 $this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
24146 break;
24150 break;
24152 case 'dl': {
24153 ++$this->listnum;
24154 if ($this->listnum == 1) {
24155 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24156 } else {
24157 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
24159 break;
24161 case 'dt': {
24162 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24163 break;
24165 case 'dd': {
24166 if ($this->rtl) {
24167 $this->rMargin += $this->listindent;
24168 } else {
24169 $this->lMargin += $this->listindent;
24171 ++$this->listindentlevel;
24172 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24173 break;
24175 case 'ul':
24176 case 'ol': {
24177 ++$this->listnum;
24178 if ($tag['value'] == 'ol') {
24179 $this->listordered[$this->listnum] = true;
24180 } else {
24181 $this->listordered[$this->listnum] = false;
24183 if (isset($tag['attribute']['start'])) {
24184 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
24185 } else {
24186 $this->listcount[$this->listnum] = 0;
24188 if ($this->rtl) {
24189 $this->rMargin += $this->listindent;
24190 $this->x -= $this->listindent;
24191 } else {
24192 $this->lMargin += $this->listindent;
24193 $this->x += $this->listindent;
24195 ++$this->listindentlevel;
24196 if ($this->listnum == 1) {
24197 if ($key > 1) {
24198 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24200 } else {
24201 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
24203 break;
24205 case 'li': {
24206 if ($key > 2) {
24207 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24209 if ($this->listordered[$this->listnum]) {
24210 // ordered item
24211 if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
24212 $this->lispacer = $parent['attribute']['type'];
24213 } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
24214 $this->lispacer = $parent['listtype'];
24215 } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
24216 $this->lispacer = $this->lisymbol;
24217 } else {
24218 $this->lispacer = '#';
24220 ++$this->listcount[$this->listnum];
24221 if (isset($tag['attribute']['value'])) {
24222 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
24224 } else {
24225 // unordered item
24226 if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
24227 $this->lispacer = $parent['attribute']['type'];
24228 } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
24229 $this->lispacer = $parent['listtype'];
24230 } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
24231 $this->lispacer = $this->lisymbol;
24232 } else {
24233 $this->lispacer = '!';
24236 break;
24238 case 'blockquote': {
24239 if ($this->rtl) {
24240 $this->rMargin += $this->listindent;
24241 } else {
24242 $this->lMargin += $this->listindent;
24244 ++$this->listindentlevel;
24245 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24246 break;
24248 case 'br': {
24249 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24250 break;
24252 case 'div': {
24253 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24254 break;
24256 case 'p': {
24257 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24258 break;
24260 case 'pre': {
24261 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24262 $this->premode = true;
24263 break;
24265 case 'sup': {
24266 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
24267 break;
24269 case 'sub': {
24270 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
24271 break;
24273 case 'h1':
24274 case 'h2':
24275 case 'h3':
24276 case 'h4':
24277 case 'h5':
24278 case 'h6': {
24279 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
24280 break;
24282 // Form fields (since 4.8.000 - 2009-09-07)
24283 case 'form': {
24284 if (isset($tag['attribute']['action'])) {
24285 $this->form_action = $tag['attribute']['action'];
24286 } else {
24287 $this->form_action = K_PATH_URL.$_SERVER['SCRIPT_NAME'];
24289 if (isset($tag['attribute']['enctype'])) {
24290 $this->form_enctype = $tag['attribute']['enctype'];
24291 } else {
24292 $this->form_enctype = 'application/x-www-form-urlencoded';
24294 if (isset($tag['attribute']['method'])) {
24295 $this->form_mode = $tag['attribute']['method'];
24296 } else {
24297 $this->form_mode = 'post';
24299 break;
24301 case 'input': {
24302 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
24303 $name = $tag['attribute']['name'];
24304 } else {
24305 break;
24307 $prop = array();
24308 $opt = array();
24309 if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
24310 $prop['readonly'] = true;
24312 if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
24313 $value = $tag['attribute']['value'];
24315 if (isset($tag['attribute']['maxlength']) AND !$this->empty_string($tag['attribute']['maxlength'])) {
24316 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
24318 $h = $this->FontSize * $this->cell_height_ratio;
24319 if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
24320 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
24321 } else {
24322 $w = $h;
24324 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
24325 $checked = true;
24326 } else {
24327 $checked = false;
24329 if (isset($tag['align'])) {
24330 switch ($tag['align']) {
24331 case 'C': {
24332 $opt['q'] = 1;
24333 break;
24335 case 'R': {
24336 $opt['q'] = 2;
24337 break;
24339 case 'L':
24340 default: {
24341 break;
24345 switch ($tag['attribute']['type']) {
24346 case 'text': {
24347 if (isset($value)) {
24348 $opt['v'] = $value;
24350 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
24351 break;
24353 case 'password': {
24354 if (isset($value)) {
24355 $opt['v'] = $value;
24357 $prop['password'] = 'true';
24358 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
24359 break;
24361 case 'checkbox': {
24362 if (!isset($value)) {
24363 break;
24365 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
24366 break;
24368 case 'radio': {
24369 if (!isset($value)) {
24370 break;
24372 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
24373 break;
24375 case 'submit': {
24376 if (!isset($value)) {
24377 $value = 'submit';
24379 $w = $this->GetStringWidth($value) * 1.5;
24380 $h *= 1.6;
24381 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
24382 $action = array();
24383 $action['S'] = 'SubmitForm';
24384 $action['F'] = $this->form_action;
24385 if ($this->form_enctype != 'FDF') {
24386 $action['Flags'] = array('ExportFormat');
24388 if ($this->form_mode == 'get') {
24389 $action['Flags'] = array('GetMethod');
24391 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
24392 break;
24394 case 'reset': {
24395 if (!isset($value)) {
24396 $value = 'reset';
24398 $w = $this->GetStringWidth($value) * 1.5;
24399 $h *= 1.6;
24400 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
24401 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
24402 break;
24404 case 'file': {
24405 $prop['fileSelect'] = 'true';
24406 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
24407 if (!isset($value)) {
24408 $value = '*';
24410 $w = $this->GetStringWidth($value) * 2;
24411 $h *= 1.2;
24412 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
24413 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
24414 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
24415 break;
24417 case 'hidden': {
24418 if (isset($value)) {
24419 $opt['v'] = $value;
24421 $opt['f'] = array('invisible', 'hidden');
24422 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
24423 break;
24425 case 'image': {
24426 // THIS TYPE MUST BE FIXED
24427 if (isset($tag['attribute']['src']) AND !$this->empty_string($tag['attribute']['src'])) {
24428 $img = $tag['attribute']['src'];
24429 } else {
24430 break;
24432 $value = 'img';
24433 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
24434 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
24435 $jsaction = $tag['attribute']['onclick'];
24436 } else {
24437 $jsaction = '';
24439 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
24440 break;
24442 case 'button': {
24443 if (!isset($value)) {
24444 $value = ' ';
24446 $w = $this->GetStringWidth($value) * 1.5;
24447 $h *= 1.6;
24448 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
24449 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
24450 $jsaction = $tag['attribute']['onclick'];
24451 } else {
24452 $jsaction = '';
24454 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
24455 break;
24458 break;
24460 case 'textarea': {
24461 $prop = array();
24462 $opt = array();
24463 if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
24464 $prop['readonly'] = true;
24466 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
24467 $name = $tag['attribute']['name'];
24468 } else {
24469 break;
24471 if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
24472 $opt['v'] = $tag['attribute']['value'];
24474 if (isset($tag['attribute']['cols']) AND !$this->empty_string($tag['attribute']['cols'])) {
24475 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
24476 } else {
24477 $w = 40;
24479 if (isset($tag['attribute']['rows']) AND !$this->empty_string($tag['attribute']['rows'])) {
24480 $h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
24481 } else {
24482 $h = 10;
24484 $prop['multiline'] = 'true';
24485 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
24486 break;
24488 case 'select': {
24489 $h = $this->FontSize * $this->cell_height_ratio;
24490 if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
24491 $h *= ($tag['attribute']['size'] + 1);
24493 $prop = array();
24494 $opt = array();
24495 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
24496 $name = $tag['attribute']['name'];
24497 } else {
24498 break;
24500 $w = 0;
24501 if (isset($tag['attribute']['opt']) AND !$this->empty_string($tag['attribute']['opt'])) {
24502 $options = explode('#!NwL!#', $tag['attribute']['opt']);
24503 $values = array();
24504 foreach ($options as $val) {
24505 if (strpos($val, '#!TaB!#') !== false) {
24506 $opts = explode('#!TaB!#', $val);
24507 $values[] = $opts;
24508 $w = max($w, $this->GetStringWidth($opts[1]));
24509 } else {
24510 $values[] = $val;
24511 $w = max($w, $this->GetStringWidth($val));
24514 } else {
24515 break;
24517 $w *= 2;
24518 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
24519 $prop['multipleSelection'] = 'true';
24520 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
24521 } else {
24522 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
24524 break;
24526 case 'tcpdf': {
24527 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
24528 // Special tag used to call TCPDF methods
24529 if (isset($tag['attribute']['method'])) {
24530 $tcpdf_method = $tag['attribute']['method'];
24531 if (method_exists($this, $tcpdf_method)) {
24532 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
24533 $params = unserialize(urldecode($tag['attribute']['params']));
24534 call_user_func_array(array($this, $tcpdf_method), $params);
24535 } else {
24536 $this->$tcpdf_method();
24538 $this->newline = true;
24542 break;
24544 default: {
24545 break;
24548 // define tags that support borders and background colors
24549 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
24550 if (in_array($tag['value'], $bordertags)) {
24551 // set border
24552 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
24554 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
24555 $pba = $dom[$key]['attribute']['pagebreakafter'];
24556 // check for pagebreak
24557 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
24558 // add a page (or trig AcceptPageBreak() for multicolumn mode)
24559 $this->checkPageBreak($this->PageBreakTrigger + 1);
24561 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
24562 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
24563 // add a page (or trig AcceptPageBreak() for multicolumn mode)
24564 $this->checkPageBreak($this->PageBreakTrigger + 1);
24567 return $dom;
24571 * Process closing tags.
24572 * @param $dom (array) html dom array
24573 * @param $key (int) current element id
24574 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
24575 * @param $maxbottomliney (int) maximum y value of current line
24576 * @return $dom array
24577 * @protected
24579 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
24580 $tag = $dom[$key];
24581 $parent = $dom[($dom[$key]['parent'])];
24582 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
24583 $in_table_head = false;
24584 // maximum x position (used to draw borders)
24585 if ($this->rtl) {
24586 $xmax = $this->w;
24587 } else {
24588 $xmax = 0;
24590 if ($tag['block']) {
24591 $hbz = 0; // distance from y to line bottom
24592 $hb = 0; // vertical space between block tags
24593 // calculate vertical space for block tags
24594 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
24595 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
24596 } elseif (isset($parent['fontsize'])) {
24597 $pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
24598 } else {
24599 $pre_h = $this->FontSize * $this->cell_height_ratio;
24601 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
24602 $n = $this->tagvspaces[$tag['value']][1]['n'];
24603 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
24604 $n = 0.6;
24605 } else {
24606 $n = 1;
24608 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
24609 $hb = 0;
24610 } else {
24611 $hb = ($n * $pre_h);
24613 if ($maxbottomliney > $this->PageBreakTrigger) {
24614 $hbz = ($this->FontSize * $this->cell_height_ratio);
24615 } elseif ($this->y < $maxbottomliney) {
24616 $hbz = ($maxbottomliney - $this->y);
24619 // Closing tag
24620 switch($tag['value']) {
24621 case 'tr': {
24622 $table_el = $dom[($dom[$key]['parent'])]['parent'];
24623 if (!isset($parent['endy'])) {
24624 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
24625 $parent['endy'] = $this->y;
24627 if (!isset($parent['endpage'])) {
24628 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
24629 $parent['endpage'] = $this->page;
24631 if (!isset($parent['endcolumn'])) {
24632 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
24633 $parent['endcolumn'] = $this->current_column;
24635 // update row-spanned cells
24636 if (isset($dom[$table_el]['rowspans'])) {
24637 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24638 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
24639 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24640 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
24641 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
24642 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
24643 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
24644 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
24645 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
24649 // report new endy and endpage to the rowspanned cells
24650 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24651 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24652 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
24653 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
24654 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
24655 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
24656 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
24657 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
24660 // update remaining rowspanned cells
24661 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24662 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24663 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
24664 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
24665 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
24669 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
24670 if ($this->num_columns > 1) {
24671 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
24673 $this->y = $dom[($dom[$key]['parent'])]['endy'];
24674 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
24675 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
24676 } elseif (isset($dom[$table_el]['border-spacing'])) {
24677 $this->y += $dom[$table_el]['border-spacing']['V'];
24679 $this->Ln(0, $cell);
24680 if ($this->current_column == $parent['startcolumn']) {
24681 $this->x = $parent['startx'];
24683 // account for booklet mode
24684 if ($this->page > $parent['startpage']) {
24685 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
24686 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
24687 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
24688 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
24691 break;
24693 case 'tablehead':
24694 // closing tag used for the thead part
24695 $in_table_head = true;
24696 $this->inthead = false;
24697 case 'table': {
24698 $table_el = $parent;
24699 // set default border
24700 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
24701 // set default border
24702 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
24703 } else {
24704 $border = 0;
24706 $default_border = $border;
24707 // fix bottom line alignment of last line before page break
24708 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
24709 // update row-spanned cells
24710 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
24711 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
24712 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
24713 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
24715 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
24716 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
24720 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
24721 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
24722 $dom[$prevtrkey]['endy'] = $pgendy;
24723 // update row-spanned cells
24724 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
24725 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
24726 if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
24727 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
24728 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
24733 $prevtrkey = $trkey;
24734 $table_el = $dom[($dom[$key]['parent'])];
24736 // for each row
24737 if (count($table_el['trids']) > 0) {
24738 unset($xmax);
24740 foreach ($table_el['trids'] as $j => $trkey) {
24741 $parent = $dom[$trkey];
24742 if (!isset($xmax)) {
24743 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
24745 // for each cell on the row
24746 foreach ($parent['cellpos'] as $k => $cellpos) {
24747 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
24748 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
24749 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
24750 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
24751 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
24752 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
24753 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
24754 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
24755 } else {
24756 $endy = $parent['endy'];
24757 $startpage = $parent['startpage'];
24758 $endpage = $parent['endpage'];
24759 $startcolumn = $parent['startcolumn'];
24760 $endcolumn = $parent['endcolumn'];
24762 if ($this->num_columns == 0) {
24763 $this->num_columns = 1;
24765 if (isset($cellpos['border'])) {
24766 $border = $cellpos['border'];
24768 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
24769 $this->SetFillColorArray($cellpos['bgcolor']);
24770 $fill = true;
24771 } else {
24772 $fill = false;
24774 $x = $cellpos['startx'];
24775 $y = $parent['starty'];
24776 $starty = $y;
24777 $w = abs($cellpos['endx'] - $cellpos['startx']);
24778 // get border modes
24779 $border_start = $this->getBorderMode($border, $position='start');
24780 $border_end = $this->getBorderMode($border, $position='end');
24781 $border_middle = $this->getBorderMode($border, $position='middle');
24782 // design borders around HTML cells.
24783 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
24784 $ccode = '';
24785 $this->setPage($page);
24786 if ($this->num_columns < 2) {
24787 // single-column mode
24788 $this->x = $x;
24789 $this->y = $this->tMargin;
24791 // account for margin changes
24792 if ($page > $startpage) {
24793 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
24794 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
24795 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
24796 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
24799 if ($startpage == $endpage) { // single page
24800 $deltacol = 0;
24801 $deltath = 0;
24802 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
24803 $this->selectColumn($column);
24804 if ($startcolumn == $endcolumn) { // single column
24805 $cborder = $border;
24806 $h = $endy - $parent['starty'];
24807 $this->y = $y;
24808 $this->x = $x;
24809 } elseif ($column == $startcolumn) { // first column
24810 $cborder = $border_start;
24811 $this->y = $starty;
24812 $this->x = $x;
24813 $h = $this->h - $this->y - $this->bMargin;
24814 if ($this->rtl) {
24815 $deltacol = $this->x + $this->rMargin - $this->w;
24816 } else {
24817 $deltacol = $this->x - $this->lMargin;
24819 } elseif ($column == $endcolumn) { // end column
24820 $cborder = $border_end;
24821 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24822 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24824 $this->x += $deltacol;
24825 $h = $endy - $this->y;
24826 } else { // middle column
24827 $cborder = $border_middle;
24828 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24829 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24831 $this->x += $deltacol;
24832 $h = $this->h - $this->y - $this->bMargin;
24834 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24835 } // end for each column
24836 } elseif ($page == $startpage) { // first page
24837 $deltacol = 0;
24838 $deltath = 0;
24839 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
24840 $this->selectColumn($column);
24841 if ($column == $startcolumn) { // first column
24842 $cborder = $border_start;
24843 $this->y = $starty;
24844 $this->x = $x;
24845 $h = $this->h - $this->y - $this->bMargin;
24846 if ($this->rtl) {
24847 $deltacol = $this->x + $this->rMargin - $this->w;
24848 } else {
24849 $deltacol = $this->x - $this->lMargin;
24851 } else { // middle column
24852 $cborder = $border_middle;
24853 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24854 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24856 $this->x += $deltacol;
24857 $h = $this->h - $this->y - $this->bMargin;
24859 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24860 } // end for each column
24861 } elseif ($page == $endpage) { // last page
24862 $deltacol = 0;
24863 $deltath = 0;
24864 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
24865 $this->selectColumn($column);
24866 if ($column == $endcolumn) { // end column
24867 $cborder = $border_end;
24868 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24869 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24871 $this->x += $deltacol;
24872 $h = $endy - $this->y;
24873 } else { // middle column
24874 $cborder = $border_middle;
24875 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24876 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24878 $this->x += $deltacol;
24879 $h = $this->h - $this->y - $this->bMargin;
24881 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24882 } // end for each column
24883 } else { // middle page
24884 $deltacol = 0;
24885 $deltath = 0;
24886 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
24887 $this->selectColumn($column);
24888 $cborder = $border_middle;
24889 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24890 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24892 $this->x += $deltacol;
24893 $h = $this->h - $this->y - $this->bMargin;
24894 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24895 } // end for each column
24897 if ($cborder OR $fill) {
24898 $offsetlen = strlen($ccode);
24899 // draw border and fill
24900 if ($this->inxobj) {
24901 // we are inside an XObject template
24902 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
24903 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
24904 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
24905 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
24906 } else {
24907 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
24908 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
24910 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
24911 $pstart = substr($pagebuff, 0, $pagemark);
24912 $pend = substr($pagebuff, $pagemark);
24913 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
24914 } else {
24915 // draw border and fill
24916 if (end($this->transfmrk[$this->page]) !== false) {
24917 $pagemarkkey = key($this->transfmrk[$this->page]);
24918 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
24919 } elseif ($this->InFooter) {
24920 $pagemark = $this->footerpos[$this->page];
24921 } else {
24922 $pagemark = $this->intmrk[$this->page];
24924 $pagebuff = $this->getPageBuffer($this->page);
24925 $pstart = substr($pagebuff, 0, $pagemark);
24926 $pend = substr($pagebuff, $pagemark);
24927 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
24930 } // end for each page
24931 // restore default border
24932 $border = $default_border;
24933 } // end for each cell on the row
24934 if (isset($table_el['attribute']['cellspacing'])) {
24935 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
24936 } elseif (isset($table_el['border-spacing'])) {
24937 $this->y += $table_el['border-spacing']['V'];
24939 $this->Ln(0, $cell);
24940 $this->x = $parent['startx'];
24941 if ($endpage > $startpage) {
24942 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
24943 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
24944 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
24945 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
24949 if (!$in_table_head) { // we are not inside a thead section
24950 $this->cell_padding = $table_el['old_cell_padding'];
24951 // reset row height
24952 $this->resetLastH();
24953 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
24954 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
24955 if (($plendiff > 0) AND ($plendiff < 60)) {
24956 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
24957 if (substr($pagediff, 0, 5) == 'BT /F') {
24958 // the difference is only a font setting
24959 $plendiff = 0;
24962 if ($plendiff == 0) {
24963 // remove last blank page
24964 $this->deletePage($this->numpages);
24967 if (isset($this->theadMargins['top'])) {
24968 // restore top margin
24969 $this->tMargin = $this->theadMargins['top'];
24971 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
24972 // reset main table header
24973 $this->thead = '';
24974 $this->theadMargins = array();
24975 $this->pagedim[$this->page]['tm'] = $this->tMargin;
24978 $parent = $table_el;
24979 break;
24981 case 'a': {
24982 $this->HREF = '';
24983 break;
24985 case 'sup': {
24986 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
24987 break;
24989 case 'sub': {
24990 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
24991 break;
24993 case 'div': {
24994 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24995 break;
24997 case 'blockquote': {
24998 if ($this->rtl) {
24999 $this->rMargin -= $this->listindent;
25000 } else {
25001 $this->lMargin -= $this->listindent;
25003 --$this->listindentlevel;
25004 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25005 break;
25007 case 'p': {
25008 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25009 break;
25011 case 'pre': {
25012 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25013 $this->premode = false;
25014 break;
25016 case 'dl': {
25017 --$this->listnum;
25018 if ($this->listnum <= 0) {
25019 $this->listnum = 0;
25020 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25021 } else {
25022 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
25024 $this->resetLastH();
25025 break;
25027 case 'dt': {
25028 $this->lispacer = '';
25029 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
25030 break;
25032 case 'dd': {
25033 $this->lispacer = '';
25034 if ($this->rtl) {
25035 $this->rMargin -= $this->listindent;
25036 } else {
25037 $this->lMargin -= $this->listindent;
25039 --$this->listindentlevel;
25040 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
25041 break;
25043 case 'ul':
25044 case 'ol': {
25045 --$this->listnum;
25046 $this->lispacer = '';
25047 if ($this->rtl) {
25048 $this->rMargin -= $this->listindent;
25049 } else {
25050 $this->lMargin -= $this->listindent;
25052 --$this->listindentlevel;
25053 if ($this->listnum <= 0) {
25054 $this->listnum = 0;
25055 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25056 } else {
25057 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
25059 $this->resetLastH();
25060 break;
25062 case 'li': {
25063 $this->lispacer = '';
25064 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
25065 break;
25067 case 'h1':
25068 case 'h2':
25069 case 'h3':
25070 case 'h4':
25071 case 'h5':
25072 case 'h6': {
25073 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
25074 break;
25076 // Form fields (since 4.8.000 - 2009-09-07)
25077 case 'form': {
25078 $this->form_action = '';
25079 $this->form_enctype = 'application/x-www-form-urlencoded';
25080 break;
25082 default : {
25083 break;
25086 // draw border and background (if any)
25087 $this->drawHTMLTagBorder($parent, $xmax);
25088 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
25089 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
25090 // check for pagebreak
25091 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
25092 // add a page (or trig AcceptPageBreak() for multicolumn mode)
25093 $this->checkPageBreak($this->PageBreakTrigger + 1);
25095 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
25096 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
25097 // add a page (or trig AcceptPageBreak() for multicolumn mode)
25098 $this->checkPageBreak($this->PageBreakTrigger + 1);
25101 $this->tmprtl = false;
25102 return $dom;
25106 * Add vertical spaces if needed.
25107 * @param $hbz (string) Distance between current y and line bottom.
25108 * @param $hb (string) The height of the break.
25109 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
25110 * @param $firsttag (boolean) set to true when the tag is the first.
25111 * @param $lasttag (boolean) set to true when the tag is the last.
25112 * @protected
25114 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
25115 if ($firsttag) {
25116 $this->Ln(0, $cell);
25117 $this->htmlvspace = 0;
25118 return;
25120 if ($lasttag) {
25121 $this->Ln($hbz, $cell);
25122 $this->htmlvspace = 0;
25123 return;
25125 if ($hb < $this->htmlvspace) {
25126 $hd = 0;
25127 } else {
25128 $hd = $hb - $this->htmlvspace;
25129 $this->htmlvspace = $hb;
25131 $this->Ln(($hbz + $hd), $cell);
25135 * Return the starting coordinates to draw an html border
25136 * @return array containing top-left border coordinates
25137 * @protected
25138 * @since 5.7.000 (2010-08-03)
25140 protected function getBorderStartPosition() {
25141 if ($this->rtl) {
25142 $xmax = $this->lMargin;
25143 } else {
25144 $xmax = $this->w - $this->rMargin;
25146 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
25150 * Draw an HTML block border and fill
25151 * @param $tag (array) array of tag properties.
25152 * @param $xmax (int) end X coordinate for border.
25153 * @protected
25154 * @since 5.7.000 (2010-08-03)
25156 protected function drawHTMLTagBorder($tag, $xmax) {
25157 if (!isset($tag['borderposition'])) {
25158 // nothing to draw
25159 return;
25161 $prev_x = $this->x;
25162 $prev_y = $this->y;
25163 $prev_lasth = $this->lasth;
25164 $border = 0;
25165 $fill = false;
25166 $this->lasth = 0;
25167 if (isset($tag['border']) AND !empty($tag['border'])) {
25168 // get border style
25169 $border = $tag['border'];
25170 if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
25171 // border for table header
25172 $border = $this->getBorderMode($border, $position='middle');
25175 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
25176 // get background color
25177 $old_bgcolor = $this->bgcolor;
25178 $this->SetFillColorArray($tag['bgcolor']);
25179 $fill = true;
25181 if (!$border AND !$fill) {
25182 // nothing to draw
25183 return;
25185 if (isset($tag['attribute']['cellspacing'])) {
25186 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
25187 $cellspacing = array('H' => $clsp, 'V' => $clsp);
25188 } elseif (isset($tag['border-spacing'])) {
25189 $cellspacing = $tag['border-spacing'];
25190 } else {
25191 $cellspacing = array('H' => 0, 'V' => 0);
25193 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
25194 // draw the border externally respect the sqare edge.
25195 $border['mode'] = 'ext';
25197 if ($this->rtl) {
25198 if ($xmax >= $tag['borderposition']['x']) {
25199 $xmax = $tag['borderposition']['xmax'];
25201 $w = ($tag['borderposition']['x'] - $xmax);
25202 } else {
25203 if ($xmax <= $tag['borderposition']['x']) {
25204 $xmax = $tag['borderposition']['xmax'];
25206 $w = ($xmax - $tag['borderposition']['x']);
25208 if ($w <= 0) {
25209 return;
25211 $w += $cellspacing['H'];
25212 $startpage = $tag['borderposition']['page'];
25213 $startcolumn = $tag['borderposition']['column'];
25214 $x = $tag['borderposition']['x'];
25215 $y = $tag['borderposition']['y'];
25216 $endpage = $this->page;
25217 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
25218 $currentY = $this->y;
25219 $this->x = $x;
25220 // get latest column
25221 $endcolumn = $this->current_column;
25222 if ($this->num_columns == 0) {
25223 $this->num_columns = 1;
25225 // get border modes
25226 $border_start = $this->getBorderMode($border, $position='start');
25227 $border_end = $this->getBorderMode($border, $position='end');
25228 $border_middle = $this->getBorderMode($border, $position='middle');
25229 // temporary disable page regions
25230 $temp_page_regions = $this->page_regions;
25231 $this->page_regions = array();
25232 // design borders around HTML cells.
25233 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
25234 $ccode = '';
25235 $this->setPage($page);
25236 if ($this->num_columns < 2) {
25237 // single-column mode
25238 $this->x = $x;
25239 $this->y = $this->tMargin;
25241 // account for margin changes
25242 if ($page > $startpage) {
25243 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
25244 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
25245 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
25246 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
25249 if ($startpage == $endpage) {
25250 // single page
25251 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
25252 $this->selectColumn($column);
25253 if ($startcolumn == $endcolumn) { // single column
25254 $cborder = $border;
25255 $h = ($currentY - $y) + $cellspacing['V'];
25256 $this->y = $starty;
25257 } elseif ($column == $startcolumn) { // first column
25258 $cborder = $border_start;
25259 $this->y = $starty;
25260 $h = $this->h - $this->y - $this->bMargin;
25261 } elseif ($column == $endcolumn) { // end column
25262 $cborder = $border_end;
25263 $h = $currentY - $this->y;
25264 } else { // middle column
25265 $cborder = $border_middle;
25266 $h = $this->h - $this->y - $this->bMargin;
25268 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
25269 } // end for each column
25270 } elseif ($page == $startpage) { // first page
25271 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
25272 $this->selectColumn($column);
25273 if ($column == $startcolumn) { // first column
25274 $cborder = $border_start;
25275 $this->y = $starty;
25276 $h = $this->h - $this->y - $this->bMargin;
25277 } else { // middle column
25278 $cborder = $border_middle;
25279 $h = $this->h - $this->y - $this->bMargin;
25281 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
25282 } // end for each column
25283 } elseif ($page == $endpage) { // last page
25284 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
25285 $this->selectColumn($column);
25286 if ($column == $endcolumn) {
25287 // end column
25288 $cborder = $border_end;
25289 $h = $currentY - $this->y;
25290 } else {
25291 // middle column
25292 $cborder = $border_middle;
25293 $h = $this->h - $this->y - $this->bMargin;
25295 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
25296 } // end for each column
25297 } else { // middle page
25298 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
25299 $this->selectColumn($column);
25300 $cborder = $border_middle;
25301 $h = $this->h - $this->y - $this->bMargin;
25302 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
25303 } // end for each column
25305 if ($cborder OR $fill) {
25306 $offsetlen = strlen($ccode);
25307 // draw border and fill
25308 if ($this->inxobj) {
25309 // we are inside an XObject template
25310 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
25311 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
25312 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
25313 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
25314 } else {
25315 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
25316 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
25318 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
25319 $pstart = substr($pagebuff, 0, $pagemark);
25320 $pend = substr($pagebuff, $pagemark);
25321 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
25322 } else {
25323 if (end($this->transfmrk[$this->page]) !== false) {
25324 $pagemarkkey = key($this->transfmrk[$this->page]);
25325 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
25326 } elseif ($this->InFooter) {
25327 $pagemark = $this->footerpos[$this->page];
25328 } else {
25329 $pagemark = $this->intmrk[$this->page];
25331 $pagebuff = $this->getPageBuffer($this->page);
25332 $pstart = substr($pagebuff, 0, $pagemark);
25333 $pend = substr($pagebuff, $pagemark);
25334 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
25335 $this->bordermrk[$this->page] += $offsetlen;
25336 $this->cntmrk[$this->page] += $offsetlen;
25339 } // end for each page
25340 // restore page regions
25341 $this->page_regions = $temp_page_regions;
25342 if (isset($old_bgcolor)) {
25343 // restore background color
25344 $this->SetFillColorArray($old_bgcolor);
25346 // restore pointer position
25347 $this->x = $prev_x;
25348 $this->y = $prev_y;
25349 $this->lasth = $prev_lasth;
25353 * Set the default bullet to be used as LI bullet symbol
25354 * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
25355 * @public
25356 * @since 4.0.028 (2008-09-26)
25358 public function setLIsymbol($symbol='!') {
25359 // check for custom image symbol
25360 if (substr($symbol, 0, 4) == 'img|') {
25361 $this->lisymbol = $symbol;
25362 return;
25364 $symbol = strtolower($symbol);
25365 switch ($symbol) {
25366 case '!' :
25367 case '#' :
25368 case 'disc' :
25369 case 'circle' :
25370 case 'square' :
25371 case '1':
25372 case 'decimal':
25373 case 'decimal-leading-zero':
25374 case 'i':
25375 case 'lower-roman':
25376 case 'I':
25377 case 'upper-roman':
25378 case 'a':
25379 case 'lower-alpha':
25380 case 'lower-latin':
25381 case 'A':
25382 case 'upper-alpha':
25383 case 'upper-latin':
25384 case 'lower-greek': {
25385 $this->lisymbol = $symbol;
25386 break;
25388 default : {
25389 $this->lisymbol = '';
25395 * Set the booklet mode for double-sided pages.
25396 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
25397 * @param $inner (float) Inner page margin.
25398 * @param $outer (float) Outer page margin.
25399 * @public
25400 * @since 4.2.000 (2008-10-29)
25402 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
25403 $this->booklet = $booklet;
25404 if ($inner >= 0) {
25405 $this->lMargin = $inner;
25407 if ($outer >= 0) {
25408 $this->rMargin = $outer;
25413 * Swap the left and right margins.
25414 * @param $reverse (boolean) if true swap left and right margins.
25415 * @protected
25416 * @since 4.2.000 (2008-10-29)
25418 protected function swapMargins($reverse=true) {
25419 if ($reverse) {
25420 // swap left and right margins
25421 $mtemp = $this->original_lMargin;
25422 $this->original_lMargin = $this->original_rMargin;
25423 $this->original_rMargin = $mtemp;
25424 $deltam = $this->original_lMargin - $this->original_rMargin;
25425 $this->lMargin += $deltam;
25426 $this->rMargin -= $deltam;
25431 * Set the vertical spaces for HTML tags.
25432 * The array must have the following structure (example):
25433 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
25434 * The first array level contains the tag names,
25435 * the second level contains 0 for opening tags or 1 for closing tags,
25436 * the third level contains the vertical space unit (h) and the number spaces to add (n).
25437 * If the h parameter is not specified, default values are used.
25438 * @param $tagvs (array) array of tags and relative vertical spaces.
25439 * @public
25440 * @since 4.2.001 (2008-10-30)
25442 public function setHtmlVSpace($tagvs) {
25443 $this->tagvspaces = $tagvs;
25447 * Set custom width for list indentation.
25448 * @param $width (float) width of the indentation. Use negative value to disable it.
25449 * @public
25450 * @since 4.2.007 (2008-11-12)
25452 public function setListIndentWidth($width) {
25453 return $this->customlistindent = floatval($width);
25457 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
25458 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
25459 * @public
25460 * @since 4.2.010 (2008-11-14)
25462 public function setOpenCell($isopen) {
25463 $this->opencell = $isopen;
25467 * Set the color and font style for HTML links.
25468 * @param $color (array) RGB array of colors
25469 * @param $fontstyle (string) additional font styles to add
25470 * @public
25471 * @since 4.4.003 (2008-12-09)
25473 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
25474 $this->htmlLinkColorArray = $color;
25475 $this->htmlLinkFontStyle = $fontstyle;
25479 * Convert HTML string containing value and unit of measure to user's units or points.
25480 * @param $htmlval (string) string containing values and unit
25481 * @param $refsize (string) reference value in points
25482 * @param $defaultunit (string) default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
25483 * @param $points (boolean) if true returns points, otherwise returns value in user's units
25484 * @return float value in user's unit or point if $points=true
25485 * @public
25486 * @since 4.4.004 (2008-12-10)
25488 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
25489 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
25490 $retval = 0;
25491 $value = 0;
25492 $unit = 'px';
25493 $k = $this->k;
25494 if ($points) {
25495 $k = 1;
25497 if (in_array($defaultunit, $supportedunits)) {
25498 $unit = $defaultunit;
25500 if (is_numeric($htmlval)) {
25501 $value = floatval($htmlval);
25502 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
25503 $value = floatval($mnum[1]);
25504 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
25505 if (in_array($munit[1], $supportedunits)) {
25506 $unit = $munit[1];
25510 switch ($unit) {
25511 // percentage
25512 case '%': {
25513 $retval = (($value * $refsize) / 100);
25514 break;
25516 // relative-size
25517 case 'em': {
25518 $retval = ($value * $refsize);
25519 break;
25521 // height of lower case 'x' (about half the font-size)
25522 case 'ex': {
25523 $retval = $value * ($refsize / 2);
25524 break;
25526 // absolute-size
25527 case 'in': {
25528 $retval = ($value * $this->dpi) / $k;
25529 break;
25531 // centimeters
25532 case 'cm': {
25533 $retval = ($value / 2.54 * $this->dpi) / $k;
25534 break;
25536 // millimeters
25537 case 'mm': {
25538 $retval = ($value / 25.4 * $this->dpi) / $k;
25539 break;
25541 // one pica is 12 points
25542 case 'pc': {
25543 $retval = ($value * 12) / $k;
25544 break;
25546 // points
25547 case 'pt': {
25548 $retval = $value / $k;
25549 break;
25551 // pixels
25552 case 'px': {
25553 $retval = $this->pixelsToUnits($value);
25554 break;
25557 return $retval;
25561 * Returns the Roman representation of an integer number
25562 * @param $number (int) number to convert
25563 * @return string roman representation of the specified number
25564 * @since 4.4.004 (2008-12-10)
25565 * @public
25567 public function intToRoman($number) {
25568 $roman = '';
25569 while ($number >= 1000) {
25570 $roman .= 'M';
25571 $number -= 1000;
25573 while ($number >= 900) {
25574 $roman .= 'CM';
25575 $number -= 900;
25577 while ($number >= 500) {
25578 $roman .= 'D';
25579 $number -= 500;
25581 while ($number >= 400) {
25582 $roman .= 'CD';
25583 $number -= 400;
25585 while ($number >= 100) {
25586 $roman .= 'C';
25587 $number -= 100;
25589 while ($number >= 90) {
25590 $roman .= 'XC';
25591 $number -= 90;
25593 while ($number >= 50) {
25594 $roman .= 'L';
25595 $number -= 50;
25597 while ($number >= 40) {
25598 $roman .= 'XL';
25599 $number -= 40;
25601 while ($number >= 10) {
25602 $roman .= 'X';
25603 $number -= 10;
25605 while ($number >= 9) {
25606 $roman .= 'IX';
25607 $number -= 9;
25609 while ($number >= 5) {
25610 $roman .= 'V';
25611 $number -= 5;
25613 while ($number >= 4) {
25614 $roman .= 'IV';
25615 $number -= 4;
25617 while ($number >= 1) {
25618 $roman .= 'I';
25619 --$number;
25621 return $roman;
25625 * Output an HTML list bullet or ordered item symbol
25626 * @param $listdepth (int) list nesting level
25627 * @param $listtype (string) type of list
25628 * @param $size (float) current font size
25629 * @protected
25630 * @since 4.4.004 (2008-12-10)
25632 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
25633 if ($this->state != 2) {
25634 return;
25636 $size /= $this->k;
25637 $fill = '';
25638 $bgcolor = $this->bgcolor;
25639 $color = $this->fgcolor;
25640 $strokecolor = $this->strokecolor;
25641 $width = 0;
25642 $textitem = '';
25643 $tmpx = $this->x;
25644 $lspace = $this->GetStringWidth(' ');
25645 if ($listtype == '^') {
25646 // special symbol used for avoid justification of rect bullet
25647 $this->lispacer = '';
25648 return;
25649 } elseif ($listtype == '!') {
25650 // set default list type for unordered list
25651 $deftypes = array('disc', 'circle', 'square');
25652 $listtype = $deftypes[($listdepth - 1) % 3];
25653 } elseif ($listtype == '#') {
25654 // set default list type for ordered list
25655 $listtype = 'decimal';
25656 } elseif (substr($listtype, 0, 4) == 'img|') {
25657 // custom image type ('img|type|width|height|image.ext')
25658 $img = explode('|', $listtype);
25659 $listtype = 'img';
25661 switch ($listtype) {
25662 // unordered types
25663 case 'none': {
25664 break;
25666 case 'disc': {
25667 $r = $size / 6;
25668 $lspace += (2 * $r);
25669 if ($this->rtl) {
25670 $this->x += $lspace;
25671 } else {
25672 $this->x -= $lspace;
25674 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
25675 break;
25677 case 'circle': {
25678 $r = $size / 6;
25679 $lspace += (2 * $r);
25680 if ($this->rtl) {
25681 $this->x += $lspace;
25682 } else {
25683 $this->x -= $lspace;
25685 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
25686 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
25687 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
25688 $this->_out($prev_line_style); // restore line settings
25689 break;
25691 case 'square': {
25692 $l = $size / 3;
25693 $lspace += $l;
25694 if ($this->rtl) {;
25695 $this->x += $lspace;
25696 } else {
25697 $this->x -= $lspace;
25699 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
25700 break;
25702 case 'img': {
25703 // 1=>type, 2=>width, 3=>height, 4=>image.ext
25704 $lspace += $img[2];
25705 if ($this->rtl) {;
25706 $this->x += $lspace;
25707 } else {
25708 $this->x -= $lspace;
25710 $imgtype = strtolower($img[1]);
25711 $prev_y = $this->y;
25712 switch ($imgtype) {
25713 case 'svg': {
25714 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
25715 break;
25717 case 'ai':
25718 case 'eps': {
25719 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
25720 break;
25722 default: {
25723 $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);
25724 break;
25727 $this->y = $prev_y;
25728 break;
25730 // ordered types
25731 // $this->listcount[$this->listnum];
25732 // $textitem
25733 case '1':
25734 case 'decimal': {
25735 $textitem = $this->listcount[$this->listnum];
25736 break;
25738 case 'decimal-leading-zero': {
25739 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
25740 break;
25742 case 'i':
25743 case 'lower-roman': {
25744 $textitem = strtolower($this->intToRoman($this->listcount[$this->listnum]));
25745 break;
25747 case 'I':
25748 case 'upper-roman': {
25749 $textitem = $this->intToRoman($this->listcount[$this->listnum]);
25750 break;
25752 case 'a':
25753 case 'lower-alpha':
25754 case 'lower-latin': {
25755 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
25756 break;
25758 case 'A':
25759 case 'upper-alpha':
25760 case 'upper-latin': {
25761 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
25762 break;
25764 case 'lower-greek': {
25765 $textitem = $this->unichr(945 + $this->listcount[$this->listnum] - 1);
25766 break;
25769 // Types to be implemented (special handling)
25770 case 'hebrew': {
25771 break;
25773 case 'armenian': {
25774 break;
25776 case 'georgian': {
25777 break;
25779 case 'cjk-ideographic': {
25780 break;
25782 case 'hiragana': {
25783 break;
25785 case 'katakana': {
25786 break;
25788 case 'hiragana-iroha': {
25789 break;
25791 case 'katakana-iroha': {
25792 break;
25795 default: {
25796 $textitem = $this->listcount[$this->listnum];
25799 if (!$this->empty_string($textitem)) {
25800 // Check whether we need a new page or new column
25801 $prev_y = $this->y;
25802 $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
25803 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
25804 $tmpx = $this->x;
25806 // print ordered item
25807 if ($this->rtl) {
25808 $textitem = '.'.$textitem;
25809 } else {
25810 $textitem = $textitem.'.';
25812 $lspace += $this->GetStringWidth($textitem);
25813 if ($this->rtl) {
25814 $this->x += $lspace;
25815 } else {
25816 $this->x -= $lspace;
25818 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
25820 $this->x = $tmpx;
25821 $this->lispacer = '^';
25822 // restore colors
25823 $this->SetFillColorArray($bgcolor);
25824 $this->SetDrawColorArray($strokecolor);
25825 $this->SettextColorArray($color);
25829 * Returns current graphic variables as array.
25830 * @return array of graphic variables
25831 * @protected
25832 * @since 4.2.010 (2008-11-14)
25834 protected function getGraphicVars() {
25835 $grapvars = array(
25836 'FontFamily' => $this->FontFamily,
25837 'FontStyle' => $this->FontStyle,
25838 'FontSizePt' => $this->FontSizePt,
25839 'rMargin' => $this->rMargin,
25840 'lMargin' => $this->lMargin,
25841 'cell_padding' => $this->cell_padding,
25842 'cell_margin' => $this->cell_margin,
25843 'LineWidth' => $this->LineWidth,
25844 'linestyleWidth' => $this->linestyleWidth,
25845 'linestyleCap' => $this->linestyleCap,
25846 'linestyleJoin' => $this->linestyleJoin,
25847 'linestyleDash' => $this->linestyleDash,
25848 'textrendermode' => $this->textrendermode,
25849 'textstrokewidth' => $this->textstrokewidth,
25850 'DrawColor' => $this->DrawColor,
25851 'FillColor' => $this->FillColor,
25852 'TextColor' => $this->TextColor,
25853 'ColorFlag' => $this->ColorFlag,
25854 'bgcolor' => $this->bgcolor,
25855 'fgcolor' => $this->fgcolor,
25856 'htmlvspace' => $this->htmlvspace,
25857 'listindent' => $this->listindent,
25858 'listindentlevel' => $this->listindentlevel,
25859 'listnum' => $this->listnum,
25860 'listordered' => $this->listordered,
25861 'listcount' => $this->listcount,
25862 'lispacer' => $this->lispacer,
25863 'cell_height_ratio' => $this->cell_height_ratio,
25864 'font_stretching' => $this->font_stretching,
25865 'font_spacing' => $this->font_spacing,
25866 'alpha' => $this->alpha,
25867 // extended
25868 'lasth' => $this->lasth,
25869 'tMargin' => $this->tMargin,
25870 'bMargin' => $this->bMargin,
25871 'AutoPageBreak' => $this->AutoPageBreak,
25872 'PageBreakTrigger' => $this->PageBreakTrigger,
25873 'x' => $this->x,
25874 'y' => $this->y,
25875 'w' => $this->w,
25876 'h' => $this->h,
25877 'wPt' => $this->wPt,
25878 'hPt' => $this->hPt,
25879 'fwPt' => $this->fwPt,
25880 'fhPt' => $this->fhPt,
25881 'page' => $this->page,
25882 'current_column' => $this->current_column,
25883 'num_columns' => $this->num_columns
25885 return $grapvars;
25889 * Set graphic variables.
25890 * @param $gvars (array) array of graphic variablesto restore
25891 * @param $extended (boolean) if true restore extended graphic variables
25892 * @protected
25893 * @since 4.2.010 (2008-11-14)
25895 protected function setGraphicVars($gvars, $extended=false) {
25896 if ($this->state != 2) {
25897 return;
25899 $this->FontFamily = $gvars['FontFamily'];
25900 $this->FontStyle = $gvars['FontStyle'];
25901 $this->FontSizePt = $gvars['FontSizePt'];
25902 $this->rMargin = $gvars['rMargin'];
25903 $this->lMargin = $gvars['lMargin'];
25904 $this->cell_padding = $gvars['cell_padding'];
25905 $this->cell_margin = $gvars['cell_margin'];
25906 $this->LineWidth = $gvars['LineWidth'];
25907 $this->linestyleWidth = $gvars['linestyleWidth'];
25908 $this->linestyleCap = $gvars['linestyleCap'];
25909 $this->linestyleJoin = $gvars['linestyleJoin'];
25910 $this->linestyleDash = $gvars['linestyleDash'];
25911 $this->textrendermode = $gvars['textrendermode'];
25912 $this->textstrokewidth = $gvars['textstrokewidth'];
25913 $this->DrawColor = $gvars['DrawColor'];
25914 $this->FillColor = $gvars['FillColor'];
25915 $this->TextColor = $gvars['TextColor'];
25916 $this->ColorFlag = $gvars['ColorFlag'];
25917 $this->bgcolor = $gvars['bgcolor'];
25918 $this->fgcolor = $gvars['fgcolor'];
25919 $this->htmlvspace = $gvars['htmlvspace'];
25920 $this->listindent = $gvars['listindent'];
25921 $this->listindentlevel = $gvars['listindentlevel'];
25922 $this->listnum = $gvars['listnum'];
25923 $this->listordered = $gvars['listordered'];
25924 $this->listcount = $gvars['listcount'];
25925 $this->lispacer = $gvars['lispacer'];
25926 $this->cell_height_ratio = $gvars['cell_height_ratio'];
25927 $this->font_stretching = $gvars['font_stretching'];
25928 $this->font_spacing = $gvars['font_spacing'];
25929 $this->alpha = $gvars['alpha'];
25930 if ($extended) {
25931 // restore extended values
25932 $this->lasth = $gvars['lasth'];
25933 $this->tMargin = $gvars['tMargin'];
25934 $this->bMargin = $gvars['bMargin'];
25935 $this->AutoPageBreak = $gvars['AutoPageBreak'];
25936 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
25937 $this->x = $gvars['x'];
25938 $this->y = $gvars['y'];
25939 $this->w = $gvars['w'];
25940 $this->h = $gvars['h'];
25941 $this->wPt = $gvars['wPt'];
25942 $this->hPt = $gvars['hPt'];
25943 $this->fwPt = $gvars['fwPt'];
25944 $this->fhPt = $gvars['fhPt'];
25945 $this->page = $gvars['page'];
25946 $this->current_column = $gvars['current_column'];
25947 $this->num_columns = $gvars['num_columns'];
25949 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
25950 if (!$this->empty_string($this->FontFamily)) {
25951 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
25956 * Returns a temporary filename for caching object on filesystem.
25957 * @param $name (string) prefix to add to filename
25958 * @return string filename.
25959 * @since 4.5.000 (2008-12-31)
25960 * @protected
25962 protected function getObjFilename($name) {
25963 return tempnam(K_PATH_CACHE, $name.'_');
25967 * Writes data to a temporary file on filesystem.
25968 * @param $filename (string) file name
25969 * @param $data (mixed) data to write on file
25970 * @param $append (boolean) if true append data, false replace.
25971 * @since 4.5.000 (2008-12-31)
25972 * @protected
25974 protected function writeDiskCache($filename, $data, $append=false) {
25975 if ($append) {
25976 $fmode = 'ab+';
25977 } else {
25978 $fmode = 'wb+';
25980 $f = @fopen($filename, $fmode);
25981 if (!$f) {
25982 $this->Error('Unable to write cache file: '.$filename);
25983 } else {
25984 fwrite($f, $data);
25985 fclose($f);
25987 // update file length (needed for transactions)
25988 if (!isset($this->cache_file_length['_'.$filename])) {
25989 $this->cache_file_length['_'.$filename] = strlen($data);
25990 } else {
25991 $this->cache_file_length['_'.$filename] += strlen($data);
25996 * Read data from a temporary file on filesystem.
25997 * @param $filename (string) file name
25998 * @return mixed retrieved data
25999 * @since 4.5.000 (2008-12-31)
26000 * @protected
26002 protected function readDiskCache($filename) {
26003 return file_get_contents($filename);
26007 * Set buffer content (always append data).
26008 * @param $data (string) data
26009 * @protected
26010 * @since 4.5.000 (2009-01-02)
26012 protected function setBuffer($data) {
26013 $this->bufferlen += strlen($data);
26014 if ($this->diskcache) {
26015 if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
26016 $this->buffer = $this->getObjFilename('buffer');
26018 $this->writeDiskCache($this->buffer, $data, true);
26019 } else {
26020 $this->buffer .= $data;
26025 * Replace the buffer content
26026 * @param $data (string) data
26027 * @protected
26028 * @since 5.5.000 (2010-06-22)
26030 protected function replaceBuffer($data) {
26031 $this->bufferlen = strlen($data);
26032 if ($this->diskcache) {
26033 if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
26034 $this->buffer = $this->getObjFilename('buffer');
26036 $this->writeDiskCache($this->buffer, $data, false);
26037 } else {
26038 $this->buffer = $data;
26043 * Get buffer content.
26044 * @return string buffer content
26045 * @protected
26046 * @since 4.5.000 (2009-01-02)
26048 protected function getBuffer() {
26049 if ($this->diskcache) {
26050 return $this->readDiskCache($this->buffer);
26051 } else {
26052 return $this->buffer;
26057 * Set page buffer content.
26058 * @param $page (int) page number
26059 * @param $data (string) page data
26060 * @param $append (boolean) if true append data, false replace.
26061 * @protected
26062 * @since 4.5.000 (2008-12-31)
26064 protected function setPageBuffer($page, $data, $append=false) {
26065 if ($this->diskcache) {
26066 if (!isset($this->pages[$page])) {
26067 $this->pages[$page] = $this->getObjFilename('page'.$page);
26069 $this->writeDiskCache($this->pages[$page], $data, $append);
26070 } else {
26071 if ($append) {
26072 $this->pages[$page] .= $data;
26073 } else {
26074 $this->pages[$page] = $data;
26077 if ($append AND isset($this->pagelen[$page])) {
26078 $this->pagelen[$page] += strlen($data);
26079 } else {
26080 $this->pagelen[$page] = strlen($data);
26085 * Get page buffer content.
26086 * @param $page (int) page number
26087 * @return string page buffer content or false in case of error
26088 * @protected
26089 * @since 4.5.000 (2008-12-31)
26091 protected function getPageBuffer($page) {
26092 if ($this->diskcache) {
26093 return $this->readDiskCache($this->pages[$page]);
26094 } elseif (isset($this->pages[$page])) {
26095 return $this->pages[$page];
26097 return false;
26101 * Set image buffer content.
26102 * @param $image (string) image key
26103 * @param $data (array) image data
26104 * @return int image index number
26105 * @protected
26106 * @since 4.5.000 (2008-12-31)
26108 protected function setImageBuffer($image, $data) {
26109 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
26110 $this->imagekeys[$this->numimages] = $image;
26111 $data['i'] = $this->numimages;
26112 ++$this->numimages;
26114 if ($this->diskcache) {
26115 if (!isset($this->images[$image])) {
26116 $this->images[$image] = $this->getObjFilename('image'.$image);
26118 $this->writeDiskCache($this->images[$image], serialize($data));
26119 } else {
26120 $this->images[$image] = $data;
26122 return $data['i'];
26126 * Set image buffer content for a specified sub-key.
26127 * @param $image (string) image key
26128 * @param $key (string) image sub-key
26129 * @param $data (array) image data
26130 * @protected
26131 * @since 4.5.000 (2008-12-31)
26133 protected function setImageSubBuffer($image, $key, $data) {
26134 if (!isset($this->images[$image])) {
26135 $this->setImageBuffer($image, array());
26137 if ($this->diskcache) {
26138 $tmpimg = $this->getImageBuffer($image);
26139 $tmpimg[$key] = $data;
26140 $this->writeDiskCache($this->images[$image], serialize($tmpimg));
26141 } else {
26142 $this->images[$image][$key] = $data;
26147 * Get image buffer content.
26148 * @param $image (string) image key
26149 * @return string image buffer content or false in case of error
26150 * @protected
26151 * @since 4.5.000 (2008-12-31)
26153 protected function getImageBuffer($image) {
26154 if ($this->diskcache AND isset($this->images[$image])) {
26155 return unserialize($this->readDiskCache($this->images[$image]));
26156 } elseif (isset($this->images[$image])) {
26157 return $this->images[$image];
26159 return false;
26163 * Set font buffer content.
26164 * @param $font (string) font key
26165 * @param $data (array) font data
26166 * @protected
26167 * @since 4.5.000 (2009-01-02)
26169 protected function setFontBuffer($font, $data) {
26170 if ($this->diskcache) {
26171 if (!isset($this->fonts[$font])) {
26172 $this->fonts[$font] = $this->getObjFilename('font');
26174 $this->writeDiskCache($this->fonts[$font], serialize($data));
26175 } else {
26176 $this->fonts[$font] = $data;
26178 if (!in_array($font, $this->fontkeys)) {
26179 $this->fontkeys[] = $font;
26180 // store object ID for current font
26181 ++$this->n;
26182 $this->font_obj_ids[$font] = $this->n;
26183 $this->setFontSubBuffer($font, 'n', $this->n);
26188 * Set font buffer content.
26189 * @param $font (string) font key
26190 * @param $key (string) font sub-key
26191 * @param $data (array) font data
26192 * @protected
26193 * @since 4.5.000 (2009-01-02)
26195 protected function setFontSubBuffer($font, $key, $data) {
26196 if (!isset($this->fonts[$font])) {
26197 $this->setFontBuffer($font, array());
26199 if ($this->diskcache) {
26200 $tmpfont = $this->getFontBuffer($font);
26201 $tmpfont[$key] = $data;
26202 $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
26203 } else {
26204 $this->fonts[$font][$key] = $data;
26209 * Get font buffer content.
26210 * @param $font (string) font key
26211 * @return string font buffer content or false in case of error
26212 * @protected
26213 * @since 4.5.000 (2009-01-02)
26215 protected function getFontBuffer($font) {
26216 if ($this->diskcache AND isset($this->fonts[$font])) {
26217 return unserialize($this->readDiskCache($this->fonts[$font]));
26218 } elseif (isset($this->fonts[$font])) {
26219 return $this->fonts[$font];
26221 return false;
26225 * Move a page to a previous position.
26226 * @param $frompage (int) number of the source page
26227 * @param $topage (int) number of the destination page (must be less than $frompage)
26228 * @return true in case of success, false in case of error.
26229 * @public
26230 * @since 4.5.000 (2009-01-02)
26232 public function movePage($frompage, $topage) {
26233 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
26234 return false;
26236 if ($frompage == $this->page) {
26237 // close the page before moving it
26238 $this->endPage();
26240 // move all page-related states
26241 $tmppage = $this->getPageBuffer($frompage);
26242 $tmppagedim = $this->pagedim[$frompage];
26243 $tmppagelen = $this->pagelen[$frompage];
26244 $tmpintmrk = $this->intmrk[$frompage];
26245 $tmpbordermrk = $this->bordermrk[$frompage];
26246 $tmpcntmrk = $this->cntmrk[$frompage];
26247 $tmppageobjects = $this->pageobjects[$frompage];
26248 if (isset($this->footerpos[$frompage])) {
26249 $tmpfooterpos = $this->footerpos[$frompage];
26251 if (isset($this->footerlen[$frompage])) {
26252 $tmpfooterlen = $this->footerlen[$frompage];
26254 if (isset($this->transfmrk[$frompage])) {
26255 $tmptransfmrk = $this->transfmrk[$frompage];
26257 if (isset($this->PageAnnots[$frompage])) {
26258 $tmpannots = $this->PageAnnots[$frompage];
26260 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
26261 for ($i = $frompage; $i > $topage; --$i) {
26262 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
26263 --$this->pagegroups[$this->newpagegroup[$i]];
26264 break;
26267 for ($i = $topage; $i > 0; --$i) {
26268 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
26269 ++$this->pagegroups[$this->newpagegroup[$i]];
26270 break;
26274 for ($i = $frompage; $i > $topage; --$i) {
26275 $j = $i - 1;
26276 // shift pages down
26277 $this->setPageBuffer($i, $this->getPageBuffer($j));
26278 $this->pagedim[$i] = $this->pagedim[$j];
26279 $this->pagelen[$i] = $this->pagelen[$j];
26280 $this->intmrk[$i] = $this->intmrk[$j];
26281 $this->bordermrk[$i] = $this->bordermrk[$j];
26282 $this->cntmrk[$i] = $this->cntmrk[$j];
26283 $this->pageobjects[$i] = $this->pageobjects[$j];
26284 if (isset($this->footerpos[$j])) {
26285 $this->footerpos[$i] = $this->footerpos[$j];
26286 } elseif (isset($this->footerpos[$i])) {
26287 unset($this->footerpos[$i]);
26289 if (isset($this->footerlen[$j])) {
26290 $this->footerlen[$i] = $this->footerlen[$j];
26291 } elseif (isset($this->footerlen[$i])) {
26292 unset($this->footerlen[$i]);
26294 if (isset($this->transfmrk[$j])) {
26295 $this->transfmrk[$i] = $this->transfmrk[$j];
26296 } elseif (isset($this->transfmrk[$i])) {
26297 unset($this->transfmrk[$i]);
26299 if (isset($this->PageAnnots[$j])) {
26300 $this->PageAnnots[$i] = $this->PageAnnots[$j];
26301 } elseif (isset($this->PageAnnots[$i])) {
26302 unset($this->PageAnnots[$i]);
26304 if (isset($this->newpagegroup[$j])) {
26305 $this->newpagegroup[$i] = $this->newpagegroup[$j];
26306 unset($this->newpagegroup[$j]);
26308 if ($this->currpagegroup == $j) {
26309 $this->currpagegroup = $i;
26312 $this->setPageBuffer($topage, $tmppage);
26313 $this->pagedim[$topage] = $tmppagedim;
26314 $this->pagelen[$topage] = $tmppagelen;
26315 $this->intmrk[$topage] = $tmpintmrk;
26316 $this->bordermrk[$topage] = $tmpbordermrk;
26317 $this->cntmrk[$topage] = $tmpcntmrk;
26318 $this->pageobjects[$topage] = $tmppageobjects;
26319 if (isset($tmpfooterpos)) {
26320 $this->footerpos[$topage] = $tmpfooterpos;
26321 } elseif (isset($this->footerpos[$topage])) {
26322 unset($this->footerpos[$topage]);
26324 if (isset($tmpfooterlen)) {
26325 $this->footerlen[$topage] = $tmpfooterlen;
26326 } elseif (isset($this->footerlen[$topage])) {
26327 unset($this->footerlen[$topage]);
26329 if (isset($tmptransfmrk)) {
26330 $this->transfmrk[$topage] = $tmptransfmrk;
26331 } elseif (isset($this->transfmrk[$topage])) {
26332 unset($this->transfmrk[$topage]);
26334 if (isset($tmpannots)) {
26335 $this->PageAnnots[$topage] = $tmpannots;
26336 } elseif (isset($this->PageAnnots[$topage])) {
26337 unset($this->PageAnnots[$topage]);
26339 // adjust outlines
26340 $tmpoutlines = $this->outlines;
26341 foreach ($tmpoutlines as $key => $outline) {
26342 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
26343 $this->outlines[$key]['p'] = ($outline['p'] + 1);
26344 } elseif ($outline['p'] == $frompage) {
26345 $this->outlines[$key]['p'] = $topage;
26348 // adjust dests
26349 $tmpdests = $this->dests;
26350 foreach ($tmpdests as $key => $dest) {
26351 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
26352 $this->dests[$key]['p'] = ($dest['p'] + 1);
26353 } elseif ($dest['p'] == $frompage) {
26354 $this->dests[$key]['p'] = $topage;
26357 // adjust links
26358 $tmplinks = $this->links;
26359 foreach ($tmplinks as $key => $link) {
26360 if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
26361 $this->links[$key][0] = ($link[0] + 1);
26362 } elseif ($link[0] == $frompage) {
26363 $this->links[$key][0] = $topage;
26366 // adjust javascript
26367 $tmpjavascript = $this->javascript;
26368 global $jfrompage, $jtopage;
26369 $jfrompage = $frompage;
26370 $jtopage = $topage;
26371 $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
26372 create_function('$matches', 'global $jfrompage, $jtopage;
26373 $pagenum = intval($matches[3]) + 1;
26374 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
26375 $newpage = ($pagenum + 1);
26376 } elseif ($pagenum == $jfrompage) {
26377 $newpage = $jtopage;
26378 } else {
26379 $newpage = $pagenum;
26381 --$newpage;
26382 return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
26383 // return to last page
26384 $this->lastPage(true);
26385 return true;
26389 * Remove the specified page.
26390 * @param $page (int) page to remove
26391 * @return true in case of success, false in case of error.
26392 * @public
26393 * @since 4.6.004 (2009-04-23)
26395 public function deletePage($page) {
26396 if (($page < 1) OR ($page > $this->numpages)) {
26397 return false;
26399 // delete current page
26400 unset($this->pages[$page]);
26401 unset($this->pagedim[$page]);
26402 unset($this->pagelen[$page]);
26403 unset($this->intmrk[$page]);
26404 unset($this->bordermrk[$page]);
26405 unset($this->cntmrk[$page]);
26406 foreach ($this->pageobjects[$page] as $oid) {
26407 if (isset($this->offsets[$oid])){
26408 unset($this->offsets[$oid]);
26411 unset($this->pageobjects[$page]);
26412 if (isset($this->footerpos[$page])) {
26413 unset($this->footerpos[$page]);
26415 if (isset($this->footerlen[$page])) {
26416 unset($this->footerlen[$page]);
26418 if (isset($this->transfmrk[$page])) {
26419 unset($this->transfmrk[$page]);
26421 if (isset($this->PageAnnots[$page])) {
26422 unset($this->PageAnnots[$page]);
26424 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
26425 for ($i = $page; $i > 0; --$i) {
26426 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
26427 --$this->pagegroups[$this->newpagegroup[$i]];
26428 break;
26432 if (isset($this->pageopen[$page])) {
26433 unset($this->pageopen[$page]);
26435 if ($page < $this->numpages) {
26436 // update remaining pages
26437 for ($i = $page; $i < $this->numpages; ++$i) {
26438 $j = $i + 1;
26439 // shift pages
26440 $this->setPageBuffer($i, $this->getPageBuffer($j));
26441 $this->pagedim[$i] = $this->pagedim[$j];
26442 $this->pagelen[$i] = $this->pagelen[$j];
26443 $this->intmrk[$i] = $this->intmrk[$j];
26444 $this->bordermrk[$i] = $this->bordermrk[$j];
26445 $this->cntmrk[$i] = $this->cntmrk[$j];
26446 $this->pageobjects[$i] = $this->pageobjects[$j];
26447 if (isset($this->footerpos[$j])) {
26448 $this->footerpos[$i] = $this->footerpos[$j];
26449 } elseif (isset($this->footerpos[$i])) {
26450 unset($this->footerpos[$i]);
26452 if (isset($this->footerlen[$j])) {
26453 $this->footerlen[$i] = $this->footerlen[$j];
26454 } elseif (isset($this->footerlen[$i])) {
26455 unset($this->footerlen[$i]);
26457 if (isset($this->transfmrk[$j])) {
26458 $this->transfmrk[$i] = $this->transfmrk[$j];
26459 } elseif (isset($this->transfmrk[$i])) {
26460 unset($this->transfmrk[$i]);
26462 if (isset($this->PageAnnots[$j])) {
26463 $this->PageAnnots[$i] = $this->PageAnnots[$j];
26464 } elseif (isset($this->PageAnnots[$i])) {
26465 unset($this->PageAnnots[$i]);
26467 if (isset($this->newpagegroup[$j])) {
26468 $this->newpagegroup[$i] = $this->newpagegroup[$j];
26469 unset($this->newpagegroup[$j]);
26471 if ($this->currpagegroup == $j) {
26472 $this->currpagegroup = $i;
26474 if (isset($this->pageopen[$j])) {
26475 $this->pageopen[$i] = $this->pageopen[$j];
26476 } elseif (isset($this->pageopen[$i])) {
26477 unset($this->pageopen[$i]);
26480 // remove last page
26481 unset($this->pages[$this->numpages]);
26482 unset($this->pagedim[$this->numpages]);
26483 unset($this->pagelen[$this->numpages]);
26484 unset($this->intmrk[$this->numpages]);
26485 unset($this->bordermrk[$this->numpages]);
26486 unset($this->cntmrk[$this->numpages]);
26487 foreach ($this->pageobjects[$this->numpages] as $oid) {
26488 if (isset($this->offsets[$oid])){
26489 unset($this->offsets[$oid]);
26492 unset($this->pageobjects[$this->numpages]);
26493 if (isset($this->footerpos[$this->numpages])) {
26494 unset($this->footerpos[$this->numpages]);
26496 if (isset($this->footerlen[$this->numpages])) {
26497 unset($this->footerlen[$this->numpages]);
26499 if (isset($this->transfmrk[$this->numpages])) {
26500 unset($this->transfmrk[$this->numpages]);
26502 if (isset($this->PageAnnots[$this->numpages])) {
26503 unset($this->PageAnnots[$this->numpages]);
26505 if (isset($this->newpagegroup[$this->numpages])) {
26506 unset($this->newpagegroup[$this->numpages]);
26508 if ($this->currpagegroup == $this->numpages) {
26509 $this->currpagegroup = ($this->numpages - 1);
26511 if (isset($this->pagegroups[$this->numpages])) {
26512 unset($this->pagegroups[$this->numpages]);
26514 if (isset($this->pageopen[$this->numpages])) {
26515 unset($this->pageopen[$this->numpages]);
26518 --$this->numpages;
26519 $this->page = $this->numpages;
26520 // adjust outlines
26521 $tmpoutlines = $this->outlines;
26522 foreach ($tmpoutlines as $key => $outline) {
26523 if ($outline['p'] > $page) {
26524 $this->outlines[$key]['p'] = $outline['p'] - 1;
26525 } elseif ($outline['p'] == $page) {
26526 unset($this->outlines[$key]);
26529 // adjust dests
26530 $tmpdests = $this->dests;
26531 foreach ($tmpdests as $key => $dest) {
26532 if ($dest['p'] > $page) {
26533 $this->dests[$key]['p'] = $dest['p'] - 1;
26534 } elseif ($dest['p'] == $page) {
26535 unset($this->dests[$key]);
26538 // adjust links
26539 $tmplinks = $this->links;
26540 foreach ($tmplinks as $key => $link) {
26541 if ($link[0] > $page) {
26542 $this->links[$key][0] = $link[0] - 1;
26543 } elseif ($link[0] == $page) {
26544 unset($this->links[$key]);
26547 // adjust javascript
26548 $tmpjavascript = $this->javascript;
26549 global $jpage;
26550 $jpage = $page;
26551 $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
26552 create_function('$matches', 'global $jpage;
26553 $pagenum = intval($matches[3]) + 1;
26554 if ($pagenum >= $jpage) {
26555 $newpage = ($pagenum - 1);
26556 } elseif ($pagenum == $jpage) {
26557 $newpage = 1;
26558 } else {
26559 $newpage = $pagenum;
26561 --$newpage;
26562 return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
26563 // return to last page
26564 $this->lastPage(true);
26565 return true;
26569 * Clone the specified page to a new page.
26570 * @param $page (int) number of page to copy (0 = current page)
26571 * @return true in case of success, false in case of error.
26572 * @public
26573 * @since 4.9.015 (2010-04-20)
26575 public function copyPage($page=0) {
26576 if ($page == 0) {
26577 // default value
26578 $page = $this->page;
26580 if (($page < 1) OR ($page > $this->numpages)) {
26581 return false;
26583 // close the last page
26584 $this->endPage();
26585 // copy all page-related states
26586 ++$this->numpages;
26587 $this->page = $this->numpages;
26588 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
26589 $this->pagedim[$this->page] = $this->pagedim[$page];
26590 $this->pagelen[$this->page] = $this->pagelen[$page];
26591 $this->intmrk[$this->page] = $this->intmrk[$page];
26592 $this->bordermrk[$this->page] = $this->bordermrk[$page];
26593 $this->cntmrk[$this->page] = $this->cntmrk[$page];
26594 $this->pageobjects[$this->page] = $this->pageobjects[$page];
26595 $this->pageopen[$this->page] = false;
26596 if (isset($this->footerpos[$page])) {
26597 $this->footerpos[$this->page] = $this->footerpos[$page];
26599 if (isset($this->footerlen[$page])) {
26600 $this->footerlen[$this->page] = $this->footerlen[$page];
26602 if (isset($this->transfmrk[$page])) {
26603 $this->transfmrk[$this->page] = $this->transfmrk[$page];
26605 if (isset($this->PageAnnots[$page])) {
26606 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
26608 if (isset($this->newpagegroup[$page])) {
26609 // start a new group
26610 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
26611 $this->currpagegroup = $this->newpagegroup[$this->page];
26612 $this->pagegroups[$this->currpagegroup] = 1;
26613 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
26614 ++$this->pagegroups[$this->currpagegroup];
26616 // copy outlines
26617 $tmpoutlines = $this->outlines;
26618 foreach ($tmpoutlines as $key => $outline) {
26619 if ($outline['p'] == $page) {
26620 $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 's' => $outline['s'], 'c' => $outline['c']);
26623 // copy links
26624 $tmplinks = $this->links;
26625 foreach ($tmplinks as $key => $link) {
26626 if ($link[0] == $page) {
26627 $this->links[] = array($this->page, $link[1]);
26630 // return to last page
26631 $this->lastPage(true);
26632 return true;
26636 * Output a Table of Content Index (TOC).
26637 * This method must be called after all Bookmarks were set.
26638 * Before calling this method you have to open the page using the addTOCPage() method.
26639 * After calling this method you have to call endTOCPage() to close the TOC page.
26640 * You can override this method to achieve different styles.
26641 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
26642 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
26643 * @param $filler (string) string used to fill the space between text and page number.
26644 * @param $toc_name (string) name to use for TOC bookmark.
26645 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
26646 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
26647 * @public
26648 * @author Nicola Asuni
26649 * @since 4.5.000 (2009-01-02)
26650 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
26652 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
26653 $fontsize = $this->FontSizePt;
26654 $fontfamily = $this->FontFamily;
26655 $fontstyle = $this->FontStyle;
26656 $w = $this->w - $this->lMargin - $this->rMargin;
26657 $spacer = $this->GetStringWidth(chr(32)) * 4;
26658 $lmargin = $this->lMargin;
26659 $rmargin = $this->rMargin;
26660 $x_start = $this->GetX();
26661 $page_first = $this->page;
26662 $current_page = $this->page;
26663 $page_fill_start = false;
26664 $page_fill_end = false;
26665 $current_column = $this->current_column;
26666 if ($this->empty_string($numbersfont)) {
26667 $numbersfont = $this->default_monospaced_font;
26669 if ($this->empty_string($filler)) {
26670 $filler = ' ';
26672 if ($this->empty_string($page)) {
26673 $gap = ' ';
26674 } else {
26675 $gap = '';
26676 if ($page < 1) {
26677 $page = 1;
26680 $this->SetFont($numbersfont, $fontstyle, $fontsize);
26681 $numwidth = $this->GetStringWidth('00000');
26682 $maxpage = 0; //used for pages on attached documents
26683 foreach ($this->outlines as $key => $outline) {
26684 // check for extra pages (used for attachments)
26685 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
26686 $outline['p'] += ($this->page - $page_first);
26688 if ($this->rtl) {
26689 $aligntext = 'R';
26690 $alignnum = 'L';
26691 } else {
26692 $aligntext = 'L';
26693 $alignnum = 'R';
26695 if ($outline['l'] == 0) {
26696 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
26697 } else {
26698 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
26700 $this->SetTextColorArray($outline['c']);
26701 // check for page break
26702 $this->checkPageBreak((2 * $this->FontSize * $this->cell_height_ratio));
26703 // set margins and X position
26704 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
26705 $this->lMargin = $lmargin;
26706 $this->rMargin = $rmargin;
26707 } else {
26708 if ($this->current_column != $current_column) {
26709 if ($this->rtl) {
26710 $x_start = $this->w - $this->columns[$this->current_column]['x'];
26711 } else {
26712 $x_start = $this->columns[$this->current_column]['x'];
26715 $lmargin = $this->lMargin;
26716 $rmargin = $this->rMargin;
26717 $current_page = $this->page;
26718 $current_column = $this->current_column;
26720 $this->SetX($x_start);
26721 $indent = ($spacer * $outline['l']);
26722 if ($this->rtl) {
26723 $this->x -= $indent;
26724 $this->rMargin = $this->w - $this->x;
26725 } else {
26726 $this->x += $indent;
26727 $this->lMargin = $this->x;
26729 $link = $this->AddLink();
26730 $this->SetLink($link, $outline['y'], $outline['p']);
26731 // write the text
26732 if ($this->rtl) {
26733 $txt = ' '.$outline['t'];
26734 } else {
26735 $txt = $outline['t'].' ';
26737 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
26738 if ($this->rtl) {
26739 $tw = $this->x - $this->lMargin;
26740 } else {
26741 $tw = $this->w - $this->rMargin - $this->x;
26743 $this->SetFont($numbersfont, $fontstyle, $fontsize);
26744 if ($this->empty_string($page)) {
26745 $pagenum = $outline['p'];
26746 } else {
26747 // placemark to be replaced with the correct number
26748 $pagenum = '{#'.($outline['p']).'}';
26749 if ($this->isUnicodeFont()) {
26750 $pagenum = '{'.$pagenum.'}';
26752 $maxpage = max($maxpage, $outline['p']);
26754 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
26755 $numfills = floor($fw / $this->GetStringWidth($filler));
26756 if ($numfills > 0) {
26757 $rowfill = str_repeat($filler, $numfills);
26758 } else {
26759 $rowfill = '';
26761 if ($this->rtl) {
26762 $pagenum = $pagenum.$gap.$rowfill;
26763 } else {
26764 $pagenum = $rowfill.$gap.$pagenum;
26766 // write the number
26767 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
26769 $page_last = $this->getPage();
26770 $numpages = ($page_last - $page_first + 1);
26771 // account for booklet mode
26772 if ($this->booklet) {
26773 // check if a blank page is required before TOC
26774 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
26775 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
26776 if ($page_fill_start) {
26777 // add a page at the end (to be moved before TOC)
26778 $this->addPage();
26779 ++$page_last;
26780 ++$numpages;
26782 if ($page_fill_end) {
26783 // add a page at the end
26784 $this->addPage();
26785 ++$page_last;
26786 ++$numpages;
26789 $maxpage = max($maxpage, $page_last);
26790 if (!$this->empty_string($page)) {
26791 for ($p = $page_first; $p <= $page_last; ++$p) {
26792 // get page data
26793 $temppage = $this->getPageBuffer($p);
26794 for ($n = 1; $n <= $maxpage; ++$n) {
26795 // update page numbers
26796 $a = '{#'.$n.'}';
26797 // get page number aliases
26798 $pnalias = $this->getInternalPageNumberAliases($a);
26799 // calculate replacement number
26800 if (($n >= $page) AND ($n <= $this->numpages)) {
26801 $np = $n + $numpages;
26802 } else {
26803 $np = $n;
26805 $na = $this->formatTOCPageNumber(($this->starting_page_number + $np - 1));
26806 $nu = $this->UTF8ToUTF16BE($na, false);
26807 // replace aliases with numbers
26808 foreach ($pnalias['u'] as $u) {
26809 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
26810 if ($this->rtl) {
26811 $nr = $nu.$this->UTF8ToUTF16BE(' '.$sfill);
26812 } else {
26813 $nr = $this->UTF8ToUTF16BE($sfill.' ').$nu;
26815 $temppage = str_replace($u, $nr, $temppage);
26817 foreach ($pnalias['a'] as $a) {
26818 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
26819 if ($this->rtl) {
26820 $nr = $na.' '.$sfill;
26821 } else {
26822 $nr = $sfill.' '.$na;
26824 $temppage = str_replace($a, $nr, $temppage);
26827 // save changes
26828 $this->setPageBuffer($p, $temppage);
26830 // move pages
26831 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
26832 if ($page_fill_start) {
26833 $this->movePage($page_last, $page_first);
26835 for ($i = 0; $i < $numpages; ++$i) {
26836 $this->movePage($page_last, $page);
26842 * Output a Table Of Content Index (TOC) using HTML templates.
26843 * This method must be called after all Bookmarks were set.
26844 * Before calling this method you have to open the page using the addTOCPage() method.
26845 * After calling this method you have to call endTOCPage() to close the TOC page.
26846 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
26847 * @param $toc_name (string) name to use for TOC bookmark.
26848 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
26849 * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
26850 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
26851 * @param $color (array) RGB color array for title (values from 0 to 255).
26852 * @public
26853 * @author Nicola Asuni
26854 * @since 5.0.001 (2010-05-06)
26855 * @see addTOCPage(), endTOCPage(), addTOC()
26857 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
26858 $filler = ' ';
26859 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
26860 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
26861 // set new style for link
26862 $this->htmlLinkColorArray = array();
26863 $this->htmlLinkFontStyle = '';
26864 $page_first = $this->getPage();
26865 $page_fill_start = false;
26866 $page_fill_end = false;
26867 // get the font type used for numbers in each template
26868 $current_font = $this->FontFamily;
26869 foreach ($templates as $level => $html) {
26870 $dom = $this->getHtmlDomArray($html);
26871 foreach ($dom as $key => $value) {
26872 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
26873 $this->SetFont($dom[($key - 1)]['fontname']);
26874 $templates['F'.$level] = $this->isUnicodeFont();
26878 $this->SetFont($current_font);
26879 $maxpage = 0; //used for pages on attached documents
26880 foreach ($this->outlines as $key => $outline) {
26881 // get HTML template
26882 $row = $templates[$outline['l']];
26883 if ($this->empty_string($page)) {
26884 $pagenum = $outline['p'];
26885 } else {
26886 // placemark to be replaced with the correct number
26887 $pagenum = '{#'.($outline['p']).'}';
26888 if ($templates['F'.$outline['l']]) {
26889 $pagenum = '{'.$pagenum.'}';
26891 $maxpage = max($maxpage, $outline['p']);
26893 // replace templates with current values
26894 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
26895 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
26896 // add link to page
26897 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
26898 // write bookmark entry
26899 $this->writeHTML($row, false, false, true, false, '');
26901 // restore link styles
26902 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
26903 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
26904 // move TOC page and replace numbers
26905 $page_last = $this->getPage();
26906 $numpages = ($page_last - $page_first + 1);
26907 // account for booklet mode
26908 if ($this->booklet) {
26909 // check if a blank page is required before TOC
26910 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
26911 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
26912 if ($page_fill_start) {
26913 // add a page at the end (to be moved before TOC)
26914 $this->addPage();
26915 ++$page_last;
26916 ++$numpages;
26918 if ($page_fill_end) {
26919 // add a page at the end
26920 $this->addPage();
26921 ++$page_last;
26922 ++$numpages;
26925 $maxpage = max($maxpage, $page_last);
26926 if (!$this->empty_string($page)) {
26927 for ($p = $page_first; $p <= $page_last; ++$p) {
26928 // get page data
26929 $temppage = $this->getPageBuffer($p);
26930 for ($n = 1; $n <= $maxpage; ++$n) {
26931 // update page numbers
26932 $a = '{#'.$n.'}';
26933 // get page number aliases
26934 $pnalias = $this->getInternalPageNumberAliases($a);
26935 // calculate replacement number
26936 if ($n >= $page) {
26937 $np = $n + $numpages;
26938 } else {
26939 $np = $n;
26941 $na = $this->formatTOCPageNumber(($this->starting_page_number + $np - 1));
26942 $nu = $this->UTF8ToUTF16BE($na, false);
26943 // replace aliases with numbers
26944 foreach ($pnalias['u'] as $u) {
26945 if ($correct_align) {
26946 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
26947 if ($this->rtl) {
26948 $nr = $nu.$this->UTF8ToUTF16BE(' '.$sfill);
26949 } else {
26950 $nr = $this->UTF8ToUTF16BE($sfill.' ').$nu;
26952 } else {
26953 $nr = $nu;
26955 $temppage = str_replace($u, $nr, $temppage);
26957 foreach ($pnalias['a'] as $a) {
26958 if ($correct_align) {
26959 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
26960 if ($this->rtl) {
26961 $nr = $na.' '.$sfill;
26962 } else {
26963 $nr = $sfill.' '.$na;
26965 } else {
26966 $nr = $na;
26968 $temppage = str_replace($a, $nr, $temppage);
26971 // save changes
26972 $this->setPageBuffer($p, $temppage);
26974 // move pages
26975 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
26976 if ($page_fill_start) {
26977 $this->movePage($page_last, $page_first);
26979 for ($i = 0; $i < $numpages; ++$i) {
26980 $this->movePage($page_last, $page);
26986 * Stores a copy of the current TCPDF object used for undo operation.
26987 * @public
26988 * @since 4.5.029 (2009-03-19)
26990 public function startTransaction() {
26991 if (isset($this->objcopy)) {
26992 // remove previous copy
26993 $this->commitTransaction();
26995 // record current page number and Y position
26996 $this->start_transaction_page = $this->page;
26997 $this->start_transaction_y = $this->y;
26998 // clone current object
26999 $this->objcopy = $this->objclone($this);
27003 * Delete the copy of the current TCPDF object used for undo operation.
27004 * @public
27005 * @since 4.5.029 (2009-03-19)
27007 public function commitTransaction() {
27008 if (isset($this->objcopy)) {
27009 $this->objcopy->_destroy(true, true);
27010 unset($this->objcopy);
27015 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
27016 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
27017 * @return TCPDF object.
27018 * @public
27019 * @since 4.5.029 (2009-03-19)
27021 public function rollbackTransaction($self=false) {
27022 if (isset($this->objcopy)) {
27023 if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
27024 // truncate files to previous values
27025 foreach ($this->objcopy->cache_file_length as $file => $length) {
27026 $file = substr($file, 1);
27027 $handle = fopen($file, 'r+');
27028 ftruncate($handle, $length);
27031 $this->_destroy(true, true);
27032 if ($self) {
27033 $objvars = get_object_vars($this->objcopy);
27034 foreach ($objvars as $key => $value) {
27035 $this->$key = $value;
27038 return $this->objcopy;
27040 return $this;
27044 * Creates a copy of a class object
27045 * @param $object (object) class object to be cloned
27046 * @return cloned object
27047 * @public
27048 * @since 4.5.029 (2009-03-19)
27050 public function objclone($object) {
27051 return @clone($object);
27055 * Determine whether a string is empty.
27056 * @param $str (string) string to be checked
27057 * @return boolean true if string is empty
27058 * @public
27059 * @since 4.5.044 (2009-04-16)
27061 public function empty_string($str) {
27062 return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
27066 * Find position of last occurrence of a substring in a string
27067 * @param $haystack (string) The string to search in.
27068 * @param $needle (string) substring to search.
27069 * @param $offset (int) May be specified to begin searching an arbitrary number of characters into the string.
27070 * @return Returns the position where the needle exists. Returns FALSE if the needle was not found.
27071 * @public
27072 * @since 4.8.038 (2010-03-13)
27074 public function revstrpos($haystack, $needle, $offset = 0) {
27075 $length = strlen($haystack);
27076 $offset = ($offset > 0)?($length - $offset):abs($offset);
27077 $pos = strpos(strrev($haystack), strrev($needle), $offset);
27078 return ($pos === false)?false:($length - $pos - strlen($needle));
27081 // --- MULTI COLUMNS METHODS -----------------------
27084 * Set multiple columns of the same size
27085 * @param $numcols (int) number of columns (set to zero to disable columns mode)
27086 * @param $width (int) column width
27087 * @param $y (int) column starting Y position (leave empty for current Y position)
27088 * @public
27089 * @since 4.9.001 (2010-03-28)
27091 public function setEqualColumns($numcols=0, $width=0, $y='') {
27092 $this->columns = array();
27093 if ($numcols < 2) {
27094 $numcols = 0;
27095 $this->columns = array();
27096 } else {
27097 // maximum column width
27098 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
27099 if (($width == 0) OR ($width > $maxwidth)) {
27100 $width = $maxwidth;
27102 if ($this->empty_string($y)) {
27103 $y = $this->y;
27105 // space between columns
27106 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
27107 // fill the columns array (with, space, starting Y position)
27108 for ($i = 0; $i < $numcols; ++$i) {
27109 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
27112 $this->num_columns = $numcols;
27113 $this->current_column = 0;
27114 $this->column_start_page = $this->page;
27115 $this->selectColumn(0);
27119 * Remove columns and reset page margins.
27120 * @public
27121 * @since 5.9.072 (2011-04-26)
27123 public function resetColumns() {
27124 $this->lMargin = $this->original_lMargin;
27125 $this->rMargin = $this->original_rMargin;
27126 $this->setEqualColumns();
27130 * Set columns array.
27131 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
27132 * @param $columns (array)
27133 * @public
27134 * @since 4.9.001 (2010-03-28)
27136 public function setColumnsArray($columns) {
27137 $this->columns = $columns;
27138 $this->num_columns = count($columns);
27139 $this->current_column = 0;
27140 $this->column_start_page = $this->page;
27141 $this->selectColumn(0);
27145 * Set position at a given column
27146 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
27147 * @public
27148 * @since 4.9.001 (2010-03-28)
27150 public function selectColumn($col='') {
27151 if (is_string($col)) {
27152 $col = $this->current_column;
27153 } elseif ($col >= $this->num_columns) {
27154 $col = 0;
27156 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
27157 $enable_thead = false;
27158 if ($this->num_columns > 1) {
27159 if ($col != $this->current_column) {
27160 // move Y pointer at the top of the column
27161 if ($this->column_start_page == $this->page) {
27162 $this->y = $this->columns[$col]['y'];
27163 } else {
27164 $this->y = $this->tMargin;
27166 // Avoid to write table headers more than once
27167 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
27168 $enable_thead = true;
27169 $this->maxselcol['page'] = $this->page;
27170 $this->maxselcol['column'] = $col;
27173 $xshift = $this->colxshift;
27174 // set X position of the current column by case
27175 $listindent = ($this->listindentlevel * $this->listindent);
27176 // calculate column X position
27177 $colpos = 0;
27178 for ($i = 0; $i < $col; ++$i) {
27179 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
27181 if ($this->rtl) {
27182 $x = $this->w - $this->original_rMargin - $colpos;
27183 $this->rMargin = ($this->w - $x + $listindent);
27184 $this->lMargin = ($x - $this->columns[$col]['w']);
27185 $this->x = $x - $listindent;
27186 } else {
27187 $x = $this->original_lMargin + $colpos;
27188 $this->lMargin = ($x + $listindent);
27189 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
27190 $this->x = $x + $listindent;
27192 $this->columns[$col]['x'] = $x;
27194 $this->current_column = $col;
27195 // fix for HTML mode
27196 $this->newline = true;
27197 // print HTML table header (if any)
27198 if ((!$this->empty_string($this->thead)) AND (!$this->inthead)) {
27199 if ($enable_thead) {
27200 // print table header
27201 $this->writeHTML($this->thead, false, false, false, false, '');
27202 $this->y += $xshift['s']['V'];
27203 // store end of header position
27204 if (!isset($this->columns[$col]['th'])) {
27205 $this->columns[$col]['th'] = array();
27207 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
27208 $this->lasth = 0;
27209 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
27210 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
27213 // account for an html table cell over multiple columns
27214 if ($this->rtl) {
27215 $this->rMargin += $xshift['x'];
27216 $this->x -= ($xshift['x'] + $xshift['p']['R']);
27217 } else {
27218 $this->lMargin += $xshift['x'];
27219 $this->x += $xshift['x'] + $xshift['p']['L'];
27224 * Return the current column number
27225 * @return int current column number
27226 * @public
27227 * @since 5.5.011 (2010-07-08)
27229 public function getColumn() {
27230 return $this->current_column;
27234 * Return the current number of columns.
27235 * @return int number of columns
27236 * @public
27237 * @since 5.8.018 (2010-08-25)
27239 public function getNumberOfColumns() {
27240 return $this->num_columns;
27244 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
27245 * @param $pararray (array) parameters array
27246 * @return sting containing serialized data
27247 * @public
27248 * @since 4.9.006 (2010-04-02)
27250 public function serializeTCPDFtagParameters($pararray) {
27251 return urlencode(serialize($pararray));
27255 * Set Text rendering mode.
27256 * @param $stroke (int) outline size in user units (0 = disable).
27257 * @param $fill (boolean) if true fills the text (default).
27258 * @param $clip (boolean) if true activate clipping mode
27259 * @public
27260 * @since 4.9.008 (2009-04-02)
27262 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
27263 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
27264 // convert text rendering parameters
27265 if ($stroke < 0) {
27266 $stroke = 0;
27268 if ($fill === true) {
27269 if ($stroke > 0) {
27270 if ($clip === true) {
27271 // Fill, then stroke text and add to path for clipping
27272 $textrendermode = 6;
27273 } else {
27274 // Fill, then stroke text
27275 $textrendermode = 2;
27277 $textstrokewidth = $stroke;
27278 } else {
27279 if ($clip === true) {
27280 // Fill text and add to path for clipping
27281 $textrendermode = 4;
27282 } else {
27283 // Fill text
27284 $textrendermode = 0;
27287 } else {
27288 if ($stroke > 0) {
27289 if ($clip === true) {
27290 // Stroke text and add to path for clipping
27291 $textrendermode = 5;
27292 } else {
27293 // Stroke text
27294 $textrendermode = 1;
27296 $textstrokewidth = $stroke;
27297 } else {
27298 if ($clip === true) {
27299 // Add text to path for clipping
27300 $textrendermode = 7;
27301 } else {
27302 // Neither fill nor stroke text (invisible)
27303 $textrendermode = 3;
27307 $this->textrendermode = $textrendermode;
27308 $this->textstrokewidth = $stroke;
27312 * Set parameters for drop shadow effect for text.
27313 * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
27314 * @since 5.9.174 (2012-07-25)
27315 * @public
27317 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
27318 if (isset($params['enabled'])) {
27319 $this->txtshadow['enabled'] = $params['enabled']?true:false;
27320 } else {
27321 $this->txtshadow['enabled'] = false;
27323 if (isset($params['depth_w'])) {
27324 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
27325 } else {
27326 $this->txtshadow['depth_w'] = 0;
27328 if (isset($params['depth_h'])) {
27329 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
27330 } else {
27331 $this->txtshadow['depth_h'] = 0;
27333 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
27334 $this->txtshadow['color'] = $params['color'];
27335 } else {
27336 $this->txtshadow['color'] = $this->strokecolor;
27338 if (isset($params['opacity'])) {
27339 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
27340 } else {
27341 $this->txtshadow['opacity'] = 1;
27343 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'))) {
27344 $this->txtshadow['blend_mode'] = $params['blend_mode'];
27345 } else {
27346 $this->txtshadow['blend_mode'] = 'Normal';
27348 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
27349 $this->txtshadow['enabled'] = false;
27354 * Return the text shadow parameters array.
27355 * @return Array of parameters.
27356 * @since 5.9.174 (2012-07-25)
27357 * @public
27359 public function getTextShadow() {
27360 return $this->txtshadow;
27364 * Returns an array of chars containing soft hyphens.
27365 * @param $word (array) array of chars
27366 * @param $patterns (array) Array of hypenation patterns.
27367 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
27368 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
27369 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
27370 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
27371 * @param $charmax (int) Maximum length of broken piece of word.
27372 * @return array text with soft hyphens
27373 * @author Nicola Asuni
27374 * @since 4.9.012 (2010-04-12)
27375 * @protected
27377 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
27378 $hyphenword = array(); // hyphens positions
27379 $numchars = count($word);
27380 if ($numchars <= $charmin) {
27381 return $word;
27383 $word_string = $this->UTF8ArrSubString($word);
27384 // some words will be returned as-is
27385 $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})(\]?)$/';
27386 if (preg_match($pattern, $word_string) > 0) {
27387 // email
27388 return $word;
27390 $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})(\]?)$/';
27391 if (preg_match($pattern, $word_string) > 0) {
27392 // URL
27393 return $word;
27395 if (isset($dictionary[$word_string])) {
27396 return $this->UTF8StringToArray($dictionary[$word_string]);
27398 // suround word with '_' characters
27399 $tmpword = array_merge(array(95), $word, array(95));
27400 $tmpnumchars = $numchars + 2;
27401 $maxpos = $tmpnumchars - $charmin;
27402 for ($pos = 0; $pos < $maxpos; ++$pos) {
27403 $imax = min(($tmpnumchars - $pos), $charmax);
27404 for ($i = $charmin; $i <= $imax; ++$i) {
27405 $subword = strtolower($this->UTF8ArrSubString($tmpword, $pos, $pos + $i));
27406 if (isset($patterns[$subword])) {
27407 $pattern = $this->UTF8StringToArray($patterns[$subword]);
27408 $pattern_length = count($pattern);
27409 $digits = 1;
27410 for ($j = 0; $j < $pattern_length; ++$j) {
27411 // check if $pattern[$j] is a number
27412 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
27413 if ($j == 0) {
27414 $zero = $pos - 1;
27415 } else {
27416 $zero = $pos + $j - $digits;
27418 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
27419 $hyphenword[$zero] = $this->unichr($pattern[$j]);
27421 ++$digits;
27427 $inserted = 0;
27428 $maxpos = $numchars - $rightmin;
27429 for ($i = $leftmin; $i <= $maxpos; ++$i) {
27430 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
27431 // 173 = soft hyphen character
27432 array_splice($word, $i + $inserted, 0, 173);
27433 ++$inserted;
27436 return $word;
27440 * Returns an array of hyphenation patterns.
27441 * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
27442 * @return array of hyphenation patterns
27443 * @author Nicola Asuni
27444 * @since 4.9.012 (2010-04-12)
27445 * @public
27447 public function getHyphenPatternsFromTEX($file) {
27448 // TEX patterns are available at:
27449 // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
27450 $data = file_get_contents($file);
27451 $patterns = array();
27452 // remove comments
27453 $data = preg_replace('/\%[^\n]*/', '', $data);
27454 // extract the patterns part
27455 preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
27456 $data = trim(substr($matches[0], 10, -1));
27457 // extract each pattern
27458 $patterns_array = preg_split('/[\s]+/', $data);
27459 // create new language array of patterns
27460 $patterns = array();
27461 foreach($patterns_array as $val) {
27462 if (!$this->empty_string($val)) {
27463 $val = trim($val);
27464 $val = str_replace('\'', '\\\'', $val);
27465 $key = preg_replace('/[0-9]+/', '', $val);
27466 $patterns[$key] = $val;
27469 return $patterns;
27473 * Returns text with soft hyphens.
27474 * @param $text (string) text to process
27475 * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
27476 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
27477 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
27478 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
27479 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
27480 * @param $charmax (int) Maximum length of broken piece of word.
27481 * @return array text with soft hyphens
27482 * @author Nicola Asuni
27483 * @since 4.9.012 (2010-04-12)
27484 * @public
27486 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
27487 $text = $this->unhtmlentities($text);
27488 $word = array(); // last word
27489 $txtarr = array(); // text to be returned
27490 $intag = false; // true if we are inside an HTML tag
27491 if (!is_array($patterns)) {
27492 $patterns = $this->getHyphenPatternsFromTEX($patterns);
27494 // get array of characters
27495 $unichars = $this->UTF8StringToArray($text);
27496 // for each char
27497 foreach ($unichars as $char) {
27498 if ((!$intag) AND $this->unicode->uni_type[$char] == 'L') {
27499 // letter character
27500 $word[] = $char;
27501 } else {
27502 // other type of character
27503 if (!$this->empty_string($word)) {
27504 // hypenate the word
27505 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
27506 $word = array();
27508 $txtarr[] = $char;
27509 if (chr($char) == '<') {
27510 // we are inside an HTML tag
27511 $intag = true;
27512 } elseif ($intag AND (chr($char) == '>')) {
27513 // end of HTML tag
27514 $intag = false;
27518 if (!$this->empty_string($word)) {
27519 // hypenate the word
27520 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
27522 // convert char array to string and return
27523 return $this->UTF8ArrSubString($txtarr);
27527 * Enable/disable rasterization of vector images using ImageMagick library.
27528 * @param $mode (boolean) if true enable rasterization, false otherwise.
27529 * @public
27530 * @since 5.0.000 (2010-04-27)
27532 public function setRasterizeVectorImages($mode) {
27533 $this->rasterize_vector_images = $mode;
27537 * Get the Path-Painting Operators.
27538 * @param $style (string) Style of rendering. Possible values are:
27539 * <ul>
27540 * <li>S or D: Stroke the path.</li>
27541 * <li>s or d: Close and stroke the path.</li>
27542 * <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
27543 * <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
27544 * <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
27545 * <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
27546 * <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
27547 * <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
27548 * <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
27549 * <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
27550 * <li>n: End the path object without filling or stroking it.</li>
27551 * </ul>
27552 * @param $default (string) default style
27553 * @author Nicola Asuni
27554 * @since 5.0.000 (2010-04-30)
27555 * @protected
27557 protected function getPathPaintOperator($style, $default='S') {
27558 $op = '';
27559 switch($style) {
27560 case 'S':
27561 case 'D': {
27562 $op = 'S';
27563 break;
27565 case 's':
27566 case 'd': {
27567 $op = 's';
27568 break;
27570 case 'f':
27571 case 'F': {
27572 $op = 'f';
27573 break;
27575 case 'f*':
27576 case 'F*': {
27577 $op = 'f*';
27578 break;
27580 case 'B':
27581 case 'FD':
27582 case 'DF': {
27583 $op = 'B';
27584 break;
27586 case 'B*':
27587 case 'F*D':
27588 case 'DF*': {
27589 $op = 'B*';
27590 break;
27592 case 'b':
27593 case 'fd':
27594 case 'df': {
27595 $op = 'b';
27596 break;
27598 case 'b*':
27599 case 'f*d':
27600 case 'df*': {
27601 $op = 'b*';
27602 break;
27604 case 'CNZ': {
27605 $op = 'W n';
27606 break;
27608 case 'CEO': {
27609 $op = 'W* n';
27610 break;
27612 case 'n': {
27613 $op = 'n';
27614 break;
27616 default: {
27617 if (!empty($default)) {
27618 $op = $this->getPathPaintOperator($default, '');
27619 } else {
27620 $op = '';
27624 return $op;
27628 * Enable or disable default option for font subsetting.
27629 * @param $enable (boolean) if true enable font subsetting by default.
27630 * @author Nicola Asuni
27631 * @public
27632 * @since 5.3.002 (2010-06-07)
27634 public function setFontSubsetting($enable=true) {
27635 if ($this->pdfa_mode) {
27636 $this->font_subsetting = false;
27637 } else {
27638 $this->font_subsetting = $enable ? true : false;
27643 * Return the default option for font subsetting.
27644 * @return boolean default font subsetting state.
27645 * @author Nicola Asuni
27646 * @public
27647 * @since 5.3.002 (2010-06-07)
27649 public function getFontSubsetting() {
27650 return $this->font_subsetting;
27654 * Left trim the input string
27655 * @param $str (string) string to trim
27656 * @param $replace (string) string that replace spaces.
27657 * @return left trimmed string
27658 * @author Nicola Asuni
27659 * @public
27660 * @since 5.8.000 (2010-08-11)
27662 public function stringLeftTrim($str, $replace='') {
27663 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
27667 * Right trim the input string
27668 * @param $str (string) string to trim
27669 * @param $replace (string) string that replace spaces.
27670 * @return right trimmed string
27671 * @author Nicola Asuni
27672 * @public
27673 * @since 5.8.000 (2010-08-11)
27675 public function stringRightTrim($str, $replace='') {
27676 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
27680 * Trim the input string
27681 * @param $str (string) string to trim
27682 * @param $replace (string) string that replace spaces.
27683 * @return trimmed string
27684 * @author Nicola Asuni
27685 * @public
27686 * @since 5.8.000 (2010-08-11)
27688 public function stringTrim($str, $replace='') {
27689 $str = $this->stringLeftTrim($str, $replace);
27690 $str = $this->stringRightTrim($str, $replace);
27691 return $str;
27695 * Return true if the current font is unicode type.
27696 * @return true for unicode font, false otherwise.
27697 * @author Nicola Asuni
27698 * @public
27699 * @since 5.8.002 (2010-08-14)
27701 public function isUnicodeFont() {
27702 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
27706 * Return normalized font name
27707 * @param $fontfamily (string) property string containing font family names
27708 * @return string normalized font name
27709 * @author Nicola Asuni
27710 * @public
27711 * @since 5.8.004 (2010-08-17)
27713 public function getFontFamilyName($fontfamily) {
27714 // remove spaces and symbols
27715 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
27716 // extract all font names
27717 $fontslist = preg_split('/[,]/', $fontfamily);
27718 // find first valid font name
27719 foreach ($fontslist as $font) {
27720 // replace font variations
27721 $font = preg_replace('/italic$/', 'I', $font);
27722 $font = preg_replace('/oblique$/', 'I', $font);
27723 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
27724 // replace common family names and core fonts
27725 $pattern = array();
27726 $replacement = array();
27727 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
27728 $replacement[] = 'times';
27729 $pattern[] = '/^sansserif/';
27730 $replacement[] = 'helvetica';
27731 $pattern[] = '/^monospace/';
27732 $replacement[] = 'courier';
27733 $font = preg_replace($pattern, $replacement, $font);
27734 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
27735 return $font;
27738 // return current font as default
27739 return $this->CurrentFont['fontkey'];
27743 * Start a new XObject Template.
27744 * 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).
27745 * 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.
27746 * Note: X,Y coordinates will be reset to 0,0.
27747 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
27748 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
27749 * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
27750 * @return int the XObject Template ID in case of success or false in case of error.
27751 * @author Nicola Asuni
27752 * @public
27753 * @since 5.8.017 (2010-08-24)
27754 * @see endTemplate(), printTemplate()
27756 public function startTemplate($w=0, $h=0, $group=false) {
27757 if ($this->inxobj) {
27758 // we are already inside an XObject template
27759 return false;
27761 $this->inxobj = true;
27762 ++$this->n;
27763 // XObject ID
27764 $this->xobjid = 'XT'.$this->n;
27765 // object ID
27766 $this->xobjects[$this->xobjid] = array('n' => $this->n);
27767 // store current graphic state
27768 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
27769 // initialize data
27770 $this->xobjects[$this->xobjid]['intmrk'] = 0;
27771 $this->xobjects[$this->xobjid]['transfmrk'] = array();
27772 $this->xobjects[$this->xobjid]['outdata'] = '';
27773 $this->xobjects[$this->xobjid]['xobjects'] = array();
27774 $this->xobjects[$this->xobjid]['images'] = array();
27775 $this->xobjects[$this->xobjid]['fonts'] = array();
27776 $this->xobjects[$this->xobjid]['annotations'] = array();
27777 $this->xobjects[$this->xobjid]['extgstates'] = array();
27778 $this->xobjects[$this->xobjid]['gradients'] = array();
27779 $this->xobjects[$this->xobjid]['spot_colors'] = array();
27780 // set new environment
27781 $this->num_columns = 1;
27782 $this->current_column = 0;
27783 $this->SetAutoPageBreak(false);
27784 if (($w === '') OR ($w <= 0)) {
27785 $w = $this->w - $this->lMargin - $this->rMargin;
27787 if (($h === '') OR ($h <= 0)) {
27788 $h = $this->h - $this->tMargin - $this->bMargin;
27790 $this->xobjects[$this->xobjid]['x'] = 0;
27791 $this->xobjects[$this->xobjid]['y'] = 0;
27792 $this->xobjects[$this->xobjid]['w'] = $w;
27793 $this->xobjects[$this->xobjid]['h'] = $h;
27794 $this->w = $w;
27795 $this->h = $h;
27796 $this->wPt = $this->w * $this->k;
27797 $this->hPt = $this->h * $this->k;
27798 $this->fwPt = $this->wPt;
27799 $this->fhPt = $this->hPt;
27800 $this->x = 0;
27801 $this->y = 0;
27802 $this->lMargin = 0;
27803 $this->rMargin = 0;
27804 $this->tMargin = 0;
27805 $this->bMargin = 0;
27806 // set group mode
27807 $this->xobjects[$this->xobjid]['group'] = $group;
27808 return $this->xobjid;
27812 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
27813 * 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).
27814 * 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.
27815 * @return int the XObject Template ID in case of success or false in case of error.
27816 * @author Nicola Asuni
27817 * @public
27818 * @since 5.8.017 (2010-08-24)
27819 * @see startTemplate(), printTemplate()
27821 public function endTemplate() {
27822 if (!$this->inxobj) {
27823 // we are not inside a template
27824 return false;
27826 $this->inxobj = false;
27827 // restore previous graphic state
27828 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
27829 return $this->xobjid;
27833 * Print an XObject Template.
27834 * You can print an XObject Template inside the currently opened Template.
27835 * 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).
27836 * 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.
27837 * @param $id (string) The ID of XObject Template to print.
27838 * @param $x (int) X position in user units (empty string = current x position)
27839 * @param $y (int) Y position in user units (empty string = current y position)
27840 * @param $w (int) Width in user units (zero = remaining page width)
27841 * @param $h (int) Height in user units (zero = remaining page height)
27842 * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
27843 * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
27844 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
27845 * @author Nicola Asuni
27846 * @public
27847 * @since 5.8.017 (2010-08-24)
27848 * @see startTemplate(), endTemplate()
27850 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
27851 if ($this->state != 2) {
27852 return;
27854 if (!isset($this->xobjects[$id])) {
27855 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
27857 if ($this->inxobj) {
27858 if ($id == $this->xobjid) {
27859 // close current template
27860 $this->endTemplate();
27861 } else {
27862 // use the template as resource for the template currently opened
27863 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
27866 // set default values
27867 if ($x === '') {
27868 $x = $this->x;
27870 if ($y === '') {
27871 $y = $this->y;
27873 // check page for no-write regions and adapt page margins if necessary
27874 list($x, $y) = $this->checkPageRegions($h, $x, $y);
27875 $ow = $this->xobjects[$id]['w'];
27876 $oh = $this->xobjects[$id]['h'];
27877 // calculate template width and height on document
27878 if (($w <= 0) AND ($h <= 0)) {
27879 $w = $ow;
27880 $h = $oh;
27881 } elseif ($w <= 0) {
27882 $w = $h * $ow / $oh;
27883 } elseif ($h <= 0) {
27884 $h = $w * $oh / $ow;
27886 // fit the template on available space
27887 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
27888 // set page alignment
27889 $rb_y = $y + $h;
27890 // set alignment
27891 if ($this->rtl) {
27892 if ($palign == 'L') {
27893 $xt = $this->lMargin;
27894 } elseif ($palign == 'C') {
27895 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27896 } elseif ($palign == 'R') {
27897 $xt = $this->w - $this->rMargin - $w;
27898 } else {
27899 $xt = $x - $w;
27901 $rb_x = $xt;
27902 } else {
27903 if ($palign == 'L') {
27904 $xt = $this->lMargin;
27905 } elseif ($palign == 'C') {
27906 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27907 } elseif ($palign == 'R') {
27908 $xt = $this->w - $this->rMargin - $w;
27909 } else {
27910 $xt = $x;
27912 $rb_x = $xt + $w;
27914 // print XObject Template + Transformation matrix
27915 $this->StartTransform();
27916 // translate and scale
27917 $sx = ($w / $this->xobjects[$id]['w']);
27918 $sy = ($h / $this->xobjects[$id]['h']);
27919 $tm = array();
27920 $tm[0] = $sx;
27921 $tm[1] = 0;
27922 $tm[2] = 0;
27923 $tm[3] = $sy;
27924 $tm[4] = $xt * $this->k;
27925 $tm[5] = ($this->h - $h - $y) * $this->k;
27926 $this->Transform($tm);
27927 // set object
27928 $this->_out('/'.$id.' Do');
27929 $this->StopTransform();
27930 // add annotations
27931 if (!empty($this->xobjects[$id]['annotations'])) {
27932 foreach ($this->xobjects[$id]['annotations'] as $annot) {
27933 // transform original coordinates
27934 $coordlt = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
27935 $ax = ($coordlt[4] / $this->k);
27936 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
27937 $coordrb = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
27938 $aw = ($coordrb[4] / $this->k) - $ax;
27939 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
27940 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
27943 // set pointer to align the next text/objects
27944 switch($align) {
27945 case 'T': {
27946 $this->y = $y;
27947 $this->x = $rb_x;
27948 break;
27950 case 'M': {
27951 $this->y = $y + round($h/2);
27952 $this->x = $rb_x;
27953 break;
27955 case 'B': {
27956 $this->y = $rb_y;
27957 $this->x = $rb_x;
27958 break;
27960 case 'N': {
27961 $this->SetY($rb_y);
27962 break;
27964 default:{
27965 break;
27971 * Set the percentage of character stretching.
27972 * @param $perc (int) percentage of stretching (100 = no stretching)
27973 * @author Nicola Asuni
27974 * @public
27975 * @since 5.9.000 (2010-09-29)
27977 public function setFontStretching($perc=100) {
27978 $this->font_stretching = $perc;
27982 * Get the percentage of character stretching.
27983 * @return float stretching value
27984 * @author Nicola Asuni
27985 * @public
27986 * @since 5.9.000 (2010-09-29)
27988 public function getFontStretching() {
27989 return $this->font_stretching;
27993 * Set the amount to increase or decrease the space between characters in a text.
27994 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
27995 * @author Nicola Asuni
27996 * @public
27997 * @since 5.9.000 (2010-09-29)
27999 public function setFontSpacing($spacing=0) {
28000 $this->font_spacing = $spacing;
28004 * Get the amount to increase or decrease the space between characters in a text.
28005 * @return int font spacing (tracking) value
28006 * @author Nicola Asuni
28007 * @public
28008 * @since 5.9.000 (2010-09-29)
28010 public function getFontSpacing() {
28011 return $this->font_spacing;
28015 * Return an array of no-write page regions
28016 * @return array of no-write page regions
28017 * @author Nicola Asuni
28018 * @public
28019 * @since 5.9.003 (2010-10-13)
28020 * @see setPageRegions(), addPageRegion()
28022 public function getPageRegions() {
28023 return $this->page_regions;
28027 * Set no-write regions on page.
28028 * 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.
28029 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
28030 * You can set multiple regions for the same page.
28031 * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
28032 * @author Nicola Asuni
28033 * @public
28034 * @since 5.9.003 (2010-10-13)
28035 * @see addPageRegion(), getPageRegions()
28037 public function setPageRegions($regions=array()) {
28038 // empty current regions array
28039 $this->page_regions = array();
28040 // add regions
28041 foreach ($regions as $data) {
28042 $this->addPageRegion($data);
28047 * Add a single no-write region on selected page.
28048 * 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.
28049 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
28050 * You can set multiple regions for the same page.
28051 * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
28052 * @author Nicola Asuni
28053 * @public
28054 * @since 5.9.003 (2010-10-13)
28055 * @see setPageRegions(), getPageRegions()
28057 public function addPageRegion($region) {
28058 if (!isset($region['page']) OR empty($region['page'])) {
28059 $region['page'] = $this->page;
28061 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
28062 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
28063 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
28064 $this->page_regions[] = $region;
28069 * Remove a single no-write region.
28070 * @param $key (int) region key
28071 * @author Nicola Asuni
28072 * @public
28073 * @since 5.9.003 (2010-10-13)
28074 * @see setPageRegions(), getPageRegions()
28076 public function removePageRegion($key) {
28077 if (isset($this->page_regions[$key])) {
28078 unset($this->page_regions[$key]);
28083 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
28084 * 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.
28085 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
28086 * @param $h (float) height of the text/image/object to print in user units
28087 * @param $x (float) current X coordinate in user units
28088 * @param $y (float) current Y coordinate in user units
28089 * @return array($x, $y)
28090 * @author Nicola Asuni
28091 * @protected
28092 * @since 5.9.003 (2010-10-13)
28094 protected function checkPageRegions($h, $x, $y) {
28095 // set default values
28096 if ($x === '') {
28097 $x = $this->x;
28099 if ($y === '') {
28100 $y = $this->y;
28102 if (!$this->check_page_regions OR empty($this->page_regions)) {
28103 // no page regions defined
28104 return array($x, $y);
28106 if (empty($h)) {
28107 $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
28109 // check for page break
28110 if ($this->checkPageBreak($h, $y)) {
28111 // the content will be printed on a new page
28112 $x = $this->x;
28113 $y = $this->y;
28115 if ($this->num_columns > 1) {
28116 if ($this->rtl) {
28117 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
28118 } else {
28119 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
28121 } else {
28122 if ($this->rtl) {
28123 $this->lMargin = max($this->clMargin, $this->original_lMargin);
28124 } else {
28125 $this->rMargin = max($this->crMargin, $this->original_rMargin);
28128 // adjust coordinates and page margins
28129 foreach ($this->page_regions as $regid => $regdata) {
28130 if ($regdata['page'] == $this->page) {
28131 // check region boundaries
28132 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
28133 // Y is inside the region
28134 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
28135 $yt = max($y, $regdata['yt']);
28136 $yb = min(($yt + $h), $regdata['yb']);
28137 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
28138 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
28139 if ($regdata['side'] == 'L') { // left side
28140 $new_margin = max($xt, $xb);
28141 if ($this->lMargin < $new_margin) {
28142 if ($this->rtl) {
28143 // adjust left page margin
28144 $this->lMargin = max(0, $new_margin);
28146 if ($x < $new_margin) {
28147 // adjust x position
28148 $x = $new_margin;
28149 if ($new_margin > ($this->w - $this->rMargin)) {
28150 // adjust y position
28151 $y = $regdata['yb'] - $h;
28155 } elseif ($regdata['side'] == 'R') { // right side
28156 $new_margin = min($xt, $xb);
28157 if (($this->w - $this->rMargin) > $new_margin) {
28158 if (!$this->rtl) {
28159 // adjust right page margin
28160 $this->rMargin = max(0, ($this->w - $new_margin));
28162 if ($x > $new_margin) {
28163 // adjust x position
28164 $x = $new_margin;
28165 if ($new_margin > $this->lMargin) {
28166 // adjust y position
28167 $y = $regdata['yb'] - $h;
28175 return array($x, $y);
28178 // --- SVG METHODS ---------------------------------------------------------
28181 * Embedd a Scalable Vector Graphics (SVG) image.
28182 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
28183 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
28184 * @param $x (float) Abscissa of the upper-left corner.
28185 * @param $y (float) Ordinate of the upper-left corner.
28186 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
28187 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
28188 * @param $link (mixed) URL or identifier returned by AddLink().
28189 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
28190 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
28191 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
28192 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
28193 * @author Nicola Asuni
28194 * @since 5.0.000 (2010-05-02)
28195 * @public
28197 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
28198 if ($this->state != 2) {
28199 return;
28201 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
28202 // convert SVG to raster image using GD or ImageMagick libraries
28203 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
28205 if ($file{0} === '@') { // image from string
28206 $this->svgdir = '';
28207 $svgdata = substr($file, 1);
28208 } else { // SVG file
28209 $this->svgdir = dirname($file);
28210 $svgdata = file_get_contents($file);
28212 if ($svgdata === false) {
28213 $this->Error('SVG file not found: '.$file);
28215 if ($x === '') {
28216 $x = $this->x;
28218 if ($y === '') {
28219 $y = $this->y;
28221 // check page for no-write regions and adapt page margins if necessary
28222 list($x, $y) = $this->checkPageRegions($h, $x, $y);
28223 $k = $this->k;
28224 $ox = 0;
28225 $oy = 0;
28226 $ow = $w;
28227 $oh = $h;
28228 $aspect_ratio_align = 'xMidYMid';
28229 $aspect_ratio_ms = 'meet';
28230 $regs = array();
28231 // get original image width and height
28232 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
28233 if (isset($regs[1]) AND !empty($regs[1])) {
28234 $tmp = array();
28235 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
28236 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
28238 $tmp = array();
28239 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
28240 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
28242 $tmp = array();
28243 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
28244 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
28246 $tmp = array();
28247 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
28248 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
28250 $tmp = array();
28251 $view_box = array();
28252 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
28253 if (count($tmp) == 5) {
28254 array_shift($tmp);
28255 foreach ($tmp as $key => $val) {
28256 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
28258 $ox = $view_box[0];
28259 $oy = $view_box[1];
28261 // get aspect ratio
28262 $tmp = array();
28263 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
28264 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
28265 switch (count($aspect_ratio)) {
28266 case 3: {
28267 $aspect_ratio_align = $aspect_ratio[1];
28268 $aspect_ratio_ms = $aspect_ratio[2];
28269 break;
28271 case 2: {
28272 $aspect_ratio_align = $aspect_ratio[0];
28273 $aspect_ratio_ms = $aspect_ratio[1];
28274 break;
28276 case 1: {
28277 $aspect_ratio_align = $aspect_ratio[0];
28278 $aspect_ratio_ms = 'meet';
28279 break;
28285 // calculate image width and height on document
28286 if (($w <= 0) AND ($h <= 0)) {
28287 // convert image size to document unit
28288 $w = $ow;
28289 $h = $oh;
28290 } elseif ($w <= 0) {
28291 $w = $h * $ow / $oh;
28292 } elseif ($h <= 0) {
28293 $h = $w * $oh / $ow;
28295 // fit the image on available space
28296 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
28297 if ($this->rasterize_vector_images) {
28298 // convert SVG to raster image using GD or ImageMagick libraries
28299 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
28301 // set alignment
28302 $this->img_rb_y = $y + $h;
28303 // set alignment
28304 if ($this->rtl) {
28305 if ($palign == 'L') {
28306 $ximg = $this->lMargin;
28307 } elseif ($palign == 'C') {
28308 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
28309 } elseif ($palign == 'R') {
28310 $ximg = $this->w - $this->rMargin - $w;
28311 } else {
28312 $ximg = $x - $w;
28314 $this->img_rb_x = $ximg;
28315 } else {
28316 if ($palign == 'L') {
28317 $ximg = $this->lMargin;
28318 } elseif ($palign == 'C') {
28319 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
28320 } elseif ($palign == 'R') {
28321 $ximg = $this->w - $this->rMargin - $w;
28322 } else {
28323 $ximg = $x;
28325 $this->img_rb_x = $ximg + $w;
28327 // store current graphic vars
28328 $gvars = $this->getGraphicVars();
28329 // store SVG position and scale factors
28330 $svgoffset_x = ($ximg - $ox) * $this->k;
28331 $svgoffset_y = -($y - $oy) * $this->k;
28332 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
28333 $ow = $view_box[2];
28334 $oh = $view_box[3];
28335 } else {
28336 if ($ow <= 0) {
28337 $ow = $w;
28339 if ($oh <= 0) {
28340 $oh = $h;
28343 $svgscale_x = $w / $ow;
28344 $svgscale_y = $h / $oh;
28345 // scaling and alignment
28346 if ($aspect_ratio_align != 'none') {
28347 // store current scaling values
28348 $svgscale_old_x = $svgscale_x;
28349 $svgscale_old_y = $svgscale_y;
28350 // force uniform scaling
28351 if ($aspect_ratio_ms == 'slice') {
28352 // the entire viewport is covered by the viewBox
28353 if ($svgscale_x > $svgscale_y) {
28354 $svgscale_y = $svgscale_x;
28355 } elseif ($svgscale_x < $svgscale_y) {
28356 $svgscale_x = $svgscale_y;
28358 } else { // meet
28359 // the entire viewBox is visible within the viewport
28360 if ($svgscale_x < $svgscale_y) {
28361 $svgscale_y = $svgscale_x;
28362 } elseif ($svgscale_x > $svgscale_y) {
28363 $svgscale_x = $svgscale_y;
28366 // correct X alignment
28367 switch (substr($aspect_ratio_align, 1, 3)) {
28368 case 'Min': {
28369 // do nothing
28370 break;
28372 case 'Max': {
28373 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
28374 break;
28376 default:
28377 case 'Mid': {
28378 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
28379 break;
28382 // correct Y alignment
28383 switch (substr($aspect_ratio_align, 5)) {
28384 case 'Min': {
28385 // do nothing
28386 break;
28388 case 'Max': {
28389 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
28390 break;
28392 default:
28393 case 'Mid': {
28394 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
28395 break;
28399 // store current page break mode
28400 $page_break_mode = $this->AutoPageBreak;
28401 $page_break_margin = $this->getBreakMargin();
28402 $cell_padding = $this->cell_padding;
28403 $this->SetCellPadding(0);
28404 $this->SetAutoPageBreak(false);
28405 // save the current graphic state
28406 $this->_out('q'.$this->epsmarker);
28407 // set initial clipping mask
28408 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
28409 // scale and translate
28410 $e = $ox * $this->k * (1 - $svgscale_x);
28411 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
28412 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
28413 // creates a new XML parser to be used by the other XML functions
28414 $this->parser = xml_parser_create('UTF-8');
28415 // the following function allows to use parser inside object
28416 xml_set_object($this->parser, $this);
28417 // disable case-folding for this XML parser
28418 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
28419 // sets the element handler functions for the XML parser
28420 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
28421 // sets the character data handler function for the XML parser
28422 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
28423 // start parsing an XML document
28424 if (!xml_parse($this->parser, $svgdata)) {
28425 $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
28426 $this->Error($error_message);
28428 // free this XML parser
28429 xml_parser_free($this->parser);
28430 // restore previous graphic state
28431 $this->_out($this->epsmarker.'Q');
28432 // restore graphic vars
28433 $this->setGraphicVars($gvars);
28434 $this->lasth = $gvars['lasth'];
28435 if (!empty($border)) {
28436 $bx = $this->x;
28437 $by = $this->y;
28438 $this->x = $ximg;
28439 if ($this->rtl) {
28440 $this->x += $w;
28442 $this->y = $y;
28443 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
28444 $this->x = $bx;
28445 $this->y = $by;
28447 if ($link) {
28448 $this->Link($ximg, $y, $w, $h, $link, 0);
28450 // set pointer to align the next text/objects
28451 switch($align) {
28452 case 'T':{
28453 $this->y = $y;
28454 $this->x = $this->img_rb_x;
28455 break;
28457 case 'M':{
28458 $this->y = $y + round($h/2);
28459 $this->x = $this->img_rb_x;
28460 break;
28462 case 'B':{
28463 $this->y = $this->img_rb_y;
28464 $this->x = $this->img_rb_x;
28465 break;
28467 case 'N':{
28468 $this->SetY($this->img_rb_y);
28469 break;
28471 default:{
28472 // restore pointer to starting position
28473 $this->x = $gvars['x'];
28474 $this->y = $gvars['y'];
28475 $this->page = $gvars['page'];
28476 $this->current_column = $gvars['current_column'];
28477 $this->tMargin = $gvars['tMargin'];
28478 $this->bMargin = $gvars['bMargin'];
28479 $this->w = $gvars['w'];
28480 $this->h = $gvars['h'];
28481 $this->wPt = $gvars['wPt'];
28482 $this->hPt = $gvars['hPt'];
28483 $this->fwPt = $gvars['fwPt'];
28484 $this->fhPt = $gvars['fhPt'];
28485 break;
28488 $this->endlinex = $this->img_rb_x;
28489 // restore page break
28490 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
28491 $this->cell_padding = $cell_padding;
28495 * Get the tranformation matrix from SVG transform attribute
28496 * @param $attribute (string) transformation
28497 * @return array of transformations
28498 * @author Nicola Asuni
28499 * @since 5.0.000 (2010-05-02)
28500 * @protected
28502 protected function getSVGTransformMatrix($attribute) {
28503 // identity matrix
28504 $tm = array(1, 0, 0, 1, 0, 0);
28505 $transform = array();
28506 if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
28507 foreach ($transform as $key => $data) {
28508 if (!empty($data[2])) {
28509 $a = 1;
28510 $b = 0;
28511 $c = 0;
28512 $d = 1;
28513 $e = 0;
28514 $f = 0;
28515 $regs = array();
28516 switch ($data[1]) {
28517 case 'matrix': {
28518 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28519 $a = $regs[1];
28520 $b = $regs[2];
28521 $c = $regs[3];
28522 $d = $regs[4];
28523 $e = $regs[5];
28524 $f = $regs[6];
28526 break;
28528 case 'translate': {
28529 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28530 $e = $regs[1];
28531 $f = $regs[2];
28532 } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28533 $e = $regs[1];
28535 break;
28537 case 'scale': {
28538 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28539 $a = $regs[1];
28540 $d = $regs[2];
28541 } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28542 $a = $regs[1];
28543 $d = $a;
28545 break;
28547 case 'rotate': {
28548 if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
28549 $ang = deg2rad($regs[1]);
28550 $x = $regs[2];
28551 $y = $regs[3];
28552 $a = cos($ang);
28553 $b = sin($ang);
28554 $c = -$b;
28555 $d = $a;
28556 $e = ($x * (1 - $a)) - ($y * $c);
28557 $f = ($y * (1 - $d)) - ($x * $b);
28558 } elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
28559 $ang = deg2rad($regs[1]);
28560 $a = cos($ang);
28561 $b = sin($ang);
28562 $c = -$b;
28563 $d = $a;
28564 $e = 0;
28565 $f = 0;
28567 break;
28569 case 'skewX': {
28570 if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
28571 $c = tan(deg2rad($regs[1]));
28573 break;
28575 case 'skewY': {
28576 if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
28577 $b = tan(deg2rad($regs[1]));
28579 break;
28582 $tm = $this->getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
28586 return $tm;
28590 * Get the product of two SVG tranformation matrices
28591 * @param $ta (array) first SVG tranformation matrix
28592 * @param $tb (array) second SVG tranformation matrix
28593 * @return transformation array
28594 * @author Nicola Asuni
28595 * @since 5.0.000 (2010-05-02)
28596 * @protected
28598 protected function getTransformationMatrixProduct($ta, $tb) {
28599 $tm = array();
28600 $tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
28601 $tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
28602 $tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
28603 $tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
28604 $tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
28605 $tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
28606 return $tm;
28610 * Convert SVG transformation matrix to PDF.
28611 * @param $tm (array) original SVG transformation matrix
28612 * @return array transformation matrix
28613 * @protected
28614 * @since 5.0.000 (2010-05-02)
28616 protected function convertSVGtMatrix($tm) {
28617 $a = $tm[0];
28618 $b = -$tm[1];
28619 $c = -$tm[2];
28620 $d = $tm[3];
28621 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
28622 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
28623 $x = 0;
28624 $y = $this->h * $this->k;
28625 $e = ($x * (1 - $a)) - ($y * $c) + $e;
28626 $f = ($y * (1 - $d)) - ($x * $b) + $f;
28627 return array($a, $b, $c, $d, $e, $f);
28631 * Apply SVG graphic transformation matrix.
28632 * @param $tm (array) original SVG transformation matrix
28633 * @protected
28634 * @since 5.0.000 (2010-05-02)
28636 protected function SVGTransform($tm) {
28637 $this->Transform($this->convertSVGtMatrix($tm));
28641 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
28642 * @param $svgstyle (array) array of SVG styles to apply
28643 * @param $prevsvgstyle (array) array of previous SVG style
28644 * @param $x (int) X origin of the bounding box
28645 * @param $y (int) Y origin of the bounding box
28646 * @param $w (int) width of the bounding box
28647 * @param $h (int) height of the bounding box
28648 * @param $clip_function (string) clip function
28649 * @param $clip_params (array) array of parameters for clipping function
28650 * @return object style
28651 * @author Nicola Asuni
28652 * @since 5.0.000 (2010-05-02)
28653 * @protected
28655 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
28656 if ($this->state != 2) {
28657 return;
28659 $objstyle = '';
28660 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
28661 if (!isset($svgstyle['opacity'])) {
28662 return $objstyle;
28664 // clip-path
28665 $regs = array();
28666 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
28667 $clip_path = $this->svgclippaths[$regs[1]];
28668 foreach ($clip_path as $cp) {
28669 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
28672 // opacity
28673 if ($svgstyle['opacity'] != 1) {
28674 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
28676 // color
28677 $fill_color = $this->convertHTMLColorToDec($svgstyle['color']);
28678 $this->SetFillColorArray($fill_color);
28679 // text color
28680 $text_color = $this->convertHTMLColorToDec($svgstyle['text-color']);
28681 $this->SetTextColorArray($text_color);
28682 // clip
28683 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
28684 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
28685 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
28686 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
28687 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
28688 $cx = $x + $left;
28689 $cy = $y + $top;
28690 $cw = $w - $left - $right;
28691 $ch = $h - $top - $bottom;
28692 if ($svgstyle['clip-rule'] == 'evenodd') {
28693 $clip_rule = 'CNZ';
28694 } else {
28695 $clip_rule = 'CEO';
28697 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
28699 // fill
28700 $regs = array();
28701 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
28702 // gradient
28703 $gradient = $this->svggradients[$regs[1]];
28704 if (isset($gradient['xref'])) {
28705 // reference to another gradient definition
28706 $newgradient = $this->svggradients[$gradient['xref']];
28707 $newgradient['coords'] = $gradient['coords'];
28708 $newgradient['mode'] = $gradient['mode'];
28709 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
28710 if (isset($gradient['gradientTransform'])) {
28711 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
28713 $gradient = $newgradient;
28715 //save current Graphic State
28716 $this->_out('q');
28717 //set clipping area
28718 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
28719 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
28720 if (is_array($bbox) AND (count($bbox) == 4)) {
28721 list($x, $y, $w, $h) = $bbox;
28724 if ($gradient['mode'] == 'measure') {
28725 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
28726 $gtm = $gradient['gradientTransform'];
28727 // apply transformation matrix
28728 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
28729 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
28730 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
28731 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
28732 if (isset($gradient['coords'][4])) {
28733 $gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
28735 $gradient['coords'][0] = $xa;
28736 $gradient['coords'][1] = $ya;
28737 $gradient['coords'][2] = $xb;
28738 $gradient['coords'][3] = $yb;
28740 // convert SVG coordinates to user units
28741 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
28742 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
28743 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
28744 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
28745 if (isset($gradient['coords'][4])) {
28746 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
28748 if ($w <= $minlen) {
28749 $w = $minlen;
28751 if ($h <= $minlen) {
28752 $h = $minlen;
28754 // shift units
28755 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
28756 // convert to SVG coordinate system
28757 $gradient['coords'][0] += $x;
28758 $gradient['coords'][1] += $y;
28759 $gradient['coords'][2] += $x;
28760 $gradient['coords'][3] += $y;
28762 // calculate percentages
28763 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
28764 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
28765 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
28766 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
28767 if (isset($gradient['coords'][4])) {
28768 $gradient['coords'][4] /= $w;
28770 } elseif ($gradient['mode'] == 'percentage') {
28771 foreach($gradient['coords'] as $key => $val) {
28772 $gradient['coords'][$key] = (intval($val) / 100);
28773 if ($val < 0) {
28774 $gradient['coords'][$key] = 0;
28775 } elseif ($val > 1) {
28776 $gradient['coords'][$key] = 1;
28780 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
28781 // single color (no shading)
28782 $gradient['coords'][0] = 1;
28783 $gradient['coords'][1] = 0;
28784 $gradient['coords'][2] = 0.999;
28785 $gradient['coords'][3] = 0;
28787 // swap Y coordinates
28788 $tmp = $gradient['coords'][1];
28789 $gradient['coords'][1] = $gradient['coords'][3];
28790 $gradient['coords'][3] = $tmp;
28791 // set transformation map for gradient
28792 if ($gradient['type'] == 3) {
28793 // circular gradient
28794 $cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
28795 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($w * $this->k), ($x * $this->k), ($cy * $this->k)));
28796 } else {
28797 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), (($this->h - ($y + $h)) * $this->k)));
28799 if (count($gradient['stops']) > 1) {
28800 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
28802 } elseif ($svgstyle['fill'] != 'none') {
28803 $fill_color = $this->convertHTMLColorToDec($svgstyle['fill']);
28804 if ($svgstyle['fill-opacity'] != 1) {
28805 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
28807 $this->SetFillColorArray($fill_color);
28808 if ($svgstyle['fill-rule'] == 'evenodd') {
28809 $objstyle .= 'F*';
28810 } else {
28811 $objstyle .= 'F';
28814 // stroke
28815 if ($svgstyle['stroke'] != 'none') {
28816 if ($svgstyle['stroke-opacity'] != 1) {
28817 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
28819 $stroke_style = array(
28820 'color' => $this->convertHTMLColorToDec($svgstyle['stroke']),
28821 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
28822 'cap' => $svgstyle['stroke-linecap'],
28823 'join' => $svgstyle['stroke-linejoin']
28825 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
28826 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
28828 $this->SetLineStyle($stroke_style);
28829 $objstyle .= 'D';
28831 // font
28832 $regs = array();
28833 if (!empty($svgstyle['font'])) {
28834 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
28835 $font_family = $this->getFontFamilyName($regs[1]);
28836 } else {
28837 $font_family = $svgstyle['font-family'];
28839 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28840 $font_size = trim($regs[1]);
28841 } else {
28842 $font_size = $svgstyle['font-size'];
28844 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28845 $font_style = trim($regs[1]);
28846 } else {
28847 $font_style = $svgstyle['font-style'];
28849 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28850 $font_weight = trim($regs[1]);
28851 } else {
28852 $font_weight = $svgstyle['font-weight'];
28854 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28855 $font_stretch = trim($regs[1]);
28856 } else {
28857 $font_stretch = $svgstyle['font-stretch'];
28859 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28860 $font_spacing = trim($regs[1]);
28861 } else {
28862 $font_spacing = $svgstyle['letter-spacing'];
28864 } else {
28865 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
28866 $font_size = $svgstyle['font-size'];
28867 $font_style = $svgstyle['font-style'];
28868 $font_weight = $svgstyle['font-weight'];
28869 $font_stretch = $svgstyle['font-stretch'];
28870 $font_spacing = $svgstyle['letter-spacing'];
28872 $font_size = $this->getHTMLUnitToUnits($font_size, $prevsvgstyle['font-size'], $this->svgunit, false) * $this->k;
28873 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
28874 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
28875 switch ($font_style) {
28876 case 'italic': {
28877 $font_style = 'I';
28878 break;
28880 case 'oblique': {
28881 $font_style = 'I';
28882 break;
28884 default:
28885 case 'normal': {
28886 $font_style = '';
28887 break;
28890 switch ($font_weight) {
28891 case 'bold':
28892 case 'bolder': {
28893 $font_style .= 'B';
28894 break;
28897 switch ($svgstyle['text-decoration']) {
28898 case 'underline': {
28899 $font_style .= 'U';
28900 break;
28902 case 'overline': {
28903 $font_style .= 'O';
28904 break;
28906 case 'line-through': {
28907 $font_style .= 'D';
28908 break;
28910 default:
28911 case 'none': {
28912 break;
28915 $this->SetFont($font_family, $font_style, $font_size);
28916 $this->setFontStretching($font_stretch);
28917 $this->setFontSpacing($font_spacing);
28918 return $objstyle;
28922 * Draws an SVG path
28923 * @param $d (string) attribute d of the path SVG element
28924 * @param $style (string) Style of rendering. Possible values are:
28925 * <ul>
28926 * <li>D or empty string: Draw (default).</li>
28927 * <li>F: Fill.</li>
28928 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
28929 * <li>DF or FD: Draw and fill.</li>
28930 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
28931 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
28932 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
28933 * </ul>
28934 * @return array of container box measures (x, y, w, h)
28935 * @author Nicola Asuni
28936 * @since 5.0.000 (2010-05-02)
28937 * @protected
28939 protected function SVGPath($d, $style='') {
28940 if ($this->state != 2) {
28941 return;
28943 // set fill/stroke style
28944 $op = $this->getPathPaintOperator($style, '');
28945 if (empty($op)) {
28946 return;
28948 $paths = array();
28949 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
28950 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
28951 $x = 0;
28952 $y = 0;
28953 $x1 = 0;
28954 $y1 = 0;
28955 $x2 = 0;
28956 $y2 = 0;
28957 $xmin = 2147483647;
28958 $xmax = 0;
28959 $ymin = 2147483647;
28960 $ymax = 0;
28961 $relcoord = false;
28962 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
28963 $firstcmd = true; // used to print first point
28964 // draw curve pieces
28965 foreach ($paths as $key => $val) {
28966 // get curve type
28967 $cmd = trim($val[1]);
28968 if (strtolower($cmd) == $cmd) {
28969 // use relative coordinated instead of absolute
28970 $relcoord = true;
28971 $xoffset = $x;
28972 $yoffset = $y;
28973 } else {
28974 $relcoord = false;
28975 $xoffset = 0;
28976 $yoffset = 0;
28978 $params = array();
28979 if (isset($val[2])) {
28980 // get curve parameters
28981 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
28982 $params = array();
28983 foreach ($rawparams as $ck => $cp) {
28984 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
28985 if (abs($params[$ck]) < $minlen) {
28986 // aproximate little values to zero
28987 $params[$ck] = 0;
28991 // store current origin point
28992 $x0 = $x;
28993 $y0 = $y;
28994 switch (strtoupper($cmd)) {
28995 case 'M': { // moveto
28996 foreach ($params as $ck => $cp) {
28997 if (($ck % 2) == 0) {
28998 $x = $cp + $xoffset;
28999 } else {
29000 $y = $cp + $yoffset;
29001 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
29002 if ($ck == 1) {
29003 $this->_outPoint($x, $y);
29004 $firstcmd = false;
29005 } else {
29006 $this->_outLine($x, $y);
29008 $x0 = $x;
29009 $y0 = $y;
29011 $xmin = min($xmin, $x);
29012 $ymin = min($ymin, $y);
29013 $xmax = max($xmax, $x);
29014 $ymax = max($ymax, $y);
29015 if ($relcoord) {
29016 $xoffset = $x;
29017 $yoffset = $y;
29021 break;
29023 case 'L': { // lineto
29024 foreach ($params as $ck => $cp) {
29025 if (($ck % 2) == 0) {
29026 $x = $cp + $xoffset;
29027 } else {
29028 $y = $cp + $yoffset;
29029 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
29030 $this->_outLine($x, $y);
29031 $x0 = $x;
29032 $y0 = $y;
29034 $xmin = min($xmin, $x);
29035 $ymin = min($ymin, $y);
29036 $xmax = max($xmax, $x);
29037 $ymax = max($ymax, $y);
29038 if ($relcoord) {
29039 $xoffset = $x;
29040 $yoffset = $y;
29044 break;
29046 case 'H': { // horizontal lineto
29047 foreach ($params as $ck => $cp) {
29048 $x = $cp + $xoffset;
29049 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
29050 $this->_outLine($x, $y);
29051 $x0 = $x;
29052 $y0 = $y;
29054 $xmin = min($xmin, $x);
29055 $xmax = max($xmax, $x);
29056 if ($relcoord) {
29057 $xoffset = $x;
29060 break;
29062 case 'V': { // vertical lineto
29063 foreach ($params as $ck => $cp) {
29064 $y = $cp + $yoffset;
29065 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
29066 $this->_outLine($x, $y);
29067 $x0 = $x;
29068 $y0 = $y;
29070 $ymin = min($ymin, $y);
29071 $ymax = max($ymax, $y);
29072 if ($relcoord) {
29073 $yoffset = $y;
29076 break;
29078 case 'C': { // curveto
29079 foreach ($params as $ck => $cp) {
29080 $params[$ck] = $cp;
29081 if ((($ck + 1) % 6) == 0) {
29082 $x1 = $params[($ck - 5)] + $xoffset;
29083 $y1 = $params[($ck - 4)] + $yoffset;
29084 $x2 = $params[($ck - 3)] + $xoffset;
29085 $y2 = $params[($ck - 2)] + $yoffset;
29086 $x = $params[($ck - 1)] + $xoffset;
29087 $y = $params[($ck)] + $yoffset;
29088 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
29089 $xmin = min($xmin, $x, $x1, $x2);
29090 $ymin = min($ymin, $y, $y1, $y2);
29091 $xmax = max($xmax, $x, $x1, $x2);
29092 $ymax = max($ymax, $y, $y1, $y2);
29093 if ($relcoord) {
29094 $xoffset = $x;
29095 $yoffset = $y;
29099 break;
29101 case 'S': { // shorthand/smooth curveto
29102 foreach ($params as $ck => $cp) {
29103 $params[$ck] = $cp;
29104 if ((($ck + 1) % 4) == 0) {
29105 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
29106 $x1 = (2 * $x) - $x2;
29107 $y1 = (2 * $y) - $y2;
29108 } else {
29109 $x1 = $x;
29110 $y1 = $y;
29112 $x2 = $params[($ck - 3)] + $xoffset;
29113 $y2 = $params[($ck - 2)] + $yoffset;
29114 $x = $params[($ck - 1)] + $xoffset;
29115 $y = $params[($ck)] + $yoffset;
29116 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
29117 $xmin = min($xmin, $x, $x1, $x2);
29118 $ymin = min($ymin, $y, $y1, $y2);
29119 $xmax = max($xmax, $x, $x1, $x2);
29120 $ymax = max($ymax, $y, $y1, $y2);
29121 if ($relcoord) {
29122 $xoffset = $x;
29123 $yoffset = $y;
29127 break;
29129 case 'Q': { // quadratic Bézier curveto
29130 foreach ($params as $ck => $cp) {
29131 $params[$ck] = $cp;
29132 if ((($ck + 1) % 4) == 0) {
29133 // convert quadratic points to cubic points
29134 $x1 = $params[($ck - 3)] + $xoffset;
29135 $y1 = $params[($ck - 2)] + $yoffset;
29136 $xa = ($x + (2 * $x1)) / 3;
29137 $ya = ($y + (2 * $y1)) / 3;
29138 $x = $params[($ck - 1)] + $xoffset;
29139 $y = $params[($ck)] + $yoffset;
29140 $xb = ($x + (2 * $x1)) / 3;
29141 $yb = ($y + (2 * $y1)) / 3;
29142 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
29143 $xmin = min($xmin, $x, $xa, $xb);
29144 $ymin = min($ymin, $y, $ya, $yb);
29145 $xmax = max($xmax, $x, $xa, $xb);
29146 $ymax = max($ymax, $y, $ya, $yb);
29147 if ($relcoord) {
29148 $xoffset = $x;
29149 $yoffset = $y;
29153 break;
29155 case 'T': { // shorthand/smooth quadratic Bézier curveto
29156 foreach ($params as $ck => $cp) {
29157 $params[$ck] = $cp;
29158 if (($ck % 2) != 0) {
29159 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
29160 $x1 = (2 * $x) - $x1;
29161 $y1 = (2 * $y) - $y1;
29162 } else {
29163 $x1 = $x;
29164 $y1 = $y;
29166 // convert quadratic points to cubic points
29167 $xa = ($x + (2 * $x1)) / 3;
29168 $ya = ($y + (2 * $y1)) / 3;
29169 $x = $params[($ck - 1)] + $xoffset;
29170 $y = $params[($ck)] + $yoffset;
29171 $xb = ($x + (2 * $x1)) / 3;
29172 $yb = ($y + (2 * $y1)) / 3;
29173 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
29174 $xmin = min($xmin, $x, $xa, $xb);
29175 $ymin = min($ymin, $y, $ya, $yb);
29176 $xmax = max($xmax, $x, $xa, $xb);
29177 $ymax = max($ymax, $y, $ya, $yb);
29178 if ($relcoord) {
29179 $xoffset = $x;
29180 $yoffset = $y;
29184 break;
29186 case 'A': { // elliptical arc
29187 foreach ($params as $ck => $cp) {
29188 $params[$ck] = $cp;
29189 if ((($ck + 1) % 7) == 0) {
29190 $x0 = $x;
29191 $y0 = $y;
29192 $rx = abs($params[($ck - 6)]);
29193 $ry = abs($params[($ck - 5)]);
29194 $ang = -$rawparams[($ck - 4)];
29195 $angle = deg2rad($ang);
29196 $fa = $rawparams[($ck - 3)]; // large-arc-flag
29197 $fs = $rawparams[($ck - 2)]; // sweep-flag
29198 $x = $params[($ck - 1)] + $xoffset;
29199 $y = $params[$ck] + $yoffset;
29200 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
29201 // endpoints are almost identical
29202 $xmin = min($xmin, $x);
29203 $ymin = min($ymin, $y);
29204 $xmax = max($xmax, $x);
29205 $ymax = max($ymax, $y);
29206 } else {
29207 $cos_ang = cos($angle);
29208 $sin_ang = sin($angle);
29209 $a = (($x0 - $x) / 2);
29210 $b = (($y0 - $y) / 2);
29211 $xa = ($a * $cos_ang) - ($b * $sin_ang);
29212 $ya = ($a * $sin_ang) + ($b * $cos_ang);
29213 $rx2 = $rx * $rx;
29214 $ry2 = $ry * $ry;
29215 $xa2 = $xa * $xa;
29216 $ya2 = $ya * $ya;
29217 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
29218 if ($delta > 1) {
29219 $rx *= sqrt($delta);
29220 $ry *= sqrt($delta);
29221 $rx2 = $rx * $rx;
29222 $ry2 = $ry * $ry;
29224 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
29225 if ($numerator < 0) {
29226 $root = 0;
29227 } else {
29228 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
29230 if ($fa == $fs){
29231 $root *= -1;
29233 $cax = $root * (($rx * $ya) / $ry);
29234 $cay = -$root * (($ry * $xa) / $rx);
29235 // coordinates of ellipse center
29236 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
29237 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
29238 // get angles
29239 $angs = $this->getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
29240 $dang = $this->getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
29241 if (($fs == 0) AND ($dang > 0)) {
29242 $dang -= (2 * M_PI);
29243 } elseif (($fs == 1) AND ($dang < 0)) {
29244 $dang += (2 * M_PI);
29246 $angf = $angs - $dang;
29247 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
29248 // reverse angles
29249 $tmp = $angs;
29250 $angs = $angf;
29251 $angf = $tmp;
29253 $angs = round(rad2deg($angs), 6);
29254 $angf = round(rad2deg($angf), 6);
29255 // covent angles to positive values
29256 if (($angs < 0) AND ($angf < 0)) {
29257 $angs += 360;
29258 $angf += 360;
29260 $pie = false;
29261 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
29262 $pie = true;
29264 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
29265 $xmin = min($xmin, $x, $axmin);
29266 $ymin = min($ymin, $y, $aymin);
29267 $xmax = max($xmax, $x, $axmax);
29268 $ymax = max($ymax, $y, $aymax);
29270 if ($relcoord) {
29271 $xoffset = $x;
29272 $yoffset = $y;
29276 break;
29278 case 'Z': {
29279 $this->_out('h');
29280 break;
29283 $firstcmd = false;
29284 } // end foreach
29285 if (!empty($op)) {
29286 $this->_out($op);
29288 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
29292 * Returns the angle in radiants between two vectors
29293 * @param $x1 (int) X coordinate of first vector point
29294 * @param $y1 (int) Y coordinate of first vector point
29295 * @param $x2 (int) X coordinate of second vector point
29296 * @param $y2 (int) Y coordinate of second vector point
29297 * @author Nicola Asuni
29298 * @since 5.0.000 (2010-05-04)
29299 * @protected
29301 protected function getVectorsAngle($x1, $y1, $x2, $y2) {
29302 $dprod = ($x1 * $x2) + ($y1 * $y2);
29303 $dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
29304 $dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
29305 $angle = acos($dprod / ($dist1 * $dist2));
29306 if (is_nan($angle)) {
29307 $angle = M_PI;
29309 if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
29310 $angle *= -1;
29312 return $angle;
29316 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
29317 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
29318 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
29319 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
29320 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
29321 * @author Nicola Asuni
29322 * @since 5.0.000 (2010-05-02)
29323 * @protected
29325 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
29326 // check if we are in clip mode
29327 if ($this->svgclipmode) {
29328 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
29329 return;
29331 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
29332 if (!isset($attribs['id'])) {
29333 $attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
29335 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
29336 return;
29338 $clipping = false;
29339 if ($parser == 'clip-path') {
29340 // set clipping mode
29341 $clipping = true;
29343 // get styling properties
29344 $prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
29345 $svgstyle = $this->svgstyles[0]; // set default style
29346 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
29347 // default fill attribute for clipping
29348 $attribs['fill'] = 'none';
29350 if (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
29351 // fix style for regular expression
29352 $attribs['style'] = ';'.$attribs['style'];
29354 foreach ($prev_svgstyle as $key => $val) {
29355 if (in_array($key, $this->svginheritprop)) {
29356 // inherit previous value
29357 $svgstyle[$key] = $val;
29359 if (isset($attribs[$key]) AND !$this->empty_string($attribs[$key])) {
29360 // specific attribute settings
29361 if ($attribs[$key] == 'inherit') {
29362 $svgstyle[$key] = $val;
29363 } else {
29364 $svgstyle[$key] = $attribs[$key];
29366 } elseif (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
29367 // CSS style syntax
29368 $attrval = array();
29369 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
29370 if ($attrval[1] == 'inherit') {
29371 $svgstyle[$key] = $val;
29372 } else {
29373 $svgstyle[$key] = $attrval[1];
29378 // transformation matrix
29379 if (!empty($ctm)) {
29380 $tm = $ctm;
29381 } else {
29382 //$tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
29383 $tm = array(1,0,0,1,0,0);
29385 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
29386 $tm = $this->getTransformationMatrixProduct($tm, $this->getSVGTransformMatrix($attribs['transform']));
29388 $svgstyle['transfmatrix'] = $tm;
29389 $invisible = false;
29390 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
29391 // the current graphics element is invisible (nothing is painted)
29392 $invisible = true;
29394 // process tag
29395 switch($name) {
29396 case 'defs': {
29397 $this->svgdefsmode = true;
29398 break;
29400 // clipPath
29401 case 'clipPath': {
29402 if ($invisible) {
29403 break;
29405 $this->svgclipmode = true;
29406 if (!isset($attribs['id'])) {
29407 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
29409 $this->svgclipid = $attribs['id'];
29410 $this->svgclippaths[$this->svgclipid] = array();
29411 $this->svgcliptm[$this->svgclipid] = $tm;
29412 break;
29414 case 'svg': {
29415 // start of SVG object
29416 break;
29418 case 'g': {
29419 // group together related graphics elements
29420 array_push($this->svgstyles, $svgstyle);
29421 $this->StartTransform();
29422 $this->SVGTransform($tm);
29423 $this->setSVGStyles($svgstyle, $prev_svgstyle);
29424 break;
29426 case 'linearGradient': {
29427 if ($this->pdfa_mode) {
29428 break;
29430 if (!isset($attribs['id'])) {
29431 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
29433 $this->svggradientid = $attribs['id'];
29434 $this->svggradients[$this->svggradientid] = array();
29435 $this->svggradients[$this->svggradientid]['type'] = 2;
29436 $this->svggradients[$this->svggradientid]['stops'] = array();
29437 if (isset($attribs['gradientUnits'])) {
29438 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
29439 } else {
29440 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
29442 //$attribs['spreadMethod']
29443 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
29444 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
29445 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
29446 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
29447 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
29448 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
29449 } else {
29450 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
29452 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
29453 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
29454 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
29455 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
29456 if (isset($attribs['gradientTransform'])) {
29457 $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
29459 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
29460 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
29461 // gradient is defined on another place
29462 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
29464 break;
29466 case 'radialGradient': {
29467 if ($this->pdfa_mode) {
29468 break;
29470 if (!isset($attribs['id'])) {
29471 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
29473 $this->svggradientid = $attribs['id'];
29474 $this->svggradients[$this->svggradientid] = array();
29475 $this->svggradients[$this->svggradientid]['type'] = 3;
29476 $this->svggradients[$this->svggradientid]['stops'] = array();
29477 if (isset($attribs['gradientUnits'])) {
29478 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
29479 } else {
29480 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
29482 //$attribs['spreadMethod']
29483 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
29484 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
29485 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')) )) {
29486 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
29487 } else {
29488 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
29490 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
29491 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
29492 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
29493 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
29494 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
29495 if (isset($attribs['gradientTransform'])) {
29496 $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
29498 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
29499 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
29500 // gradient is defined on another place
29501 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
29503 break;
29505 case 'stop': {
29506 // gradient stops
29507 if (substr($attribs['offset'], -1) == '%') {
29508 $offset = floatval(substr($attribs['offset'], -1)) / 100;
29509 } else {
29510 $offset = floatval($attribs['offset']);
29511 if ($offset > 1) {
29512 $offset /= 100;
29515 $stop_color = isset($svgstyle['stop-color'])?$this->convertHTMLColorToDec($svgstyle['stop-color']):'black';
29516 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
29517 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
29518 break;
29520 // paths
29521 case 'path': {
29522 if ($invisible) {
29523 break;
29525 if (isset($attribs['d'])) {
29526 $d = trim($attribs['d']);
29527 if (!empty($d)) {
29528 if ($clipping) {
29529 $this->SVGTransform($tm);
29530 $this->SVGPath($d, 'CNZ');
29531 } else {
29532 $this->StartTransform();
29533 $this->SVGTransform($tm);
29534 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
29535 if (!empty($obstyle)) {
29536 $this->SVGPath($d, $obstyle);
29538 $this->StopTransform();
29542 break;
29544 // shapes
29545 case 'rect': {
29546 if ($invisible) {
29547 break;
29549 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
29550 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
29551 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
29552 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
29553 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
29554 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
29555 if ($clipping) {
29556 $this->SVGTransform($tm);
29557 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
29558 } else {
29559 $this->StartTransform();
29560 $this->SVGTransform($tm);
29561 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
29562 if (!empty($obstyle)) {
29563 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
29565 $this->StopTransform();
29567 break;
29569 case 'circle': {
29570 if ($invisible) {
29571 break;
29573 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
29574 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
29575 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
29576 $x = ($cx - $r);
29577 $y = ($cy - $r);
29578 $w = (2 * $r);
29579 $h = $w;
29580 if ($clipping) {
29581 $this->SVGTransform($tm);
29582 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
29583 } else {
29584 $this->StartTransform();
29585 $this->SVGTransform($tm);
29586 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
29587 if (!empty($obstyle)) {
29588 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
29590 $this->StopTransform();
29592 break;
29594 case 'ellipse': {
29595 if ($invisible) {
29596 break;
29598 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
29599 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
29600 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
29601 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
29602 $x = ($cx - $rx);
29603 $y = ($cy - $ry);
29604 $w = (2 * $rx);
29605 $h = (2 * $ry);
29606 if ($clipping) {
29607 $this->SVGTransform($tm);
29608 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
29609 } else {
29610 $this->StartTransform();
29611 $this->SVGTransform($tm);
29612 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
29613 if (!empty($obstyle)) {
29614 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
29616 $this->StopTransform();
29618 break;
29620 case 'line': {
29621 if ($invisible) {
29622 break;
29624 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
29625 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
29626 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
29627 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
29628 $x = $x1;
29629 $y = $y1;
29630 $w = abs($x2 - $x1);
29631 $h = abs($y2 - $y1);
29632 if (!$clipping) {
29633 $this->StartTransform();
29634 $this->SVGTransform($tm);
29635 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
29636 $this->Line($x1, $y1, $x2, $y2);
29637 $this->StopTransform();
29639 break;
29641 case 'polyline':
29642 case 'polygon': {
29643 if ($invisible) {
29644 break;
29646 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
29647 $points = trim($points);
29648 // note that point may use a complex syntax not covered here
29649 $points = preg_split('/[\,\s]+/si', $points);
29650 if (count($points) < 4) {
29651 break;
29653 $p = array();
29654 $xmin = 2147483647;
29655 $xmax = 0;
29656 $ymin = 2147483647;
29657 $ymax = 0;
29658 foreach ($points as $key => $val) {
29659 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
29660 if (($key % 2) == 0) {
29661 // X coordinate
29662 $xmin = min($xmin, $p[$key]);
29663 $xmax = max($xmax, $p[$key]);
29664 } else {
29665 // Y coordinate
29666 $ymin = min($ymin, $p[$key]);
29667 $ymax = max($ymax, $p[$key]);
29670 $x = $xmin;
29671 $y = $ymin;
29672 $w = ($xmax - $xmin);
29673 $h = ($ymax - $ymin);
29674 if ($name == 'polyline') {
29675 $this->StartTransform();
29676 $this->SVGTransform($tm);
29677 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
29678 if (!empty($obstyle)) {
29679 $this->PolyLine($p, $obstyle, array(), array());
29681 $this->StopTransform();
29682 } else { // polygon
29683 if ($clipping) {
29684 $this->SVGTransform($tm);
29685 $this->Polygon($p, 'CNZ', array(), array(), true);
29686 } else {
29687 $this->StartTransform();
29688 $this->SVGTransform($tm);
29689 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
29690 if (!empty($obstyle)) {
29691 $this->Polygon($p, $obstyle, array(), array(), true);
29693 $this->StopTransform();
29696 break;
29698 // image
29699 case 'image': {
29700 if ($invisible) {
29701 break;
29703 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
29704 break;
29706 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
29707 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
29708 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
29709 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
29710 $img = $attribs['xlink:href'];
29711 if (!$clipping) {
29712 $this->StartTransform();
29713 $this->SVGTransform($tm);
29714 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
29715 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
29716 // embedded image encoded as base64
29717 $img = '@'.base64_decode(substr($img, strlen($m[0])));
29718 } else {
29719 // fix image path
29720 if (!$this->empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
29721 // replace relative path with full server path
29722 $img = $this->svgdir.'/'.$img;
29724 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
29725 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
29726 if (($findroot === false) OR ($findroot > 1)) {
29727 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
29728 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
29729 } else {
29730 $img = $_SERVER['DOCUMENT_ROOT'].$img;
29734 $img = urldecode($img);
29735 $testscrtype = @parse_url($img);
29736 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
29737 // convert URL to server path
29738 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
29741 // get image type
29742 $imgtype = $this->getImageFileType($img);
29743 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
29744 $this->ImageEps($img, $x, $y, $w, $h);
29745 } elseif ($imgtype == 'svg') {
29746 $this->ImageSVG($img, $x, $y, $w, $h);
29747 } else {
29748 $this->Image($img, $x, $y, $w, $h);
29750 $this->StopTransform();
29752 break;
29754 // text
29755 case 'text':
29756 case 'tspan': {
29757 // only basic support - advanced features must be implemented
29758 $this->svgtextmode['invisible'] = $invisible;
29759 if ($invisible) {
29760 break;
29762 array_push($this->svgstyles, $svgstyle);
29763 if (isset($attribs['x'])) {
29764 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
29765 } elseif ($name == 'tspan') {
29766 $x = $this->x;
29767 } else {
29768 $x = 0;
29770 if (isset($attribs['y'])) {
29771 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
29772 } elseif ($name == 'tspan') {
29773 $y = $this->y;
29774 } else {
29775 $y = 0;
29777 $svgstyle['text-color'] = $svgstyle['fill'];
29778 $this->svgtext = '';
29779 if (isset($svgstyle['text-anchor'])) {
29780 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
29781 } else {
29782 $this->svgtextmode['text-anchor'] = 'start';
29784 if (isset($svgstyle['direction'])) {
29785 if ($svgstyle['direction'] == 'rtl') {
29786 $this->svgtextmode['rtl'] = true;
29787 } else {
29788 $this->svgtextmode['rtl'] = false;
29790 } else {
29791 $this->svgtextmode['rtl'] = false;
29793 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
29794 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
29795 } else {
29796 $this->svgtextmode['stroke'] = false;
29798 $this->StartTransform();
29799 $this->SVGTransform($tm);
29800 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
29801 $this->x = $x;
29802 $this->y = $y;
29803 break;
29805 // use
29806 case 'use': {
29807 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
29808 $svgdefid = substr($attribs['xlink:href'], 1);
29809 if (isset($this->svgdefs[$svgdefid])) {
29810 $use = $this->svgdefs[$svgdefid];
29811 if (isset($attribs['xlink:href'])) {
29812 unset($attribs['xlink:href']);
29814 if (isset($attribs['id'])) {
29815 unset($attribs['id']);
29817 $attribs = array_merge($attribs, $use['attribs']);
29818 $this->startSVGElementHandler($parser, $use['name'], $attribs);
29821 break;
29823 default: {
29824 break;
29826 } // end of switch
29830 * Sets the closing SVG element handler function for the XML parser.
29831 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
29832 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
29833 * @author Nicola Asuni
29834 * @since 5.0.000 (2010-05-02)
29835 * @protected
29837 protected function endSVGElementHandler($parser, $name) {
29838 switch($name) {
29839 case 'defs': {
29840 $this->svgdefsmode = false;
29841 break;
29843 // clipPath
29844 case 'clipPath': {
29845 $this->svgclipmode = false;
29846 break;
29848 case 'g': {
29849 // ungroup: remove last style from array
29850 array_pop($this->svgstyles);
29851 $this->StopTransform();
29852 break;
29854 case 'text':
29855 case 'tspan': {
29856 if ($this->svgtextmode['invisible']) {
29857 // This implementation must be fixed to following the rule:
29858 // 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.
29859 break;
29861 // print text
29862 $text = $this->svgtext;
29863 //$text = $this->stringTrim($text);
29864 $textlen = $this->GetStringWidth($text);
29865 if ($this->svgtextmode['text-anchor'] != 'start') {
29866 // check if string is RTL text
29867 if ($this->svgtextmode['text-anchor'] == 'end') {
29868 if ($this->svgtextmode['rtl']) {
29869 $this->x += $textlen;
29870 } else {
29871 $this->x -= $textlen;
29873 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
29874 if ($this->svgtextmode['rtl']) {
29875 $this->x += ($textlen / 2);
29876 } else {
29877 $this->x -= ($textlen / 2);
29881 $textrendermode = $this->textrendermode;
29882 $textstrokewidth = $this->textstrokewidth;
29883 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
29884 if ($name == 'text') {
29885 // store current coordinates
29886 $tmpx = $this->x;
29887 $tmpy = $this->y;
29889 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
29890 if ($name == 'text') {
29891 // restore coordinates
29892 $this->x = $tmpx;
29893 $this->y = $tmpy;
29895 // restore previous rendering mode
29896 $this->textrendermode = $textrendermode;
29897 $this->textstrokewidth = $textstrokewidth;
29898 $this->svgtext = '';
29899 $this->StopTransform();
29900 array_pop($this->svgstyles);
29901 break;
29903 default: {
29904 break;
29910 * Sets the character data handler function for the XML parser.
29911 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
29912 * @param $data (string) The second parameter, data, contains the character data as a string.
29913 * @author Nicola Asuni
29914 * @since 5.0.000 (2010-05-02)
29915 * @protected
29917 protected function segSVGContentHandler($parser, $data) {
29918 $this->svgtext .= $data;
29921 // --- END SVG METHODS -----------------------------------------------------
29923 } // END OF TCPDF CLASS
29925 //============================================================+
29926 // END OF FILE
29927 //============================================================+