1<?php
2//============================================================+
3// File name   : tcpdf.php
4// Version     : 6.2.12
5// Begin       : 2002-08-03
6// Last Update : 2015-06-18
7// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9// -------------------------------------------------------------------
10// Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD
11//
12// This file is part of TCPDF software library.
13//
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.
18//
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.
23//
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>.
27//
28// See LICENSE.TXT file for more information.
29// -------------------------------------------------------------------
30//
31// Description :
32//   This is a PHP class for generating PDF documents without requiring external extensions.
33//
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.
39//
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 Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50//  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51//  * automatic page header and footer management;
52//  * document encryption up to 256 bit and digital signature certifications;
53//  * transactions to UNDO commands;
54//  * PDF annotations, including links, text and file attachments;
55//  * text rendering modes (fill, stroke and clipping);
56//  * multiple columns mode;
57//  * no-write page regions;
58//  * bookmarks, named destinations and table of content;
59//  * text hyphenation;
60//  * text stretching and spacing (tracking);
61//  * automatic page break, line break and text alignments including justification;
62//  * automatic page numbering and page groups;
63//  * move and delete pages;
64//  * page compression (requires php-zlib extension);
65//  * XOBject Templates;
66//  * Layers and object visibility.
67//	* PDF/A-1b support
68//============================================================+
69
70/**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.2.8
108 */
109
110// TCPDF configuration
111require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112// TCPDF static font methods and data
113require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114// TCPDF static font methods and data
115require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116// TCPDF static color methods and data
117require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118// TCPDF static image methods and data
119require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120// TCPDF static methods and data
121require_once(dirname(__FILE__).'/include/tcpdf_static.php');
122
123// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
124
125/**
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.2.8
132 * @author Nicola Asuni - info@tecnick.com
133 */
134class TCPDF {
135
136	// Protected properties
137
138	/**
139	 * Current page number.
140	 * @protected
141	 */
142	protected $page;
143
144	/**
145	 * Current object number.
146	 * @protected
147	 */
148	protected $n;
149
150	/**
151	 * Array of object offsets.
152	 * @protected
153	 */
154	protected $offsets = array();
155
156	/**
157	 * Array of object IDs for each page.
158	 * @protected
159	 */
160	protected $pageobjects = array();
161
162	/**
163	 * Buffer holding in-memory PDF.
164	 * @protected
165	 */
166	protected $buffer;
167
168	/**
169	 * Array containing pages.
170	 * @protected
171	 */
172	protected $pages = array();
173
174	/**
175	 * Current document state.
176	 * @protected
177	 */
178	protected $state;
179
180	/**
181	 * Compression flag.
182	 * @protected
183	 */
184	protected $compress;
185
186	/**
187	 * Current page orientation (P = Portrait, L = Landscape).
188	 * @protected
189	 */
190	protected $CurOrientation;
191
192	/**
193	 * Page dimensions.
194	 * @protected
195	 */
196	protected $pagedim = array();
197
198	/**
199	 * Scale factor (number of points in user unit).
200	 * @protected
201	 */
202	protected $k;
203
204	/**
205	 * Width of page format in points.
206	 * @protected
207	 */
208	protected $fwPt;
209
210	/**
211	 * Height of page format in points.
212	 * @protected
213	 */
214	protected $fhPt;
215
216	/**
217	 * Current width of page in points.
218	 * @protected
219	 */
220	protected $wPt;
221
222	/**
223	 * Current height of page in points.
224	 * @protected
225	 */
226	protected $hPt;
227
228	/**
229	 * Current width of page in user unit.
230	 * @protected
231	 */
232	protected $w;
233
234	/**
235	 * Current height of page in user unit.
236	 * @protected
237	 */
238	protected $h;
239
240	/**
241	 * Left margin.
242	 * @protected
243	 */
244	protected $lMargin;
245
246	/**
247	 * Right margin.
248	 * @protected
249	 */
250	protected $rMargin;
251
252	/**
253	 * Cell left margin (used by regions).
254	 * @protected
255	 */
256	protected $clMargin;
257
258	/**
259	 * Cell right margin (used by regions).
260	 * @protected
261	 */
262	protected $crMargin;
263
264	/**
265	 * Top margin.
266	 * @protected
267	 */
268	protected $tMargin;
269
270	/**
271	 * Page break margin.
272	 * @protected
273	 */
274	protected $bMargin;
275
276	/**
277	 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
278	 * @since 5.9.000 (2010-10-03)
279	 * @protected
280	 */
281	protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
282
283	/**
284	 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
285	 * @since 5.9.000 (2010-10-04)
286	 * @protected
287	 */
288	protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
289
290	/**
291	 * Current horizontal position in user unit for cell positioning.
292	 * @protected
293	 */
294	protected $x;
295
296	/**
297	 * Current vertical position in user unit for cell positioning.
298	 * @protected
299	 */
300	protected $y;
301
302	/**
303	 * Height of last cell printed.
304	 * @protected
305	 */
306	protected $lasth;
307
308	/**
309	 * Line width in user unit.
310	 * @protected
311	 */
312	protected $LineWidth;
313
314	/**
315	 * Array of standard font names.
316	 * @protected
317	 */
318	protected $CoreFonts;
319
320	/**
321	 * Array of used fonts.
322	 * @protected
323	 */
324	protected $fonts = array();
325
326	/**
327	 * Array of font files.
328	 * @protected
329	 */
330	protected $FontFiles = array();
331
332	/**
333	 * Array of encoding differences.
334	 * @protected
335	 */
336	protected $diffs = array();
337
338	/**
339	 * Array of used images.
340	 * @protected
341	 */
342	protected $images = array();
343
344	/**
345	 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
346	 * @protected
347	 */
348	protected $svg_tag_depth = 0;
349
350	/**
351	 * Array of Annotations in pages.
352	 * @protected
353	 */
354	protected $PageAnnots = array();
355
356	/**
357	 * Array of internal links.
358	 * @protected
359	 */
360	protected $links = array();
361
362	/**
363	 * Current font family.
364	 * @protected
365	 */
366	protected $FontFamily;
367
368	/**
369	 * Current font style.
370	 * @protected
371	 */
372	protected $FontStyle;
373
374	/**
375	 * Current font ascent (distance between font top and baseline).
376	 * @protected
377	 * @since 2.8.000 (2007-03-29)
378	 */
379	protected $FontAscent;
380
381	/**
382	 * Current font descent (distance between font bottom and baseline).
383	 * @protected
384	 * @since 2.8.000 (2007-03-29)
385	 */
386	protected $FontDescent;
387
388	/**
389	 * Underlining flag.
390	 * @protected
391	 */
392	protected $underline;
393
394	/**
395	 * Overlining flag.
396	 * @protected
397	 */
398	protected $overline;
399
400	/**
401	 * Current font info.
402	 * @protected
403	 */
404	protected $CurrentFont;
405
406	/**
407	 * Current font size in points.
408	 * @protected
409	 */
410	protected $FontSizePt;
411
412	/**
413	 * Current font size in user unit.
414	 * @protected
415	 */
416	protected $FontSize;
417
418	/**
419	 * Commands for drawing color.
420	 * @protected
421	 */
422	protected $DrawColor;
423
424	/**
425	 * Commands for filling color.
426	 * @protected
427	 */
428	protected $FillColor;
429
430	/**
431	 * Commands for text color.
432	 * @protected
433	 */
434	protected $TextColor;
435
436	/**
437	 * Indicates whether fill and text colors are different.
438	 * @protected
439	 */
440	protected $ColorFlag;
441
442	/**
443	 * Automatic page breaking.
444	 * @protected
445	 */
446	protected $AutoPageBreak;
447
448	/**
449	 * Threshold used to trigger page breaks.
450	 * @protected
451	 */
452	protected $PageBreakTrigger;
453
454	/**
455	 * Flag set when processing page header.
456	 * @protected
457	 */
458	protected $InHeader = false;
459
460	/**
461	 * Flag set when processing page footer.
462	 * @protected
463	 */
464	protected $InFooter = false;
465
466	/**
467	 * Zoom display mode.
468	 * @protected
469	 */
470	protected $ZoomMode;
471
472	/**
473	 * Layout display mode.
474	 * @protected
475	 */
476	protected $LayoutMode;
477
478	/**
479	 * If true set the document information dictionary in Unicode.
480	 * @protected
481	 */
482	protected $docinfounicode = true;
483
484	/**
485	 * Document title.
486	 * @protected
487	 */
488	protected $title = '';
489
490	/**
491	 * Document subject.
492	 * @protected
493	 */
494	protected $subject = '';
495
496	/**
497	 * Document author.
498	 * @protected
499	 */
500	protected $author = '';
501
502	/**
503	 * Document keywords.
504	 * @protected
505	 */
506	protected $keywords = '';
507
508	/**
509	 * Document creator.
510	 * @protected
511	 */
512	protected $creator = '';
513
514	/**
515	 * Starting page number.
516	 * @protected
517	 */
518	protected $starting_page_number = 1;
519
520	/**
521	 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
522	 * @since 2002-07-31
523	 * @author Nicola Asuni
524	 * @protected
525	 */
526	protected $img_rb_x;
527
528	/**
529	 * The right-bottom corner Y coordinate of last inserted image.
530	 * @since 2002-07-31
531	 * @author Nicola Asuni
532	 * @protected
533	 */
534	protected $img_rb_y;
535
536	/**
537	 * Adjusting factor to convert pixels to user units.
538	 * @since 2004-06-14
539	 * @author Nicola Asuni
540	 * @protected
541	 */
542	protected $imgscale = 1;
543
544	/**
545	 * Boolean flag set to true when the input text is unicode (require unicode fonts).
546	 * @since 2005-01-02
547	 * @author Nicola Asuni
548	 * @protected
549	 */
550	protected $isunicode = false;
551
552	/**
553	 * PDF version.
554	 * @since 1.5.3
555	 * @protected
556	 */
557	protected $PDFVersion = '1.7';
558
559	/**
560	 * ID of the stored default header template (-1 = not set).
561	 * @protected
562	 */
563	protected $header_xobjid = false;
564
565	/**
566	 * If true reset the Header Xobject template at each page
567	 * @protected
568	 */
569	protected $header_xobj_autoreset = false;
570
571	/**
572	 * Minimum distance between header and top page margin.
573	 * @protected
574	 */
575	protected $header_margin;
576
577	/**
578	 * Minimum distance between footer and bottom page margin.
579	 * @protected
580	 */
581	protected $footer_margin;
582
583	/**
584	 * Original left margin value.
585	 * @protected
586	 * @since 1.53.0.TC013
587	 */
588	protected $original_lMargin;
589
590	/**
591	 * Original right margin value.
592	 * @protected
593	 * @since 1.53.0.TC013
594	 */
595	protected $original_rMargin;
596
597	/**
598	 * Default font used on page header.
599	 * @protected
600	 */
601	protected $header_font;
602
603	/**
604	 * Default font used on page footer.
605	 * @protected
606	 */
607	protected $footer_font;
608
609	/**
610	 * Language templates.
611	 * @protected
612	 */
613	protected $l;
614
615	/**
616	 * Barcode to print on page footer (only if set).
617	 * @protected
618	 */
619	protected $barcode = false;
620
621	/**
622	 * Boolean flag to print/hide page header.
623	 * @protected
624	 */
625	protected $print_header = true;
626
627	/**
628	 * Boolean flag to print/hide page footer.
629	 * @protected
630	 */
631	protected $print_footer = true;
632
633	/**
634	 * Header image logo.
635	 * @protected
636	 */
637	protected $header_logo = '';
638
639	/**
640	 * Width of header image logo in user units.
641	 * @protected
642	 */
643	protected $header_logo_width = 30;
644
645	/**
646	 * Title to be printed on default page header.
647	 * @protected
648	 */
649	protected $header_title = '';
650
651	/**
652	 * String to pring on page header after title.
653	 * @protected
654	 */
655	protected $header_string = '';
656
657	/**
658	 * Color for header text (RGB array).
659	 * @since 5.9.174 (2012-07-25)
660	 * @protected
661	 */
662	protected $header_text_color = array(0,0,0);
663
664	/**
665	 * Color for header line (RGB array).
666	 * @since 5.9.174 (2012-07-25)
667	 * @protected
668	 */
669	protected $header_line_color = array(0,0,0);
670
671	/**
672	 * Color for footer text (RGB array).
673	 * @since 5.9.174 (2012-07-25)
674	 * @protected
675	 */
676	protected $footer_text_color = array(0,0,0);
677
678	/**
679	 * Color for footer line (RGB array).
680	 * @since 5.9.174 (2012-07-25)
681	 * @protected
682	 */
683	protected $footer_line_color = array(0,0,0);
684
685	/**
686	 * Text shadow data array.
687	 * @since 5.9.174 (2012-07-25)
688	 * @protected
689	 */
690	protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
691
692	/**
693	 * Default number of columns for html table.
694	 * @protected
695	 */
696	protected $default_table_columns = 4;
697
698	// variables for html parser
699
700	/**
701	 * HTML PARSER: array to store current link and rendering styles.
702	 * @protected
703	 */
704	protected $HREF = array();
705
706	/**
707	 * List of available fonts on filesystem.
708	 * @protected
709	 */
710	protected $fontlist = array();
711
712	/**
713	 * Current foreground color.
714	 * @protected
715	 */
716	protected $fgcolor;
717
718	/**
719	 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
720	 * @protected
721	 */
722	protected $listordered = array();
723
724	/**
725	 * HTML PARSER: array count list items on nested lists.
726	 * @protected
727	 */
728	protected $listcount = array();
729
730	/**
731	 * HTML PARSER: current list nesting level.
732	 * @protected
733	 */
734	protected $listnum = 0;
735
736	/**
737	 * HTML PARSER: indent amount for lists.
738	 * @protected
739	 */
740	protected $listindent = 0;
741
742	/**
743	 * HTML PARSER: current list indententation level.
744	 * @protected
745	 */
746	protected $listindentlevel = 0;
747
748	/**
749	 * Current background color.
750	 * @protected
751	 */
752	protected $bgcolor;
753
754	/**
755	 * Temporary font size in points.
756	 * @protected
757	 */
758	protected $tempfontsize = 10;
759
760	/**
761	 * Spacer string for LI tags.
762	 * @protected
763	 */
764	protected $lispacer = '';
765
766	/**
767	 * Default encoding.
768	 * @protected
769	 * @since 1.53.0.TC010
770	 */
771	protected $encoding = 'UTF-8';
772
773	/**
774	 * PHP internal encoding.
775	 * @protected
776	 * @since 1.53.0.TC016
777	 */
778	protected $internal_encoding;
779
780	/**
781	 * Boolean flag to indicate if the document language is Right-To-Left.
782	 * @protected
783	 * @since 2.0.000
784	 */
785	protected $rtl = false;
786
787	/**
788	 * Boolean flag used to force RTL or LTR string direction.
789	 * @protected
790	 * @since 2.0.000
791	 */
792	protected $tmprtl = false;
793
794	// --- Variables used for document encryption:
795
796	/**
797	 * IBoolean flag indicating whether document is protected.
798	 * @protected
799	 * @since 2.0.000 (2008-01-02)
800	 */
801	protected $encrypted;
802
803	/**
804	 * Array containing encryption settings.
805	 * @protected
806	 * @since 5.0.005 (2010-05-11)
807	 */
808	protected $encryptdata = array();
809
810	/**
811	 * Last RC4 key encrypted (cached for optimisation).
812	 * @protected
813	 * @since 2.0.000 (2008-01-02)
814	 */
815	protected $last_enc_key;
816
817	/**
818	 * Last RC4 computed key.
819	 * @protected
820	 * @since 2.0.000 (2008-01-02)
821	 */
822	protected $last_enc_key_c;
823
824	/**
825	 * File ID (used on document trailer).
826	 * @protected
827	 * @since 5.0.005 (2010-05-12)
828	 */
829	protected $file_id;
830
831	// --- bookmark ---
832
833	/**
834	 * Outlines for bookmark.
835	 * @protected
836	 * @since 2.1.002 (2008-02-12)
837	 */
838	protected $outlines = array();
839
840	/**
841	 * Outline root for bookmark.
842	 * @protected
843	 * @since 2.1.002 (2008-02-12)
844	 */
845	protected $OutlineRoot;
846
847	// --- javascript and form ---
848
849	/**
850	 * Javascript code.
851	 * @protected
852	 * @since 2.1.002 (2008-02-12)
853	 */
854	protected $javascript = '';
855
856	/**
857	 * Javascript counter.
858	 * @protected
859	 * @since 2.1.002 (2008-02-12)
860	 */
861	protected $n_js;
862
863	/**
864	 * line through state
865	 * @protected
866	 * @since 2.8.000 (2008-03-19)
867	 */
868	protected $linethrough;
869
870	/**
871	 * Array with additional document-wide usage rights for the document.
872	 * @protected
873	 * @since 5.8.014 (2010-08-23)
874	 */
875	protected $ur = array();
876
877	/**
878	 * DPI (Dot Per Inch) Document Resolution (do not change).
879	 * @protected
880	 * @since 3.0.000 (2008-03-27)
881	 */
882	protected $dpi = 72;
883
884	/**
885	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
886	 * @protected
887	 * @since 3.0.000 (2008-03-27)
888	 */
889	protected $newpagegroup = array();
890
891	/**
892	 * Array that contains the number of pages in each page group.
893	 * @protected
894	 * @since 3.0.000 (2008-03-27)
895	 */
896	protected $pagegroups = array();
897
898	/**
899	 * Current page group number.
900	 * @protected
901	 * @since 3.0.000 (2008-03-27)
902	 */
903	protected $currpagegroup = 0;
904
905	/**
906	 * Array of transparency objects and parameters.
907	 * @protected
908	 * @since 3.0.000 (2008-03-27)
909	 */
910	protected $extgstates;
911
912	/**
913	 * Set the default JPEG compression quality (1-100).
914	 * @protected
915	 * @since 3.0.000 (2008-03-27)
916	 */
917	protected $jpeg_quality;
918
919	/**
920	 * Default cell height ratio.
921	 * @protected
922	 * @since 3.0.014 (2008-05-23)
923	 */
924	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
925
926	/**
927	 * PDF viewer preferences.
928	 * @protected
929	 * @since 3.1.000 (2008-06-09)
930	 */
931	protected $viewer_preferences;
932
933	/**
934	 * A name object specifying how the document should be displayed when opened.
935	 * @protected
936	 * @since 3.1.000 (2008-06-09)
937	 */
938	protected $PageMode;
939
940	/**
941	 * Array for storing gradient information.
942	 * @protected
943	 * @since 3.1.000 (2008-06-09)
944	 */
945	protected $gradients = array();
946
947	/**
948	 * Array used to store positions inside the pages buffer (keys are the page numbers).
949	 * @protected
950	 * @since 3.2.000 (2008-06-26)
951	 */
952	protected $intmrk = array();
953
954	/**
955	 * Array used to store positions inside the pages buffer (keys are the page numbers).
956	 * @protected
957	 * @since 5.7.000 (2010-08-03)
958	 */
959	protected $bordermrk = array();
960
961	/**
962	 * Array used to store page positions to track empty pages (keys are the page numbers).
963	 * @protected
964	 * @since 5.8.007 (2010-08-18)
965	 */
966	protected $emptypagemrk = array();
967
968	/**
969	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
970	 * @protected
971	 * @since 4.6.021 (2009-07-20)
972	 */
973	protected $cntmrk = array();
974
975	/**
976	 * Array used to store footer positions of each page.
977	 * @protected
978	 * @since 3.2.000 (2008-07-01)
979	 */
980	protected $footerpos = array();
981
982	/**
983	 * Array used to store footer length of each page.
984	 * @protected
985	 * @since 4.0.014 (2008-07-29)
986	 */
987	protected $footerlen = array();
988
989	/**
990	 * Boolean flag to indicate if a new line is created.
991	 * @protected
992	 * @since 3.2.000 (2008-07-01)
993	 */
994	protected $newline = true;
995
996	/**
997	 * End position of the latest inserted line.
998	 * @protected
999	 * @since 3.2.000 (2008-07-01)
1000	 */
1001	protected $endlinex = 0;
1002
1003	/**
1004	 * PDF string for width value of the last line.
1005	 * @protected
1006	 * @since 4.0.006 (2008-07-16)
1007	 */
1008	protected $linestyleWidth = '';
1009
1010	/**
1011	 * PDF string for CAP value of the last line.
1012	 * @protected
1013	 * @since 4.0.006 (2008-07-16)
1014	 */
1015	protected $linestyleCap = '0 J';
1016
1017	/**
1018	 * PDF string for join value of the last line.
1019	 * @protected
1020	 * @since 4.0.006 (2008-07-16)
1021	 */
1022	protected $linestyleJoin = '0 j';
1023
1024	/**
1025	 * PDF string for dash value of the last line.
1026	 * @protected
1027	 * @since 4.0.006 (2008-07-16)
1028	 */
1029	protected $linestyleDash = '[] 0 d';
1030
1031	/**
1032	 * Boolean flag to indicate if marked-content sequence is open.
1033	 * @protected
1034	 * @since 4.0.013 (2008-07-28)
1035	 */
1036	protected $openMarkedContent = false;
1037
1038	/**
1039	 * Count the latest inserted vertical spaces on HTML.
1040	 * @protected
1041	 * @since 4.0.021 (2008-08-24)
1042	 */
1043	protected $htmlvspace = 0;
1044
1045	/**
1046	 * Array of Spot colors.
1047	 * @protected
1048	 * @since 4.0.024 (2008-09-12)
1049	 */
1050	protected $spot_colors = array();
1051
1052	/**
1053	 * Symbol used for HTML unordered list items.
1054	 * @protected
1055	 * @since 4.0.028 (2008-09-26)
1056	 */
1057	protected $lisymbol = '';
1058
1059	/**
1060	 * String used to mark the beginning and end of EPS image blocks.
1061	 * @protected
1062	 * @since 4.1.000 (2008-10-18)
1063	 */
1064	protected $epsmarker = 'x#!#EPS#!#x';
1065
1066	/**
1067	 * Array of transformation matrix.
1068	 * @protected
1069	 * @since 4.2.000 (2008-10-29)
1070	 */
1071	protected $transfmatrix = array();
1072
1073	/**
1074	 * Current key for transformation matrix.
1075	 * @protected
1076	 * @since 4.8.005 (2009-09-17)
1077	 */
1078	protected $transfmatrix_key = 0;
1079
1080	/**
1081	 * Booklet mode for double-sided pages.
1082	 * @protected
1083	 * @since 4.2.000 (2008-10-29)
1084	 */
1085	protected $booklet = false;
1086
1087	/**
1088	 * Epsilon value used for float calculations.
1089	 * @protected
1090	 * @since 4.2.000 (2008-10-29)
1091	 */
1092	protected $feps = 0.005;
1093
1094	/**
1095	 * Array used for custom vertical spaces for HTML tags.
1096	 * @protected
1097	 * @since 4.2.001 (2008-10-30)
1098	 */
1099	protected $tagvspaces = array();
1100
1101	/**
1102	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1103	 * @protected
1104	 * @since 4.2.007 (2008-11-12)
1105	 */
1106	protected $customlistindent = -1;
1107
1108	/**
1109	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1110	 * @protected
1111	 * @since 4.2.010 (2008-11-14)
1112	 */
1113	protected $opencell = true;
1114
1115	/**
1116	 * Array of files to embedd.
1117	 * @protected
1118	 * @since 4.4.000 (2008-12-07)
1119	 */
1120	protected $embeddedfiles = array();
1121
1122	/**
1123	 * Boolean flag to indicate if we are inside a PRE tag.
1124	 * @protected
1125	 * @since 4.4.001 (2008-12-08)
1126	 */
1127	protected $premode = false;
1128
1129	/**
1130	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1131	 * keys are the page numbers
1132	 * @protected
1133	 * @since 4.4.002 (2008-12-09)
1134	 */
1135	protected $transfmrk = array();
1136
1137	/**
1138	 * Default color for html links.
1139	 * @protected
1140	 * @since 4.4.003 (2008-12-09)
1141	 */
1142	protected $htmlLinkColorArray = array(0, 0, 255);
1143
1144	/**
1145	 * Default font style to add to html links.
1146	 * @protected
1147	 * @since 4.4.003 (2008-12-09)
1148	 */
1149	protected $htmlLinkFontStyle = 'U';
1150
1151	/**
1152	 * Counts the number of pages.
1153	 * @protected
1154	 * @since 4.5.000 (2008-12-31)
1155	 */
1156	protected $numpages = 0;
1157
1158	/**
1159	 * Array containing page lengths in bytes.
1160	 * @protected
1161	 * @since 4.5.000 (2008-12-31)
1162	 */
1163	protected $pagelen = array();
1164
1165	/**
1166	 * Counts the number of pages.
1167	 * @protected
1168	 * @since 4.5.000 (2008-12-31)
1169	 */
1170	protected $numimages = 0;
1171
1172	/**
1173	 * Store the image keys.
1174	 * @protected
1175	 * @since 4.5.000 (2008-12-31)
1176	 */
1177	protected $imagekeys = array();
1178
1179	/**
1180	 * Length of the buffer in bytes.
1181	 * @protected
1182	 * @since 4.5.000 (2008-12-31)
1183	 */
1184	protected $bufferlen = 0;
1185
1186	/**
1187	 * Counts the number of fonts.
1188	 * @protected
1189	 * @since 4.5.000 (2009-01-02)
1190	 */
1191	protected $numfonts = 0;
1192
1193	/**
1194	 * Store the font keys.
1195	 * @protected
1196	 * @since 4.5.000 (2009-01-02)
1197	 */
1198	protected $fontkeys = array();
1199
1200	/**
1201	 * Store the font object IDs.
1202	 * @protected
1203	 * @since 4.8.001 (2009-09-09)
1204	 */
1205	protected $font_obj_ids = array();
1206
1207	/**
1208	 * Store the fage status (true when opened, false when closed).
1209	 * @protected
1210	 * @since 4.5.000 (2009-01-02)
1211	 */
1212	protected $pageopen = array();
1213
1214	/**
1215	 * Default monospace font.
1216	 * @protected
1217	 * @since 4.5.025 (2009-03-10)
1218	 */
1219	protected $default_monospaced_font = 'courier';
1220
1221	/**
1222	 * Cloned copy of the current class object.
1223	 * @protected
1224	 * @since 4.5.029 (2009-03-19)
1225	 */
1226	protected $objcopy;
1227
1228	/**
1229	 * Array used to store the lengths of cache files.
1230	 * @protected
1231	 * @since 4.5.029 (2009-03-19)
1232	 */
1233	protected $cache_file_length = array();
1234
1235	/**
1236	 * Table header content to be repeated on each new page.
1237	 * @protected
1238	 * @since 4.5.030 (2009-03-20)
1239	 */
1240	protected $thead = '';
1241
1242	/**
1243	 * Margins used for table header.
1244	 * @protected
1245	 * @since 4.5.030 (2009-03-20)
1246	 */
1247	protected $theadMargins = array();
1248
1249	/**
1250	 * Boolean flag to enable document digital signature.
1251	 * @protected
1252	 * @since 4.6.005 (2009-04-24)
1253	 */
1254	protected $sign = false;
1255
1256	/**
1257	 * Digital signature data.
1258	 * @protected
1259	 * @since 4.6.005 (2009-04-24)
1260	 */
1261	protected $signature_data = array();
1262
1263	/**
1264	 * Digital signature max length.
1265	 * @protected
1266	 * @since 4.6.005 (2009-04-24)
1267	 */
1268	protected $signature_max_length = 11742;
1269
1270	/**
1271	 * Data for digital signature appearance.
1272	 * @protected
1273	 * @since 5.3.011 (2010-06-16)
1274	 */
1275	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1276
1277	/**
1278	 * Array of empty digital signature appearances.
1279	 * @protected
1280	 * @since 5.9.101 (2011-07-06)
1281	 */
1282	protected $empty_signature_appearance = array();
1283
1284	/**
1285	 * Boolean flag to enable document timestamping with TSA.
1286	 * @protected
1287	 * @since 6.0.085 (2014-06-19)
1288	 */
1289	protected $tsa_timestamp = false;
1290
1291	/**
1292	 * Timestamping data.
1293	 * @protected
1294	 * @since 6.0.085 (2014-06-19)
1295	 */
1296	protected $tsa_data = array();
1297
1298	/**
1299	 * Regular expression used to find blank characters (required for word-wrapping).
1300	 * @protected
1301	 * @since 4.6.006 (2009-04-28)
1302	 */
1303	protected $re_spaces = '/[^\S\xa0]/';
1304
1305	/**
1306	 * Array of $re_spaces parts.
1307	 * @protected
1308	 * @since 5.5.011 (2010-07-09)
1309	 */
1310	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1311
1312	/**
1313	 * Digital signature object ID.
1314	 * @protected
1315	 * @since 4.6.022 (2009-06-23)
1316	 */
1317	protected $sig_obj_id = 0;
1318
1319	/**
1320	 * ID of page objects.
1321	 * @protected
1322	 * @since 4.7.000 (2009-08-29)
1323	 */
1324	protected $page_obj_id = array();
1325
1326	/**
1327	 * List of form annotations IDs.
1328	 * @protected
1329	 * @since 4.8.000 (2009-09-07)
1330	 */
1331	protected $form_obj_id = array();
1332
1333	/**
1334	 * 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.
1335	 * @protected
1336	 * @since 4.8.000 (2009-09-07)
1337	 */
1338	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1339
1340	/**
1341	 * Javascript objects array.
1342	 * @protected
1343	 * @since 4.8.000 (2009-09-07)
1344	 */
1345	protected $js_objects = array();
1346
1347	/**
1348	 * Current form action (used during XHTML rendering).
1349	 * @protected
1350	 * @since 4.8.000 (2009-09-07)
1351	 */
1352	protected $form_action = '';
1353
1354	/**
1355	 * Current form encryption type (used during XHTML rendering).
1356	 * @protected
1357	 * @since 4.8.000 (2009-09-07)
1358	 */
1359	protected $form_enctype = 'application/x-www-form-urlencoded';
1360
1361	/**
1362	 * Current method to submit forms.
1363	 * @protected
1364	 * @since 4.8.000 (2009-09-07)
1365	 */
1366	protected $form_mode = 'post';
1367
1368	/**
1369	 * List of fonts used on form fields (fontname => fontkey).
1370	 * @protected
1371	 * @since 4.8.001 (2009-09-09)
1372	 */
1373	protected $annotation_fonts = array();
1374
1375	/**
1376	 * List of radio buttons parent objects.
1377	 * @protected
1378	 * @since 4.8.001 (2009-09-09)
1379	 */
1380	protected $radiobutton_groups = array();
1381
1382	/**
1383	 * List of radio group objects IDs.
1384	 * @protected
1385	 * @since 4.8.001 (2009-09-09)
1386	 */
1387	protected $radio_groups = array();
1388
1389	/**
1390	 * Text indentation value (used for text-indent CSS attribute).
1391	 * @protected
1392	 * @since 4.8.006 (2009-09-23)
1393	 */
1394	protected $textindent = 0;
1395
1396	/**
1397	 * Store page number when startTransaction() is called.
1398	 * @protected
1399	 * @since 4.8.006 (2009-09-23)
1400	 */
1401	protected $start_transaction_page = 0;
1402
1403	/**
1404	 * Store Y position when startTransaction() is called.
1405	 * @protected
1406	 * @since 4.9.001 (2010-03-28)
1407	 */
1408	protected $start_transaction_y = 0;
1409
1410	/**
1411	 * True when we are printing the thead section on a new page.
1412	 * @protected
1413	 * @since 4.8.027 (2010-01-25)
1414	 */
1415	protected $inthead = false;
1416
1417	/**
1418	 * Array of column measures (width, space, starting Y position).
1419	 * @protected
1420	 * @since 4.9.001 (2010-03-28)
1421	 */
1422	protected $columns = array();
1423
1424	/**
1425	 * Number of colums.
1426	 * @protected
1427	 * @since 4.9.001 (2010-03-28)
1428	 */
1429	protected $num_columns = 1;
1430
1431	/**
1432	 * Current column number.
1433	 * @protected
1434	 * @since 4.9.001 (2010-03-28)
1435	 */
1436	protected $current_column = 0;
1437
1438	/**
1439	 * Starting page for columns.
1440	 * @protected
1441	 * @since 4.9.001 (2010-03-28)
1442	 */
1443	protected $column_start_page = 0;
1444
1445	/**
1446	 * Maximum page and column selected.
1447	 * @protected
1448	 * @since 5.8.000 (2010-08-11)
1449	 */
1450	protected $maxselcol = array('page' => 0, 'column' => 0);
1451
1452	/**
1453	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1454	 * @protected
1455	 * @since 5.8.000 (2010-08-11)
1456	 */
1457	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1458
1459	/**
1460	 * 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.
1461	 * @protected
1462	 * @since 4.9.008 (2010-04-03)
1463	 */
1464	protected $textrendermode = 0;
1465
1466	/**
1467	 * Text stroke width in doc units.
1468	 * @protected
1469	 * @since 4.9.008 (2010-04-03)
1470	 */
1471	protected $textstrokewidth = 0;
1472
1473	/**
1474	 * Current stroke color.
1475	 * @protected
1476	 * @since 4.9.008 (2010-04-03)
1477	 */
1478	protected $strokecolor;
1479
1480	/**
1481	 * Default unit of measure for document.
1482	 * @protected
1483	 * @since 5.0.000 (2010-04-22)
1484	 */
1485	protected $pdfunit = 'mm';
1486
1487	/**
1488	 * Boolean flag true when we are on TOC (Table Of Content) page.
1489	 * @protected
1490	 */
1491	protected $tocpage = false;
1492
1493	/**
1494	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1495	 * @protected
1496	 * @since 5.0.000 (2010-04-26)
1497	 */
1498	protected $rasterize_vector_images = false;
1499
1500	/**
1501	 * Boolean flag: if true enables font subsetting by default.
1502	 * @protected
1503	 * @since 5.3.002 (2010-06-07)
1504	 */
1505	protected $font_subsetting = true;
1506
1507	/**
1508	 * Array of default graphic settings.
1509	 * @protected
1510	 * @since 5.5.008 (2010-07-02)
1511	 */
1512	protected $default_graphic_vars = array();
1513
1514	/**
1515	 * Array of XObjects.
1516	 * @protected
1517	 * @since 5.8.014 (2010-08-23)
1518	 */
1519	protected $xobjects = array();
1520
1521	/**
1522	 * Boolean value true when we are inside an XObject.
1523	 * @protected
1524	 * @since 5.8.017 (2010-08-24)
1525	 */
1526	protected $inxobj = false;
1527
1528	/**
1529	 * Current XObject ID.
1530	 * @protected
1531	 * @since 5.8.017 (2010-08-24)
1532	 */
1533	protected $xobjid = '';
1534
1535	/**
1536	 * Percentage of character stretching.
1537	 * @protected
1538	 * @since 5.9.000 (2010-09-29)
1539	 */
1540	protected $font_stretching = 100;
1541
1542	/**
1543	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1544	 * @protected
1545	 * @since 5.9.000 (2010-09-29)
1546	 */
1547	protected $font_spacing = 0;
1548
1549	/**
1550	 * Array of no-write regions.
1551	 * ('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)
1552	 * @protected
1553	 * @since 5.9.003 (2010-10-14)
1554	 */
1555	protected $page_regions = array();
1556
1557	/**
1558	 * Boolean value true when page region check is active.
1559	 * @protected
1560	 */
1561	protected $check_page_regions = true;
1562
1563	/**
1564	 * Array of PDF layers data.
1565	 * @protected
1566	 * @since 5.9.102 (2011-07-13)
1567	 */
1568	protected $pdflayers = array();
1569
1570	/**
1571	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1572	 * @protected
1573	 * @since 5.9.097 (2011-06-23)
1574	 */
1575	protected $dests = array();
1576
1577	/**
1578	 * Object ID for Named Destinations
1579	 * @protected
1580	 * @since 5.9.097 (2011-06-23)
1581	 */
1582	protected $n_dests;
1583
1584	/**
1585	 * Embedded Files Names
1586	 * @protected
1587	 * @since 5.9.204 (2013-01-23)
1588	 */
1589	protected $efnames = array();
1590
1591	/**
1592	 * Directory used for the last SVG image.
1593	 * @protected
1594	 * @since 5.0.000 (2010-05-05)
1595	 */
1596	protected $svgdir = '';
1597
1598	/**
1599	 *  Deafult unit of measure for SVG.
1600	 * @protected
1601	 * @since 5.0.000 (2010-05-02)
1602	 */
1603	protected $svgunit = 'px';
1604
1605	/**
1606	 * Array of SVG gradients.
1607	 * @protected
1608	 * @since 5.0.000 (2010-05-02)
1609	 */
1610	protected $svggradients = array();
1611
1612	/**
1613	 * ID of last SVG gradient.
1614	 * @protected
1615	 * @since 5.0.000 (2010-05-02)
1616	 */
1617	protected $svggradientid = 0;
1618
1619	/**
1620	 * Boolean value true when in SVG defs group.
1621	 * @protected
1622	 * @since 5.0.000 (2010-05-02)
1623	 */
1624	protected $svgdefsmode = false;
1625
1626	/**
1627	 * Array of SVG defs.
1628	 * @protected
1629	 * @since 5.0.000 (2010-05-02)
1630	 */
1631	protected $svgdefs = array();
1632
1633	/**
1634	 * Boolean value true when in SVG clipPath tag.
1635	 * @protected
1636	 * @since 5.0.000 (2010-04-26)
1637	 */
1638	protected $svgclipmode = false;
1639
1640	/**
1641	 * Array of SVG clipPath commands.
1642	 * @protected
1643	 * @since 5.0.000 (2010-05-02)
1644	 */
1645	protected $svgclippaths = array();
1646
1647	/**
1648	 * Array of SVG clipPath tranformation matrix.
1649	 * @protected
1650	 * @since 5.8.022 (2010-08-31)
1651	 */
1652	protected $svgcliptm = array();
1653
1654	/**
1655	 * ID of last SVG clipPath.
1656	 * @protected
1657	 * @since 5.0.000 (2010-05-02)
1658	 */
1659	protected $svgclipid = 0;
1660
1661	/**
1662	 * SVG text.
1663	 * @protected
1664	 * @since 5.0.000 (2010-05-02)
1665	 */
1666	protected $svgtext = '';
1667
1668	/**
1669	 * SVG text properties.
1670	 * @protected
1671	 * @since 5.8.013 (2010-08-23)
1672	 */
1673	protected $svgtextmode = array();
1674
1675	/**
1676	 * Array of SVG properties.
1677	 * @protected
1678	 * @since 5.0.000 (2010-05-02)
1679	 */
1680	protected $svgstyles = array(array(
1681		'alignment-baseline' => 'auto',
1682		'baseline-shift' => 'baseline',
1683		'clip' => 'auto',
1684		'clip-path' => 'none',
1685		'clip-rule' => 'nonzero',
1686		'color' => 'black',
1687		'color-interpolation' => 'sRGB',
1688		'color-interpolation-filters' => 'linearRGB',
1689		'color-profile' => 'auto',
1690		'color-rendering' => 'auto',
1691		'cursor' => 'auto',
1692		'direction' => 'ltr',
1693		'display' => 'inline',
1694		'dominant-baseline' => 'auto',
1695		'enable-background' => 'accumulate',
1696		'fill' => 'black',
1697		'fill-opacity' => 1,
1698		'fill-rule' => 'nonzero',
1699		'filter' => 'none',
1700		'flood-color' => 'black',
1701		'flood-opacity' => 1,
1702		'font' => '',
1703		'font-family' => 'helvetica',
1704		'font-size' => 'medium',
1705		'font-size-adjust' => 'none',
1706		'font-stretch' => 'normal',
1707		'font-style' => 'normal',
1708		'font-variant' => 'normal',
1709		'font-weight' => 'normal',
1710		'glyph-orientation-horizontal' => '0deg',
1711		'glyph-orientation-vertical' => 'auto',
1712		'image-rendering' => 'auto',
1713		'kerning' => 'auto',
1714		'letter-spacing' => 'normal',
1715		'lighting-color' => 'white',
1716		'marker' => '',
1717		'marker-end' => 'none',
1718		'marker-mid' => 'none',
1719		'marker-start' => 'none',
1720		'mask' => 'none',
1721		'opacity' => 1,
1722		'overflow' => 'auto',
1723		'pointer-events' => 'visiblePainted',
1724		'shape-rendering' => 'auto',
1725		'stop-color' => 'black',
1726		'stop-opacity' => 1,
1727		'stroke' => 'none',
1728		'stroke-dasharray' => 'none',
1729		'stroke-dashoffset' => 0,
1730		'stroke-linecap' => 'butt',
1731		'stroke-linejoin' => 'miter',
1732		'stroke-miterlimit' => 4,
1733		'stroke-opacity' => 1,
1734		'stroke-width' => 1,
1735		'text-anchor' => 'start',
1736		'text-decoration' => 'none',
1737		'text-rendering' => 'auto',
1738		'unicode-bidi' => 'normal',
1739		'visibility' => 'visible',
1740		'word-spacing' => 'normal',
1741		'writing-mode' => 'lr-tb',
1742		'text-color' => 'black',
1743		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1744		));
1745
1746	/**
1747	 * If true force sRGB color profile for all document.
1748	 * @protected
1749	 * @since 5.9.121 (2011-09-28)
1750	 */
1751	protected $force_srgb = false;
1752
1753	/**
1754	 * If true set the document to PDF/A mode.
1755	 * @protected
1756	 * @since 5.9.121 (2011-09-27)
1757	 */
1758	protected $pdfa_mode = false;
1759
1760	/**
1761	 * Document creation date-time
1762	 * @protected
1763	 * @since 5.9.152 (2012-03-22)
1764	 */
1765	protected $doc_creation_timestamp;
1766
1767	/**
1768	 * Document modification date-time
1769	 * @protected
1770	 * @since 5.9.152 (2012-03-22)
1771	 */
1772	protected $doc_modification_timestamp;
1773
1774	/**
1775	 * Custom XMP data.
1776	 * @protected
1777	 * @since 5.9.128 (2011-10-06)
1778	 */
1779	protected $custom_xmp = '';
1780
1781	/**
1782	 * Overprint mode array.
1783	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1784	 * @protected
1785	 * @since 5.9.152 (2012-03-23)
1786	 */
1787	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1788
1789	/**
1790	 * Alpha mode array.
1791	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1792	 * @protected
1793	 * @since 5.9.152 (2012-03-23)
1794	 */
1795	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1796
1797	/**
1798	 * Define the page boundaries boxes to be set on document.
1799	 * @protected
1800	 * @since 5.9.152 (2012-03-23)
1801	 */
1802	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1803
1804	/**
1805	 * If true print TCPDF meta link.
1806	 * @protected
1807	 * @since 5.9.152 (2012-03-23)
1808	 */
1809	protected $tcpdflink = true;
1810
1811	/**
1812	 * Cache array for computed GD gamma values.
1813	 * @protected
1814	 * @since 5.9.1632 (2012-06-05)
1815	 */
1816	protected $gdgammacache = array();
1817
1818	//------------------------------------------------------------
1819	// METHODS
1820	//------------------------------------------------------------
1821
1822	/**
1823	 * This is the class constructor.
1824	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1825	 *
1826	 * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
1827	 *
1828	 * @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>
1829	 * @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.
1830	 * @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().
1831	 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1832	 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1833	 * @param $diskcache (boolean) DEPRECATED FEATURE
1834	 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1835	 * @public
1836	 * @see getPageSizeFromFormat(), setPageFormat()
1837	 */
1838	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1839		/* Set internal character encoding to ASCII */
1840		if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1841			$this->internal_encoding = mb_internal_encoding();
1842			mb_internal_encoding('ASCII');
1843		}
1844		// set file ID for trailer
1845		$serformat = (is_array($format) ? json_encode($format) : $format);
1846		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1847		$this->font_obj_ids = array();
1848		$this->page_obj_id = array();
1849		$this->form_obj_id = array();
1850		// set pdf/a mode
1851		$this->pdfa_mode = $pdfa;
1852		$this->force_srgb = false;
1853		// set language direction
1854		$this->rtl = false;
1855		$this->tmprtl = false;
1856		// some checks
1857		$this->_dochecks();
1858		// initialization of properties
1859		$this->isunicode = $unicode;
1860		$this->page = 0;
1861		$this->transfmrk[0] = array();
1862		$this->pagedim = array();
1863		$this->n = 2;
1864		$this->buffer = '';
1865		$this->pages = array();
1866		$this->state = 0;
1867		$this->fonts = array();
1868		$this->FontFiles = array();
1869		$this->diffs = array();
1870		$this->images = array();
1871		$this->links = array();
1872		$this->gradients = array();
1873		$this->InFooter = false;
1874		$this->lasth = 0;
1875		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1876		$this->FontStyle = '';
1877		$this->FontSizePt = 12;
1878		$this->underline = false;
1879		$this->overline = false;
1880		$this->linethrough = false;
1881		$this->DrawColor = '0 G';
1882		$this->FillColor = '0 g';
1883		$this->TextColor = '0 g';
1884		$this->ColorFlag = false;
1885		$this->pdflayers = array();
1886		// encryption values
1887		$this->encrypted = false;
1888		$this->last_enc_key = '';
1889		// standard Unicode fonts
1890		$this->CoreFonts = array(
1891			'courier'=>'Courier',
1892			'courierB'=>'Courier-Bold',
1893			'courierI'=>'Courier-Oblique',
1894			'courierBI'=>'Courier-BoldOblique',
1895			'helvetica'=>'Helvetica',
1896			'helveticaB'=>'Helvetica-Bold',
1897			'helveticaI'=>'Helvetica-Oblique',
1898			'helveticaBI'=>'Helvetica-BoldOblique',
1899			'times'=>'Times-Roman',
1900			'timesB'=>'Times-Bold',
1901			'timesI'=>'Times-Italic',
1902			'timesBI'=>'Times-BoldItalic',
1903			'symbol'=>'Symbol',
1904			'zapfdingbats'=>'ZapfDingbats'
1905		);
1906		// set scale factor
1907		$this->setPageUnit($unit);
1908		// set page format and orientation
1909		$this->setPageFormat($format, $orientation);
1910		// page margins (1 cm)
1911		$margin = 28.35 / $this->k;
1912		$this->SetMargins($margin, $margin);
1913		$this->clMargin = $this->lMargin;
1914		$this->crMargin = $this->rMargin;
1915		// internal cell padding
1916		$cpadding = $margin / 10;
1917		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1918		// cell margins
1919		$this->setCellMargins(0, 0, 0, 0);
1920		// line width (0.2 mm)
1921		$this->LineWidth = 0.57 / $this->k;
1922		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1923		$this->linestyleCap = '0 J';
1924		$this->linestyleJoin = '0 j';
1925		$this->linestyleDash = '[] 0 d';
1926		// automatic page break
1927		$this->SetAutoPageBreak(true, (2 * $margin));
1928		// full width display mode
1929		$this->SetDisplayMode('fullwidth');
1930		// compression
1931		$this->SetCompression();
1932		// set default PDF version number
1933		$this->setPDFVersion();
1934		$this->tcpdflink = true;
1935		$this->encoding = $encoding;
1936		$this->HREF = array();
1937		$this->getFontsList();
1938		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1939		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1940		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1941		$this->extgstates = array();
1942		$this->setTextShadow();
1943		// signature
1944		$this->sign = false;
1945		$this->tsa_timestamp = false;
1946		$this->tsa_data = array();
1947		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1948		$this->empty_signature_appearance = array();
1949		// user's rights
1950		$this->ur['enabled'] = false;
1951		$this->ur['document'] = '/FullSave';
1952		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1953		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1954		$this->ur['signature'] = '/Modify';
1955		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1956		$this->ur['formex'] = '';
1957		// set default JPEG quality
1958		$this->jpeg_quality = 75;
1959		// initialize some settings
1960		TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1961		// set default font
1962		$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1963		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1964		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1965		// check if PCRE Unicode support is enabled
1966		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1967			// PCRE unicode support is turned ON
1968			// \s     : any whitespace character
1969			// \p{Z}  : any separator
1970			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1971			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1972			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1973			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1974		} else {
1975			// PCRE unicode support is turned OFF
1976			$this->setSpacesRE('/[^\S\xa0]/');
1977		}
1978		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1979		// set document creation and modification timestamp
1980		$this->doc_creation_timestamp = time();
1981		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
1982		// get default graphic vars
1983		$this->default_graphic_vars = $this->getGraphicVars();
1984		$this->header_xobj_autoreset = false;
1985		$this->custom_xmp = '';
1986		// Call cleanup method after script execution finishes or exit() is called.
1987		// NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
1988		register_shutdown_function(array($this, '_destroy'), true);
1989	}
1990
1991	/**
1992	 * Default destructor.
1993	 * @public
1994	 * @since 1.53.0.TC016
1995	 */
1996	public function __destruct() {
1997		// restore internal encoding
1998		if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1999			mb_internal_encoding($this->internal_encoding);
2000		}
2001		// cleanup
2002		$this->_destroy(true);
2003	}
2004
2005	/**
2006	 * Set the units of measure for the document.
2007	 * @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.
2008	 * @public
2009	 * @since 3.0.015 (2008-06-06)
2010	 */
2011	public function setPageUnit($unit) {
2012		$unit = strtolower($unit);
2013		//Set scale factor
2014		switch ($unit) {
2015			// points
2016			case 'px':
2017			case 'pt': {
2018				$this->k = 1;
2019				break;
2020			}
2021			// millimeters
2022			case 'mm': {
2023				$this->k = $this->dpi / 25.4;
2024				break;
2025			}
2026			// centimeters
2027			case 'cm': {
2028				$this->k = $this->dpi / 2.54;
2029				break;
2030			}
2031			// inches
2032			case 'in': {
2033				$this->k = $this->dpi;
2034				break;
2035			}
2036			// unsupported unit
2037			default : {
2038				$this->Error('Incorrect unit: '.$unit);
2039				break;
2040			}
2041		}
2042		$this->pdfunit = $unit;
2043		if (isset($this->CurOrientation)) {
2044			$this->setPageOrientation($this->CurOrientation);
2045		}
2046	}
2047
2048	/**
2049	 * Change the format of the current page
2050	 * @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 numbers (width, height) or an array containing the following measures and options:<ul>
2051	 * <li>['format'] = page format name (one of the above);</li>
2052	 * <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>
2053	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2054	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2055	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2056	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2057	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2058	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2059	 * <li>['CropBox'] : the visible region of default user space:</li>
2060	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2061	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2062	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2063	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2064	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2065	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2066	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2067	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2068	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2069	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2070	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2071	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2072	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2073	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2074	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2075	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2076	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2077	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2078	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2079	 * <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>
2080	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2081	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2082	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2083	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2084	 * <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>
2085	 * <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>
2086	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2087	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2088	 * <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>
2089	 * <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>
2090	 * <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>
2091	 * <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>
2092	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2093	 * </ul>
2094	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2095	 * <li>P or Portrait (default)</li>
2096	 * <li>L or Landscape</li>
2097	 * <li>'' (empty string) for automatic orientation</li>
2098	 * </ul>
2099	 * @protected
2100	 * @since 3.0.015 (2008-06-06)
2101	 * @see getPageSizeFromFormat()
2102	 */
2103	protected function setPageFormat($format, $orientation='P') {
2104		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2105			// remove inherited values
2106			unset($this->pagedim[$this->page]);
2107		}
2108		if (is_string($format)) {
2109			// get page measures from format name
2110			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2111			$this->fwPt = $pf[0];
2112			$this->fhPt = $pf[1];
2113		} else {
2114			// the boundaries of the physical medium on which the page shall be displayed or printed
2115			if (isset($format['MediaBox'])) {
2116				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
2117				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2118				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2119			} else {
2120				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2121					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2122				} else {
2123					if (!isset($format['format'])) {
2124						// default value
2125						$format['format'] = 'A4';
2126					}
2127					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2128				}
2129				$this->fwPt = $pf[0];
2130				$this->fhPt = $pf[1];
2131				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2132			}
2133			// the visible region of default user space
2134			if (isset($format['CropBox'])) {
2135				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
2136			}
2137			// the region to which the contents of the page shall be clipped when output in a production environment
2138			if (isset($format['BleedBox'])) {
2139				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
2140			}
2141			// the intended dimensions of the finished page after trimming
2142			if (isset($format['TrimBox'])) {
2143				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
2144			}
2145			// the page's meaningful content (including potential white space)
2146			if (isset($format['ArtBox'])) {
2147				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
2148			}
2149			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2150			if (isset($format['BoxColorInfo'])) {
2151				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2152			}
2153			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2154				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2155				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2156			}
2157			if (isset($format['PZ'])) {
2158				// The page's preferred zoom (magnification) factor
2159				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2160			}
2161			if (isset($format['trans'])) {
2162				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2163				if (isset($format['trans']['Dur'])) {
2164					// The page's display duration
2165					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2166				}
2167				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2168				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2169					// The transition style that shall be used when moving to this page from another during a presentation
2170					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2171					$valid_effect = array('Split', 'Blinds');
2172					$valid_vals = array('H', 'V');
2173					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2174						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2175					}
2176					$valid_effect = array('Split', 'Box', 'Fly');
2177					$valid_vals = array('I', 'O');
2178					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2179						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2180					}
2181					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2182					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2183						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2184							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2185							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2186							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2187						}
2188					}
2189					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2190						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2191					}
2192					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2193						$this->pagedim[$this->page]['trans']['B'] = 'true';
2194					}
2195				} else {
2196					$this->pagedim[$this->page]['trans']['S'] = 'R';
2197				}
2198				if (isset($format['trans']['D'])) {
2199					// The duration of the transition effect, in seconds
2200					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2201				} else {
2202					$this->pagedim[$this->page]['trans']['D'] = 1;
2203				}
2204			}
2205		}
2206		$this->setPageOrientation($orientation);
2207	}
2208
2209	/**
2210	 * Set page orientation.
2211	 * @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>
2212	 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2213	 * @param $bottommargin (float) bottom margin of the page.
2214	 * @public
2215	 * @since 3.0.015 (2008-06-06)
2216	 */
2217	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2218		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2219			// the boundaries of the physical medium on which the page shall be displayed or printed
2220			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2221		}
2222		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2223			// the visible region of default user space
2224			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
2225		}
2226		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2227			// the region to which the contents of the page shall be clipped when output in a production environment
2228			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2229		}
2230		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2231			// the intended dimensions of the finished page after trimming
2232			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2233		}
2234		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2235			// the page's meaningful content (including potential white space)
2236			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2237		}
2238		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2239			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2240			$this->pagedim[$this->page]['Rotate'] = 0;
2241		}
2242		if (!isset($this->pagedim[$this->page]['PZ'])) {
2243			// The page's preferred zoom (magnification) factor
2244			$this->pagedim[$this->page]['PZ'] = 1;
2245		}
2246		if ($this->fwPt > $this->fhPt) {
2247			// landscape
2248			$default_orientation = 'L';
2249		} else {
2250			// portrait
2251			$default_orientation = 'P';
2252		}
2253		$valid_orientations = array('P', 'L');
2254		if (empty($orientation)) {
2255			$orientation = $default_orientation;
2256		} else {
2257			$orientation = strtoupper($orientation[0]);
2258		}
2259		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2260			$this->CurOrientation = $orientation;
2261			$this->wPt = $this->fhPt;
2262			$this->hPt = $this->fwPt;
2263		} else {
2264			$this->CurOrientation = $default_orientation;
2265			$this->wPt = $this->fwPt;
2266			$this->hPt = $this->fhPt;
2267		}
2268		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2269			// swap X and Y coordinates (change page orientation)
2270			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2271		}
2272		$this->w = ($this->wPt / $this->k);
2273		$this->h = ($this->hPt / $this->k);
2274		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2275			if (isset($this->AutoPageBreak)) {
2276				$autopagebreak = $this->AutoPageBreak;
2277			} else {
2278				$autopagebreak = true;
2279			}
2280		}
2281		if (TCPDF_STATIC::empty_string($bottommargin)) {
2282			if (isset($this->bMargin)) {
2283				$bottommargin = $this->bMargin;
2284			} else {
2285				// default value = 2 cm
2286				$bottommargin = 2 * 28.35 / $this->k;
2287			}
2288		}
2289		$this->SetAutoPageBreak($autopagebreak, $bottommargin);
2290		// store page dimensions
2291		$this->pagedim[$this->page]['w'] = $this->wPt;
2292		$this->pagedim[$this->page]['h'] = $this->hPt;
2293		$this->pagedim[$this->page]['wk'] = $this->w;
2294		$this->pagedim[$this->page]['hk'] = $this->h;
2295		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2296		$this->pagedim[$this->page]['bm'] = $bottommargin;
2297		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2298		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2299		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2300		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2301		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2302		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2303	}
2304
2305	/**
2306	 * Set regular expression to detect withespaces or word separators.
2307	 * The pattern delimiter must be the forward-slash character "/".
2308	 * Some example patterns are:
2309	 * <pre>
2310	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2311	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2312	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2313	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2314	 *      \s     : any whitespace character
2315	 *      \p{Z}  : any separator
2316	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2317	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2318	 * </pre>
2319	 * @param $re (string) regular expression (leave empty for default).
2320	 * @public
2321	 * @since 4.6.016 (2009-06-15)
2322	 */
2323	public function setSpacesRE($re='/[^\S\xa0]/') {
2324		$this->re_spaces = $re;
2325		$re_parts = explode('/', $re);
2326		// get pattern parts
2327		$this->re_space = array();
2328		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2329			$this->re_space['p'] = $re_parts[1];
2330		} else {
2331			$this->re_space['p'] = '[\s]';
2332		}
2333		// set pattern modifiers
2334		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2335			$this->re_space['m'] = $re_parts[2];
2336		} else {
2337			$this->re_space['m'] = '';
2338		}
2339	}
2340
2341	/**
2342	 * Enable or disable Right-To-Left language mode
2343	 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2344	 * @param $resetx (Boolean) if true reset the X position on direction change.
2345	 * @public
2346	 * @since 2.0.000 (2008-01-03)
2347	 */
2348	public function setRTL($enable, $resetx=true) {
2349		$enable = $enable ? true : false;
2350		$resetx = ($resetx AND ($enable != $this->rtl));
2351		$this->rtl = $enable;
2352		$this->tmprtl = false;
2353		if ($resetx) {
2354			$this->Ln(0);
2355		}
2356	}
2357
2358	/**
2359	 * Return the RTL status
2360	 * @return boolean
2361	 * @public
2362	 * @since 4.0.012 (2008-07-24)
2363	 */
2364	public function getRTL() {
2365		return $this->rtl;
2366	}
2367
2368	/**
2369	 * Force temporary RTL language direction
2370	 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2371	 * @public
2372	 * @since 2.1.000 (2008-01-09)
2373	 */
2374	public function setTempRTL($mode) {
2375		$newmode = false;
2376		switch (strtoupper($mode)) {
2377			case 'LTR':
2378			case 'L': {
2379				if ($this->rtl) {
2380					$newmode = 'L';
2381				}
2382				break;
2383			}
2384			case 'RTL':
2385			case 'R': {
2386				if (!$this->rtl) {
2387					$newmode = 'R';
2388				}
2389				break;
2390			}
2391			case false:
2392			default: {
2393				$newmode = false;
2394				break;
2395			}
2396		}
2397		$this->tmprtl = $newmode;
2398	}
2399
2400	/**
2401	 * Return the current temporary RTL status
2402	 * @return boolean
2403	 * @public
2404	 * @since 4.8.014 (2009-11-04)
2405	 */
2406	public function isRTLTextDir() {
2407		return ($this->rtl OR ($this->tmprtl == 'R'));
2408	}
2409
2410	/**
2411	 * Set the last cell height.
2412	 * @param $h (float) cell height.
2413	 * @author Nicola Asuni
2414	 * @public
2415	 * @since 1.53.0.TC034
2416	 */
2417	public function setLastH($h) {
2418		$this->lasth = $h;
2419	}
2420
2421	/**
2422	 * Return the cell height
2423	 * @param $fontsize (int) Font size in internal units
2424	 * @param $padding (boolean) If true add cell padding
2425	 * @public
2426	 */
2427	public function getCellHeight($fontsize, $padding=TRUE) {
2428		$height = ($fontsize * $this->cell_height_ratio);
2429		if ($padding) {
2430			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2431		}
2432		return round($height, 6);
2433	}
2434
2435	/**
2436	 * Reset the last cell height.
2437	 * @public
2438	 * @since 5.9.000 (2010-10-03)
2439	 */
2440	public function resetLastH() {
2441		$this->lasth = $this->getCellHeight($this->FontSize);
2442	}
2443
2444	/**
2445	 * Get the last cell height.
2446	 * @return last cell height
2447	 * @public
2448	 * @since 4.0.017 (2008-08-05)
2449	 */
2450	public function getLastH() {
2451		return $this->lasth;
2452	}
2453
2454	/**
2455	 * Set the adjusting factor to convert pixels to user units.
2456	 * @param $scale (float) adjusting factor to convert pixels to user units.
2457	 * @author Nicola Asuni
2458	 * @public
2459	 * @since 1.5.2
2460	 */
2461	public function setImageScale($scale) {
2462		$this->imgscale = $scale;
2463	}
2464
2465	/**
2466	 * Returns the adjusting factor to convert pixels to user units.
2467	 * @return float adjusting factor to convert pixels to user units.
2468	 * @author Nicola Asuni
2469	 * @public
2470	 * @since 1.5.2
2471	 */
2472	public function getImageScale() {
2473		return $this->imgscale;
2474	}
2475
2476	/**
2477	 * Returns an array of page dimensions:
2478	 * <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>
2479	 * @param $pagenum (int) page number (empty = current page)
2480	 * @return array of page dimensions.
2481	 * @author Nicola Asuni
2482	 * @public
2483	 * @since 4.5.027 (2009-03-16)
2484	 */
2485	public function getPageDimensions($pagenum='') {
2486		if (empty($pagenum)) {
2487			$pagenum = $this->page;
2488		}
2489		return $this->pagedim[$pagenum];
2490	}
2491
2492	/**
2493	 * Returns the page width in units.
2494	 * @param $pagenum (int) page number (empty = current page)
2495	 * @return int page width.
2496	 * @author Nicola Asuni
2497	 * @public
2498	 * @since 1.5.2
2499	 * @see getPageDimensions()
2500	 */
2501	public function getPageWidth($pagenum='') {
2502		if (empty($pagenum)) {
2503			return $this->w;
2504		}
2505		return $this->pagedim[$pagenum]['w'];
2506	}
2507
2508	/**
2509	 * Returns the page height in units.
2510	 * @param $pagenum (int) page number (empty = current page)
2511	 * @return int page height.
2512	 * @author Nicola Asuni
2513	 * @public
2514	 * @since 1.5.2
2515	 * @see getPageDimensions()
2516	 */
2517	public function getPageHeight($pagenum='') {
2518		if (empty($pagenum)) {
2519			return $this->h;
2520		}
2521		return $this->pagedim[$pagenum]['h'];
2522	}
2523
2524	/**
2525	 * Returns the page break margin.
2526	 * @param $pagenum (int) page number (empty = current page)
2527	 * @return int page break margin.
2528	 * @author Nicola Asuni
2529	 * @public
2530	 * @since 1.5.2
2531	 * @see getPageDimensions()
2532	 */
2533	public function getBreakMargin($pagenum='') {
2534		if (empty($pagenum)) {
2535			return $this->bMargin;
2536		}
2537		return $this->pagedim[$pagenum]['bm'];
2538	}
2539
2540	/**
2541	 * Returns the scale factor (number of points in user unit).
2542	 * @return int scale factor.
2543	 * @author Nicola Asuni
2544	 * @public
2545	 * @since 1.5.2
2546	 */
2547	public function getScaleFactor() {
2548		return $this->k;
2549	}
2550
2551	/**
2552	 * Defines the left, top and right margins.
2553	 * @param $left (float) Left margin.
2554	 * @param $top (float) Top margin.
2555	 * @param $right (float) Right margin. Default value is the left one.
2556	 * @param $keepmargins (boolean) if true overwrites the default page margins
2557	 * @public
2558	 * @since 1.0
2559	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2560	 */
2561	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2562		//Set left, top and right margins
2563		$this->lMargin = $left;
2564		$this->tMargin = $top;
2565		if ($right == -1) {
2566			$right = $left;
2567		}
2568		$this->rMargin = $right;
2569		if ($keepmargins) {
2570			// overwrite original values
2571			$this->original_lMargin = $this->lMargin;
2572			$this->original_rMargin = $this->rMargin;
2573		}
2574	}
2575
2576	/**
2577	 * 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.
2578	 * @param $margin (float) The margin.
2579	 * @public
2580	 * @since 1.4
2581	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2582	 */
2583	public function SetLeftMargin($margin) {
2584		//Set left margin
2585		$this->lMargin = $margin;
2586		if (($this->page > 0) AND ($this->x < $margin)) {
2587			$this->x = $margin;
2588		}
2589	}
2590
2591	/**
2592	 * Defines the top margin. The method can be called before creating the first page.
2593	 * @param $margin (float) The margin.
2594	 * @public
2595	 * @since 1.5
2596	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2597	 */
2598	public function SetTopMargin($margin) {
2599		//Set top margin
2600		$this->tMargin = $margin;
2601		if (($this->page > 0) AND ($this->y < $margin)) {
2602			$this->y = $margin;
2603		}
2604	}
2605
2606	/**
2607	 * Defines the right margin. The method can be called before creating the first page.
2608	 * @param $margin (float) The margin.
2609	 * @public
2610	 * @since 1.5
2611	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2612	 */
2613	public function SetRightMargin($margin) {
2614		$this->rMargin = $margin;
2615		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2616			$this->x = $this->w - $margin;
2617		}
2618	}
2619
2620	/**
2621	 * Set the same internal Cell padding for top, right, bottom, left-
2622	 * @param $pad (float) internal padding.
2623	 * @public
2624	 * @since 2.1.000 (2008-01-09)
2625	 * @see getCellPaddings(), setCellPaddings()
2626	 */
2627	public function SetCellPadding($pad) {
2628		if ($pad >= 0) {
2629			$this->cell_padding['L'] = $pad;
2630			$this->cell_padding['T'] = $pad;
2631			$this->cell_padding['R'] = $pad;
2632			$this->cell_padding['B'] = $pad;
2633		}
2634	}
2635
2636	/**
2637	 * Set the internal Cell paddings.
2638	 * @param $left (float) left padding
2639	 * @param $top (float) top padding
2640	 * @param $right (float) right padding
2641	 * @param $bottom (float) bottom padding
2642	 * @public
2643	 * @since 5.9.000 (2010-10-03)
2644	 * @see getCellPaddings(), SetCellPadding()
2645	 */
2646	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2647		if (($left !== '') AND ($left >= 0)) {
2648			$this->cell_padding['L'] = $left;
2649		}
2650		if (($top !== '') AND ($top >= 0)) {
2651			$this->cell_padding['T'] = $top;
2652		}
2653		if (($right !== '') AND ($right >= 0)) {
2654			$this->cell_padding['R'] = $right;
2655		}
2656		if (($bottom !== '') AND ($bottom >= 0)) {
2657			$this->cell_padding['B'] = $bottom;
2658		}
2659	}
2660
2661	/**
2662	 * Get the internal Cell padding array.
2663	 * @return array of padding values
2664	 * @public
2665	 * @since 5.9.000 (2010-10-03)
2666	 * @see setCellPaddings(), SetCellPadding()
2667	 */
2668	public function getCellPaddings() {
2669		return $this->cell_padding;
2670	}
2671
2672	/**
2673	 * Set the internal Cell margins.
2674	 * @param $left (float) left margin
2675	 * @param $top (float) top margin
2676	 * @param $right (float) right margin
2677	 * @param $bottom (float) bottom margin
2678	 * @public
2679	 * @since 5.9.000 (2010-10-03)
2680	 * @see getCellMargins()
2681	 */
2682	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2683		if (($left !== '') AND ($left >= 0)) {
2684			$this->cell_margin['L'] = $left;
2685		}
2686		if (($top !== '') AND ($top >= 0)) {
2687			$this->cell_margin['T'] = $top;
2688		}
2689		if (($right !== '') AND ($right >= 0)) {
2690			$this->cell_margin['R'] = $right;
2691		}
2692		if (($bottom !== '') AND ($bottom >= 0)) {
2693			$this->cell_margin['B'] = $bottom;
2694		}
2695	}
2696
2697	/**
2698	 * Get the internal Cell margin array.
2699	 * @return array of margin values
2700	 * @public
2701	 * @since 5.9.000 (2010-10-03)
2702	 * @see setCellMargins()
2703	 */
2704	public function getCellMargins() {
2705		return $this->cell_margin;
2706	}
2707
2708	/**
2709	 * Adjust the internal Cell padding array to take account of the line width.
2710	 * @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)))
2711	 * @return array of adjustments
2712	 * @public
2713	 * @since 5.9.000 (2010-10-03)
2714	 */
2715	protected function adjustCellPadding($brd=0) {
2716		if (empty($brd)) {
2717			return;
2718		}
2719		if (is_string($brd)) {
2720			// convert string to array
2721			$slen = strlen($brd);
2722			$newbrd = array();
2723			for ($i = 0; $i < $slen; ++$i) {
2724				$newbrd[$brd[$i]] = true;
2725			}
2726			$brd = $newbrd;
2727		} elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2728			$brd = array('LRTB' => true);
2729		}
2730		if (!is_array($brd)) {
2731			return;
2732		}
2733		// store current cell padding
2734		$cp = $this->cell_padding;
2735		// select border mode
2736		if (isset($brd['mode'])) {
2737			$mode = $brd['mode'];
2738			unset($brd['mode']);
2739		} else {
2740			$mode = 'normal';
2741		}
2742		// process borders
2743		foreach ($brd as $border => $style) {
2744			$line_width = $this->LineWidth;
2745			if (is_array($style) AND isset($style['width'])) {
2746				// get border width
2747				$line_width = $style['width'];
2748			}
2749			$adj = 0; // line width inside the cell
2750			switch ($mode) {
2751				case 'ext': {
2752					$adj = 0;
2753					break;
2754				}
2755				case 'int': {
2756					$adj = $line_width;
2757					break;
2758				}
2759				case 'normal':
2760				default: {
2761					$adj = ($line_width / 2);
2762					break;
2763				}
2764			}
2765			// correct internal cell padding if required to avoid overlap between text and lines
2766			if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2767				$this->cell_padding['T'] = $adj;
2768			}
2769			if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2770				$this->cell_padding['R'] = $adj;
2771			}
2772			if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2773				$this->cell_padding['B'] = $adj;
2774			}
2775			if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2776				$this->cell_padding['L'] = $adj;
2777			}
2778		}
2779		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']));
2780	}
2781
2782	/**
2783	 * 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.
2784	 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2785	 * @param $margin (float) Distance from the bottom of the page.
2786	 * @public
2787	 * @since 1.0
2788	 * @see Cell(), MultiCell(), AcceptPageBreak()
2789	 */
2790	public function SetAutoPageBreak($auto, $margin=0) {
2791		$this->AutoPageBreak = $auto ? true : false;
2792		$this->bMargin = $margin;
2793		$this->PageBreakTrigger = $this->h - $margin;
2794	}
2795
2796	/**
2797	 * Return the auto-page-break mode (true or false).
2798	 * @return boolean auto-page-break mode
2799	 * @public
2800	 * @since 5.9.088
2801	 */
2802	public function getAutoPageBreak() {
2803		return $this->AutoPageBreak;
2804	}
2805
2806	/**
2807	 * Defines the way the document is to be displayed by the viewer.
2808	 * @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>
2809	 * @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>
2810	 * @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>
2811	 * @public
2812	 * @since 1.2
2813	 */
2814	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2815		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2816			$this->ZoomMode = $zoom;
2817		} else {
2818			$this->Error('Incorrect zoom display mode: '.$zoom);
2819		}
2820		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2821		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2822	}
2823
2824	/**
2825	 * 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.
2826	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2827	 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2828	 * @public
2829	 * @since 1.4
2830	 */
2831	public function SetCompression($compress=true) {
2832		if (function_exists('gzcompress')) {
2833			$this->compress = $compress ? true : false;
2834		} else {
2835			$this->compress = false;
2836		}
2837	}
2838
2839	/**
2840	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2841	 * @param $mode (boolean) If true force sRGB output intent.
2842	 * @public
2843	 * @since 5.9.121 (2011-09-28)
2844	 */
2845	public function setSRGBmode($mode=false) {
2846		$this->force_srgb = $mode ? true : false;
2847	}
2848
2849	/**
2850	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2851	 * This has effect only when unicode mode is set to false.
2852	 * @param $unicode (boolean) if true set the meta information in Unicode
2853	 * @since 5.9.027 (2010-12-01)
2854	 * @public
2855	 */
2856	public function SetDocInfoUnicode($unicode=true) {
2857		$this->docinfounicode = $unicode ? true : false;
2858	}
2859
2860	/**
2861	 * Defines the title of the document.
2862	 * @param $title (string) The title.
2863	 * @public
2864	 * @since 1.2
2865	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2866	 */
2867	public function SetTitle($title) {
2868		$this->title = $title;
2869	}
2870
2871	/**
2872	 * Defines the subject of the document.
2873	 * @param $subject (string) The subject.
2874	 * @public
2875	 * @since 1.2
2876	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2877	 */
2878	public function SetSubject($subject) {
2879		$this->subject = $subject;
2880	}
2881
2882	/**
2883	 * Defines the author of the document.
2884	 * @param $author (string) The name of the author.
2885	 * @public
2886	 * @since 1.2
2887	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2888	 */
2889	public function SetAuthor($author) {
2890		$this->author = $author;
2891	}
2892
2893	/**
2894	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2895	 * @param $keywords (string) The list of keywords.
2896	 * @public
2897	 * @since 1.2
2898	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2899	 */
2900	public function SetKeywords($keywords) {
2901		$this->keywords = $keywords;
2902	}
2903
2904	/**
2905	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2906	 * @param $creator (string) The name of the creator.
2907	 * @public
2908	 * @since 1.2
2909	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2910	 */
2911	public function SetCreator($creator) {
2912		$this->creator = $creator;
2913	}
2914
2915	/**
2916	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2917	 * @param $msg (string) The error message
2918	 * @public
2919	 * @since 1.0
2920	 */
2921	public function Error($msg) {
2922		// unset all class variables
2923		$this->_destroy(true);
2924		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2925			die('<strong>TCPDF ERROR: </strong>'.$msg);
2926		} else {
2927			throw new Exception('TCPDF ERROR: '.$msg);
2928		}
2929	}
2930
2931	/**
2932	 * This method begins the generation of the PDF document.
2933	 * It is not necessary to call it explicitly because AddPage() does it automatically.
2934	 * Note: no page is created by this method
2935	 * @public
2936	 * @since 1.0
2937	 * @see AddPage(), Close()
2938	 */
2939	public function Open() {
2940		$this->state = 1;
2941	}
2942
2943	/**
2944	 * Terminates the PDF document.
2945	 * It is not necessary to call this method explicitly because Output() does it automatically.
2946	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2947	 * @public
2948	 * @since 1.0
2949	 * @see Open(), Output()
2950	 */
2951	public function Close() {
2952		if ($this->state == 3) {
2953			return;
2954		}
2955		if ($this->page == 0) {
2956			$this->AddPage();
2957		}
2958		$this->endLayer();
2959		if ($this->tcpdflink) {
2960			// save current graphic settings
2961			$gvars = $this->getGraphicVars();
2962			$this->setEqualColumns();
2963			$this->lastpage(true);
2964			$this->SetAutoPageBreak(false);
2965			$this->x = 0;
2966			$this->y = $this->h - (1 / $this->k);
2967			$this->lMargin = 0;
2968			$this->_outSaveGraphicsState();
2969			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2970			$this->SetFont($font, '', 1);
2971			$this->setTextRenderingMode(0, false, false);
2972			$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";
2973			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2974			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2975			$this->_outRestoreGraphicsState();
2976			// restore graphic settings
2977			$this->setGraphicVars($gvars);
2978		}
2979		// close page
2980		$this->endPage();
2981		// close document
2982		$this->_enddoc();
2983		// unset all class variables (except critical ones)
2984		$this->_destroy(false);
2985	}
2986
2987	/**
2988	 * Move pointer at the specified document page and update page dimensions.
2989	 * @param $pnum (int) page number (1 ... numpages)
2990	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2991	 * @public
2992	 * @since 2.1.000 (2008-01-07)
2993	 * @see getPage(), lastpage(), getNumPages()
2994	 */
2995	public function setPage($pnum, $resetmargins=false) {
2996		if (($pnum == $this->page) AND ($this->state == 2)) {
2997			return;
2998		}
2999		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3000			$this->state = 2;
3001			// save current graphic settings
3002			//$gvars = $this->getGraphicVars();
3003			$oldpage = $this->page;
3004			$this->page = $pnum;
3005			$this->wPt = $this->pagedim[$this->page]['w'];
3006			$this->hPt = $this->pagedim[$this->page]['h'];
3007			$this->w = $this->pagedim[$this->page]['wk'];
3008			$this->h = $this->pagedim[$this->page]['hk'];
3009			$this->tMargin = $this->pagedim[$this->page]['tm'];
3010			$this->bMargin = $this->pagedim[$this->page]['bm'];
3011			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3012			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3013			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3014			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3015			$this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3016			// restore graphic settings
3017			//$this->setGraphicVars($gvars);
3018			if ($resetmargins) {
3019				$this->lMargin = $this->pagedim[$this->page]['olm'];
3020				$this->rMargin = $this->pagedim[$this->page]['orm'];
3021				$this->SetY($this->tMargin);
3022			} else {
3023				// account for booklet mode
3024				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3025					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3026					$this->lMargin += $deltam;
3027					$this->rMargin -= $deltam;
3028				}
3029			}
3030		} else {
3031			$this->Error('Wrong page number on setPage() function: '.$pnum);
3032		}
3033	}
3034
3035	/**
3036	 * Reset pointer to the last document page.
3037	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3038	 * @public
3039	 * @since 2.0.000 (2008-01-04)
3040	 * @see setPage(), getPage(), getNumPages()
3041	 */
3042	public function lastPage($resetmargins=false) {
3043		$this->setPage($this->getNumPages(), $resetmargins);
3044	}
3045
3046	/**
3047	 * Get current document page number.
3048	 * @return int page number
3049	 * @public
3050	 * @since 2.1.000 (2008-01-07)
3051	 * @see setPage(), lastpage(), getNumPages()
3052	 */
3053	public function getPage() {
3054		return $this->page;
3055	}
3056
3057	/**
3058	 * Get the total number of insered pages.
3059	 * @return int number of pages
3060	 * @public
3061	 * @since 2.1.000 (2008-01-07)
3062	 * @see setPage(), getPage(), lastpage()
3063	 */
3064	public function getNumPages() {
3065		return $this->numpages;
3066	}
3067
3068	/**
3069	 * Adds a new TOC (Table Of Content) page to the document.
3070	 * @param $orientation (string) page orientation.
3071	 * @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().
3072	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3073	 * @public
3074	 * @since 5.0.001 (2010-05-06)
3075	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3076	 */
3077	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3078		$this->AddPage($orientation, $format, $keepmargins, true);
3079	}
3080
3081	/**
3082	 * Terminate the current TOC (Table Of Content) page
3083	 * @public
3084	 * @since 5.0.001 (2010-05-06)
3085	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3086	 */
3087	public function endTOCPage() {
3088		$this->endPage(true);
3089	}
3090
3091	/**
3092	 * 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).
3093	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3094	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3095	 * @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().
3096	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3097	 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3098	 * @public
3099	 * @since 1.0
3100	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3101	 */
3102	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3103		if ($this->inxobj) {
3104			// we are inside an XObject template
3105			return;
3106		}
3107		if (!isset($this->original_lMargin) OR $keepmargins) {
3108			$this->original_lMargin = $this->lMargin;
3109		}
3110		if (!isset($this->original_rMargin) OR $keepmargins) {
3111			$this->original_rMargin = $this->rMargin;
3112		}
3113		// terminate previous page
3114		$this->endPage();
3115		// start new page
3116		$this->startPage($orientation, $format, $tocpage);
3117	}
3118
3119	/**
3120	 * Terminate the current page
3121	 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3122	 * @public
3123	 * @since 4.2.010 (2008-11-14)
3124	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3125	 */
3126	public function endPage($tocpage=false) {
3127		// check if page is already closed
3128		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3129			return;
3130		}
3131		// print page footer
3132		$this->setFooter();
3133		// close page
3134		$this->_endpage();
3135		// mark page as closed
3136		$this->pageopen[$this->page] = false;
3137		if ($tocpage) {
3138			$this->tocpage = false;
3139		}
3140	}
3141
3142	/**
3143	 * Starts a new page to the document. The page must be closed using the endPage() function.
3144	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3145	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3146	 * @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().
3147	 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3148	 * @since 4.2.010 (2008-11-14)
3149	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3150	 * @public
3151	 */
3152	public function startPage($orientation='', $format='', $tocpage=false) {
3153		if ($tocpage) {
3154			$this->tocpage = true;
3155		}
3156		// move page numbers of documents to be attached
3157		if ($this->tocpage) {
3158			// move reference to unexistent pages (used for page attachments)
3159			// adjust outlines
3160			$tmpoutlines = $this->outlines;
3161			foreach ($tmpoutlines as $key => $outline) {
3162				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3163					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3164				}
3165			}
3166			// adjust dests
3167			$tmpdests = $this->dests;
3168			foreach ($tmpdests as $key => $dest) {
3169				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3170					$this->dests[$key]['p'] = ($dest['p'] + 1);
3171				}
3172			}
3173			// adjust links
3174			$tmplinks = $this->links;
3175			foreach ($tmplinks as $key => $link) {
3176				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3177					$this->links[$key]['p'] = ($link['p'] + 1);
3178				}
3179			}
3180		}
3181		if ($this->numpages > $this->page) {
3182			// this page has been already added
3183			$this->setPage($this->page + 1);
3184			$this->SetY($this->tMargin);
3185			return;
3186		}
3187		// start a new page
3188		if ($this->state == 0) {
3189			$this->Open();
3190		}
3191		++$this->numpages;
3192		$this->swapMargins($this->booklet);
3193		// save current graphic settings
3194		$gvars = $this->getGraphicVars();
3195		// start new page
3196		$this->_beginpage($orientation, $format);
3197		// mark page as open
3198		$this->pageopen[$this->page] = true;
3199		// restore graphic settings
3200		$this->setGraphicVars($gvars);
3201		// mark this point
3202		$this->setPageMark();
3203		// print page header
3204		$this->setHeader();
3205		// restore graphic settings
3206		$this->setGraphicVars($gvars);
3207		// mark this point
3208		$this->setPageMark();
3209		// print table header (if any)
3210		$this->setTableHeader();
3211		// set mark for empty page check
3212		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3213	}
3214
3215	/**
3216	 * Set start-writing mark on current page stream used to put borders and fills.
3217	 * Borders and fills are always created after content and inserted on the position marked by this method.
3218	 * This function must be called after calling Image() function for a background image.
3219	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3220	 * @public
3221	 * @since 4.0.016 (2008-07-30)
3222	 */
3223	public function setPageMark() {
3224		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3225		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3226		$this->setContentMark();
3227	}
3228
3229	/**
3230	 * Set start-writing mark on selected page.
3231	 * Borders and fills are always created after content and inserted on the position marked by this method.
3232	 * @param $page (int) page number (default is the current page)
3233	 * @protected
3234	 * @since 4.6.021 (2009-07-20)
3235	 */
3236	protected function setContentMark($page=0) {
3237		if ($page <= 0) {
3238			$page = $this->page;
3239		}
3240		if (isset($this->footerlen[$page])) {
3241			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3242		} else {
3243			$this->cntmrk[$page] = $this->pagelen[$page];
3244		}
3245	}
3246
3247	/**
3248	 * Set header data.
3249	 * @param $ln (string) header image logo
3250	 * @param $lw (string) header image logo width in mm
3251	 * @param $ht (string) string to print as title on document header
3252	 * @param $hs (string) string to print on document header
3253	 * @param $tc (array) RGB array color for text.
3254	 * @param $lc (array) RGB array color for line.
3255	 * @public
3256	 */
3257	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3258		$this->header_logo = $ln;
3259		$this->header_logo_width = $lw;
3260		$this->header_title = $ht;
3261		$this->header_string = $hs;
3262		$this->header_text_color = $tc;
3263		$this->header_line_color = $lc;
3264	}
3265
3266	/**
3267	 * Set footer data.
3268	 * @param $tc (array) RGB array color for text.
3269	 * @param $lc (array) RGB array color for line.
3270	 * @public
3271	 */
3272	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3273		$this->footer_text_color = $tc;
3274		$this->footer_line_color = $lc;
3275	}
3276
3277	/**
3278	 * Returns header data:
3279	 * <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>
3280	 * @return array()
3281	 * @public
3282	 * @since 4.0.012 (2008-07-24)
3283	 */
3284	public function getHeaderData() {
3285		$ret = array();
3286		$ret['logo'] = $this->header_logo;
3287		$ret['logo_width'] = $this->header_logo_width;
3288		$ret['title'] = $this->header_title;
3289		$ret['string'] = $this->header_string;
3290		$ret['text_color'] = $this->header_text_color;
3291		$ret['line_color'] = $this->header_line_color;
3292		return $ret;
3293	}
3294
3295	/**
3296	 * Set header margin.
3297	 * (minimum distance between header and top page margin)
3298	 * @param $hm (int) distance in user units
3299	 * @public
3300	 */
3301	public function setHeaderMargin($hm=10) {
3302		$this->header_margin = $hm;
3303	}
3304
3305	/**
3306	 * Returns header margin in user units.
3307	 * @return float
3308	 * @since 4.0.012 (2008-07-24)
3309	 * @public
3310	 */
3311	public function getHeaderMargin() {
3312		return $this->header_margin;
3313	}
3314
3315	/**
3316	 * Set footer margin.
3317	 * (minimum distance between footer and bottom page margin)
3318	 * @param $fm (int) distance in user units
3319	 * @public
3320	 */
3321	public function setFooterMargin($fm=10) {
3322		$this->footer_margin = $fm;
3323	}
3324
3325	/**
3326	 * Returns footer margin in user units.
3327	 * @return float
3328	 * @since 4.0.012 (2008-07-24)
3329	 * @public
3330	 */
3331	public function getFooterMargin() {
3332		return $this->footer_margin;
3333	}
3334	/**
3335	 * Set a flag to print page header.
3336	 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3337	 * @public
3338	 */
3339	public function setPrintHeader($val=true) {
3340		$this->print_header = $val ? true : false;
3341	}
3342
3343	/**
3344	 * Set a flag to print page footer.
3345	 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3346	 * @public
3347	 */
3348	public function setPrintFooter($val=true) {
3349		$this->print_footer = $val ? true : false;
3350	}
3351
3352	/**
3353	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3354	 * @return float
3355	 * @public
3356	 */
3357	public function getImageRBX() {
3358		return $this->img_rb_x;
3359	}
3360
3361	/**
3362	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3363	 * @return float
3364	 * @public
3365	 */
3366	public function getImageRBY() {
3367		return $this->img_rb_y;
3368	}
3369
3370	/**
3371	 * Reset the xobject template used by Header() method.
3372	 * @public
3373	 */
3374	public function resetHeaderTemplate() {
3375		$this->header_xobjid = false;
3376	}
3377
3378	/**
3379	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3380	 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3381	 * @public
3382	 */
3383	public function setHeaderTemplateAutoreset($val=true) {
3384		$this->header_xobj_autoreset = $val ? true : false;
3385	}
3386
3387	/**
3388	 * This method is used to render the page header.
3389	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3390	 * @public
3391	 */
3392	public function Header() {
3393		if ($this->header_xobjid === false) {
3394			// start a new XObject Template
3395			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3396			$headerfont = $this->getHeaderFont();
3397			$headerdata = $this->getHeaderData();
3398			$this->y = $this->header_margin;
3399			if ($this->rtl) {
3400				$this->x = $this->w - $this->original_rMargin;
3401			} else {
3402				$this->x = $this->original_lMargin;
3403			}
3404			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3405				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3406				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3407					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3408				} elseif ($imgtype == 'svg') {
3409					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3410				} else {
3411					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3412				}
3413				$imgy = $this->getImageRBY();
3414			} else {
3415				$imgy = $this->y;
3416			}
3417			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3418			// set starting margin for text data cell
3419			if ($this->getRTL()) {
3420				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3421			} else {
3422				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3423			}
3424			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3425			$this->SetTextColorArray($this->header_text_color);
3426			// header title
3427			$this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3428			$this->SetX($header_x);
3429			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3430			// header string
3431			$this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3432			$this->SetX($header_x);
3433			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3434			// print an ending header line
3435			$this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3436			$this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3437			if ($this->rtl) {
3438				$this->SetX($this->original_rMargin);
3439			} else {
3440				$this->SetX($this->original_lMargin);
3441			}
3442			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3443			$this->endTemplate();
3444		}
3445		// print header template
3446		$x = 0;
3447		$dx = 0;
3448		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3449			// adjust margins for booklet mode
3450			$dx = ($this->original_lMargin - $this->original_rMargin);
3451		}
3452		if ($this->rtl) {
3453			$x = $this->w + $dx;
3454		} else {
3455			$x = 0 + $dx;
3456		}
3457		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3458		if ($this->header_xobj_autoreset) {
3459			// reset header xobject template at each page
3460			$this->header_xobjid = false;
3461		}
3462	}
3463
3464	/**
3465	 * This method is used to render the page footer.
3466	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3467	 * @public
3468	 */
3469	public function Footer() {
3470		$cur_y = $this->y;
3471		$this->SetTextColorArray($this->footer_text_color);
3472		//set style for cell border
3473		$line_width = (0.85 / $this->k);
3474		$this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3475		//print document barcode
3476		$barcode = $this->getBarcode();
3477		if (!empty($barcode)) {
3478			$this->Ln($line_width);
3479			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3480			$style = array(
3481				'position' => $this->rtl?'R':'L',
3482				'align' => $this->rtl?'R':'L',
3483				'stretch' => false,
3484				'fitwidth' => true,
3485				'cellfitalign' => '',
3486				'border' => false,
3487				'padding' => 0,
3488				'fgcolor' => array(0,0,0),
3489				'bgcolor' => false,
3490				'text' => false
3491			);
3492			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3493		}
3494		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3495		if (empty($this->pagegroups)) {
3496			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3497		} else {
3498			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3499		}
3500		$this->SetY($cur_y);
3501		//Print page number
3502		if ($this->getRTL()) {
3503			$this->SetX($this->original_rMargin);
3504			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3505		} else {
3506			$this->SetX($this->original_lMargin);
3507			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3508		}
3509	}
3510
3511	/**
3512	 * This method is used to render the page header.
3513	 * @protected
3514	 * @since 4.0.012 (2008-07-24)
3515	 */
3516	protected function setHeader() {
3517		if (!$this->print_header OR ($this->state != 2)) {
3518			return;
3519		}
3520		$this->InHeader = true;
3521		$this->setGraphicVars($this->default_graphic_vars);
3522		$temp_thead = $this->thead;
3523		$temp_theadMargins = $this->theadMargins;
3524		$lasth = $this->lasth;
3525		$newline = $this->newline;
3526		$this->_outSaveGraphicsState();
3527		$this->rMargin = $this->original_rMargin;
3528		$this->lMargin = $this->original_lMargin;
3529		$this->SetCellPadding(0);
3530		//set current position
3531		if ($this->rtl) {
3532			$this->SetXY($this->original_rMargin, $this->header_margin);
3533		} else {
3534			$this->SetXY($this->original_lMargin, $this->header_margin);
3535		}
3536		$this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3537		$this->Header();
3538		//restore position
3539		if ($this->rtl) {
3540			$this->SetXY($this->original_rMargin, $this->tMargin);
3541		} else {
3542			$this->SetXY($this->original_lMargin, $this->tMargin);
3543		}
3544		$this->_outRestoreGraphicsState();
3545		$this->lasth = $lasth;
3546		$this->thead = $temp_thead;
3547		$this->theadMargins = $temp_theadMargins;
3548		$this->newline = $newline;
3549		$this->InHeader = false;
3550	}
3551
3552	/**
3553	 * This method is used to render the page footer.
3554	 * @protected
3555	 * @since 4.0.012 (2008-07-24)
3556	 */
3557	protected function setFooter() {
3558		if ($this->state != 2) {
3559			return;
3560		}
3561		$this->InFooter = true;
3562		// save current graphic settings
3563		$gvars = $this->getGraphicVars();
3564		// mark this point
3565		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3566		$this->_out("\n");
3567		if ($this->print_footer) {
3568			$this->setGraphicVars($this->default_graphic_vars);
3569			$this->current_column = 0;
3570			$this->num_columns = 1;
3571			$temp_thead = $this->thead;
3572			$temp_theadMargins = $this->theadMargins;
3573			$lasth = $this->lasth;
3574			$this->_outSaveGraphicsState();
3575			$this->rMargin = $this->original_rMargin;
3576			$this->lMargin = $this->original_lMargin;
3577			$this->SetCellPadding(0);
3578			//set current position
3579			$footer_y = $this->h - $this->footer_margin;
3580			if ($this->rtl) {
3581				$this->SetXY($this->original_rMargin, $footer_y);
3582			} else {
3583				$this->SetXY($this->original_lMargin, $footer_y);
3584			}
3585			$this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3586			$this->Footer();
3587			//restore position
3588			if ($this->rtl) {
3589				$this->SetXY($this->original_rMargin, $this->tMargin);
3590			} else {
3591				$this->SetXY($this->original_lMargin, $this->tMargin);
3592			}
3593			$this->_outRestoreGraphicsState();
3594			$this->lasth = $lasth;
3595			$this->thead = $temp_thead;
3596			$this->theadMargins = $temp_theadMargins;
3597		}
3598		// restore graphic settings
3599		$this->setGraphicVars($gvars);
3600		$this->current_column = $gvars['current_column'];
3601		$this->num_columns = $gvars['num_columns'];
3602		// calculate footer length
3603		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3604		$this->InFooter = false;
3605	}
3606
3607	/**
3608	 * Check if we are on the page body (excluding page header and footer).
3609	 * @return true if we are not in page header nor in page footer, false otherwise.
3610	 * @protected
3611	 * @since 5.9.091 (2011-06-15)
3612	 */
3613	protected function inPageBody() {
3614		return (($this->InHeader === false) AND ($this->InFooter === false));
3615	}
3616
3617	/**
3618	 * This method is used to render the table header on new page (if any).
3619	 * @protected
3620	 * @since 4.5.030 (2009-03-25)
3621	 */
3622	protected function setTableHeader() {
3623		if ($this->num_columns > 1) {
3624			// multi column mode
3625			return;
3626		}
3627		if (isset($this->theadMargins['top'])) {
3628			// restore the original top-margin
3629			$this->tMargin = $this->theadMargins['top'];
3630			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3631			$this->y = $this->tMargin;
3632		}
3633		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3634			// set margins
3635			$prev_lMargin = $this->lMargin;
3636			$prev_rMargin = $this->rMargin;
3637			$prev_cell_padding = $this->cell_padding;
3638			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3639			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3640			$this->cell_padding = $this->theadMargins['cell_padding'];
3641			if ($this->rtl) {
3642				$this->x = $this->w - $this->rMargin;
3643			} else {
3644				$this->x = $this->lMargin;
3645			}
3646			// account for special "cell" mode
3647			if ($this->theadMargins['cell']) {
3648				if ($this->rtl) {
3649					$this->x -= $this->cell_padding['R'];
3650				} else {
3651					$this->x += $this->cell_padding['L'];
3652				}
3653			}
3654			$gvars = $this->getGraphicVars();
3655			if (!empty($this->theadMargins['gvars'])) {
3656				// set the correct graphic style
3657				$this->setGraphicVars($this->theadMargins['gvars']);
3658				$this->rMargin = $gvars['rMargin'];
3659				$this->lMargin = $gvars['lMargin'];
3660			}
3661			// print table header
3662			$this->writeHTML($this->thead, false, false, false, false, '');
3663			$this->setGraphicVars($gvars);
3664			// set new top margin to skip the table headers
3665			if (!isset($this->theadMargins['top'])) {
3666				$this->theadMargins['top'] = $this->tMargin;
3667			}
3668			// store end of header position
3669			if (!isset($this->columns[0]['th'])) {
3670				$this->columns[0]['th'] = array();
3671			}
3672			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3673			$this->tMargin = $this->y;
3674			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3675			$this->lasth = 0;
3676			$this->lMargin = $prev_lMargin;
3677			$this->rMargin = $prev_rMargin;
3678			$this->cell_padding = $prev_cell_padding;
3679		}
3680	}
3681
3682	/**
3683	 * Returns the current page number.
3684	 * @return int page number
3685	 * @public
3686	 * @since 1.0
3687	 * @see getAliasNbPages()
3688	 */
3689	public function PageNo() {
3690		return $this->page;
3691	}
3692
3693	/**
3694	 * Returns the array of spot colors.
3695	 * @return (array) Spot colors array.
3696	 * @public
3697	 * @since 6.0.038 (2013-09-30)
3698	 */
3699	public function getAllSpotColors() {
3700		return $this->spot_colors;
3701	}
3702
3703	/**
3704	 * Defines a new spot color.
3705	 * It can be expressed in RGB components or gray scale.
3706	 * The method can be called before the first page is created and the value is retained from page to page.
3707	 * @param $name (string) Full name of the spot color.
3708	 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3709	 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3710	 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3711	 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3712	 * @public
3713	 * @since 4.0.024 (2008-09-12)
3714	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3715	 */
3716	public function AddSpotColor($name, $c, $m, $y, $k) {
3717		if (!isset($this->spot_colors[$name])) {
3718			$i = (1 + count($this->spot_colors));
3719			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3720		}
3721	}
3722
3723	/**
3724	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3725	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3726	 * @param $name (string) Name of the spot color.
3727	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3728	 * @return (string) PDF color command.
3729	 * @public
3730	 * @since 5.9.125 (2011-10-03)
3731	 */
3732	public function setSpotColor($type, $name, $tint=100) {
3733		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3734		if ($spotcolor === false) {
3735			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3736		}
3737		$tint = (max(0, min(100, $tint)) / 100);
3738		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3739		switch ($type) {
3740			case 'draw': {
3741				$pdfcolor .= sprintf('CS %F SCN', $tint);
3742				$this->DrawColor = $pdfcolor;
3743				$this->strokecolor = $spotcolor;
3744				break;
3745			}
3746			case 'fill': {
3747				$pdfcolor .= sprintf('cs %F scn', $tint);
3748				$this->FillColor = $pdfcolor;
3749				$this->bgcolor = $spotcolor;
3750				break;
3751			}
3752			case 'text': {
3753				$pdfcolor .= sprintf('cs %F scn', $tint);
3754				$this->TextColor = $pdfcolor;
3755				$this->fgcolor = $spotcolor;
3756				break;
3757			}
3758		}
3759		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3760		if ($this->state == 2) {
3761			$this->_out($pdfcolor);
3762		}
3763		if ($this->inxobj) {
3764			// we are inside an XObject template
3765			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3766		}
3767		return $pdfcolor;
3768	}
3769
3770	/**
3771	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3772	 * @param $name (string) Name of the spot color.
3773	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3774	 * @public
3775	 * @since 4.0.024 (2008-09-12)
3776	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3777	 */
3778	public function SetDrawSpotColor($name, $tint=100) {
3779		$this->setSpotColor('draw', $name, $tint);
3780	}
3781
3782	/**
3783	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3784	 * @param $name (string) Name of the spot color.
3785	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3786	 * @public
3787	 * @since 4.0.024 (2008-09-12)
3788	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3789	 */
3790	public function SetFillSpotColor($name, $tint=100) {
3791		$this->setSpotColor('fill', $name, $tint);
3792	}
3793
3794	/**
3795	 * Defines the spot color used for text.
3796	 * @param $name (string) Name of the spot color.
3797	 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3798	 * @public
3799	 * @since 4.0.024 (2008-09-12)
3800	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3801	 */
3802	public function SetTextSpotColor($name, $tint=100) {
3803		$this->setSpotColor('text', $name, $tint);
3804	}
3805
3806	/**
3807	 * Set the color array for the specified type ('draw', 'fill', 'text').
3808	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3809	 * The method can be called before the first page is created and the value is retained from page to page.
3810	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3811	 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3812	 * @param $ret (boolean) If true do not send the PDF command.
3813	 * @return (string) The PDF command or empty string.
3814	 * @public
3815	 * @since 3.1.000 (2008-06-11)
3816	 */
3817	public function setColorArray($type, $color, $ret=false) {
3818		if (is_array($color)) {
3819			$color = array_values($color);
3820			// component: grey, RGB red or CMYK cyan
3821			$c = isset($color[0]) ? $color[0] : -1;
3822			// component: RGB green or CMYK magenta
3823			$m = isset($color[1]) ? $color[1] : -1;
3824			// component: RGB blue or CMYK yellow
3825			$y = isset($color[2]) ? $color[2] : -1;
3826			// component: CMYK black
3827			$k = isset($color[3]) ? $color[3] : -1;
3828			// color name
3829			$name = isset($color[4]) ? $color[4] : '';
3830			if ($c >= 0) {
3831				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3832			}
3833		}
3834		return '';
3835	}
3836
3837	/**
3838	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3839	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3840	 * The method can be called before the first page is created and the value is retained from page to page.
3841	 * @param $color (array) Array of colors (1, 3 or 4 values).
3842	 * @param $ret (boolean) If true do not send the PDF command.
3843	 * @return string the PDF command
3844	 * @public
3845	 * @since 3.1.000 (2008-06-11)
3846	 * @see SetDrawColor()
3847	 */
3848	public function SetDrawColorArray($color, $ret=false) {
3849		return $this->setColorArray('draw', $color, $ret);
3850	}
3851
3852	/**
3853	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3854	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3855	 * The method can be called before the first page is created and the value is retained from page to page.
3856	 * @param $color (array) Array of colors (1, 3 or 4 values).
3857	 * @param $ret (boolean) If true do not send the PDF command.
3858	 * @public
3859	 * @since 3.1.000 (2008-6-11)
3860	 * @see SetFillColor()
3861	 */
3862	public function SetFillColorArray($color, $ret=false) {
3863		return $this->setColorArray('fill', $color, $ret);
3864	}
3865
3866	/**
3867	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3868	 * The method can be called before the first page is created and the value is retained from page to page.
3869	 * @param $color (array) Array of colors (1, 3 or 4 values).
3870	 * @param $ret (boolean) If true do not send the PDF command.
3871	 * @public
3872	 * @since 3.1.000 (2008-6-11)
3873	 * @see SetFillColor()
3874	 */
3875	public function SetTextColorArray($color, $ret=false) {
3876		return $this->setColorArray('text', $color, $ret);
3877	}
3878
3879	/**
3880	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3881	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3882	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3883	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3884	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3885	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3886	 * @param $ret (boolean) If true do not send the command.
3887	 * @param $name (string) spot color name (if any)
3888	 * @return (string) The PDF command or empty string.
3889	 * @public
3890	 * @since 5.9.125 (2011-10-03)
3891	 */
3892	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3893		// set default values
3894		if (!is_numeric($col1)) {
3895			$col1 = 0;
3896		}
3897		if (!is_numeric($col2)) {
3898			$col2 = -1;
3899		}
3900		if (!is_numeric($col3)) {
3901			$col3 = -1;
3902		}
3903		if (!is_numeric($col4)) {
3904			$col4 = -1;
3905		}
3906		// set color by case
3907		$suffix = '';
3908		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3909			// Grey scale
3910			$col1 = max(0, min(255, $col1));
3911			$intcolor = array('G' => $col1);
3912			$pdfcolor = sprintf('%F ', ($col1 / 255));
3913			$suffix = 'g';
3914		} elseif ($col4 == -1) {
3915			// RGB
3916			$col1 = max(0, min(255, $col1));
3917			$col2 = max(0, min(255, $col2));
3918			$col3 = max(0, min(255, $col3));
3919			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3920			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3921			$suffix = 'rg';
3922		} else {
3923			$col1 = max(0, min(100, $col1));
3924			$col2 = max(0, min(100, $col2));
3925			$col3 = max(0, min(100, $col3));
3926			$col4 = max(0, min(100, $col4));
3927			if (empty($name)) {
3928				// CMYK
3929				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3930				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3931				$suffix = 'k';
3932			} else {
3933				// SPOT COLOR
3934				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3935				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3936				$pdfcolor = $this->setSpotColor($type, $name, 100);
3937			}
3938		}
3939		switch ($type) {
3940			case 'draw': {
3941				$pdfcolor .= strtoupper($suffix);
3942				$this->DrawColor = $pdfcolor;
3943				$this->strokecolor = $intcolor;
3944				break;
3945			}
3946			case 'fill': {
3947				$pdfcolor .= $suffix;
3948				$this->FillColor = $pdfcolor;
3949				$this->bgcolor = $intcolor;
3950				break;
3951			}
3952			case 'text': {
3953				$pdfcolor .= $suffix;
3954				$this->TextColor = $pdfcolor;
3955				$this->fgcolor = $intcolor;
3956				break;
3957			}
3958		}
3959		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3960		if (($type != 'text') AND ($this->state == 2)) {
3961			if (!$ret) {
3962				$this->_out($pdfcolor);
3963			}
3964			return $pdfcolor;
3965		}
3966		return '';
3967	}
3968
3969	/**
3970	 * 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.
3971	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3972	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3973	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3974	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3975	 * @param $ret (boolean) If true do not send the command.
3976	 * @param $name (string) spot color name (if any)
3977	 * @return string the PDF command
3978	 * @public
3979	 * @since 1.3
3980	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3981	 */
3982	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3983		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3984	}
3985
3986	/**
3987	 * 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.
3988	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3989	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3990	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3991	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3992	 * @param $ret (boolean) If true do not send the command.
3993	 * @param $name (string) Spot color name (if any).
3994	 * @return (string) The PDF command.
3995	 * @public
3996	 * @since 1.3
3997	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
3998	 */
3999	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4000		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4001	}
4002
4003	/**
4004	 * 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.
4005	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4006	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4007	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4008	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4009	 * @param $ret (boolean) If true do not send the command.
4010	 * @param $name (string) Spot color name (if any).
4011	 * @return (string) Empty string.
4012	 * @public
4013	 * @since 1.3
4014	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4015	 */
4016	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4017		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4018	}
4019
4020	/**
4021	 * Returns the length of a string in user unit. A font must be selected.<br>
4022	 * @param $s (string) The string whose length is to be computed
4023	 * @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.
4024	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4025	 * @param $fontsize (float) Font size in points. The default value is the current size.
4026	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4027	 * @return mixed int total string length or array of characted widths
4028	 * @author Nicola Asuni
4029	 * @public
4030	 * @since 1.2
4031	 */
4032	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4033		return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
4034	}
4035
4036	/**
4037	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4038	 * @param $sa (string) The array of chars whose total length is to be computed
4039	 * @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.
4040	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4041	 * @param $fontsize (float) Font size in points. The default value is the current size.
4042	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4043	 * @return mixed int total string length or array of characted widths
4044	 * @author Nicola Asuni
4045	 * @public
4046	 * @since 2.4.000 (2008-03-06)
4047	 */
4048	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4049		// store current values
4050		if (!TCPDF_STATIC::empty_string($fontname)) {
4051			$prev_FontFamily = $this->FontFamily;
4052			$prev_FontStyle = $this->FontStyle;
4053			$prev_FontSizePt = $this->FontSizePt;
4054			$this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4055		}
4056		// convert UTF-8 array to Latin1 if required
4057		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4058			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4059		}
4060		$w = 0; // total width
4061		$wa = array(); // array of characters widths
4062		foreach ($sa as $ck => $char) {
4063			// character width
4064			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4065			$wa[] = $cw;
4066			$w += $cw;
4067		}
4068		// restore previous values
4069		if (!TCPDF_STATIC::empty_string($fontname)) {
4070			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4071		}
4072		if ($getarray) {
4073			return $wa;
4074		}
4075		return $w;
4076	}
4077
4078	/**
4079	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4080	 * @param $char (int) The char code whose length is to be returned
4081	 * @param $notlast (boolean) If false ignore the font-spacing.
4082	 * @return float char width
4083	 * @author Nicola Asuni
4084	 * @public
4085	 * @since 2.4.000 (2008-03-06)
4086	 */
4087	public function GetCharWidth($char, $notlast=true) {
4088		// get raw width
4089		$chw = $this->getRawCharWidth($char);
4090		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4091			// increase/decrease font spacing
4092			$chw += $this->font_spacing;
4093		}
4094		if ($this->font_stretching != 100) {
4095			// fixed stretching mode
4096			$chw *= ($this->font_stretching / 100);
4097		}
4098		return $chw;
4099	}
4100
4101	/**
4102	 * Returns the length of the char in user unit for the current font.
4103	 * @param $char (int) The char code whose length is to be returned
4104	 * @return float char width
4105	 * @author Nicola Asuni
4106	 * @public
4107	 * @since 5.9.000 (2010-09-28)
4108	 */
4109	public function getRawCharWidth($char) {
4110		if ($char == 173) {
4111			// SHY character will not be printed
4112			return (0);
4113		}
4114		if (isset($this->CurrentFont['cw'][$char])) {
4115			$w = $this->CurrentFont['cw'][$char];
4116		} elseif (isset($this->CurrentFont['dw'])) {
4117			// default width
4118			$w = $this->CurrentFont['dw'];
4119		} elseif (isset($this->CurrentFont['cw'][32])) {
4120			// default width
4121			$w = $this->CurrentFont['cw'][32];
4122		} else {
4123			$w = 600;
4124		}
4125		return $this->getAbsFontMeasure($w);
4126	}
4127
4128	/**
4129	 * Returns the numbero of characters in a string.
4130	 * @param $s (string) The input string.
4131	 * @return int number of characters
4132	 * @public
4133	 * @since 2.0.0001 (2008-01-07)
4134	 */
4135	public function GetNumChars($s) {
4136		if ($this->isUnicodeFont()) {
4137			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4138		}
4139		return strlen($s);
4140	}
4141
4142	/**
4143	 * Fill the list of available fonts ($this->fontlist).
4144	 * @protected
4145	 * @since 4.0.013 (2008-07-28)
4146	 */
4147	protected function getFontsList() {
4148		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4149			while (($file = readdir($fontsdir)) !== false) {
4150				if (substr($file, -4) == '.php') {
4151					array_push($this->fontlist, strtolower(basename($file, '.php')));
4152				}
4153			}
4154			closedir($fontsdir);
4155		}
4156	}
4157
4158	/**
4159	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4160	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4161	 * 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.
4162	 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4163	 * @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>
4164	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4165	 * @return array containing the font data, or false in case of error.
4166	 * @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.
4167	 * @public
4168	 * @since 1.5
4169	 * @see SetFont(), setFontSubsetting()
4170	 */
4171	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4172		if ($subset === 'default') {
4173			$subset = $this->font_subsetting;
4174		}
4175		if ($this->pdfa_mode) {
4176			$subset = false;
4177		}
4178		if (TCPDF_STATIC::empty_string($family)) {
4179			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4180				$family = $this->FontFamily;
4181			} else {
4182				$this->Error('Empty font family');
4183			}
4184		}
4185		// move embedded styles on $style
4186		if (substr($family, -1) == 'I') {
4187			$style .= 'I';
4188			$family = substr($family, 0, -1);
4189		}
4190		if (substr($family, -1) == 'B') {
4191			$style .= 'B';
4192			$family = substr($family, 0, -1);
4193		}
4194		// normalize family name
4195		$family = strtolower($family);
4196		if ((!$this->isunicode) AND ($family == 'arial')) {
4197			$family = 'helvetica';
4198		}
4199		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4200			$style = '';
4201		}
4202		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4203			// all fonts must be embedded
4204			$family = 'pdfa'.$family;
4205		}
4206		$tempstyle = strtoupper($style);
4207		$style = '';
4208		// underline
4209		if (strpos($tempstyle, 'U') !== false) {
4210			$this->underline = true;
4211		} else {
4212			$this->underline = false;
4213		}
4214		// line-through (deleted)
4215		if (strpos($tempstyle, 'D') !== false) {
4216			$this->linethrough = true;
4217		} else {
4218			$this->linethrough = false;
4219		}
4220		// overline
4221		if (strpos($tempstyle, 'O') !== false) {
4222			$this->overline = true;
4223		} else {
4224			$this->overline = false;
4225		}
4226		// bold
4227		if (strpos($tempstyle, 'B') !== false) {
4228			$style .= 'B';
4229		}
4230		// oblique
4231		if (strpos($tempstyle, 'I') !== false) {
4232			$style .= 'I';
4233		}
4234		$bistyle = $style;
4235		$fontkey = $family.$style;
4236		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4237		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4238		// check if the font has been already added
4239		$fb = $this->getFontBuffer($fontkey);
4240		if ($fb !== false) {
4241			if ($this->inxobj) {
4242				// we are inside an XObject template
4243				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4244			}
4245			return $fontdata;
4246		}
4247		// get specified font directory (if any)
4248		$fontdir = false;
4249		if (!TCPDF_STATIC::empty_string($fontfile)) {
4250			$fontdir = dirname($fontfile);
4251			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4252				$fontdir = '';
4253			} else {
4254				$fontdir .= '/';
4255			}
4256		}
4257		// true when the font style variation is missing
4258		$missing_style = false;
4259		// search and include font file
4260		if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4261			// build a standard filenames for specified font
4262			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4263			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4264			if (TCPDF_STATIC::empty_string($fontfile)) {
4265				$missing_style = true;
4266				// try to remove the style part
4267				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4268				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4269			}
4270		}
4271		// include font file
4272		if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4273			include($fontfile);
4274		} else {
4275			$this->Error('Could not include font definition file: '.$family.'');
4276		}
4277		// check font parameters
4278		if ((!isset($type)) OR (!isset($cw))) {
4279			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4280		}
4281		// SET default parameters
4282		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4283			$file = '';
4284		}
4285		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4286			$enc = '';
4287		}
4288		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4289			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4290			$cidinfo['uni2cid'] = array();
4291		}
4292		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4293			$ctg = '';
4294		}
4295		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4296			$desc = array();
4297		}
4298		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4299			$up = -100;
4300		}
4301		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4302			$ut = 50;
4303		}
4304		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4305			$cw = array();
4306		}
4307		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4308			// set default width
4309			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4310				$dw = $desc['MissingWidth'];
4311			} elseif (isset($cw[32])) {
4312				$dw = $cw[32];
4313			} else {
4314				$dw = 600;
4315			}
4316		}
4317		++$this->numfonts;
4318		if ($type == 'core') {
4319			$name = $this->CoreFonts[$fontkey];
4320			$subset = false;
4321		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4322			$subset = false;
4323		} elseif ($type == 'TrueTypeUnicode') {
4324			$enc = 'Identity-H';
4325		} elseif ($type == 'cidfont0') {
4326			if ($this->pdfa_mode) {
4327				$this->Error('All fonts must be embedded in PDF/A mode!');
4328			}
4329		} else {
4330			$this->Error('Unknow font type: '.$type.'');
4331		}
4332		// set name if unset
4333		if (!isset($name) OR empty($name)) {
4334			$name = $fontkey;
4335		}
4336		// create artificial font style variations if missing (only works with non-embedded fonts)
4337		if (($type != 'core') AND $missing_style) {
4338			// style variations
4339			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4340			$name .= $styles[$bistyle];
4341			// artificial bold
4342			if (strpos($bistyle, 'B') !== false) {
4343				if (isset($desc['StemV'])) {
4344					// from normal to bold
4345					$desc['StemV'] = round($desc['StemV'] * 1.75);
4346				} else {
4347					// bold
4348					$desc['StemV'] = 123;
4349				}
4350			}
4351			// artificial italic
4352			if (strpos($bistyle, 'I') !== false) {
4353				if (isset($desc['ItalicAngle'])) {
4354					$desc['ItalicAngle'] -= 11;
4355				} else {
4356					$desc['ItalicAngle'] = -11;
4357				}
4358				if (isset($desc['Flags'])) {
4359					$desc['Flags'] |= 64; //bit 7
4360				} else {
4361					$desc['Flags'] = 64;
4362				}
4363			}
4364		}
4365		// check if the array of characters bounding boxes is defined
4366		if (!isset($cbbox)) {
4367			$cbbox = array();
4368		}
4369		// initialize subsetchars
4370		$subsetchars = array_fill(0, 255, true);
4371		$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));
4372		if ($this->inxobj) {
4373			// we are inside an XObject template
4374			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4375		}
4376		if (isset($diff) AND (!empty($diff))) {
4377			//Search existing encodings
4378			$d = 0;
4379			$nb = count($this->diffs);
4380			for ($i=1; $i <= $nb; ++$i) {
4381				if ($this->diffs[$i] == $diff) {
4382					$d = $i;
4383					break;
4384				}
4385			}
4386			if ($d == 0) {
4387				$d = $nb + 1;
4388				$this->diffs[$d] = $diff;
4389			}
4390			$this->setFontSubBuffer($fontkey, 'diff', $d);
4391		}
4392		if (!TCPDF_STATIC::empty_string($file)) {
4393			if (!isset($this->FontFiles[$file])) {
4394				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4395					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4396				} elseif ($type != 'core') {
4397					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4398				}
4399			} else {
4400				// update fontkeys that are sharing this font file
4401				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4402				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4403					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4404				}
4405			}
4406		}
4407		return $fontdata;
4408	}
4409
4410	/**
4411	 * Sets the font used to print character strings.
4412	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4413	 * The method can be called before the first page is created and the font is retained from page to page.
4414	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4415	 * 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 />
4416	 * @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.
4417	 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4418	 * @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
4419	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4420	 * @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.
4421	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4422	 * @author Nicola Asuni
4423	 * @public
4424	 * @since 1.0
4425	 * @see AddFont(), SetFontSize()
4426	 */
4427	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4428		//Select a font; size given in points
4429		if ($size === null) {
4430			$size = $this->FontSizePt;
4431		}
4432		if ($size < 0) {
4433			$size = 0;
4434		}
4435		// try to add font (if not already added)
4436		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4437		$this->FontFamily = $fontdata['family'];
4438		$this->FontStyle = $fontdata['style'];
4439		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4440			// save subset chars of the previous font
4441			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4442		}
4443		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4444		$this->SetFontSize($size, $out);
4445	}
4446
4447	/**
4448	 * Defines the size of the current font.
4449	 * @param $size (float) The font size in points.
4450	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4451	 * @public
4452	 * @since 1.0
4453	 * @see SetFont()
4454	 */
4455	public function SetFontSize($size, $out=true) {
4456		// font size in points
4457		$this->FontSizePt = $size;
4458		// font size in user units
4459		$this->FontSize = $size / $this->k;
4460		// calculate some font metrics
4461		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4462			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4463			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4464		} else {
4465			$font_height = $size * 1.219;
4466		}
4467		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4468			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4469		}
4470		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4471			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4472		}
4473		if (!isset($font_ascent) AND !isset($font_descent)) {
4474			// core font
4475			$font_ascent = 0.76 * $font_height;
4476			$font_descent = $font_height - $font_ascent;
4477		} elseif (!isset($font_descent)) {
4478			$font_descent = $font_height - $font_ascent;
4479		} elseif (!isset($font_ascent)) {
4480			$font_ascent = $font_height - $font_descent;
4481		}
4482		$this->FontAscent = ($font_ascent / $this->k);
4483		$this->FontDescent = ($font_descent / $this->k);
4484		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4485			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4486		}
4487	}
4488
4489	/**
4490	 * Returns the bounding box of the current font in user units.
4491	 * @return array
4492	 * @public
4493	 * @since 5.9.152 (2012-03-23)
4494	 */
4495	public function getFontBBox() {
4496		$fbbox = array();
4497		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4498			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4499			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4500		} else {
4501			// Find max width
4502			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4503				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4504			} else {
4505				$maxw = 0;
4506				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4507					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4508				}
4509				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4510					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4511				}
4512				if (isset($this->CurrentFont['dw'])) {
4513					$maxw = max($maxw, $this->CurrentFont['dw']);
4514				}
4515				foreach ($this->CurrentFont['cw'] as $char => $w) {
4516					$maxw = max($maxw, $w);
4517				}
4518				if ($maxw == 0) {
4519					$maxw = 600;
4520				}
4521				$maxw = $this->getAbsFontMeasure($maxw);
4522			}
4523			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4524		}
4525		return $fbbox;
4526	}
4527
4528	/**
4529	 * Convert a relative font measure into absolute value.
4530	 * @param $s (int) Font measure.
4531	 * @return float Absolute measure.
4532	 * @since 5.9.186 (2012-09-13)
4533	 */
4534	public function getAbsFontMeasure($s) {
4535		return ($s * $this->FontSize / 1000);
4536	}
4537
4538	/**
4539	 * Returns the glyph bounding box of the specified character in the current font in user units.
4540	 * @param $char (int) Input character code.
4541	 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4542	 * @since 5.9.186 (2012-09-13)
4543	 */
4544	public function getCharBBox($char) {
4545		$c = intval($char);
4546		if (isset($this->CurrentFont['cw'][$c])) {
4547			// glyph is defined ... use zero width & height for glyphs without outlines
4548			$result = array(0,0,0,0);
4549			if (isset($this->CurrentFont['cbbox'][$c])) {
4550				$result = $this->CurrentFont['cbbox'][$c];
4551			}
4552			return array_map(array($this,'getAbsFontMeasure'), $result);
4553		}
4554		return false;
4555	}
4556
4557	/**
4558	 * Return the font descent value
4559	 * @param $font (string) font name
4560	 * @param $style (string) font style
4561	 * @param $size (float) The size (in points)
4562	 * @return int font descent
4563	 * @public
4564	 * @author Nicola Asuni
4565	 * @since 4.9.003 (2010-03-30)
4566	 */
4567	public function getFontDescent($font, $style='', $size=0) {
4568		$fontdata = $this->AddFont($font, $style);
4569		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4570		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4571			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4572		} else {
4573			$descent = (1.219 * 0.24 * $size);
4574		}
4575		return ($descent / $this->k);
4576	}
4577
4578	/**
4579	 * Return the font ascent value.
4580	 * @param $font (string) font name
4581	 * @param $style (string) font style
4582	 * @param $size (float) The size (in points)
4583	 * @return int font ascent
4584	 * @public
4585	 * @author Nicola Asuni
4586	 * @since 4.9.003 (2010-03-30)
4587	 */
4588	public function getFontAscent($font, $style='', $size=0) {
4589		$fontdata = $this->AddFont($font, $style);
4590		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4591		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4592			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4593		} else {
4594			$ascent = 1.219 * 0.76 * $size;
4595		}
4596		return ($ascent / $this->k);
4597	}
4598
4599	/**
4600	 * Return true in the character is present in the specified font.
4601	 * @param $char (mixed) Character to check (integer value or string)
4602	 * @param $font (string) Font name (family name).
4603	 * @param $style (string) Font style.
4604	 * @return (boolean) true if the char is defined, false otherwise.
4605	 * @public
4606	 * @since 5.9.153 (2012-03-28)
4607	 */
4608	public function isCharDefined($char, $font='', $style='') {
4609		if (is_string($char)) {
4610			// get character code
4611			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4612			$char = $char[0];
4613		}
4614		if (TCPDF_STATIC::empty_string($font)) {
4615			if (TCPDF_STATIC::empty_string($style)) {
4616				return (isset($this->CurrentFont['cw'][intval($char)]));
4617			}
4618			$font = $this->FontFamily;
4619		}
4620		$fontdata = $this->AddFont($font, $style);
4621		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4622		return (isset($fontinfo['cw'][intval($char)]));
4623	}
4624
4625	/**
4626	 * Replace missing font characters on selected font with specified substitutions.
4627	 * @param $text (string) Text to process.
4628	 * @param $font (string) Font name (family name).
4629	 * @param $style (string) Font style.
4630	 * @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.
4631	 * @return (string) Processed text.
4632	 * @public
4633	 * @since 5.9.153 (2012-03-28)
4634	 */
4635	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4636		if (empty($subs)) {
4637			return $text;
4638		}
4639		if (TCPDF_STATIC::empty_string($font)) {
4640			$font = $this->FontFamily;
4641		}
4642		$fontdata = $this->AddFont($font, $style);
4643		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4644		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4645		foreach ($uniarr as $k => $chr) {
4646			if (!isset($fontinfo['cw'][$chr])) {
4647				// this character is missing on the selected font
4648				if (isset($subs[$chr])) {
4649					// we have available substitutions
4650					if (is_array($subs[$chr])) {
4651						foreach($subs[$chr] as $s) {
4652							if (isset($fontinfo['cw'][$s])) {
4653								$uniarr[$k] = $s;
4654								break;
4655							}
4656						}
4657					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4658						$uniarr[$k] = $subs[$chr];
4659					}
4660				}
4661			}
4662		}
4663		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4664	}
4665
4666	/**
4667	 * Defines the default monospaced font.
4668	 * @param $font (string) Font name.
4669	 * @public
4670	 * @since 4.5.025
4671	 */
4672	public function SetDefaultMonospacedFont($font) {
4673		$this->default_monospaced_font = $font;
4674	}
4675
4676	/**
4677	 * 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 />
4678	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4679	 * @public
4680	 * @since 1.5
4681	 * @see Cell(), Write(), Image(), Link(), SetLink()
4682	 */
4683	public function AddLink() {
4684		// create a new internal link
4685		$n = count($this->links) + 1;
4686		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4687		return $n;
4688	}
4689
4690	/**
4691	 * Defines the page and position a link points to.
4692	 * @param $link (int) The link identifier returned by AddLink()
4693	 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4694	 * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4695	 * @public
4696	 * @since 1.5
4697	 * @see AddLink()
4698	 */
4699	public function SetLink($link, $y=0, $page=-1) {
4700		$fixed = false;
4701		if (!empty($page) AND ($page[0] == '*')) {
4702			$page = intval(substr($page, 1));
4703			// this page number will not be changed when moving/add/deleting pages
4704			$fixed = true;
4705		}
4706		if ($page < 0) {
4707			$page = $this->page;
4708		}
4709		if ($y == -1) {
4710			$y = $this->y;
4711		}
4712		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4713	}
4714
4715	/**
4716	 * Puts a link on a rectangular area of the page.
4717	 * 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.
4718	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4719	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4720	 * @param $w (float) Width of the rectangle
4721	 * @param $h (float) Height of the rectangle
4722	 * @param $link (mixed) URL or identifier returned by AddLink()
4723	 * @param $spaces (int) number of spaces on the text to link
4724	 * @public
4725	 * @since 1.5
4726	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4727	 */
4728	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4729		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4730	}
4731
4732	/**
4733	 * Puts a markup annotation on a rectangular area of the page.
4734	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4735	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4736	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4737	 * @param $w (float) Width of the rectangle
4738	 * @param $h (float) Height of the rectangle
4739	 * @param $text (string) annotation text or alternate content
4740	 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4741	 * @param $spaces (int) number of spaces on the text to link
4742	 * @public
4743	 * @since 4.0.018 (2008-08-06)
4744	 */
4745	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4746		if ($this->inxobj) {
4747			// store parameters for later use on template
4748			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4749			return;
4750		}
4751		if ($x === '') {
4752			$x = $this->x;
4753		}
4754		if ($y === '') {
4755			$y = $this->y;
4756		}
4757		// check page for no-write regions and adapt page margins if necessary
4758		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4759		// recalculate coordinates to account for graphic transformations
4760		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4761			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4762				$maxid = count($this->transfmatrix[$i]) - 1;
4763				for ($j=$maxid; $j >= 0; --$j) {
4764					$ctm = $this->transfmatrix[$i][$j];
4765					if (isset($ctm['a'])) {
4766						$x = $x * $this->k;
4767						$y = ($this->h - $y) * $this->k;
4768						$w = $w * $this->k;
4769						$h = $h * $this->k;
4770						// top left
4771						$xt = $x;
4772						$yt = $y;
4773						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4774						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4775						// top right
4776						$xt = $x + $w;
4777						$yt = $y;
4778						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4779						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4780						// bottom left
4781						$xt = $x;
4782						$yt = $y - $h;
4783						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4784						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4785						// bottom right
4786						$xt = $x + $w;
4787						$yt = $y - $h;
4788						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4789						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4790						// new coordinates (rectangle area)
4791						$x = min($x1, $x2, $x3, $x4);
4792						$y = max($y1, $y2, $y3, $y4);
4793						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4794						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4795						$x = $x / $this->k;
4796						$y = $this->h - ($y / $this->k);
4797					}
4798				}
4799			}
4800		}
4801		if ($this->page <= 0) {
4802			$page = 1;
4803		} else {
4804			$page = $this->page;
4805		}
4806		if (!isset($this->PageAnnots[$page])) {
4807			$this->PageAnnots[$page] = array();
4808		}
4809		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4810		if (!$this->pdfa_mode) {
4811			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4812				AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4813				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4814				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4815			}
4816		}
4817		// Add widgets annotation's icons
4818		if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4819			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4820		}
4821		if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4822			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4823		}
4824		if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4825			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4826		}
4827	}
4828
4829	/**
4830	 * Embedd the attached files.
4831	 * @since 4.4.000 (2008-12-07)
4832	 * @protected
4833	 * @see Annotation()
4834	 */
4835	protected function _putEmbeddedFiles() {
4836		if ($this->pdfa_mode) {
4837			// embedded files are not allowed in PDF/A mode
4838			return;
4839		}
4840		reset($this->embeddedfiles);
4841		foreach ($this->embeddedfiles as $filename => $filedata) {
4842			$data = TCPDF_STATIC::fileGetContents($filedata['file']);
4843			if ($data !== FALSE) {
4844				$rawsize = strlen($data);
4845				if ($rawsize > 0) {
4846					// update name tree
4847					$this->efnames[$filename] = $filedata['f'].' 0 R';
4848					// embedded file specification object
4849					$out = $this->_getobj($filedata['f'])."\n";
4850					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4851					$out .= "\n".'endobj';
4852					$this->_out($out);
4853					// embedded file object
4854					$filter = '';
4855					if ($this->compress) {
4856						$data = gzcompress($data);
4857						$filter = ' /Filter /FlateDecode';
4858					}
4859					$stream = $this->_getrawstream($data, $filedata['n']);
4860					$out = $this->_getobj($filedata['n'])."\n";
4861					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4862					$out .= ' stream'."\n".$stream."\n".'endstream';
4863					$out .= "\n".'endobj';
4864					$this->_out($out);
4865				}
4866			}
4867		}
4868	}
4869
4870	/**
4871	 * Prints a text cell at the specified position.
4872	 * This method allows to place a string precisely on the page.
4873	 * @param $x (float) Abscissa of the cell origin
4874	 * @param $y (float) Ordinate of the cell origin
4875	 * @param $txt (string) String to print
4876	 * @param $fstroke (int) outline size in user units (false = disable)
4877	 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4878	 * @param $ffill (boolean) if true fills the text
4879	 * @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)))
4880	 * @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.
4881	 * @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>
4882	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4883	 * @param $link (mixed) URL or identifier returned by AddLink().
4884	 * @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.
4885	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4886	 * @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>
4887	 * @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>
4888	 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4889	 * @public
4890	 * @since 1.0
4891	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4892	 */
4893	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) {
4894		$textrendermode = $this->textrendermode;
4895		$textstrokewidth = $this->textstrokewidth;
4896		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
4897		$this->SetXY($x, $y, $rtloff);
4898		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4899		// restore previous rendering mode
4900		$this->textrendermode = $textrendermode;
4901		$this->textstrokewidth = $textstrokewidth;
4902	}
4903
4904	/**
4905	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4906	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4907	 * This method is called automatically and should not be called directly by the application.
4908	 * @return boolean
4909	 * @public
4910	 * @since 1.4
4911	 * @see SetAutoPageBreak()
4912	 */
4913	public function AcceptPageBreak() {
4914		if ($this->num_columns > 1) {
4915			// multi column mode
4916			if ($this->current_column < ($this->num_columns - 1)) {
4917				// go to next column
4918				$this->selectColumn($this->current_column + 1);
4919			} elseif ($this->AutoPageBreak) {
4920				// add a new page
4921				$this->AddPage();
4922				// set first column
4923				$this->selectColumn(0);
4924			}
4925			// avoid page breaking from checkPageBreak()
4926			return false;
4927		}
4928		return $this->AutoPageBreak;
4929	}
4930
4931	/**
4932	 * Add page if needed.
4933	 * @param $h (float) Cell height. Default value: 0.
4934	 * @param $y (mixed) starting y position, leave empty for current position.
4935	 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4936	 * @return boolean true in case of page break, false otherwise.
4937	 * @since 3.2.000 (2008-07-01)
4938	 * @protected
4939	 */
4940	protected function checkPageBreak($h=0, $y='', $addpage=true) {
4941		if (TCPDF_STATIC::empty_string($y)) {
4942			$y = $this->y;
4943		}
4944		$current_page = $this->page;
4945		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4946			if ($addpage) {
4947				//Automatic page break
4948				$x = $this->x;
4949				$this->AddPage($this->CurOrientation);
4950				$this->y = $this->tMargin;
4951				$oldpage = $this->page - 1;
4952				if ($this->rtl) {
4953					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4954						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4955					} else {
4956						$this->x = $x;
4957					}
4958				} else {
4959					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4960						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4961					} else {
4962						$this->x = $x;
4963					}
4964				}
4965			}
4966			return true;
4967		}
4968		if ($current_page != $this->page) {
4969			// account for columns mode
4970			return true;
4971		}
4972		return false;
4973	}
4974
4975	/**
4976	 * 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 />
4977	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
4978	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
4979	 * @param $h (float) Cell height. Default value: 0.
4980	 * @param $txt (string) String to print. Default value: empty string.
4981	 * @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)))
4982	 * @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.
4983	 * @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>
4984	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4985	 * @param $link (mixed) URL or identifier returned by AddLink().
4986	 * @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.
4987	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4988	 * @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>
4989	 * @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>
4990	 * @public
4991	 * @since 1.0
4992	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
4993	 */
4994	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') {
4995		$prev_cell_margin = $this->cell_margin;
4996		$prev_cell_padding = $this->cell_padding;
4997		$this->adjustCellPadding($border);
4998		if (!$ignore_min_height) {
4999			$min_cell_height = $this->getCellHeight($this->FontSize);
5000			if ($h < $min_cell_height) {
5001				$h = $min_cell_height;
5002			}
5003		}
5004		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5005		// apply text shadow if enabled
5006		if ($this->txtshadow['enabled']) {
5007			// save data
5008			$x = $this->x;
5009			$y = $this->y;
5010			$bc = $this->bgcolor;
5011			$fc = $this->fgcolor;
5012			$sc = $this->strokecolor;
5013			$alpha = $this->alpha;
5014			// print shadow
5015			$this->x += $this->txtshadow['depth_w'];
5016			$this->y += $this->txtshadow['depth_h'];
5017			$this->SetFillColorArray($this->txtshadow['color']);
5018			$this->SetTextColorArray($this->txtshadow['color']);
5019			$this->SetDrawColorArray($this->txtshadow['color']);
5020			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5021				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5022			}
5023			if ($this->state == 2) {
5024				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5025			}
5026			//restore data
5027			$this->x = $x;
5028			$this->y = $y;
5029			$this->SetFillColorArray($bc);
5030			$this->SetTextColorArray($fc);
5031			$this->SetDrawColorArray($sc);
5032			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5033				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5034			}
5035		}
5036		if ($this->state == 2) {
5037			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5038		}
5039		$this->cell_padding = $prev_cell_padding;
5040		$this->cell_margin = $prev_cell_margin;
5041	}
5042
5043	/**
5044	 * 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 />
5045	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5046	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5047	 * @param $h (float) Cell height. Default value: 0.
5048	 * @param $txt (string) String to print. Default value: empty string.
5049	 * @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)))
5050	 * @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.
5051	 * @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>
5052	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5053	 * @param $link (mixed) URL or identifier returned by AddLink().
5054	 * @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.
5055	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5056	 * @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>
5057	 * @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>
5058	 * @return string containing cell code
5059	 * @protected
5060	 * @since 1.0
5061	 * @see Cell()
5062	 */
5063	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') {
5064		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5065		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5066		$prev_cell_margin = $this->cell_margin;
5067		$prev_cell_padding = $this->cell_padding;
5068		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5069		$rs = ''; //string to be returned
5070		$this->adjustCellPadding($border);
5071		if (!$ignore_min_height) {
5072			$min_cell_height = $this->getCellHeight($this->FontSize);
5073			if ($h < $min_cell_height) {
5074				$h = $min_cell_height;
5075			}
5076		}
5077		$k = $this->k;
5078		// check page for no-write regions and adapt page margins if necessary
5079		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5080		if ($this->rtl) {
5081			$x = $this->x - $this->cell_margin['R'];
5082		} else {
5083			$x = $this->x + $this->cell_margin['L'];
5084		}
5085		$y = $this->y + $this->cell_margin['T'];
5086		$prev_font_stretching = $this->font_stretching;
5087		$prev_font_spacing = $this->font_spacing;
5088		// cell vertical alignment
5089		switch ($calign) {
5090			case 'A': {
5091				// font top
5092				switch ($valign) {
5093					case 'T': {
5094						// top
5095						$y -= $this->cell_padding['T'];
5096						break;
5097					}
5098					case 'B': {
5099						// bottom
5100						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5101						break;
5102					}
5103					default:
5104					case 'C':
5105					case 'M': {
5106						// center
5107						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5108						break;
5109					}
5110				}
5111				break;
5112			}
5113			case 'L': {
5114				// font baseline
5115				switch ($valign) {
5116					case 'T': {
5117						// top
5118						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5119						break;
5120					}
5121					case 'B': {
5122						// bottom
5123						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5124						break;
5125					}
5126					default:
5127					case 'C':
5128					case 'M': {
5129						// center
5130						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5131						break;
5132					}
5133				}
5134				break;
5135			}
5136			case 'D': {
5137				// font bottom
5138				switch ($valign) {
5139					case 'T': {
5140						// top
5141						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5142						break;
5143					}
5144					case 'B': {
5145						// bottom
5146						$y -= ($h - $this->cell_padding['B']);
5147						break;
5148					}
5149					default:
5150					case 'C':
5151					case 'M': {
5152						// center
5153						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5154						break;
5155					}
5156				}
5157				break;
5158			}
5159			case 'B': {
5160				// cell bottom
5161				$y -= $h;
5162				break;
5163			}
5164			case 'C':
5165			case 'M': {
5166				// cell center
5167				$y -= ($h / 2);
5168				break;
5169			}
5170			default:
5171			case 'T': {
5172				// cell top
5173				break;
5174			}
5175		}
5176		// text vertical alignment
5177		switch ($valign) {
5178			case 'T': {
5179				// top
5180				$yt = $y + $this->cell_padding['T'];
5181				break;
5182			}
5183			case 'B': {
5184				// bottom
5185				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5186				break;
5187			}
5188			default:
5189			case 'C':
5190			case 'M': {
5191				// center
5192				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5193				break;
5194			}
5195		}
5196		$basefonty = $yt + $this->FontAscent;
5197		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5198			if ($this->rtl) {
5199				$w = $x - $this->lMargin;
5200			} else {
5201				$w = $this->w - $this->rMargin - $x;
5202			}
5203		}
5204		$s = '';
5205		// fill and borders
5206		if (is_string($border) AND (strlen($border) == 4)) {
5207			// full border
5208			$border = 1;
5209		}
5210		if ($fill OR ($border == 1)) {
5211			if ($fill) {
5212				$op = ($border == 1) ? 'B' : 'f';
5213			} else {
5214				$op = 'S';
5215			}
5216			if ($this->rtl) {
5217				$xk = (($x - $w) * $k);
5218			} else {
5219				$xk = ($x * $k);
5220			}
5221			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5222		}
5223		// draw borders
5224		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5225		if ($txt != '') {
5226			$txt2 = $txt;
5227			if ($this->isunicode) {
5228				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5229					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5230				} else {
5231					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5232					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5233					// replace thai chars (if any)
5234					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5235						// number of chars
5236						$numchars = count($unicode);
5237						// po pla, for far, for fan
5238						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5239						// do chada, to patak
5240						$lowtail = array(0x0e0e, 0x0e0f);
5241						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5242						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5243						// mai ek, mai tho, mai tri, mai chattawa, karan
5244						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5245						// sara u, sara uu, pinthu
5246						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5247						$output = array();
5248						for ($i = 0; $i < $numchars; $i++) {
5249							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5250								$ch0 = $unicode[$i];
5251								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5252								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5253								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5254								if (in_array($ch0, $tonemark)) {
5255									if ($chn == 0x0e33) {
5256										// sara um
5257										if (in_array($ch1, $longtail)) {
5258											// tonemark at upper left
5259											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5260										} else {
5261											// tonemark at upper right (normal position)
5262											$output[] = $ch0;
5263										}
5264									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5265										// tonemark at lower left
5266										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5267									} elseif (in_array($ch1, $upvowel)) {
5268										if (in_array($ch2, $longtail)) {
5269											// tonemark at upper left
5270											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5271										} else {
5272											// tonemark at upper right (normal position)
5273											$output[] = $ch0;
5274										}
5275									} else {
5276										// tonemark at lower right
5277										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5278									}
5279								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5280									// add lower left nikhahit and sara aa
5281									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5282										$output[] = 0xf711;
5283										$this->CurrentFont['subsetchars'][0xf711] = true;
5284										$output[] = 0x0e32;
5285										$this->CurrentFont['subsetchars'][0x0e32] = true;
5286									} else {
5287										$output[] = $ch0;
5288									}
5289								} elseif (in_array($ch1, $longtail)) {
5290									if ($ch0 == 0x0e31) {
5291										// lower left mai hun arkad
5292										$output[] = $this->replaceChar($ch0, 0xf710);
5293									} elseif (in_array($ch0, $upvowel)) {
5294										// lower left
5295										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5296									} elseif ($ch0 == 0x0e47) {
5297										// lower left mai tai koo
5298										$output[] = $this->replaceChar($ch0, 0xf712);
5299									} else {
5300										// normal character
5301										$output[] = $ch0;
5302									}
5303								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5304									// lower vowel
5305									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5306								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5307									// yo ying without lower part
5308									$output[] = $this->replaceChar($ch0, 0xf70f);
5309								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5310									// tho santan without lower part
5311									$output[] = $this->replaceChar($ch0, 0xf700);
5312								} else {
5313									$output[] = $ch0;
5314								}
5315							} else {
5316								// non-thai character
5317								$output[] = $unicode[$i];
5318							}
5319						}
5320						$unicode = $output;
5321						// update font subsetchars
5322						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5323					} // end of K_THAI_TOPCHARS
5324					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5325				}
5326			}
5327			$txt2 = TCPDF_STATIC::_escape($txt2);
5328			// get current text width (considering general font stretching and spacing)
5329			$txwidth = $this->GetStringWidth($txt);
5330			$width = $txwidth;
5331			// check for stretch mode
5332			if ($stretch > 0) {
5333				// calculate ratio between cell width and text width
5334				if ($width <= 0) {
5335					$ratio = 1;
5336				} else {
5337					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5338				}
5339				// check if stretching is required
5340				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5341					// the text will be stretched to fit cell width
5342					if ($stretch > 2) {
5343						// set new character spacing
5344						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5345					} else {
5346						// set new horizontal stretching
5347						$this->font_stretching *= $ratio;
5348					}
5349					// recalculate text width (the text fills the entire cell)
5350					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5351					// reset alignment
5352					$align = '';
5353				}
5354			}
5355			if ($this->font_stretching != 100) {
5356				// apply font stretching
5357				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5358			}
5359			if ($this->font_spacing != 0) {
5360				// increase/decrease font spacing
5361				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5362			}
5363			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5364				$s .= 'q '.$this->TextColor.' ';
5365			}
5366			// rendering mode
5367			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5368			// count number of spaces
5369			$ns = substr_count($txt, chr(32));
5370			// Justification
5371			$spacewidth = 0;
5372			if (($align == 'J') AND ($ns > 0)) {
5373				if ($this->isUnicodeFont()) {
5374					// get string width without spaces
5375					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5376					// calculate average space width
5377					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5378					if ($this->font_stretching != 100) {
5379						// word spacing is affected by stretching
5380						$spacewidth /= ($this->font_stretching / 100);
5381					}
5382					// set word position to be used with TJ operator
5383					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5384					$unicode_justification = true;
5385				} else {
5386					// get string width
5387					$width = $txwidth;
5388					// new space width
5389					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5390					if ($this->font_stretching != 100) {
5391						// word spacing (Tw) is affected by stretching
5392						$spacewidth /= ($this->font_stretching / 100);
5393					}
5394					// set word spacing
5395					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5396				}
5397				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5398			}
5399			// replace carriage return characters
5400			$txt2 = str_replace("\r", ' ', $txt2);
5401			switch ($align) {
5402				case 'C': {
5403					$dx = ($w - $width) / 2;
5404					break;
5405				}
5406				case 'R': {
5407					if ($this->rtl) {
5408						$dx = $this->cell_padding['R'];
5409					} else {
5410						$dx = $w - $width - $this->cell_padding['R'];
5411					}
5412					break;
5413				}
5414				case 'L': {
5415					if ($this->rtl) {
5416						$dx = $w - $width - $this->cell_padding['L'];
5417					} else {
5418						$dx = $this->cell_padding['L'];
5419					}
5420					break;
5421				}
5422				case 'J':
5423				default: {
5424					if ($this->rtl) {
5425						$dx = $this->cell_padding['R'];
5426					} else {
5427						$dx = $this->cell_padding['L'];
5428					}
5429					break;
5430				}
5431			}
5432			if ($this->rtl) {
5433				$xdx = $x - $dx - $width;
5434			} else {
5435				$xdx = $x + $dx;
5436			}
5437			$xdk = $xdx * $k;
5438			// print text
5439			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5440			if (isset($uniblock)) {
5441				// print overlapping characters as separate string
5442				$xshift = 0; // horizontal shift
5443				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5444				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5445				foreach ($uniblock as $uk => $uniarr) {
5446					if (($uk % 2) == 0) {
5447						// x space to skip
5448						if ($spacewidth != 0) {
5449							// justification shift
5450							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5451						}
5452						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5453					} else {
5454						// character to print
5455						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5456						$topchr = TCPDF_STATIC::_escape($topchr);
5457						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5458					}
5459				}
5460			}
5461			if ($this->underline) {
5462				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5463			}
5464			if ($this->linethrough) {
5465				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5466			}
5467			if ($this->overline) {
5468				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5469			}
5470			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5471				$s .= ' Q';
5472			}
5473			if ($link) {
5474				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5475			}
5476		}
5477		// output cell
5478		if ($s) {
5479			// output cell
5480			$rs .= $s;
5481			if ($this->font_spacing != 0) {
5482				// reset font spacing mode
5483				$rs .= ' BT 0 Tc ET';
5484			}
5485			if ($this->font_stretching != 100) {
5486				// reset font stretching mode
5487				$rs .= ' BT 100 Tz ET';
5488			}
5489		}
5490		// reset word spacing
5491		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5492			$rs .= ' BT 0 Tw ET';
5493		}
5494		// reset stretching and spacing
5495		$this->font_stretching = $prev_font_stretching;
5496		$this->font_spacing = $prev_font_spacing;
5497		$this->lasth = $h;
5498		if ($ln > 0) {
5499			//Go to the beginning of the next line
5500			$this->y = $y + $h + $this->cell_margin['B'];
5501			if ($ln == 1) {
5502				if ($this->rtl) {
5503					$this->x = $this->w - $this->rMargin;
5504				} else {
5505					$this->x = $this->lMargin;
5506				}
5507			}
5508		} else {
5509			// go left or right by case
5510			if ($this->rtl) {
5511				$this->x = $x - $w - $this->cell_margin['L'];
5512			} else {
5513				$this->x = $x + $w + $this->cell_margin['R'];
5514			}
5515		}
5516		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5517		$rs = $gstyles.$rs;
5518		$this->cell_padding = $prev_cell_padding;
5519		$this->cell_margin = $prev_cell_margin;
5520		return $rs;
5521	}
5522
5523	/**
5524	 * Replace a char if is defined on the current font.
5525	 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5526	 * @param $newchar (int) Integer code (unicode) of the new character.
5527	 * @return int the replaced char or the old char in case the new char i not defined
5528	 * @protected
5529	 * @since 5.9.167 (2012-06-22)
5530	 */
5531	protected function replaceChar($oldchar, $newchar) {
5532		if ($this->isCharDefined($newchar)) {
5533			// add the new char on the subset list
5534			$this->CurrentFont['subsetchars'][$newchar] = true;
5535			// return the new character
5536			return $newchar;
5537		}
5538		// return the old char
5539		return $oldchar;
5540	}
5541
5542	/**
5543	 * Returns the code to draw the cell border
5544	 * @param $x (float) X coordinate.
5545	 * @param $y (float) Y coordinate.
5546	 * @param $w (float) Cell width.
5547	 * @param $h (float) Cell height.
5548	 * @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)))
5549	 * @return string containing cell border code
5550	 * @protected
5551	 * @see SetLineStyle()
5552	 * @since 5.7.000 (2010-08-02)
5553	 */
5554	protected function getCellBorder($x, $y, $w, $h, $brd) {
5555		$s = ''; // string to be returned
5556		if (empty($brd)) {
5557			return $s;
5558		}
5559		if ($brd == 1) {
5560			$brd = array('LRTB' => true);
5561		}
5562		// calculate coordinates for border
5563		$k = $this->k;
5564		if ($this->rtl) {
5565			$xeL = ($x - $w) * $k;
5566			$xeR = $x * $k;
5567		} else {
5568			$xeL = $x * $k;
5569			$xeR = ($x + $w) * $k;
5570		}
5571		$yeL = (($this->h - ($y + $h)) * $k);
5572		$yeT = (($this->h - $y) * $k);
5573		$xeT = $xeL;
5574		$xeB = $xeR;
5575		$yeR = $yeT;
5576		$yeB = $yeL;
5577		if (is_string($brd)) {
5578			// convert string to array
5579			$slen = strlen($brd);
5580			$newbrd = array();
5581			for ($i = 0; $i < $slen; ++$i) {
5582				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5583			}
5584			$brd = $newbrd;
5585		}
5586		if (isset($brd['mode'])) {
5587			$mode = $brd['mode'];
5588			unset($brd['mode']);
5589		} else {
5590			$mode = 'normal';
5591		}
5592		foreach ($brd as $border => $style) {
5593			if (is_array($style) AND !empty($style)) {
5594				// apply border style
5595				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5596				$s .= $this->SetLineStyle($style, true)."\n";
5597			}
5598			switch ($mode) {
5599				case 'ext': {
5600					$off = (($this->LineWidth / 2) * $k);
5601					$xL = $xeL - $off;
5602					$xR = $xeR + $off;
5603					$yT = $yeT + $off;
5604					$yL = $yeL - $off;
5605					$xT = $xL;
5606					$xB = $xR;
5607					$yR = $yT;
5608					$yB = $yL;
5609					$w += $this->LineWidth;
5610					$h += $this->LineWidth;
5611					break;
5612				}
5613				case 'int': {
5614					$off = ($this->LineWidth / 2) * $k;
5615					$xL = $xeL + $off;
5616					$xR = $xeR - $off;
5617					$yT = $yeT - $off;
5618					$yL = $yeL + $off;
5619					$xT = $xL;
5620					$xB = $xR;
5621					$yR = $yT;
5622					$yB = $yL;
5623					$w -= $this->LineWidth;
5624					$h -= $this->LineWidth;
5625					break;
5626				}
5627				case 'normal':
5628				default: {
5629					$xL = $xeL;
5630					$xT = $xeT;
5631					$xB = $xeB;
5632					$xR = $xeR;
5633					$yL = $yeL;
5634					$yT = $yeT;
5635					$yB = $yeB;
5636					$yR = $yeR;
5637					break;
5638				}
5639			}
5640			// draw borders by case
5641			if (strlen($border) == 4) {
5642				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5643			} elseif (strlen($border) == 3) {
5644				if (strpos($border,'B') === false) { // LTR
5645					$s .= sprintf('%F %F m ', $xL, $yL);
5646					$s .= sprintf('%F %F l ', $xT, $yT);
5647					$s .= sprintf('%F %F l ', $xR, $yR);
5648					$s .= sprintf('%F %F l ', $xB, $yB);
5649					$s .= 'S ';
5650				} elseif (strpos($border,'L') === false) { // TRB
5651					$s .= sprintf('%F %F m ', $xT, $yT);
5652					$s .= sprintf('%F %F l ', $xR, $yR);
5653					$s .= sprintf('%F %F l ', $xB, $yB);
5654					$s .= sprintf('%F %F l ', $xL, $yL);
5655					$s .= 'S ';
5656				} elseif (strpos($border,'T') === false) { // RBL
5657					$s .= sprintf('%F %F m ', $xR, $yR);
5658					$s .= sprintf('%F %F l ', $xB, $yB);
5659					$s .= sprintf('%F %F l ', $xL, $yL);
5660					$s .= sprintf('%F %F l ', $xT, $yT);
5661					$s .= 'S ';
5662				} elseif (strpos($border,'R') === false) { // BLT
5663					$s .= sprintf('%F %F m ', $xB, $yB);
5664					$s .= sprintf('%F %F l ', $xL, $yL);
5665					$s .= sprintf('%F %F l ', $xT, $yT);
5666					$s .= sprintf('%F %F l ', $xR, $yR);
5667					$s .= 'S ';
5668				}
5669			} elseif (strlen($border) == 2) {
5670				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5671					$s .= sprintf('%F %F m ', $xL, $yL);
5672					$s .= sprintf('%F %F l ', $xT, $yT);
5673					$s .= sprintf('%F %F l ', $xR, $yR);
5674					$s .= 'S ';
5675				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5676					$s .= sprintf('%F %F m ', $xT, $yT);
5677					$s .= sprintf('%F %F l ', $xR, $yR);
5678					$s .= sprintf('%F %F l ', $xB, $yB);
5679					$s .= 'S ';
5680				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5681					$s .= sprintf('%F %F m ', $xR, $yR);
5682					$s .= sprintf('%F %F l ', $xB, $yB);
5683					$s .= sprintf('%F %F l ', $xL, $yL);
5684					$s .= 'S ';
5685				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5686					$s .= sprintf('%F %F m ', $xB, $yB);
5687					$s .= sprintf('%F %F l ', $xL, $yL);
5688					$s .= sprintf('%F %F l ', $xT, $yT);
5689					$s .= 'S ';
5690				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5691					$s .= sprintf('%F %F m ', $xL, $yL);
5692					$s .= sprintf('%F %F l ', $xT, $yT);
5693					$s .= 'S ';
5694					$s .= sprintf('%F %F m ', $xR, $yR);
5695					$s .= sprintf('%F %F l ', $xB, $yB);
5696					$s .= 'S ';
5697				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5698					$s .= sprintf('%F %F m ', $xT, $yT);
5699					$s .= sprintf('%F %F l ', $xR, $yR);
5700					$s .= 'S ';
5701					$s .= sprintf('%F %F m ', $xB, $yB);
5702					$s .= sprintf('%F %F l ', $xL, $yL);
5703					$s .= 'S ';
5704				}
5705			} else { // strlen($border) == 1
5706				if (strpos($border,'L') !== false) { // L
5707					$s .= sprintf('%F %F m ', $xL, $yL);
5708					$s .= sprintf('%F %F l ', $xT, $yT);
5709					$s .= 'S ';
5710				} elseif (strpos($border,'T') !== false) { // T
5711					$s .= sprintf('%F %F m ', $xT, $yT);
5712					$s .= sprintf('%F %F l ', $xR, $yR);
5713					$s .= 'S ';
5714				} elseif (strpos($border,'R') !== false) { // R
5715					$s .= sprintf('%F %F m ', $xR, $yR);
5716					$s .= sprintf('%F %F l ', $xB, $yB);
5717					$s .= 'S ';
5718				} elseif (strpos($border,'B') !== false) { // B
5719					$s .= sprintf('%F %F m ', $xB, $yB);
5720					$s .= sprintf('%F %F l ', $xL, $yL);
5721					$s .= 'S ';
5722				}
5723			}
5724			if (is_array($style) AND !empty($style)) {
5725				// reset border style to previous value
5726				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5727			}
5728		}
5729		return $s;
5730	}
5731
5732	/**
5733	 * This method allows printing text with line breaks.
5734	 * 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 />
5735	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5736	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5737	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5738	 * @param $txt (string) String to print
5739	 * @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)))
5740	 * @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>
5741	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5742	 * @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>
5743	 * @param $x (float) x position in user units
5744	 * @param $y (float) y position in user units
5745	 * @param $reseth (boolean) if true reset the last cell height (default true).
5746	 * @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.
5747	 * @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.
5748	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5749	 * @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.
5750	 * @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.
5751	 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5752	 * @return int Return the number of cells or 1 for html mode.
5753	 * @public
5754	 * @since 1.3
5755	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5756	 */
5757	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) {
5758		$prev_cell_margin = $this->cell_margin;
5759		$prev_cell_padding = $this->cell_padding;
5760		// adjust internal padding
5761		$this->adjustCellPadding($border);
5762		$mc_padding = $this->cell_padding;
5763		$mc_margin = $this->cell_margin;
5764		$this->cell_padding['T'] = 0;
5765		$this->cell_padding['B'] = 0;
5766		$this->setCellMargins(0, 0, 0, 0);
5767		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5768			// reset row height
5769			$this->resetLastH();
5770		}
5771		if (!TCPDF_STATIC::empty_string($y)) {
5772			$this->SetY($y);
5773		} else {
5774			$y = $this->GetY();
5775		}
5776		$resth = 0;
5777		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5778			// spit cell in more pages/columns
5779			$newh = ($this->PageBreakTrigger - $y);
5780			$resth = ($h - $newh); // cell to be printed on the next page/column
5781			$h = $newh;
5782		}
5783		// get current page number
5784		$startpage = $this->page;
5785		// get current column
5786		$startcolumn = $this->current_column;
5787		if (!TCPDF_STATIC::empty_string($x)) {
5788			$this->SetX($x);
5789		} else {
5790			$x = $this->GetX();
5791		}
5792		// check page for no-write regions and adapt page margins if necessary
5793		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5794		// apply margins
5795		$oy = $y + $mc_margin['T'];
5796		if ($this->rtl) {
5797			$ox = ($this->w - $x - $mc_margin['R']);
5798		} else {
5799			$ox = ($x + $mc_margin['L']);
5800		}
5801		$this->x = $ox;
5802		$this->y = $oy;
5803		// set width
5804		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5805			if ($this->rtl) {
5806				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5807			} else {
5808				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5809			}
5810		}
5811		// store original margin values
5812		$lMargin = $this->lMargin;
5813		$rMargin = $this->rMargin;
5814		if ($this->rtl) {
5815			$this->rMargin = ($this->w - $this->x);
5816			$this->lMargin = ($this->x - $w);
5817		} else {
5818			$this->lMargin = ($this->x);
5819			$this->rMargin = ($this->w - $this->x - $w);
5820		}
5821		$this->clMargin = $this->lMargin;
5822		$this->crMargin = $this->rMargin;
5823		if ($autopadding) {
5824			// add top padding
5825			$this->y += $mc_padding['T'];
5826		}
5827		if ($ishtml) { // ******* Write HTML text
5828			$this->writeHTML($txt, true, false, $reseth, true, $align);
5829			$nl = 1;
5830		} else { // ******* Write simple text
5831			$prev_FontSizePt = $this->FontSizePt;
5832			if ($fitcell) {
5833				// ajust height values
5834				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5835				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5836			}
5837			// vertical alignment
5838			if ($maxh > 0) {
5839				// get text height
5840				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5841				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5842					// try to reduce font size to fit text on cell (use a quick search algorithm)
5843					$fmin = 1;
5844					$fmax = $this->FontSizePt;
5845					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5846					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5847					while ($maxit >= 0) {
5848						$fmid = (($fmax + $fmin) / 2);
5849						$this->SetFontSize($fmid, false);
5850						$this->resetLastH();
5851						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5852						$diff = ($maxh - $text_height);
5853						if ($diff >= 0) {
5854							if ($diff <= $diff_epsilon) {
5855								break;
5856							}
5857							$fmin = $fmid;
5858						} else {
5859							$fmax = $fmid;
5860						}
5861						--$maxit;
5862					}
5863					if ($maxit < 0) {
5864						// premature exit, we get the minimum font value to fit the cell
5865						$this->SetFontSize($fmin);
5866						$this->resetLastH();
5867						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5868					} else {
5869						$this->SetFontSize($fmid);
5870						$this->resetLastH();
5871					}
5872				}
5873				if ($text_height < $maxh) {
5874					if ($valign == 'M') {
5875						// text vertically centered
5876						$this->y += (($maxh - $text_height) / 2);
5877					} elseif ($valign == 'B') {
5878						// text vertically aligned on bottom
5879						$this->y += ($maxh - $text_height);
5880					}
5881				}
5882			}
5883			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5884			if ($fitcell) {
5885				// restore font size
5886				$this->SetFontSize($prev_FontSizePt);
5887			}
5888		}
5889		if ($autopadding) {
5890			// add bottom padding
5891			$this->y += $mc_padding['B'];
5892		}
5893		// Get end-of-text Y position
5894		$currentY = $this->y;
5895		// get latest page number
5896		$endpage = $this->page;
5897		if ($resth > 0) {
5898			$skip = ($endpage - $startpage);
5899			$tmpresth = $resth;
5900			while ($tmpresth > 0) {
5901				if ($skip <= 0) {
5902					// add a page (or trig AcceptPageBreak() for multicolumn mode)
5903					$this->checkPageBreak($this->PageBreakTrigger + 1);
5904				}
5905				if ($this->num_columns > 1) {
5906					$tmpresth -= ($this->h - $this->y - $this->bMargin);
5907				} else {
5908					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5909				}
5910				--$skip;
5911			}
5912			$currentY = $this->y;
5913			$endpage = $this->page;
5914		}
5915		// get latest column
5916		$endcolumn = $this->current_column;
5917		if ($this->num_columns == 0) {
5918			$this->num_columns = 1;
5919		}
5920		// disable page regions check
5921		$check_page_regions = $this->check_page_regions;
5922		$this->check_page_regions = false;
5923		// get border modes
5924		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5925		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5926		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5927		// design borders around HTML cells.
5928		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5929			$ccode = '';
5930			$this->setPage($page);
5931			if ($this->num_columns < 2) {
5932				// single-column mode
5933				$this->SetX($x);
5934				$this->y = $this->tMargin;
5935			}
5936			// account for margin changes
5937			if ($page > $startpage) {
5938				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5939					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5940				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5941					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5942				}
5943			}
5944			if ($startpage == $endpage) {
5945				// single page
5946				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5947					$this->selectColumn($column);
5948					if ($this->rtl) {
5949						$this->x -= $mc_margin['R'];
5950					} else {
5951						$this->x += $mc_margin['L'];
5952					}
5953					if ($startcolumn == $endcolumn) { // single column
5954						$cborder = $border;
5955						$h = max($h, ($currentY - $oy));
5956						$this->y = $oy;
5957					} elseif ($column == $startcolumn) { // first column
5958						$cborder = $border_start;
5959						$this->y = $oy;
5960						$h = $this->h - $this->y - $this->bMargin;
5961					} elseif ($column == $endcolumn) { // end column
5962						$cborder = $border_end;
5963						$h = $currentY - $this->y;
5964						if ($resth > $h) {
5965							$h = $resth;
5966						}
5967					} else { // middle column
5968						$cborder = $border_middle;
5969						$h = $this->h - $this->y - $this->bMargin;
5970						$resth -= $h;
5971					}
5972					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5973				} // end for each column
5974			} elseif ($page == $startpage) { // first page
5975				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
5976					$this->selectColumn($column);
5977					if ($this->rtl) {
5978						$this->x -= $mc_margin['R'];
5979					} else {
5980						$this->x += $mc_margin['L'];
5981					}
5982					if ($column == $startcolumn) { // first column
5983						$cborder = $border_start;
5984						$this->y = $oy;
5985						$h = $this->h - $this->y - $this->bMargin;
5986					} else { // middle column
5987						$cborder = $border_middle;
5988						$h = $this->h - $this->y - $this->bMargin;
5989						$resth -= $h;
5990					}
5991					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5992				} // end for each column
5993			} elseif ($page == $endpage) { // last page
5994				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
5995					$this->selectColumn($column);
5996					if ($this->rtl) {
5997						$this->x -= $mc_margin['R'];
5998					} else {
5999						$this->x += $mc_margin['L'];
6000					}
6001					if ($column == $endcolumn) {
6002						// end column
6003						$cborder = $border_end;
6004						$h = $currentY - $this->y;
6005						if ($resth > $h) {
6006							$h = $resth;
6007						}
6008					} else {
6009						// middle column
6010						$cborder = $border_middle;
6011						$h = $this->h - $this->y - $this->bMargin;
6012						$resth -= $h;
6013					}
6014					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6015				} // end for each column
6016			} else { // middle page
6017				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6018					$this->selectColumn($column);
6019					if ($this->rtl) {
6020						$this->x -= $mc_margin['R'];
6021					} else {
6022						$this->x += $mc_margin['L'];
6023					}
6024					$cborder = $border_middle;
6025					$h = $this->h - $this->y - $this->bMargin;
6026					$resth -= $h;
6027					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6028				} // end for each column
6029			}
6030			if ($cborder OR $fill) {
6031				$offsetlen = strlen($ccode);
6032				// draw border and fill
6033				if ($this->inxobj) {
6034					// we are inside an XObject template
6035					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6036						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6037						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6038						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6039					} else {
6040						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6041						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6042					}
6043					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6044					$pstart = substr($pagebuff, 0, $pagemark);
6045					$pend = substr($pagebuff, $pagemark);
6046					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6047				} else {
6048					if (end($this->transfmrk[$this->page]) !== false) {
6049						$pagemarkkey = key($this->transfmrk[$this->page]);
6050						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6051						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6052					} elseif ($this->InFooter) {
6053						$pagemark = $this->footerpos[$this->page];
6054						$this->footerpos[$this->page] += $offsetlen;
6055					} else {
6056						$pagemark = $this->intmrk[$this->page];
6057						$this->intmrk[$this->page] += $offsetlen;
6058					}
6059					$pagebuff = $this->getPageBuffer($this->page);
6060					$pstart = substr($pagebuff, 0, $pagemark);
6061					$pend = substr($pagebuff, $pagemark);
6062					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6063				}
6064			}
6065		} // end for each page
6066		// restore page regions check
6067		$this->check_page_regions = $check_page_regions;
6068		// Get end-of-cell Y position
6069		$currentY = $this->GetY();
6070		// restore previous values
6071		if ($this->num_columns > 1) {
6072			$this->selectColumn();
6073		} else {
6074			// restore original margins
6075			$this->lMargin = $lMargin;
6076			$this->rMargin = $rMargin;
6077			if ($this->page > $startpage) {
6078				// check for margin variations between pages (i.e. booklet mode)
6079				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6080				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6081				if (($dl != 0) OR ($dr != 0)) {
6082					$this->lMargin += $dl;
6083					$this->rMargin += $dr;
6084				}
6085			}
6086		}
6087		if ($ln > 0) {
6088			//Go to the beginning of the next line
6089			$this->SetY($currentY + $mc_margin['B']);
6090			if ($ln == 2) {
6091				$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6092			}
6093		} else {
6094			// go left or right by case
6095			$this->setPage($startpage);
6096			$this->y = $y;
6097			$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6098		}
6099		$this->setContentMark();
6100		$this->cell_padding = $prev_cell_padding;
6101		$this->cell_margin = $prev_cell_margin;
6102		$this->clMargin = $this->lMargin;
6103		$this->crMargin = $this->rMargin;
6104		return $nl;
6105	}
6106
6107	/**
6108	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6109	 * @param $txt (string) String for calculating his height
6110	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6111	 * @param $reseth (boolean) if true reset the last cell height (default false).
6112	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6113	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6114	 * @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)))
6115	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6116	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6117	 * @public
6118	 * @since 4.5.011
6119	 */
6120	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6121		if ($txt === NULL) {
6122			return 0;
6123		}
6124		if ($txt === '') {
6125			// empty string
6126			return 1;
6127		}
6128		// adjust internal padding
6129		$prev_cell_padding = $this->cell_padding;
6130		$prev_lasth = $this->lasth;
6131		if (is_array($cellpadding)) {
6132			$this->cell_padding = $cellpadding;
6133		}
6134		$this->adjustCellPadding($border);
6135		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6136			if ($this->rtl) {
6137				$w = $this->x - $this->lMargin;
6138			} else {
6139				$w = $this->w - $this->rMargin - $this->x;
6140			}
6141		}
6142		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6143		if ($reseth) {
6144			// reset row height
6145			$this->resetLastH();
6146		}
6147		$lines = 1;
6148		$sum = 0;
6149		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6150		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6151		$length = count($chars);
6152		$lastSeparator = -1;
6153		for ($i = 0; $i < $length; ++$i) {
6154			$c = $chars[$i];
6155			$charWidth = $charsWidth[$i];
6156			if (($c != 160)
6157					AND (($c == 173)
6158						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6159						OR (($c == 45)
6160							AND ($i > 0) AND ($i < ($length - 1))
6161							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6162							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6163						)
6164					)
6165				) {
6166				$lastSeparator = $i;
6167			}
6168			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6169				++$lines;
6170				if ($c == 10) {
6171					$lastSeparator = -1;
6172					$sum = 0;
6173				} elseif ($lastSeparator != -1) {
6174					$i = $lastSeparator;
6175					$lastSeparator = -1;
6176					$sum = 0;
6177				} else {
6178					$sum = $charWidth;
6179				}
6180			} else {
6181				$sum += $charWidth;
6182			}
6183		}
6184		if ($chars[($length - 1)] == 10) {
6185			--$lines;
6186		}
6187		$this->cell_padding = $prev_cell_padding;
6188		$this->lasth = $prev_lasth;
6189		return $lines;
6190	}
6191
6192	/**
6193	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6194	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6195	 * @pre
6196	 *  // store current object
6197	 *  $pdf->startTransaction();
6198	 *  // store starting values
6199	 *  $start_y = $pdf->GetY();
6200	 *  $start_page = $pdf->getPage();
6201	 *  // call your printing functions with your parameters
6202	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6203	 *  $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);
6204	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6205	 *  // get the new Y
6206	 *  $end_y = $pdf->GetY();
6207	 *  $end_page = $pdf->getPage();
6208	 *  // calculate height
6209	 *  $height = 0;
6210	 *  if ($end_page == $start_page) {
6211	 *  	$height = $end_y - $start_y;
6212	 *  } else {
6213	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6214	 *  		$this->setPage($page);
6215	 *  		if ($page == $start_page) {
6216	 *  			// first page
6217	 *  			$height = $this->h - $start_y - $this->bMargin;
6218	 *  		} elseif ($page == $end_page) {
6219	 *  			// last page
6220	 *  			$height = $end_y - $this->tMargin;
6221	 *  		} else {
6222	 *  			$height = $this->h - $this->tMargin - $this->bMargin;
6223	 *  		}
6224	 *  	}
6225	 *  }
6226	 *  // restore previous object
6227	 *  $pdf = $pdf->rollbackTransaction();
6228	 *
6229	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6230	 * @param $txt (string) String for calculating his height
6231	 * @param $reseth (boolean) if true reset the last cell height (default false).
6232	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6233	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6234	 * @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)))
6235	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6236	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6237	 * @public
6238	 */
6239	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6240		// adjust internal padding
6241		$prev_cell_padding = $this->cell_padding;
6242		$prev_lasth = $this->lasth;
6243		if (is_array($cellpadding)) {
6244			$this->cell_padding = $cellpadding;
6245		}
6246		$this->adjustCellPadding($border);
6247		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6248		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6249		$this->cell_padding = $prev_cell_padding;
6250		$this->lasth = $prev_lasth;
6251		return $height;
6252	}
6253
6254	/**
6255	 * This method prints text from the current position.<br />
6256	 * @param $h (float) Line height
6257	 * @param $txt (string) String to print
6258	 * @param $link (mixed) URL or identifier returned by AddLink()
6259	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6260	 * @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>
6261	 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6262	 * @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.
6263	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6264	 * @param $firstblock (boolean) if true the string is the starting of a line.
6265	 * @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.
6266	 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6267	 * @param $margin (array) margin array of the parent container
6268	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6269	 * @public
6270	 * @since 1.5
6271	 */
6272	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6273		// check page for no-write regions and adapt page margins if necessary
6274		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6275		if (strlen($txt) == 0) {
6276			// fix empty text
6277			$txt = ' ';
6278		}
6279		if ($margin === '') {
6280			// set default margins
6281			$margin = $this->cell_margin;
6282		}
6283		// remove carriage returns
6284		$s = str_replace("\r", '', $txt);
6285		// check if string contains arabic text
6286		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6287			$arabic = true;
6288		} else {
6289			$arabic = false;
6290		}
6291		// check if string contains RTL text
6292		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6293			$rtlmode = true;
6294		} else {
6295			$rtlmode = false;
6296		}
6297		// get a char width
6298		$chrwidth = $this->GetCharWidth(46); // dot character
6299		// get array of unicode values
6300		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6301		// calculate maximum width for a single character on string
6302		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6303		array_walk($chrw, array($this, 'getRawCharWidth'));
6304		$maxchwidth = max($chrw);
6305		// get array of chars
6306		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6307		// get the number of characters
6308		$nb = count($chars);
6309		// replacement for SHY character (minus symbol)
6310		$shy_replacement = 45;
6311		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6312		// widht for SHY replacement
6313		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6314		// page width
6315		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6316		// calculate remaining line width ($w)
6317		if ($this->rtl) {
6318			$w = $this->x - $this->lMargin;
6319		} else {
6320			$w = $this->w - $this->rMargin - $this->x;
6321		}
6322		// max column width
6323		$wmax = ($w - $wadj);
6324		if (!$firstline) {
6325			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6326		}
6327		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6328			// the maximum width character do not fit on column
6329			return '';
6330		}
6331		// minimum row height
6332		$row_height = max($h, $this->getCellHeight($this->FontSize));
6333		// max Y
6334		$maxy = $this->y + $maxh - max($row_height, $h);
6335		$start_page = $this->page;
6336		$i = 0; // character position
6337		$j = 0; // current starting position
6338		$sep = -1; // position of the last blank space
6339		$prevsep = $sep; // previous separator
6340		$shy = false; // true if the last blank is a soft hypen (SHY)
6341		$prevshy = $shy; // previous shy mode
6342		$l = 0; // current string length
6343		$nl = 0; //number of lines
6344		$linebreak = false;
6345		$pc = 0; // previous character
6346		// for each character
6347		while ($i < $nb) {
6348			if (($maxh > 0) AND ($this->y > $maxy) ) {
6349				break;
6350			}
6351			//Get the current character
6352			$c = $chars[$i];
6353			if ($c == 10) { // 10 = "\n" = new line
6354				//Explicit line break
6355				if ($align == 'J') {
6356					if ($this->rtl) {
6357						$talign = 'R';
6358					} else {
6359						$talign = 'L';
6360					}
6361				} else {
6362					$talign = $align;
6363				}
6364				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6365				if ($firstline) {
6366					$startx = $this->x;
6367					$tmparr = array_slice($chars, $j, ($i - $j));
6368					if ($rtlmode) {
6369						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6370					}
6371					$linew = $this->GetArrStringWidth($tmparr);
6372					unset($tmparr);
6373					if ($this->rtl) {
6374						$this->endlinex = $startx - $linew;
6375					} else {
6376						$this->endlinex = $startx + $linew;
6377					}
6378					$w = $linew;
6379					$tmpcellpadding = $this->cell_padding;
6380					if ($maxh == 0) {
6381						$this->SetCellPadding(0);
6382					}
6383				}
6384				if ($firstblock AND $this->isRTLTextDir()) {
6385					$tmpstr = $this->stringRightTrim($tmpstr);
6386				}
6387				// Skip newlines at the beginning of a page or column
6388				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6389					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6390				}
6391				unset($tmpstr);
6392				if ($firstline) {
6393					$this->cell_padding = $tmpcellpadding;
6394					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6395				}
6396				++$nl;
6397				$j = $i + 1;
6398				$l = 0;
6399				$sep = -1;
6400				$prevsep = $sep;
6401				$shy = false;
6402				// account for margin changes
6403				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6404					$this->AcceptPageBreak();
6405					if ($this->rtl) {
6406						$this->x -= $margin['R'];
6407					} else {
6408						$this->x += $margin['L'];
6409					}
6410					$this->lMargin += $margin['L'];
6411					$this->rMargin += $margin['R'];
6412				}
6413				$w = $this->getRemainingWidth();
6414				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6415			} else {
6416				// 160 is the non-breaking space.
6417				// 173 is SHY (Soft Hypen).
6418				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6419				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6420				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6421				if (($c != 160)
6422					AND (($c == 173)
6423						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6424						OR (($c == 45)
6425							AND ($i < ($nb - 1))
6426							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6427							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6428						)
6429					)
6430				) {
6431					// update last blank space position
6432					$prevsep = $sep;
6433					$sep = $i;
6434					// check if is a SHY
6435					if (($c == 173) OR ($c == 45)) {
6436						$prevshy = $shy;
6437						$shy = true;
6438						if ($pc == 45) {
6439							$tmp_shy_replacement_width = 0;
6440							$tmp_shy_replacement_char = '';
6441						} else {
6442							$tmp_shy_replacement_width = $shy_replacement_width;
6443							$tmp_shy_replacement_char = $shy_replacement_char;
6444						}
6445					} else {
6446						$shy = false;
6447					}
6448				}
6449				// update string length
6450				if ($this->isUnicodeFont() AND ($arabic)) {
6451					// with bidirectional algorithm some chars may be changed affecting the line length
6452					// *** very slow ***
6453					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6454				} else {
6455					$l += $this->GetCharWidth($c);
6456				}
6457				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6458					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6459						$sep = $prevsep;
6460						$shy = $prevshy;
6461					}
6462					// we have reached the end of column
6463					if ($sep == -1) {
6464						// check if the line was already started
6465						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6466							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6467							// print a void cell and go to next line
6468							$this->Cell($w, $h, '', 0, 1);
6469							$linebreak = true;
6470							if ($firstline) {
6471								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6472							}
6473						} else {
6474							// truncate the word because do not fit on column
6475							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6476							if ($firstline) {
6477								$startx = $this->x;
6478								$tmparr = array_slice($chars, $j, ($i - $j));
6479								if ($rtlmode) {
6480									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6481								}
6482								$linew = $this->GetArrStringWidth($tmparr);
6483								unset($tmparr);
6484								if ($this->rtl) {
6485									$this->endlinex = $startx - $linew;
6486								} else {
6487									$this->endlinex = $startx + $linew;
6488								}
6489								$w = $linew;
6490								$tmpcellpadding = $this->cell_padding;
6491								if ($maxh == 0) {
6492									$this->SetCellPadding(0);
6493								}
6494							}
6495							if ($firstblock AND $this->isRTLTextDir()) {
6496								$tmpstr = $this->stringRightTrim($tmpstr);
6497							}
6498							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6499							unset($tmpstr);
6500							if ($firstline) {
6501								$this->cell_padding = $tmpcellpadding;
6502								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6503							}
6504							$j = $i;
6505							--$i;
6506						}
6507					} else {
6508						// word wrapping
6509						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6510							$endspace = 1;
6511						} else {
6512							$endspace = 0;
6513						}
6514						// check the length of the next string
6515						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6516						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6517						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6518							// truncate the word because do not fit on a full page width
6519							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6520							if ($firstline) {
6521								$startx = $this->x;
6522								$tmparr = array_slice($chars, $j, ($i - $j));
6523								if ($rtlmode) {
6524									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6525								}
6526								$linew = $this->GetArrStringWidth($tmparr);
6527								unset($tmparr);
6528								if ($this->rtl) {
6529									$this->endlinex = ($startx - $linew);
6530								} else {
6531									$this->endlinex = ($startx + $linew);
6532								}
6533								$w = $linew;
6534								$tmpcellpadding = $this->cell_padding;
6535								if ($maxh == 0) {
6536									$this->SetCellPadding(0);
6537								}
6538							}
6539							if ($firstblock AND $this->isRTLTextDir()) {
6540								$tmpstr = $this->stringRightTrim($tmpstr);
6541							}
6542							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6543							unset($tmpstr);
6544							if ($firstline) {
6545								$this->cell_padding = $tmpcellpadding;
6546								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6547							}
6548							$j = $i;
6549							--$i;
6550						} else {
6551							// word wrapping
6552							if ($shy) {
6553								// add hypen (minus symbol) at the end of the line
6554								$shy_width = $tmp_shy_replacement_width;
6555								if ($this->rtl) {
6556									$shy_char_left = $tmp_shy_replacement_char;
6557									$shy_char_right = '';
6558								} else {
6559									$shy_char_left = '';
6560									$shy_char_right = $tmp_shy_replacement_char;
6561								}
6562							} else {
6563								$shy_width = 0;
6564								$shy_char_left = '';
6565								$shy_char_right = '';
6566							}
6567							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6568							if ($firstline) {
6569								$startx = $this->x;
6570								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6571								if ($rtlmode) {
6572									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6573								}
6574								$linew = $this->GetArrStringWidth($tmparr);
6575								unset($tmparr);
6576								if ($this->rtl) {
6577									$this->endlinex = $startx - $linew - $shy_width;
6578								} else {
6579									$this->endlinex = $startx + $linew + $shy_width;
6580								}
6581								$w = $linew;
6582								$tmpcellpadding = $this->cell_padding;
6583								if ($maxh == 0) {
6584									$this->SetCellPadding(0);
6585								}
6586							}
6587							// print the line
6588							if ($firstblock AND $this->isRTLTextDir()) {
6589								$tmpstr = $this->stringRightTrim($tmpstr);
6590							}
6591							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6592							unset($tmpstr);
6593							if ($firstline) {
6594								if ($chars[$sep] == 45) {
6595									$endspace += 1;
6596								}
6597								// return the remaining text
6598								$this->cell_padding = $tmpcellpadding;
6599								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6600							}
6601							$i = $sep;
6602							$sep = -1;
6603							$shy = false;
6604							$j = ($i + 1);
6605						}
6606					}
6607					// account for margin changes
6608					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6609						$this->AcceptPageBreak();
6610						if ($this->rtl) {
6611							$this->x -= $margin['R'];
6612						} else {
6613							$this->x += $margin['L'];
6614						}
6615						$this->lMargin += $margin['L'];
6616						$this->rMargin += $margin['R'];
6617					}
6618					$w = $this->getRemainingWidth();
6619					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6620					if ($linebreak) {
6621						$linebreak = false;
6622					} else {
6623						++$nl;
6624						$l = 0;
6625					}
6626				}
6627			}
6628			// save last character
6629			$pc = $c;
6630			++$i;
6631		} // end while i < nb
6632		// print last substring (if any)
6633		if ($l > 0) {
6634			switch ($align) {
6635				case 'J':
6636				case 'C': {
6637					$w = $w;
6638					break;
6639				}
6640				case 'L': {
6641					if ($this->rtl) {
6642						$w = $w;
6643					} else {
6644						$w = $l;
6645					}
6646					break;
6647				}
6648				case 'R': {
6649					if ($this->rtl) {
6650						$w = $l;
6651					} else {
6652						$w = $w;
6653					}
6654					break;
6655				}
6656				default: {
6657					$w = $l;
6658					break;
6659				}
6660			}
6661			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6662			if ($firstline) {
6663				$startx = $this->x;
6664				$tmparr = array_slice($chars, $j, ($nb - $j));
6665				if ($rtlmode) {
6666					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6667				}
6668				$linew = $this->GetArrStringWidth($tmparr);
6669				unset($tmparr);
6670				if ($this->rtl) {
6671					$this->endlinex = $startx - $linew;
6672				} else {
6673					$this->endlinex = $startx + $linew;
6674				}
6675				$w = $linew;
6676				$tmpcellpadding = $this->cell_padding;
6677				if ($maxh == 0) {
6678					$this->SetCellPadding(0);
6679				}
6680			}
6681			if ($firstblock AND $this->isRTLTextDir()) {
6682				$tmpstr = $this->stringRightTrim($tmpstr);
6683			}
6684			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6685			unset($tmpstr);
6686			if ($firstline) {
6687				$this->cell_padding = $tmpcellpadding;
6688				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6689			}
6690			++$nl;
6691		}
6692		if ($firstline) {
6693			return '';
6694		}
6695		return $nl;
6696	}
6697
6698	/**
6699	 * Returns the remaining width between the current position and margins.
6700	 * @return int Return the remaining width
6701	 * @protected
6702	 */
6703	protected function getRemainingWidth() {
6704		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6705		if ($this->rtl) {
6706			return ($this->x - $this->lMargin);
6707		} else {
6708			return ($this->w - $this->rMargin - $this->x);
6709		}
6710	}
6711
6712	/**
6713	 * Set the block dimensions accounting for page breaks and page/column fitting
6714	 * @param $w (float) width
6715	 * @param $h (float) height
6716	 * @param $x (float) X coordinate
6717	 * @param $y (float) Y coodiante
6718	 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6719	 * @return array($w, $h, $x, $y)
6720	 * @protected
6721	 * @since 5.5.009 (2010-07-05)
6722	 */
6723	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6724		if ($w <= 0) {
6725			// set maximum width
6726			$w = ($this->w - $this->lMargin - $this->rMargin);
6727			if ($w <= 0) {
6728				$w = 1;
6729			}
6730		}
6731		if ($h <= 0) {
6732			// set maximum height
6733			$h = ($this->PageBreakTrigger - $this->tMargin);
6734			if ($h <= 0) {
6735				$h = 1;
6736			}
6737		}
6738		// resize the block to be vertically contained on a single page or single column
6739		if ($fitonpage OR $this->AutoPageBreak) {
6740			$ratio_wh = ($w / $h);
6741			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6742				$h = $this->PageBreakTrigger - $this->tMargin;
6743				$w = ($h * $ratio_wh);
6744			}
6745			// resize the block to be horizontally contained on a single page or single column
6746			if ($fitonpage) {
6747				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6748				if ($w > $maxw) {
6749					$w = $maxw;
6750					$h = ($w / $ratio_wh);
6751				}
6752			}
6753		}
6754		// Check whether we need a new page or new column first as this does not fit
6755		$prev_x = $this->x;
6756		$prev_y = $this->y;
6757		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6758			$y = $this->y;
6759			if ($this->rtl) {
6760				$x += ($prev_x - $this->x);
6761			} else {
6762				$x += ($this->x - $prev_x);
6763			}
6764			$this->newline = true;
6765		}
6766		// resize the block to be contained on the remaining available page or column space
6767		if ($fitonpage) {
6768			// tcpdf-patch: begin
6769			// 2016-06-02 mbecker will create a pull request to make this patch
6770			if($h)
6771			{
6772				$ratio_wh = ($w / $h);
6773			}
6774			else
6775			{
6776				$ratio_wh = 1;
6777			}
6778			// tcpdf-patch: end
6779			if (($y + $h) > $this->PageBreakTrigger) {
6780				$h = $this->PageBreakTrigger - $y;
6781				$w = ($h * $ratio_wh);
6782			}
6783			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6784				$w = $this->w - $this->rMargin - $x;
6785				$h = ($w / $ratio_wh);
6786			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6787				$w = $x - $this->lMargin;
6788				$h = ($w / $ratio_wh);
6789			}
6790		}
6791		return array($w, $h, $x, $y);
6792	}
6793
6794	/**
6795	 * Puts an image in the page.
6796	 * The upper-left corner must be given.
6797	 * The dimensions can be specified in different ways:<ul>
6798	 * <li>explicit width and height (expressed in user unit)</li>
6799	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6800	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6801	 * 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;
6802	 * The format can be specified explicitly or inferred from the file extension.<br />
6803	 * It is possible to put a link on the image.<br />
6804	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6805	 * @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').
6806	 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6807	 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6808	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6809	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6810	 * @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.
6811	 * @param $link (mixed) URL or identifier returned by AddLink().
6812	 * @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>
6813	 * @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).
6814	 * @param $dpi (int) dot-per-inch resolution used on resize
6815	 * @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>
6816	 * @param $ismask (boolean) true if this image is a mask, false otherwise
6817	 * @param $imgmask (mixed) image object returned by this function or false
6818	 * @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)))
6819	 * @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).
6820	 * @param $hidden (boolean) If true do not display the image.
6821	 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6822	 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6823	 * @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.
6824	 * @return image information
6825	 * @public
6826	 * @since 1.1
6827	 */
6828	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()) {
6829		if ($this->state != 2) {
6830			return;
6831		}
6832		if (strcmp($x, '') === 0) {
6833			$x = $this->x;
6834		}
6835		if (strcmp($y, '') === 0) {
6836			$y = $this->y;
6837		}
6838		// check page for no-write regions and adapt page margins if necessary
6839		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6840		$exurl = ''; // external streams
6841		$imsize = FALSE;
6842		// check if we are passing an image as file or string
6843		if ($file[0] === '@') {
6844			// image from string
6845			$imgdata = substr($file, 1);
6846		} else { // image file
6847			if ($file[0] === '*') {
6848				// image as external stream
6849				$file = substr($file, 1);
6850				$exurl = $file;
6851			}
6852			// check if is a local file
6853			if (!@file_exists($file)) {
6854				// try to encode spaces on filename
6855				$tfile = str_replace(' ', '%20', $file);
6856				if (@file_exists($tfile)) {
6857					$file = $tfile;
6858				}
6859			}
6860			if (($imsize = @getimagesize($file)) === FALSE) {
6861				if (in_array($file, $this->imagekeys)) {
6862					// get existing image data
6863					$info = $this->getImageBuffer($file);
6864					$imsize = array($info['w'], $info['h']);
6865				} elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
6866					$imgdata = TCPDF_STATIC::fileGetContents($file);
6867				}
6868			}
6869		}
6870		if (!empty($imgdata)) {
6871			// copy image to cache
6872			$original_file = $file;
6873			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6874			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6875			if (!$fp) {
6876				$this->Error('Unable to write file: '.$file);
6877			}
6878			fwrite($fp, $imgdata);
6879			fclose($fp);
6880			unset($imgdata);
6881			$imsize = @getimagesize($file);
6882			if ($imsize === FALSE) {
6883				unlink($file);
6884				$file = $original_file;
6885			}
6886		}
6887		if ($imsize === FALSE) {
6888			if (($w > 0) AND ($h > 0)) {
6889				// get measures from specified data
6890				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6891				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6892				$imsize = array($pw, $ph);
6893			} else {
6894				$this->Error('[Image] Unable to get the size of the image: '.$file);
6895			}
6896		}
6897		// file hash
6898		$filehash = md5($file);
6899		// get original image width and height in pixels
6900		list($pixw, $pixh) = $imsize;
6901		// calculate image width and height on document
6902		if (($w <= 0) AND ($h <= 0)) {
6903			// convert image size to document unit
6904			$w = $this->pixelsToUnits($pixw);
6905			$h = $this->pixelsToUnits($pixh);
6906		} elseif ($w <= 0) {
6907			$w = $h * $pixw / $pixh;
6908		} elseif ($h <= 0) {
6909			$h = $w * $pixh / $pixw;
6910		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6911			if (strlen($fitbox) !== 2) {
6912				// set default alignment
6913				$fitbox = '--';
6914			}
6915			// scale image dimensions proportionally to fit within the ($w, $h) box
6916			if ((($w * $pixh) / ($h * $pixw)) < 1) {
6917				// store current height
6918				$oldh = $h;
6919				// calculate new height
6920				$h = $w * $pixh / $pixw;
6921				// height difference
6922				$hdiff = ($oldh - $h);
6923				// vertical alignment
6924				switch (strtoupper($fitbox[1])) {
6925					case 'T': {
6926						break;
6927					}
6928					case 'M': {
6929						$y += ($hdiff / 2);
6930						break;
6931					}
6932					case 'B': {
6933						$y += $hdiff;
6934						break;
6935					}
6936				}
6937			} else {
6938				// store current width
6939				$oldw = $w;
6940				// calculate new width
6941				$w = $h * $pixw / $pixh;
6942				// width difference
6943				$wdiff = ($oldw - $w);
6944				// horizontal alignment
6945				switch (strtoupper($fitbox[0])) {
6946					case 'L': {
6947						if ($this->rtl) {
6948							$x -= $wdiff;
6949						}
6950						break;
6951					}
6952					case 'C': {
6953						if ($this->rtl) {
6954							$x -= ($wdiff / 2);
6955						} else {
6956							$x += ($wdiff / 2);
6957						}
6958						break;
6959					}
6960					case 'R': {
6961						if (!$this->rtl) {
6962							$x += $wdiff;
6963						}
6964						break;
6965					}
6966				}
6967			}
6968		}
6969		// fit the image on available space
6970		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6971		// calculate new minimum dimensions in pixels
6972		$neww = round($w * $this->k * $dpi / $this->dpi);
6973		$newh = round($h * $this->k * $dpi / $this->dpi);
6974		// check if resize is necessary (resize is used only to reduce the image)
6975		$newsize = ($neww * $newh);
6976		$pixsize = ($pixw * $pixh);
6977		if (intval($resize) == 2) {
6978			$resize = true;
6979		} elseif ($newsize >= $pixsize) {
6980			$resize = false;
6981		}
6982		// check if image has been already added on document
6983		$newimage = true;
6984		if (in_array($file, $this->imagekeys)) {
6985			$newimage = false;
6986			// get existing image data
6987			$info = $this->getImageBuffer($file);
6988			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
6989				// check if the newer image is larger
6990				$oldsize = ($info['w'] * $info['h']);
6991				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6992					$newimage = true;
6993				}
6994			}
6995		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
6996			// create temp image file (without alpha channel)
6997			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
6998			// create temp alpha file
6999			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7000			// check for cached images
7001			if (in_array($tempfile_plain, $this->imagekeys)) {
7002				// get existing image data
7003				$info = $this->getImageBuffer($tempfile_plain);
7004				// check if the newer image is larger
7005				$oldsize = ($info['w'] * $info['h']);
7006				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7007					$newimage = true;
7008				} else {
7009					$newimage = false;
7010					// embed mask image
7011					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7012					// embed image, masked with previously embedded mask
7013					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7014				}
7015			}
7016		}
7017		if ($newimage) {
7018			//First use of image, get info
7019			$type = strtolower($type);
7020			if ($type == '') {
7021				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7022			} elseif ($type == 'jpg') {
7023				$type = 'jpeg';
7024			}
7025			$mqr = TCPDF_STATIC::get_mqr();
7026			TCPDF_STATIC::set_mqr(false);
7027			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7028			$mtd = '_parse'.$type;
7029			// GD image handler function
7030			$gdfunction = 'imagecreatefrom'.$type;
7031			$info = false;
7032			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7033				// TCPDF image functions
7034				$info = TCPDF_IMAGES::$mtd($file);
7035				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7036					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7037					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7038				}
7039			}
7040			if (($info === false) AND function_exists($gdfunction)) {
7041				try {
7042					// GD library
7043					$img = $gdfunction($file);
7044					if ($img !== false) {
7045						if ($resize) {
7046							$imgr = imagecreatetruecolor($neww, $newh);
7047							if (($type == 'gif') OR ($type == 'png')) {
7048								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7049							}
7050							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7051							$img = $imgr;
7052						}
7053						if (($type == 'gif') OR ($type == 'png')) {
7054							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7055						} else {
7056							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7057						}
7058					}
7059				} catch(Exception $e) {
7060					$info = false;
7061				}
7062			}
7063			if (($info === false) AND extension_loaded('imagick')) {
7064				try {
7065					// ImageMagick library
7066					$img = new Imagick();
7067					if ($type == 'svg') {
7068						if ($file[0] === '@') {
7069							// image from string
7070							$svgimg = substr($file, 1);
7071						} else {
7072							// get SVG file content
7073							$svgimg = TCPDF_STATIC::fileGetContents($file);
7074						}
7075						if ($svgimg !== FALSE) {
7076							// get width and height
7077							$regs = array();
7078							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7079								$svgtag = $regs[1];
7080								$tmp = array();
7081								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7082									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7083									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7084									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7085								} else {
7086									$ow = $w;
7087								}
7088								$tmp = array();
7089								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7090									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7091									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7092									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7093								} else {
7094									$oh = $h;
7095								}
7096								$tmp = array();
7097								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7098									$vbw = ($ow * $this->imgscale * $this->k);
7099									$vbh = ($oh * $this->imgscale * $this->k);
7100									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7101									$svgtag = $vbox.$svgtag;
7102								}
7103								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7104							}
7105							$img->readImageBlob($svgimg);
7106						}
7107					} else {
7108						$img->readImage($file);
7109					}
7110					if ($resize) {
7111						$img->resizeImage($neww, $newh, 10, 1, false);
7112					}
7113					$img->setCompressionQuality($this->jpeg_quality);
7114					$img->setImageFormat('jpeg');
7115					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7116					$img->writeImage($tempname);
7117					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7118					unlink($tempname);
7119					$img->destroy();
7120				} catch(Exception $e) {
7121					$info = false;
7122				}
7123			}
7124			if ($info === false) {
7125				// unable to process image
7126				return;
7127			}
7128			TCPDF_STATIC::set_mqr($mqr);
7129			if ($ismask) {
7130				// force grayscale
7131				$info['cs'] = 'DeviceGray';
7132			}
7133			if ($imgmask !== false) {
7134				$info['masked'] = $imgmask;
7135			}
7136			if (!empty($exurl)) {
7137				$info['exurl'] = $exurl;
7138			}
7139			// array of alternative images
7140			$info['altimgs'] = $altimgs;
7141			// add image to document
7142			$info['i'] = $this->setImageBuffer($file, $info);
7143		}
7144		// set alignment
7145		$this->img_rb_y = $y + $h;
7146		// set alignment
7147		if ($this->rtl) {
7148			if ($palign == 'L') {
7149				$ximg = $this->lMargin;
7150			} elseif ($palign == 'C') {
7151				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7152			} elseif ($palign == 'R') {
7153				$ximg = $this->w - $this->rMargin - $w;
7154			} else {
7155				$ximg = $x - $w;
7156			}
7157			$this->img_rb_x = $ximg;
7158		} else {
7159			if ($palign == 'L') {
7160				$ximg = $this->lMargin;
7161			} elseif ($palign == 'C') {
7162				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7163			} elseif ($palign == 'R') {
7164				$ximg = $this->w - $this->rMargin - $w;
7165			} else {
7166				$ximg = $x;
7167			}
7168			$this->img_rb_x = $ximg + $w;
7169		}
7170		if ($ismask OR $hidden) {
7171			// image is not displayed
7172			return $info['i'];
7173		}
7174		$xkimg = $ximg * $this->k;
7175		if (!$alt) {
7176			// only non-alternative immages will be set
7177			$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']));
7178		}
7179		if (!empty($border)) {
7180			$bx = $this->x;
7181			$by = $this->y;
7182			$this->x = $ximg;
7183			if ($this->rtl) {
7184				$this->x += $w;
7185			}
7186			$this->y = $y;
7187			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7188			$this->x = $bx;
7189			$this->y = $by;
7190		}
7191		if ($link) {
7192			$this->Link($ximg, $y, $w, $h, $link, 0);
7193		}
7194		// set pointer to align the next text/objects
7195		switch($align) {
7196			case 'T': {
7197				$this->y = $y;
7198				$this->x = $this->img_rb_x;
7199				break;
7200			}
7201			case 'M': {
7202				$this->y = $y + round($h/2);
7203				$this->x = $this->img_rb_x;
7204				break;
7205			}
7206			case 'B': {
7207				$this->y = $this->img_rb_y;
7208				$this->x = $this->img_rb_x;
7209				break;
7210			}
7211			case 'N': {
7212				$this->SetY($this->img_rb_y);
7213				break;
7214			}
7215			default:{
7216				break;
7217			}
7218		}
7219		$this->endlinex = $this->img_rb_x;
7220		if ($this->inxobj) {
7221			// we are inside an XObject template
7222			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7223		}
7224		return $info['i'];
7225	}
7226
7227	/**
7228	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7229	 * @param $file (string) Name of the file containing the image.
7230	 * @param $x (float) Abscissa of the upper-left corner.
7231	 * @param $y (float) Ordinate of the upper-left corner.
7232	 * @param $wpx (float) Original width of the image in pixels.
7233	 * @param $hpx (float) original height of the image in pixels.
7234	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7235	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7236	 * @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.
7237	 * @param $link (mixed) URL or identifier returned by AddLink().
7238	 * @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>
7239	 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7240	 * @param $dpi (int) dot-per-inch resolution used on resize
7241	 * @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>
7242	 * @param $filehash (string) File hash used to build unique file names.
7243	 * @author Nicola Asuni
7244	 * @protected
7245	 * @since 4.3.007 (2008-12-04)
7246	 * @see Image()
7247	 */
7248	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7249		// create temp images
7250		if (empty($filehash)) {
7251			$filehash = md5($file);
7252		}
7253		// create temp image file (without alpha channel)
7254		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7255		// create temp alpha file
7256		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7257		$parsed = false;
7258		$parse_error = '';
7259		// ImageMagick extension
7260		if (($parsed === false) AND extension_loaded('imagick')) {
7261			try {
7262				// ImageMagick library
7263				$img = new Imagick();
7264				$img->readImage($file);
7265				// clone image object
7266				$imga = TCPDF_STATIC::objclone($img);
7267				// extract alpha channel
7268				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7269					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7270				} else {
7271					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7272					$img->negateImage(true);
7273				}
7274				$img->setImageFormat('png');
7275				$img->writeImage($tempfile_alpha);
7276				// remove alpha channel
7277				if (method_exists($imga, 'setImageMatte')) {
7278					$imga->setImageMatte(false);
7279				} else {
7280					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7281				}
7282				$imga->setImageFormat('png');
7283				$imga->writeImage($tempfile_plain);
7284				$parsed = true;
7285			} catch (Exception $e) {
7286				// Imagemagick fails, try with GD
7287				$parse_error = 'Imagick library error: '.$e->getMessage();
7288			}
7289		}
7290		// GD extension
7291		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7292			try {
7293				// generate images
7294				$img = imagecreatefrompng($file);
7295				$imgalpha = imagecreate($wpx, $hpx);
7296				// generate gray scale palette (0 -> 255)
7297				for ($c = 0; $c < 256; ++$c) {
7298					ImageColorAllocate($imgalpha, $c, $c, $c);
7299				}
7300				// extract alpha channel
7301				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7302					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7303						$color = imagecolorat($img, $xpx, $ypx);
7304						// get and correct gamma color
7305						$alpha = $this->getGDgamma($img, $color);
7306						imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7307					}
7308				}
7309				imagepng($imgalpha, $tempfile_alpha);
7310				imagedestroy($imgalpha);
7311				// extract image without alpha channel
7312				$imgplain = imagecreatetruecolor($wpx, $hpx);
7313				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7314				imagepng($imgplain, $tempfile_plain);
7315				imagedestroy($imgplain);
7316				$parsed = true;
7317			} catch (Exception $e) {
7318				// GD fails
7319				$parse_error = 'GD library error: '.$e->getMessage();
7320			}
7321		}
7322		if ($parsed === false) {
7323			if (empty($parse_error)) {
7324				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7325			} else {
7326				$this->Error($parse_error);
7327			}
7328		}
7329		// embed mask image
7330		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7331		// embed image, masked with previously embedded mask
7332		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7333	}
7334
7335	/**
7336	 * Get the GD-corrected PNG gamma value from alpha color
7337	 * @param $img (int) GD image Resource ID.
7338	 * @param $c (int) alpha color
7339	 * @protected
7340	 * @since 4.3.007 (2008-12-04)
7341	 */
7342	protected function getGDgamma($img, $c) {
7343		if (!isset($this->gdgammacache['#'.$c])) {
7344			$colors = imagecolorsforindex($img, $c);
7345			// GD alpha is only 7 bit (0 -> 127)
7346			$this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7347			// correct gamma
7348			$this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7349			// store the latest values on cache to improve performances
7350			if (count($this->gdgammacache) > 8) {
7351				// remove one element from the cache array
7352				array_shift($this->gdgammacache);
7353			}
7354		}
7355		return $this->gdgammacache['#'.$c];
7356	}
7357
7358	/**
7359	 * Performs a line break.
7360	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7361	 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7362	 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7363	 * @public
7364	 * @since 1.0
7365	 * @see Cell()
7366	 */
7367	public function Ln($h='', $cell=false) {
7368		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'])) {
7369			// revove vertical space from the top of the column
7370			return;
7371		}
7372		if ($cell) {
7373			if ($this->rtl) {
7374				$cellpadding = $this->cell_padding['R'];
7375			} else {
7376				$cellpadding = $this->cell_padding['L'];
7377			}
7378		} else {
7379			$cellpadding = 0;
7380		}
7381		if ($this->rtl) {
7382			$this->x = $this->w - $this->rMargin - $cellpadding;
7383		} else {
7384			$this->x = $this->lMargin + $cellpadding;
7385		}
7386		if (is_string($h)) {
7387			$h = $this->lasth;
7388		}
7389		$this->y += $h;
7390		$this->newline = true;
7391	}
7392
7393	/**
7394	 * Returns the relative X value of current position.
7395	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7396	 * @return float
7397	 * @public
7398	 * @since 1.2
7399	 * @see SetX(), GetY(), SetY()
7400	 */
7401	public function GetX() {
7402		//Get x position
7403		if ($this->rtl) {
7404			return ($this->w - $this->x);
7405		} else {
7406			return $this->x;
7407		}
7408	}
7409
7410	/**
7411	 * Returns the absolute X value of current position.
7412	 * @return float
7413	 * @public
7414	 * @since 1.2
7415	 * @see SetX(), GetY(), SetY()
7416	 */
7417	public function GetAbsX() {
7418		return $this->x;
7419	}
7420
7421	/**
7422	 * Returns the ordinate of the current position.
7423	 * @return float
7424	 * @public
7425	 * @since 1.0
7426	 * @see SetY(), GetX(), SetX()
7427	 */
7428	public function GetY() {
7429		return $this->y;
7430	}
7431
7432	/**
7433	 * Defines the abscissa of the current position.
7434	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7435	 * @param $x (float) The value of the abscissa in user units.
7436	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7437	 * @public
7438	 * @since 1.2
7439	 * @see GetX(), GetY(), SetY(), SetXY()
7440	 */
7441	public function SetX($x, $rtloff=false) {
7442		$x = floatval($x);
7443		if (!$rtloff AND $this->rtl) {
7444			if ($x >= 0) {
7445				$this->x = $this->w - $x;
7446			} else {
7447				$this->x = abs($x);
7448			}
7449		} else {
7450			if ($x >= 0) {
7451				$this->x = $x;
7452			} else {
7453				$this->x = $this->w + $x;
7454			}
7455		}
7456		if ($this->x < 0) {
7457			$this->x = 0;
7458		}
7459		if ($this->x > $this->w) {
7460			$this->x = $this->w;
7461		}
7462	}
7463
7464	/**
7465	 * Moves the current abscissa back to the left margin and sets the ordinate.
7466	 * If the passed value is negative, it is relative to the bottom of the page.
7467	 * @param $y (float) The value of the ordinate in user units.
7468	 * @param $resetx (bool) if true (default) reset the X position.
7469	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7470	 * @public
7471	 * @since 1.0
7472	 * @see GetX(), GetY(), SetY(), SetXY()
7473	 */
7474	public function SetY($y, $resetx=true, $rtloff=false) {
7475		$y = floatval($y);
7476		if ($resetx) {
7477			//reset x
7478			if (!$rtloff AND $this->rtl) {
7479				$this->x = $this->w - $this->rMargin;
7480			} else {
7481				$this->x = $this->lMargin;
7482			}
7483		}
7484		if ($y >= 0) {
7485			$this->y = $y;
7486		} else {
7487			$this->y = $this->h + $y;
7488		}
7489		if ($this->y < 0) {
7490			$this->y = 0;
7491		}
7492		if ($this->y > $this->h) {
7493			$this->y = $this->h;
7494		}
7495	}
7496
7497	/**
7498	 * Defines the abscissa and ordinate of the current position.
7499	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7500	 * @param $x (float) The value of the abscissa.
7501	 * @param $y (float) The value of the ordinate.
7502	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7503	 * @public
7504	 * @since 1.2
7505	 * @see SetX(), SetY()
7506	 */
7507	public function SetXY($x, $y, $rtloff=false) {
7508		$this->SetY($y, false, $rtloff);
7509		$this->SetX($x, $rtloff);
7510	}
7511
7512	/**
7513	 * Set the absolute X coordinate of the current pointer.
7514	 * @param $x (float) The value of the abscissa in user units.
7515	 * @public
7516	 * @since 5.9.186 (2012-09-13)
7517	 * @see setAbsX(), setAbsY(), SetAbsXY()
7518	 */
7519	public function SetAbsX($x) {
7520		$this->x = floatval($x);
7521	}
7522
7523	/**
7524	 * Set the absolute Y coordinate of the current pointer.
7525	 * @param $y (float) (float) The value of the ordinate in user units.
7526	 * @public
7527	 * @since 5.9.186 (2012-09-13)
7528	 * @see setAbsX(), setAbsY(), SetAbsXY()
7529	 */
7530	public function SetAbsY($y) {
7531		$this->y = floatval($y);
7532	}
7533
7534	/**
7535	 * Set the absolute X and Y coordinates of the current pointer.
7536	 * @param $x (float) The value of the abscissa in user units.
7537	 * @param $y (float) (float) The value of the ordinate in user units.
7538	 * @public
7539	 * @since 5.9.186 (2012-09-13)
7540	 * @see setAbsX(), setAbsY(), SetAbsXY()
7541	 */
7542	public function SetAbsXY($x, $y) {
7543		$this->SetAbsX($x);
7544		$this->SetAbsY($y);
7545	}
7546
7547	/**
7548	 * Send the document to a given destination: string, local file or browser.
7549	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7550	 * The method first calls Close() if necessary to terminate the document.
7551	 * @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.
7552	 * @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>
7553	 * @return string
7554	 * @public
7555	 * @since 1.0
7556	 * @see Close()
7557	 */
7558	public function Output($name='doc.pdf', $dest='I') {
7559		//Output PDF to some destination
7560		//Finish document if necessary
7561		if ($this->state < 3) {
7562			$this->Close();
7563		}
7564		//Normalize parameters
7565		if (is_bool($dest)) {
7566			$dest = $dest ? 'D' : 'F';
7567		}
7568		$dest = strtoupper($dest);
7569		if ($dest[0] != 'F') {
7570			$name = preg_replace('/[\s]+/', '_', $name);
7571			$name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7572		}
7573		if ($this->sign) {
7574			// *** apply digital signature to the document ***
7575			// get the document content
7576			$pdfdoc = $this->getBuffer();
7577			// remove last newline
7578			$pdfdoc = substr($pdfdoc, 0, -1);
7579			// remove filler space
7580			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7581			// define the ByteRange
7582			$byte_range = array();
7583			$byte_range[0] = 0;
7584			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7585			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7586			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7587			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7588			// replace the ByteRange
7589			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7590			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7591			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7592			// write the document to a temporary folder
7593			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7594			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7595			if (!$f) {
7596				$this->Error('Unable to create temporary file: '.$tempdoc);
7597			}
7598			$pdfdoc_length = strlen($pdfdoc);
7599			fwrite($f, $pdfdoc, $pdfdoc_length);
7600			fclose($f);
7601			// get digital signature via openssl library
7602			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7603			if (empty($this->signature_data['extracerts'])) {
7604				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7605			} else {
7606				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']);
7607			}
7608			// read signature
7609			$signature = file_get_contents($tempsign);
7610			// extract signature
7611			$signature = substr($signature, $pdfdoc_length);
7612			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7613			$tmparr = explode("\n\n", $signature);
7614			$signature = $tmparr[1];
7615			// decode signature
7616			$signature = base64_decode(trim($signature));
7617			// add TSA timestamp to signature
7618			$signature = $this->applyTSA($signature);
7619			// convert signature to hex
7620			$signature = current(unpack('H*', $signature));
7621			$signature = str_pad($signature, $this->signature_max_length, '0');
7622			// Add signature to the document
7623			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7624			$this->bufferlen = strlen($this->buffer);
7625		}
7626		switch($dest) {
7627			case 'I': {
7628				// Send PDF to the standard output
7629				if (ob_get_contents()) {
7630					$this->Error('Some data has already been output, can\'t send PDF file');
7631				}
7632				if (php_sapi_name() != 'cli') {
7633					// send output to a browser
7634					header('Content-Type: application/pdf');
7635					if (headers_sent()) {
7636						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7637					}
7638					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7639					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7640					header('Pragma: public');
7641					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7642					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7643					header('Content-Disposition: inline; filename="'.basename($name).'"');
7644					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7645				} else {
7646					echo $this->getBuffer();
7647				}
7648				break;
7649			}
7650			case 'D': {
7651				// download PDF as file
7652				if (ob_get_contents()) {
7653					$this->Error('Some data has already been output, can\'t send PDF file');
7654				}
7655				header('Content-Description: File Transfer');
7656				if (headers_sent()) {
7657					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7658				}
7659				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7660				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7661				header('Pragma: public');
7662				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7663				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7664				// force download dialog
7665				if (strpos(php_sapi_name(), 'cgi') === false) {
7666					header('Content-Type: application/force-download');
7667					header('Content-Type: application/octet-stream', false);
7668					header('Content-Type: application/download', false);
7669					header('Content-Type: application/pdf', false);
7670				} else {
7671					header('Content-Type: application/pdf');
7672				}
7673				// use the Content-Disposition header to supply a recommended filename
7674				header('Content-Disposition: attachment; filename="'.basename($name).'"');
7675				header('Content-Transfer-Encoding: binary');
7676				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7677				break;
7678			}
7679			case 'F':
7680			case 'FI':
7681			case 'FD': {
7682				// save PDF to a local file
7683				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7684				if (!$f) {
7685					$this->Error('Unable to create output file: '.$name);
7686				}
7687				fwrite($f, $this->getBuffer(), $this->bufferlen);
7688				fclose($f);
7689				if ($dest == 'FI') {
7690					// send headers to browser
7691					header('Content-Type: application/pdf');
7692					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7693					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7694					header('Pragma: public');
7695					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7696					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7697					header('Content-Disposition: inline; filename="'.basename($name).'"');
7698					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7699				} elseif ($dest == 'FD') {
7700					// send headers to browser
7701					if (ob_get_contents()) {
7702						$this->Error('Some data has already been output, can\'t send PDF file');
7703					}
7704					header('Content-Description: File Transfer');
7705					if (headers_sent()) {
7706						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7707					}
7708					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7709					header('Pragma: public');
7710					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7711					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7712					// force download dialog
7713					if (strpos(php_sapi_name(), 'cgi') === false) {
7714						header('Content-Type: application/force-download');
7715						header('Content-Type: application/octet-stream', false);
7716						header('Content-Type: application/download', false);
7717						header('Content-Type: application/pdf', false);
7718					} else {
7719						header('Content-Type: application/pdf');
7720					}
7721					// use the Content-Disposition header to supply a recommended filename
7722					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7723					header('Content-Transfer-Encoding: binary');
7724					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7725				}
7726				break;
7727			}
7728			case 'E': {
7729				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7730				$retval = 'Content-Type: application/pdf;'."\r\n";
7731				$retval .= ' name="'.$name.'"'."\r\n";
7732				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7733				$retval .= 'Content-Disposition: attachment;'."\r\n";
7734				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7735				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7736				return $retval;
7737			}
7738			case 'S': {
7739				// returns PDF as a string
7740				return $this->getBuffer();
7741			}
7742			default: {
7743				$this->Error('Incorrect output destination: '.$dest);
7744			}
7745		}
7746		return '';
7747	}
7748
7749	/**
7750	 * Unset all class variables except the following critical variables.
7751	 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7752	 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7753	 * @public
7754	 * @since 4.5.016 (2009-02-24)
7755	 */
7756	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7757		if ($destroyall AND !$preserve_objcopy) {
7758			// remove all temporary files
7759			$tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
7760			if (!empty($tmpfiles)) {
7761				array_map('unlink', $tmpfiles);
7762			}
7763		}
7764		$preserve = array(
7765			'file_id',
7766			'internal_encoding',
7767			'state',
7768			'bufferlen',
7769			'buffer',
7770			'cached_files',
7771			'sign',
7772			'signature_data',
7773			'signature_max_length',
7774			'byterange_string',
7775			'tsa_timestamp',
7776			'tsa_data'
7777		);
7778		foreach (array_keys(get_object_vars($this)) as $val) {
7779			if ($destroyall OR !in_array($val, $preserve)) {
7780				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7781					unset($this->$val);
7782				}
7783			}
7784		}
7785	}
7786
7787	/**
7788	 * Check for locale-related bug
7789	 * @protected
7790	 */
7791	protected function _dochecks() {
7792		//Check for locale-related bug
7793		if (1.1 == 1) {
7794			$this->Error('Don\'t alter the locale before including class file');
7795		}
7796		//Check for decimal separator
7797		if (sprintf('%.1F', 1.0) != '1.0') {
7798			setlocale(LC_NUMERIC, 'C');
7799		}
7800	}
7801
7802	/**
7803	 * Return an array containing variations for the basic page number alias.
7804	 * @param $a (string) Base alias.
7805	 * @return array of page number aliases
7806	 * @protected
7807	 */
7808	protected function getInternalPageNumberAliases($a= '') {
7809		$alias = array();
7810		// build array of Unicode + ASCII variants (the order is important)
7811		$alias = array('u' => array(), 'a' => array());
7812		$u = '{'.$a.'}';
7813		$alias['u'][] = TCPDF_STATIC::_escape($u);
7814		if ($this->isunicode) {
7815			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7816			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7817			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7818			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7819		}
7820		$alias['a'][] = TCPDF_STATIC::_escape($a);
7821		return $alias;
7822	}
7823
7824	/**
7825	 * Return an array containing all internal page aliases.
7826	 * @return array of page number aliases
7827	 * @protected
7828	 */
7829	protected function getAllInternalPageNumberAliases() {
7830		$basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
7831		$pnalias = array();
7832		foreach($basic_alias as $k => $a) {
7833			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7834		}
7835		return $pnalias;
7836	}
7837
7838	/**
7839	 * Replace right shift page number aliases with spaces to correct right alignment.
7840	 * This works perfectly only when using monospaced fonts.
7841	 * @param $page (string) Page content.
7842	 * @param $aliases (array) Array of page aliases.
7843	 * @param $diff (int) initial difference to add.
7844	 * @return replaced page content.
7845	 * @protected
7846	 */
7847	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7848		foreach ($aliases as $type => $alias) {
7849			foreach ($alias as $a) {
7850				// find position of compensation factor
7851				$startnum = (strpos($a, ':') + 1);
7852				$a = substr($a, 0, $startnum);
7853				if (($pos = strpos($page, $a)) !== false) {
7854					// end of alias
7855					$endnum = strpos($page, '}', $pos);
7856					// string to be replaced
7857					$aa = substr($page, $pos, ($endnum - $pos + 1));
7858					// get compensation factor
7859					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7860					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7861					$ratio = floatval($ratio);
7862					if ($type == 'u') {
7863						$chrdiff = floor(($diff + 12) * $ratio);
7864						$shift = str_repeat(' ', $chrdiff);
7865						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7866					} else {
7867						$chrdiff = floor(($diff + 11) * $ratio);
7868						$shift = str_repeat(' ', $chrdiff);
7869					}
7870					$page = str_replace($aa, $shift, $page);
7871				}
7872			}
7873		}
7874		return $page;
7875	}
7876
7877	/**
7878	 * Set page boxes to be included on page descriptions.
7879	 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7880	 * @protected
7881	 */
7882	protected function setPageBoxTypes($boxes) {
7883		$this->page_boxes = array();
7884		foreach ($boxes as $box) {
7885			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7886				$this->page_boxes[] = $box;
7887			}
7888		}
7889	}
7890
7891	/**
7892	 * Output pages (and replace page number aliases).
7893	 * @protected
7894	 */
7895	protected function _putpages() {
7896		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7897		// get internal aliases for page numbers
7898		$pnalias = $this->getAllInternalPageNumberAliases();
7899		$num_pages = $this->numpages;
7900		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7901		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7902		$ptp_num_chars = $this->GetNumChars($ptpa);
7903		$pagegroupnum = 0;
7904		$groupnum = 0;
7905		$ptgu = 1;
7906		$ptga = 1;
7907		$ptg_num_chars = 1;
7908		for ($n = 1; $n <= $num_pages; ++$n) {
7909			// get current page
7910			$temppage = $this->getPageBuffer($n);
7911			$pagelen = strlen($temppage);
7912			// set replacements for total pages number
7913			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7914			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7915			$pnp_num_chars = $this->GetNumChars($pnpa);
7916			$pdiff = 0; // difference used for right shift alignment of page numbers
7917			$gdiff = 0; // difference used for right shift alignment of page group numbers
7918			if (!empty($this->pagegroups)) {
7919				if (isset($this->newpagegroup[$n])) {
7920					$pagegroupnum = 0;
7921					++$groupnum;
7922					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7923					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7924					$ptg_num_chars = $this->GetNumChars($ptga);
7925				}
7926				++$pagegroupnum;
7927				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7928				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7929				$png_num_chars = $this->GetNumChars($pnga);
7930				// replace page numbers
7931				$replace = array();
7932				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7933				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7934				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7935				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7936				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7937			}
7938			// replace page numbers
7939			$replace = array();
7940			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7941			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7942			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7943			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7944			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7945			// replace right shift alias
7946			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7947			// replace EPS marker
7948			$temppage = str_replace($this->epsmarker, '', $temppage);
7949			//Page
7950			$this->page_obj_id[$n] = $this->_newobj();
7951			$out = '<<';
7952			$out .= ' /Type /Page';
7953			$out .= ' /Parent 1 0 R';
7954			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
7955				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7956			}
7957			$out .= ' /Resources 2 0 R';
7958			foreach ($this->page_boxes as $box) {
7959				$out .= ' /'.$box;
7960				$out .= sprintf(' [%F %F %F %F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
7961			}
7962			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
7963				$out .= ' /BoxColorInfo <<';
7964				foreach ($this->page_boxes as $box) {
7965					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
7966						$out .= ' /'.$box.' <<';
7967						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
7968							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
7969							$out .= ' /C [';
7970							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
7971							$out .= ' ]';
7972						}
7973						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
7974							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
7975						}
7976						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
7977							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
7978						}
7979						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
7980							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
7981							$out .= ' /D [';
7982							foreach ($dashes as $dash) {
7983								$out .= sprintf(' %F', ($dash * $this->k));
7984							}
7985							$out .= ' ]';
7986						}
7987						$out .= ' >>';
7988					}
7989				}
7990				$out .= ' >>';
7991			}
7992			$out .= ' /Contents '.($this->n + 1).' 0 R';
7993			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
7994			if (!$this->pdfa_mode) {
7995				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
7996			}
7997			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
7998				// page transitions
7999				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8000					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8001				}
8002				$out .= ' /Trans <<';
8003				$out .= ' /Type /Trans';
8004				if (isset($this->pagedim[$n]['trans']['S'])) {
8005					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8006				}
8007				if (isset($this->pagedim[$n]['trans']['D'])) {
8008					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8009				}
8010				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8011					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8012				}
8013				if (isset($this->pagedim[$n]['trans']['M'])) {
8014					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8015				}
8016				if (isset($this->pagedim[$n]['trans']['Di'])) {
8017					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8018				}
8019				if (isset($this->pagedim[$n]['trans']['SS'])) {
8020					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8021				}
8022				if (isset($this->pagedim[$n]['trans']['B'])) {
8023					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8024				}
8025				$out .= ' >>';
8026			}
8027			$out .= $this->_getannotsrefs($n);
8028			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8029			$out .= ' >>';
8030			$out .= "\n".'endobj';
8031			$this->_out($out);
8032			//Page content
8033			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8034			$this->_newobj();
8035			$p = $this->_getrawstream($p);
8036			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8037		}
8038		//Pages root
8039		$out = $this->_getobj(1)."\n";
8040		$out .= '<< /Type /Pages /Kids [';
8041		foreach($this->page_obj_id as $page_obj) {
8042			$out .= ' '.$page_obj.' 0 R';
8043		}
8044		$out .= ' ] /Count '.$num_pages.' >>';
8045		$out .= "\n".'endobj';
8046		$this->_out($out);
8047	}
8048
8049	/**
8050	 * Get references to page annotations.
8051	 * @param $n (int) page number
8052	 * @return string
8053	 * @protected
8054	 * @author Nicola Asuni
8055	 * @since 5.0.010 (2010-05-17)
8056	 */
8057	protected function _getannotsrefs($n) {
8058		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8059			return '';
8060		}
8061		$out = ' /Annots [';
8062		if (isset($this->PageAnnots[$n])) {
8063			foreach ($this->PageAnnots[$n] as $key => $val) {
8064				if (!in_array($val['n'], $this->radio_groups)) {
8065					$out .= ' '.$val['n'].' 0 R';
8066				}
8067			}
8068			// add radiobutton groups
8069			if (isset($this->radiobutton_groups[$n])) {
8070				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8071					if (isset($data['n'])) {
8072						$out .= ' '.$data['n'].' 0 R';
8073					}
8074				}
8075			}
8076		}
8077		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8078			// set reference for signature object
8079			$out .= ' '.$this->sig_obj_id.' 0 R';
8080		}
8081		if (!empty($this->empty_signature_appearance)) {
8082			foreach ($this->empty_signature_appearance as $esa) {
8083				if ($esa['page'] == $n) {
8084					// set reference for empty signature objects
8085					$out .= ' '.$esa['objid'].' 0 R';
8086				}
8087			}
8088		}
8089		$out .= ' ]';
8090		return $out;
8091	}
8092
8093	/**
8094	 * Output annotations objects for all pages.
8095	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8096	 * See section 12.5 of PDF 32000_2008 reference.
8097	 * @protected
8098	 * @author Nicola Asuni
8099	 * @since 4.0.018 (2008-08-06)
8100	 */
8101	protected function _putannotsobjs() {
8102		// reset object counter
8103		for ($n=1; $n <= $this->numpages; ++$n) {
8104			if (isset($this->PageAnnots[$n])) {
8105				// set page annotations
8106				foreach ($this->PageAnnots[$n] as $key => $pl) {
8107					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8108					// create annotation object for grouping radiobuttons
8109					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8110						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8111						$annots = '<<';
8112						$annots .= ' /Type /Annot';
8113						$annots .= ' /Subtype /Widget';
8114						$annots .= ' /Rect [0 0 0 0]';
8115						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8116							// read only
8117							$annots .= ' /F 68';
8118							$annots .= ' /Ff 49153';
8119						} else {
8120							$annots .= ' /F 4'; // default print for PDF/A
8121							$annots .= ' /Ff 49152';
8122						}
8123						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8124						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8125							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8126						}
8127						$annots .= ' /FT /Btn';
8128						$annots .= ' /Kids [';
8129						$defval = '';
8130						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8131							if (isset($data['kid'])) {
8132								$annots .= ' '.$data['kid'].' 0 R';
8133								if ($data['def'] !== 'Off') {
8134									$defval = $data['def'];
8135								}
8136							}
8137						}
8138						$annots .= ' ]';
8139						if (!empty($defval)) {
8140							$annots .= ' /V /'.$defval;
8141						}
8142						$annots .= ' >>';
8143						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8144						$this->form_obj_id[] = $radio_button_obj_id;
8145						// store object id to be used on Parent entry of Kids
8146						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8147					}
8148					$formfield = false;
8149					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8150					$a = $pl['x'] * $this->k;
8151					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8152					$c = $pl['w'] * $this->k;
8153					$d = $pl['h'] * $this->k;
8154					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8155					// create new annotation object
8156					$annots = '<</Type /Annot';
8157					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8158					$annots .= ' /Rect ['.$rect.']';
8159					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8160					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8161						$annots .= ' /FT /'.$pl['opt']['ft'];
8162						$formfield = true;
8163					}
8164					$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8165					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8166					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8167					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8168					if (isset($pl['opt']['f'])) {
8169						$fval = 0;
8170						if (is_array($pl['opt']['f'])) {
8171							foreach ($pl['opt']['f'] as $f) {
8172								switch (strtolower($f)) {
8173									case 'invisible': {
8174										$fval += 1 << 0;
8175										break;
8176									}
8177									case 'hidden': {
8178										$fval += 1 << 1;
8179										break;
8180									}
8181									case 'print': {
8182										$fval += 1 << 2;
8183										break;
8184									}
8185									case 'nozoom': {
8186										$fval += 1 << 3;
8187										break;
8188									}
8189									case 'norotate': {
8190										$fval += 1 << 4;
8191										break;
8192									}
8193									case 'noview': {
8194										$fval += 1 << 5;
8195										break;
8196									}
8197									case 'readonly': {
8198										$fval += 1 << 6;
8199										break;
8200									}
8201									case 'locked': {
8202										$fval += 1 << 8;
8203										break;
8204									}
8205									case 'togglenoview': {
8206										$fval += 1 << 9;
8207										break;
8208									}
8209									case 'lockedcontents': {
8210										$fval += 1 << 10;
8211										break;
8212									}
8213									default: {
8214										break;
8215									}
8216								}
8217							}
8218						} else {
8219							$fval = intval($pl['opt']['f']);
8220						}
8221					} else {
8222						$fval = 4;
8223					}
8224					if ($this->pdfa_mode) {
8225						// force print flag for PDF/A mode
8226						$fval |= 4;
8227					}
8228					$annots .= ' /F '.intval($fval);
8229					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8230						$annots .= ' /AS /'.$pl['opt']['as'];
8231					}
8232					if (isset($pl['opt']['ap'])) {
8233						// appearance stream
8234						$annots .= ' /AP <<';
8235						if (is_array($pl['opt']['ap'])) {
8236							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8237								// $apmode can be: n = normal; r = rollover; d = down;
8238								$annots .= ' /'.strtoupper($apmode);
8239								if (is_array($apdef)) {
8240									$annots .= ' <<';
8241									foreach ($apdef as $apstate => $stream) {
8242										// reference to XObject that define the appearance for this mode-state
8243										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8244										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8245									}
8246									$annots .= ' >>';
8247								} else {
8248									// reference to XObject that define the appearance for this mode
8249									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8250									$annots .= ' '.$apsobjid.' 0 R';
8251								}
8252							}
8253						} else {
8254							$annots .= $pl['opt']['ap'];
8255						}
8256						$annots .= ' >>';
8257					}
8258					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8259						$annots .= ' /BS <<';
8260						$annots .= ' /Type /Border';
8261						if (isset($pl['opt']['bs']['w'])) {
8262							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8263						}
8264						$bstyles = array('S', 'D', 'B', 'I', 'U');
8265						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8266							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8267						}
8268						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8269							$annots .= ' /D [';
8270							foreach ($pl['opt']['bs']['d'] as $cord) {
8271								$annots .= ' '.intval($cord);
8272							}
8273							$annots .= ']';
8274						}
8275						$annots .= ' >>';
8276					} else {
8277						$annots .= ' /Border [';
8278						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8279							$annots .= intval($pl['opt']['border'][0]).' ';
8280							$annots .= intval($pl['opt']['border'][1]).' ';
8281							$annots .= intval($pl['opt']['border'][2]);
8282							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8283								$annots .= ' [';
8284								foreach ($pl['opt']['border'][3] as $dash) {
8285									$annots .= intval($dash).' ';
8286								}
8287								$annots .= ']';
8288							}
8289						} else {
8290							$annots .= '0 0 0';
8291						}
8292						$annots .= ']';
8293					}
8294					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8295						$annots .= ' /BE <<';
8296						$bstyles = array('S', 'C');
8297						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8298							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8299						} else {
8300							$annots .= ' /S /S';
8301						}
8302						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8303							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8304						}
8305						$annots .= '>>';
8306					}
8307					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8308						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8309					}
8310					//$annots .= ' /StructParent ';
8311					//$annots .= ' /OC ';
8312					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8313					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8314						// this is a markup type
8315						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8316							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8317						}
8318						//$annots .= ' /Popup ';
8319						if (isset($pl['opt']['ca'])) {
8320							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8321						}
8322						if (isset($pl['opt']['rc'])) {
8323							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8324						}
8325						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8326						//$annots .= ' /IRT ';
8327						if (isset($pl['opt']['subj'])) {
8328							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8329						}
8330						//$annots .= ' /RT ';
8331						//$annots .= ' /IT ';
8332						//$annots .= ' /ExData ';
8333					}
8334					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8335					// Annotation types
8336					switch (strtolower($pl['opt']['subtype'])) {
8337						case 'text': {
8338							if (isset($pl['opt']['open'])) {
8339								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8340							}
8341							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8342							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8343								$annots .= ' /Name /'.$pl['opt']['name'];
8344							} else {
8345								$annots .= ' /Name /Note';
8346							}
8347							$statemodels = array('Marked', 'Review');
8348							if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8349								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8350							} else {
8351								$pl['opt']['statemodel'] = 'Marked';
8352								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8353							}
8354							if ($pl['opt']['statemodel'] == 'Marked') {
8355								$states = array('Accepted', 'Unmarked');
8356							} else {
8357								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8358							}
8359							if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8360								$annots .= ' /State /'.$pl['opt']['state'];
8361							} else {
8362								if ($pl['opt']['statemodel'] == 'Marked') {
8363									$annots .= ' /State /Unmarked';
8364								} else {
8365									$annots .= ' /State /None';
8366								}
8367							}
8368							break;
8369						}
8370						case 'link': {
8371							if (is_string($pl['txt'])) {
8372								if ($pl['txt'][0] == '#') {
8373									// internal destination
8374									$annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8375								} elseif ($pl['txt'][0] == '%') {
8376									// embedded PDF file
8377									$filename = basename(substr($pl['txt'], 1));
8378									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8379								} elseif ($pl['txt'][0] == '*') {
8380									// embedded generic file
8381									$filename = basename(substr($pl['txt'], 1));
8382									$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});';
8383									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8384								} else {
8385									$parsedUrl = parse_url($pl['txt']);
8386									if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8387										// relative link to a PDF file
8388										$dest = '[0 /Fit]'; // default page 0
8389										if (!empty($parsedUrl['fragment'])) {
8390											// check for named destination
8391											$tmp = explode('=', $parsedUrl['fragment']);
8392											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8393										}
8394										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8395									} else {
8396										// external URI link
8397										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8398									}
8399								}
8400							} elseif (isset($this->links[$pl['txt']])) {
8401								// internal link ID
8402								$l = $this->links[$pl['txt']];
8403								if (isset($this->page_obj_id[($l['p'])])) {
8404									$annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
8405								}
8406							}
8407							$hmodes = array('N', 'I', 'O', 'P');
8408							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8409								$annots .= ' /H /'.$pl['opt']['h'];
8410							} else {
8411								$annots .= ' /H /I';
8412							}
8413							//$annots .= ' /PA ';
8414							//$annots .= ' /Quadpoints ';
8415							break;
8416						}
8417						case 'freetext': {
8418							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8419								$annots .= ' /DA ('.$pl['opt']['da'].')';
8420							}
8421							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8422								$annots .= ' /Q '.intval($pl['opt']['q']);
8423							}
8424							if (isset($pl['opt']['rc'])) {
8425								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8426							}
8427							if (isset($pl['opt']['ds'])) {
8428								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8429							}
8430							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8431								$annots .= ' /CL [';
8432								foreach ($pl['opt']['cl'] as $cl) {
8433									$annots .= sprintf('%F ', $cl * $this->k);
8434								}
8435								$annots .= ']';
8436							}
8437							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8438							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8439								$annots .= ' /IT /'.$pl['opt']['it'];
8440							}
8441							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8442								$l = $pl['opt']['rd'][0] * $this->k;
8443								$r = $pl['opt']['rd'][1] * $this->k;
8444								$t = $pl['opt']['rd'][2] * $this->k;
8445								$b = $pl['opt']['rd'][3] * $this->k;
8446								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8447							}
8448							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8449								$annots .= ' /LE /'.$pl['opt']['le'];
8450							}
8451							break;
8452						}
8453						case 'line': {
8454							break;
8455						}
8456						case 'square': {
8457							break;
8458						}
8459						case 'circle': {
8460							break;
8461						}
8462						case 'polygon': {
8463							break;
8464						}
8465						case 'polyline': {
8466							break;
8467						}
8468						case 'highlight': {
8469							break;
8470						}
8471						case 'underline': {
8472							break;
8473						}
8474						case 'squiggly': {
8475							break;
8476						}
8477						case 'strikeout': {
8478							break;
8479						}
8480						case 'stamp': {
8481							break;
8482						}
8483						case 'caret': {
8484							break;
8485						}
8486						case 'ink': {
8487							break;
8488						}
8489						case 'popup': {
8490							break;
8491						}
8492						case 'fileattachment': {
8493							if ($this->pdfa_mode) {
8494								// embedded files are not allowed in PDF/A mode
8495								break;
8496							}
8497							if (!isset($pl['opt']['fs'])) {
8498								break;
8499							}
8500							$filename = basename($pl['opt']['fs']);
8501							if (isset($this->embeddedfiles[$filename]['f'])) {
8502								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8503								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8504								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8505									$annots .= ' /Name /'.$pl['opt']['name'];
8506								} else {
8507									$annots .= ' /Name /PushPin';
8508								}
8509								// index (zero-based) of the annotation in the Annots array of this page
8510								$this->embeddedfiles[$filename]['a'] = $key;
8511							}
8512							break;
8513						}
8514						case 'sound': {
8515							if (!isset($pl['opt']['fs'])) {
8516								break;
8517							}
8518							$filename = basename($pl['opt']['fs']);
8519							if (isset($this->embeddedfiles[$filename]['f'])) {
8520								// ... TO BE COMPLETED ...
8521								// /R /C /B /E /CO /CP
8522								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8523								$iconsapp = array('Speaker', 'Mic');
8524								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8525									$annots .= ' /Name /'.$pl['opt']['name'];
8526								} else {
8527									$annots .= ' /Name /Speaker';
8528								}
8529							}
8530							break;
8531						}
8532						case 'movie': {
8533							break;
8534						}
8535						case 'widget': {
8536							$hmode = array('N', 'I', 'O', 'P', 'T');
8537							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8538								$annots .= ' /H /'.$pl['opt']['h'];
8539							}
8540							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8541								$annots .= ' /MK <<';
8542								if (isset($pl['opt']['mk']['r'])) {
8543									$annots .= ' /R '.$pl['opt']['mk']['r'];
8544								}
8545								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8546									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8547								}
8548								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8549									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8550								}
8551								if (isset($pl['opt']['mk']['ca'])) {
8552									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8553								}
8554								if (isset($pl['opt']['mk']['rc'])) {
8555									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8556								}
8557								if (isset($pl['opt']['mk']['ac'])) {
8558									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8559								}
8560								if (isset($pl['opt']['mk']['i'])) {
8561									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8562									if ($info !== false) {
8563										$annots .= ' /I '.$info['n'].' 0 R';
8564									}
8565								}
8566								if (isset($pl['opt']['mk']['ri'])) {
8567									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8568									if ($info !== false) {
8569										$annots .= ' /RI '.$info['n'].' 0 R';
8570									}
8571								}
8572								if (isset($pl['opt']['mk']['ix'])) {
8573									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8574									if ($info !== false) {
8575										$annots .= ' /IX '.$info['n'].' 0 R';
8576									}
8577								}
8578								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8579									$annots .= ' /IF <<';
8580									$if_sw = array('A', 'B', 'S', 'N');
8581									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8582										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8583									}
8584									$if_s = array('A', 'P');
8585									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8586										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8587									}
8588									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8589										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8590									}
8591									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8592										$annots .= ' /FB true';
8593									}
8594									$annots .= '>>';
8595								}
8596								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8597									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8598								}
8599								$annots .= '>>';
8600							} // end MK
8601							// --- Entries for field dictionaries ---
8602							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8603								// set parent
8604								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8605							}
8606							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8607								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8608							}
8609							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8610								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8611							}
8612							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8613								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8614							}
8615							if (isset($pl['opt']['ff'])) {
8616								if (is_array($pl['opt']['ff'])) {
8617									// array of bit settings
8618									$flag = 0;
8619									foreach($pl['opt']['ff'] as $val) {
8620										$flag += 1 << ($val - 1);
8621									}
8622								} else {
8623									$flag = intval($pl['opt']['ff']);
8624								}
8625								$annots .= ' /Ff '.$flag;
8626							}
8627							if (isset($pl['opt']['maxlen'])) {
8628								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8629							}
8630							if (isset($pl['opt']['v'])) {
8631								$annots .= ' /V';
8632								if (is_array($pl['opt']['v'])) {
8633									foreach ($pl['opt']['v'] AS $optval) {
8634										if (is_float($optval)) {
8635											$optval = sprintf('%F', $optval);
8636										}
8637										$annots .= ' '.$optval;
8638									}
8639								} else {
8640									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8641								}
8642							}
8643							if (isset($pl['opt']['dv'])) {
8644								$annots .= ' /DV';
8645								if (is_array($pl['opt']['dv'])) {
8646									foreach ($pl['opt']['dv'] AS $optval) {
8647										if (is_float($optval)) {
8648											$optval = sprintf('%F', $optval);
8649										}
8650										$annots .= ' '.$optval;
8651									}
8652								} else {
8653									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8654								}
8655							}
8656							if (isset($pl['opt']['rv'])) {
8657								$annots .= ' /RV';
8658								if (is_array($pl['opt']['rv'])) {
8659									foreach ($pl['opt']['rv'] AS $optval) {
8660										if (is_float($optval)) {
8661											$optval = sprintf('%F', $optval);
8662										}
8663										$annots .= ' '.$optval;
8664									}
8665								} else {
8666									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8667								}
8668							}
8669							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8670								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8671							}
8672							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8673								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8674							}
8675							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8676								$annots .= ' /DA ('.$pl['opt']['da'].')';
8677							}
8678							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8679								$annots .= ' /Q '.intval($pl['opt']['q']);
8680							}
8681							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8682								$annots .= ' /Opt [';
8683								foreach($pl['opt']['opt'] AS $copt) {
8684									if (is_array($copt)) {
8685										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8686									} else {
8687										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8688									}
8689								}
8690								$annots .= ']';
8691							}
8692							if (isset($pl['opt']['ti'])) {
8693								$annots .= ' /TI '.intval($pl['opt']['ti']);
8694							}
8695							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8696								$annots .= ' /I [';
8697								foreach($pl['opt']['i'] AS $copt) {
8698									$annots .= intval($copt).' ';
8699								}
8700								$annots .= ']';
8701							}
8702							break;
8703						}
8704						case 'screen': {
8705							break;
8706						}
8707						case 'printermark': {
8708							break;
8709						}
8710						case 'trapnet': {
8711							break;
8712						}
8713						case 'watermark': {
8714							break;
8715						}
8716						case '3d': {
8717							break;
8718						}
8719						default: {
8720							break;
8721						}
8722					}
8723					$annots .= '>>';
8724					// create new annotation object
8725					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8726					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8727						// store reference of form object
8728						$this->form_obj_id[] = $annot_obj_id;
8729					}
8730				}
8731			}
8732		} // end for each page
8733	}
8734
8735	/**
8736	 * Put appearance streams XObject used to define annotation's appearance states.
8737	 * @param $w (int) annotation width
8738	 * @param $h (int) annotation height
8739	 * @param $stream (string) appearance stream
8740	 * @return int object ID
8741	 * @protected
8742	 * @since 4.8.001 (2009-09-09)
8743	 */
8744	protected function _putAPXObject($w=0, $h=0, $stream='') {
8745		$stream = trim($stream);
8746		$out = $this->_getobj()."\n";
8747		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8748		$out .= '<<';
8749		$out .= ' /Type /XObject';
8750		$out .= ' /Subtype /Form';
8751		$out .= ' /FormType 1';
8752		if ($this->compress) {
8753			$stream = gzcompress($stream);
8754			$out .= ' /Filter /FlateDecode';
8755		}
8756		$rect = sprintf('%F %F', $w, $h);
8757		$out .= ' /BBox [0 0 '.$rect.']';
8758		$out .= ' /Matrix [1 0 0 1 0 0]';
8759		$out .= ' /Resources 2 0 R';
8760		$stream = $this->_getrawstream($stream);
8761		$out .= ' /Length '.strlen($stream);
8762		$out .= ' >>';
8763		$out .= ' stream'."\n".$stream."\n".'endstream';
8764		$out .= "\n".'endobj';
8765		$this->_out($out);
8766		return $this->n;
8767	}
8768
8769	/**
8770	 * Output fonts.
8771	 * @author Nicola Asuni
8772	 * @protected
8773	 */
8774	protected function _putfonts() {
8775		$nf = $this->n;
8776		foreach ($this->diffs as $diff) {
8777			//Encodings
8778			$this->_newobj();
8779			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8780		}
8781		$mqr = TCPDF_STATIC::get_mqr();
8782		TCPDF_STATIC::set_mqr(false);
8783		foreach ($this->FontFiles as $file => $info) {
8784			// search and get font file to embedd
8785			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8786			if (!TCPDF_STATIC::empty_string($fontfile)) {
8787				$font = file_get_contents($fontfile);
8788				$compressed = (substr($file, -2) == '.z');
8789				if ((!$compressed) AND (isset($info['length2']))) {
8790					$header = (ord($font[0]) == 128);
8791					if ($header) {
8792						// strip first binary header
8793						$font = substr($font, 6);
8794					}
8795					if ($header AND (ord($font[$info['length1']]) == 128)) {
8796						// strip second binary header
8797						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8798					}
8799				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8800					if ($compressed) {
8801						// uncompress font
8802						$font = gzuncompress($font);
8803					}
8804					// merge subset characters
8805					$subsetchars = array(); // used chars
8806					foreach ($info['fontkeys'] as $fontkey) {
8807						$fontinfo = $this->getFontBuffer($fontkey);
8808						$subsetchars += $fontinfo['subsetchars'];
8809					}
8810					// rebuild a font subset
8811					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8812					// calculate new font length
8813					$info['length1'] = strlen($font);
8814					if ($compressed) {
8815						// recompress font
8816						$font = gzcompress($font);
8817					}
8818				}
8819				$this->_newobj();
8820				$this->FontFiles[$file]['n'] = $this->n;
8821				$stream = $this->_getrawstream($font);
8822				$out = '<< /Length '.strlen($stream);
8823				if ($compressed) {
8824					$out .= ' /Filter /FlateDecode';
8825				}
8826				$out .= ' /Length1 '.$info['length1'];
8827				if (isset($info['length2'])) {
8828					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8829				}
8830				$out .= ' >>';
8831				$out .= ' stream'."\n".$stream."\n".'endstream';
8832				$out .= "\n".'endobj';
8833				$this->_out($out);
8834			}
8835		}
8836		TCPDF_STATIC::set_mqr($mqr);
8837		foreach ($this->fontkeys as $k) {
8838			//Font objects
8839			$font = $this->getFontBuffer($k);
8840			$type = $font['type'];
8841			$name = $font['name'];
8842			if ($type == 'core') {
8843				// standard core font
8844				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8845				$out .= '<</Type /Font';
8846				$out .= ' /Subtype /Type1';
8847				$out .= ' /BaseFont /'.$name;
8848				$out .= ' /Name /F'.$font['i'];
8849				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8850					$out .= ' /Encoding /WinAnsiEncoding';
8851				}
8852				if ($k == 'helvetica') {
8853					// add default font for annotations
8854					$this->annotation_fonts[$k] = $font['i'];
8855				}
8856				$out .= ' >>';
8857				$out .= "\n".'endobj';
8858				$this->_out($out);
8859			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8860				// additional Type1 or TrueType font
8861				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8862				$out .= '<</Type /Font';
8863				$out .= ' /Subtype /'.$type;
8864				$out .= ' /BaseFont /'.$name;
8865				$out .= ' /Name /F'.$font['i'];
8866				$out .= ' /FirstChar 32 /LastChar 255';
8867				$out .= ' /Widths '.($this->n + 1).' 0 R';
8868				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8869				if ($font['enc']) {
8870					if (isset($font['diff'])) {
8871						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8872					} else {
8873						$out .= ' /Encoding /WinAnsiEncoding';
8874					}
8875				}
8876				$out .= ' >>';
8877				$out .= "\n".'endobj';
8878				$this->_out($out);
8879				// Widths
8880				$this->_newobj();
8881				$s = '[';
8882				for ($i = 32; $i < 256; ++$i) {
8883					if (isset($font['cw'][$i])) {
8884						$s .= $font['cw'][$i].' ';
8885					} else {
8886						$s .= $font['dw'].' ';
8887					}
8888				}
8889				$s .= ']';
8890				$s .= "\n".'endobj';
8891				$this->_out($s);
8892				//Descriptor
8893				$this->_newobj();
8894				$s = '<</Type /FontDescriptor /FontName /'.$name;
8895				foreach ($font['desc'] as $fdk => $fdv) {
8896					if (is_float($fdv)) {
8897						$fdv = sprintf('%F', $fdv);
8898					}
8899					$s .= ' /'.$fdk.' '.$fdv.'';
8900				}
8901				if (!TCPDF_STATIC::empty_string($font['file'])) {
8902					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8903				}
8904				$s .= '>>';
8905				$s .= "\n".'endobj';
8906				$this->_out($s);
8907			} else {
8908				// additional types
8909				$mtd = '_put'.strtolower($type);
8910				if (!method_exists($this, $mtd)) {
8911					$this->Error('Unsupported font type: '.$type);
8912				}
8913				$this->$mtd($font);
8914			}
8915		}
8916	}
8917
8918	/**
8919	 * Adds unicode fonts.<br>
8920	 * Based on PDF Reference 1.3 (section 5)
8921	 * @param $font (array) font data
8922	 * @protected
8923	 * @author Nicola Asuni
8924	 * @since 1.52.0.TC005 (2005-01-05)
8925	 */
8926	protected function _puttruetypeunicode($font) {
8927		$fontname = '';
8928		if ($font['subset']) {
8929			// change name for font subsetting
8930			$subtag = sprintf('%06u', $font['i']);
8931			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8932			$fontname .= $subtag.'+';
8933		}
8934		$fontname .= $font['name'];
8935		// Type0 Font
8936		// A composite font composed of other fonts, organized hierarchically
8937		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8938		$out .= '<< /Type /Font';
8939		$out .= ' /Subtype /Type0';
8940		$out .= ' /BaseFont /'.$fontname;
8941		$out .= ' /Name /F'.$font['i'];
8942		$out .= ' /Encoding /'.$font['enc'];
8943		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8944		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8945		$out .= ' >>';
8946		$out .= "\n".'endobj';
8947		$this->_out($out);
8948		// ToUnicode map for Identity-H
8949		$stream = TCPDF_FONT_DATA::$uni_identity_h;
8950		// ToUnicode Object
8951		$this->_newobj();
8952		$stream = ($this->compress) ? gzcompress($stream) : $stream;
8953		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8954		$stream = $this->_getrawstream($stream);
8955		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
8956		// CIDFontType2
8957		// A CIDFont whose glyph descriptions are based on TrueType font technology
8958		$oid = $this->_newobj();
8959		$out = '<< /Type /Font';
8960		$out .= ' /Subtype /CIDFontType2';
8961		$out .= ' /BaseFont /'.$fontname;
8962		// A dictionary containing entries that define the character collection of the CIDFont.
8963		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
8964		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
8965		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
8966		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
8967		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
8968		$out .= ' /DW '.$font['dw']; // default width
8969		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
8970		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8971			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
8972		}
8973		$out .= ' >>';
8974		$out .= "\n".'endobj';
8975		$this->_out($out);
8976		// Font descriptor
8977		// A font descriptor describing the CIDFont default metrics other than its glyph widths
8978		$this->_newobj();
8979		$out = '<< /Type /FontDescriptor';
8980		$out .= ' /FontName /'.$fontname;
8981		foreach ($font['desc'] as $key => $value) {
8982			if (is_float($value)) {
8983				$value = sprintf('%F', $value);
8984			}
8985			$out .= ' /'.$key.' '.$value;
8986		}
8987		$fontdir = false;
8988		if (!TCPDF_STATIC::empty_string($font['file'])) {
8989			// A stream containing a TrueType font
8990			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
8991			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
8992		}
8993		$out .= ' >>';
8994		$out .= "\n".'endobj';
8995		$this->_out($out);
8996		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8997			$this->_newobj();
8998			// Embed CIDToGIDMap
8999			// A specification of the mapping from CIDs to glyph indices
9000			// search and get CTG font file to embedd
9001			$ctgfile = strtolower($font['ctg']);
9002			// search and get ctg font file to embedd
9003			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9004			if (TCPDF_STATIC::empty_string($fontfile)) {
9005				$this->Error('Font file not found: '.$ctgfile);
9006			}
9007			$stream = $this->_getrawstream(file_get_contents($fontfile));
9008			$out = '<< /Length '.strlen($stream).'';
9009			if (substr($fontfile, -2) == '.z') { // check file extension
9010				// Decompresses data encoded using the public-domain
9011				// zlib/deflate compression method, reproducing the
9012				// original text or binary data
9013				$out .= ' /Filter /FlateDecode';
9014			}
9015			$out .= ' >>';
9016			$out .= ' stream'."\n".$stream."\n".'endstream';
9017			$out .= "\n".'endobj';
9018			$this->_out($out);
9019		}
9020	}
9021
9022	/**
9023	 * Output CID-0 fonts.
9024	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9025	 * @param $font (array) font data
9026	 * @protected
9027	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9028	 * @since 3.2.000 (2008-06-23)
9029	 */
9030	protected function _putcidfont0($font) {
9031		$cidoffset = 0;
9032		if (!isset($font['cw'][1])) {
9033			$cidoffset = 31;
9034		}
9035		if (isset($font['cidinfo']['uni2cid'])) {
9036			// convert unicode to cid.
9037			$uni2cid = $font['cidinfo']['uni2cid'];
9038			$cw = array();
9039			foreach ($font['cw'] as $uni => $width) {
9040				if (isset($uni2cid[$uni])) {
9041					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9042				} elseif ($uni < 256) {
9043					$cw[$uni] = $width;
9044				} // else unknown character
9045			}
9046			$font = array_merge($font, array('cw' => $cw));
9047		}
9048		$name = $font['name'];
9049		$enc = $font['enc'];
9050		if ($enc) {
9051			$longname = $name.'-'.$enc;
9052		} else {
9053			$longname = $name;
9054		}
9055		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9056		$out .= '<</Type /Font';
9057		$out .= ' /Subtype /Type0';
9058		$out .= ' /BaseFont /'.$longname;
9059		$out .= ' /Name /F'.$font['i'];
9060		if ($enc) {
9061			$out .= ' /Encoding /'.$enc;
9062		}
9063		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9064		$out .= ' >>';
9065		$out .= "\n".'endobj';
9066		$this->_out($out);
9067		$oid = $this->_newobj();
9068		$out = '<</Type /Font';
9069		$out .= ' /Subtype /CIDFontType0';
9070		$out .= ' /BaseFont /'.$name;
9071		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9072		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9073		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9074		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9075		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9076		$out .= ' /DW '.$font['dw'];
9077		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9078		$out .= ' >>';
9079		$out .= "\n".'endobj';
9080		$this->_out($out);
9081		$this->_newobj();
9082		$s = '<</Type /FontDescriptor /FontName /'.$name;
9083		foreach ($font['desc'] as $k => $v) {
9084			if ($k != 'Style') {
9085				if (is_float($v)) {
9086					$v = sprintf('%F', $v);
9087				}
9088				$s .= ' /'.$k.' '.$v.'';
9089			}
9090		}
9091		$s .= '>>';
9092		$s .= "\n".'endobj';
9093		$this->_out($s);
9094	}
9095
9096	/**
9097	 * Output images.
9098	 * @protected
9099	 */
9100	protected function _putimages() {
9101		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9102		foreach ($this->imagekeys as $file) {
9103			$info = $this->getImageBuffer($file);
9104			// set object for alternate images array
9105			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9106				$altoid = $this->_newobj();
9107				$out = '[';
9108				foreach ($info['altimgs'] as $altimage) {
9109					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9110						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9111						$out .= ' /DefaultForPrinting';
9112						if ($altimage[1] === true) {
9113							$out .= ' true';
9114						} else {
9115							$out .= ' false';
9116						}
9117						$out .= ' >>';
9118					}
9119				}
9120				$out .= ' ]';
9121				$out .= "\n".'endobj';
9122				$this->_out($out);
9123			}
9124			// set image object
9125			$oid = $this->_newobj();
9126			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9127			$this->setImageSubBuffer($file, 'n', $this->n);
9128			$out = '<</Type /XObject';
9129			$out .= ' /Subtype /Image';
9130			$out .= ' /Width '.$info['w'];
9131			$out .= ' /Height '.$info['h'];
9132			if (array_key_exists('masked', $info)) {
9133				$out .= ' /SMask '.($this->n - 1).' 0 R';
9134			}
9135			// set color space
9136			$icc = false;
9137			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9138				// ICC Colour Space
9139				$icc = true;
9140				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9141			} elseif ($info['cs'] == 'Indexed') {
9142				// Indexed Colour Space
9143				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9144			} else {
9145				// Device Colour Space
9146				$out .= ' /ColorSpace /'.$info['cs'];
9147			}
9148			if ($info['cs'] == 'DeviceCMYK') {
9149				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9150			}
9151			$out .= ' /BitsPerComponent '.$info['bpc'];
9152			if (isset($altoid) AND ($altoid > 0)) {
9153				// reference to alternate images dictionary
9154				$out .= ' /Alternates '.$altoid.' 0 R';
9155			}
9156			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9157				// external stream
9158				$out .= ' /Length 0';
9159				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9160				if (isset($info['f'])) {
9161					$out .= ' /FFilter /'.$info['f'];
9162				}
9163				$out .= ' >>';
9164				$out .= ' stream'."\n".'endstream';
9165			} else {
9166				if (isset($info['f'])) {
9167					$out .= ' /Filter /'.$info['f'];
9168				}
9169				if (isset($info['parms'])) {
9170					$out .= ' '.$info['parms'];
9171				}
9172				if (isset($info['trns']) AND is_array($info['trns'])) {
9173					$trns = '';
9174					$count_info = count($info['trns']);
9175					if ($info['cs'] == 'Indexed') {
9176						$maxval =(pow(2, $info['bpc']) - 1);
9177						for ($i = 0; $i < $count_info; ++$i) {
9178							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9179								// this is not a binary type mask @TODO: create a SMask
9180								$trns = '';
9181								break;
9182							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9183								// store the first fully transparent value
9184								$trns .= $i.' '.$i.' ';
9185							}
9186						}
9187					} else {
9188						// grayscale or RGB
9189						for ($i = 0; $i < $count_info; ++$i) {
9190							if ($info['trns'][$i] == 0) {
9191								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9192							}
9193						}
9194					}
9195					// Colour Key Masking
9196					if (!empty($trns)) {
9197						$out .= ' /Mask ['.$trns.']';
9198					}
9199				}
9200				$stream = $this->_getrawstream($info['data']);
9201				$out .= ' /Length '.strlen($stream).' >>';
9202				$out .= ' stream'."\n".$stream."\n".'endstream';
9203			}
9204			$out .= "\n".'endobj';
9205			$this->_out($out);
9206			if ($icc) {
9207				// ICC colour profile
9208				$this->_newobj();
9209				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9210				$icc = $this->_getrawstream($icc);
9211				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9212			} elseif ($info['cs'] == 'Indexed') {
9213				// colour palette
9214				$this->_newobj();
9215				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9216				$pal = $this->_getrawstream($pal);
9217				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9218			}
9219		}
9220	}
9221
9222	/**
9223	 * Output Form XObjects Templates.
9224	 * @author Nicola Asuni
9225	 * @since 5.8.017 (2010-08-24)
9226	 * @protected
9227	 * @see startTemplate(), endTemplate(), printTemplate()
9228	 */
9229	protected function _putxobjects() {
9230		foreach ($this->xobjects as $key => $data) {
9231			if (isset($data['outdata'])) {
9232				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9233				$out = $this->_getobj($data['n'])."\n";
9234				$out .= '<<';
9235				$out .= ' /Type /XObject';
9236				$out .= ' /Subtype /Form';
9237				$out .= ' /FormType 1';
9238				if ($this->compress) {
9239					$stream = gzcompress($stream);
9240					$out .= ' /Filter /FlateDecode';
9241				}
9242				$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));
9243				$out .= ' /Matrix [1 0 0 1 0 0]';
9244				$out .= ' /Resources <<';
9245				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9246				if (!$this->pdfa_mode) {
9247					// transparency
9248					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9249						$out .= ' /ExtGState <<';
9250						foreach ($data['extgstates'] as $k => $extgstate) {
9251							if (isset($this->extgstates[$k]['name'])) {
9252								$out .= ' /'.$this->extgstates[$k]['name'];
9253							} else {
9254								$out .= ' /GS'.$k;
9255							}
9256							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9257						}
9258						$out .= ' >>';
9259					}
9260					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9261						$gp = '';
9262						$gs = '';
9263						foreach ($data['gradients'] as $id => $grad) {
9264							// gradient patterns
9265							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9266							// gradient shadings
9267							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9268						}
9269						$out .= ' /Pattern <<'.$gp.' >>';
9270						$out .= ' /Shading <<'.$gs.' >>';
9271					}
9272				}
9273				// spot colors
9274				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9275					$out .= ' /ColorSpace <<';
9276					foreach ($data['spot_colors'] as $name => $color) {
9277						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9278					}
9279					$out .= ' >>';
9280				}
9281				// fonts
9282				if (!empty($data['fonts'])) {
9283					$out .= ' /Font <<';
9284					foreach ($data['fonts'] as $fontkey => $fontid) {
9285						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9286					}
9287					$out .= ' >>';
9288				}
9289				// images or nested xobjects
9290				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9291					$out .= ' /XObject <<';
9292					foreach ($data['images'] as $imgid) {
9293						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9294					}
9295					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9296						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9297					}
9298					$out .= ' >>';
9299				}
9300				$out .= ' >>'; //end resources
9301				if (isset($data['group']) AND ($data['group'] !== false)) {
9302					// set transparency group
9303					$out .= ' /Group << /Type /Group /S /Transparency';
9304					if (is_array($data['group'])) {
9305						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9306							$out .= ' /CS /'.$data['group']['CS'];
9307						}
9308						if (isset($data['group']['I'])) {
9309							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9310						}
9311						if (isset($data['group']['K'])) {
9312							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9313						}
9314					}
9315					$out .= ' >>';
9316				}
9317				$stream = $this->_getrawstream($stream, $data['n']);
9318				$out .= ' /Length '.strlen($stream);
9319				$out .= ' >>';
9320				$out .= ' stream'."\n".$stream."\n".'endstream';
9321				$out .= "\n".'endobj';
9322				$this->_out($out);
9323			}
9324		}
9325	}
9326
9327	/**
9328	 * Output Spot Colors Resources.
9329	 * @protected
9330	 * @since 4.0.024 (2008-09-12)
9331	 */
9332	protected function _putspotcolors() {
9333		foreach ($this->spot_colors as $name => $color) {
9334			$this->_newobj();
9335			$this->spot_colors[$name]['n'] = $this->n;
9336			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9337			$out .= ' /DeviceCMYK <<';
9338			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9339			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9340			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9341			$out .= "\n".'endobj';
9342			$this->_out($out);
9343		}
9344	}
9345
9346	/**
9347	 * Return XObjects Dictionary.
9348	 * @return string XObjects dictionary
9349	 * @protected
9350	 * @since 5.8.014 (2010-08-23)
9351	 */
9352	protected function _getxobjectdict() {
9353		$out = '';
9354		foreach ($this->xobjects as $id => $objid) {
9355			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9356		}
9357		return $out;
9358	}
9359
9360	/**
9361	 * Output Resources Dictionary.
9362	 * @protected
9363	 */
9364	protected function _putresourcedict() {
9365		$out = $this->_getobj(2)."\n";
9366		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9367		$out .= ' /Font <<';
9368		foreach ($this->fontkeys as $fontkey) {
9369			$font = $this->getFontBuffer($fontkey);
9370			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9371		}
9372		$out .= ' >>';
9373		$out .= ' /XObject <<';
9374		$out .= $this->_getxobjectdict();
9375		$out .= ' >>';
9376		// layers
9377		if (!empty($this->pdflayers)) {
9378			$out .= ' /Properties <<';
9379			foreach ($this->pdflayers as $layer) {
9380				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9381			}
9382			$out .= ' >>';
9383		}
9384		if (!$this->pdfa_mode) {
9385			// transparency
9386			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9387				$out .= ' /ExtGState <<';
9388				foreach ($this->extgstates as $k => $extgstate) {
9389					if (isset($extgstate['name'])) {
9390						$out .= ' /'.$extgstate['name'];
9391					} else {
9392						$out .= ' /GS'.$k;
9393					}
9394					$out .= ' '.$extgstate['n'].' 0 R';
9395				}
9396				$out .= ' >>';
9397			}
9398			if (isset($this->gradients) AND !empty($this->gradients)) {
9399				$gp = '';
9400				$gs = '';
9401				foreach ($this->gradients as $id => $grad) {
9402					// gradient patterns
9403					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9404					// gradient shadings
9405					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9406				}
9407				$out .= ' /Pattern <<'.$gp.' >>';
9408				$out .= ' /Shading <<'.$gs.' >>';
9409			}
9410		}
9411		// spot colors
9412		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9413			$out .= ' /ColorSpace <<';
9414			foreach ($this->spot_colors as $color) {
9415				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9416			}
9417			$out .= ' >>';
9418		}
9419		$out .= ' >>';
9420		$out .= "\n".'endobj';
9421		$this->_out($out);
9422	}
9423
9424	/**
9425	 * Output Resources.
9426	 * @protected
9427	 */
9428	protected function _putresources() {
9429		$this->_putextgstates();
9430		$this->_putocg();
9431		$this->_putfonts();
9432		$this->_putimages();
9433		$this->_putspotcolors();
9434		$this->_putshaders();
9435		$this->_putxobjects();
9436		$this->_putresourcedict();
9437		$this->_putdests();
9438		$this->_putEmbeddedFiles();
9439		$this->_putannotsobjs();
9440		$this->_putjavascript();
9441		$this->_putbookmarks();
9442		$this->_putencryption();
9443	}
9444
9445	/**
9446	 * Adds some Metadata information (Document Information Dictionary)
9447	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9448	 * @return int object id
9449	 * @protected
9450	 */
9451	protected function _putinfo() {
9452		$oid = $this->_newobj();
9453		$out = '<<';
9454		// store current isunicode value
9455		$prev_isunicode = $this->isunicode;
9456		if ($this->docinfounicode) {
9457			$this->isunicode = true;
9458		}
9459		if (!TCPDF_STATIC::empty_string($this->title)) {
9460			// The document's title.
9461			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9462		}
9463		if (!TCPDF_STATIC::empty_string($this->author)) {
9464			// The name of the person who created the document.
9465			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9466		}
9467		if (!TCPDF_STATIC::empty_string($this->subject)) {
9468			// The subject of the document.
9469			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9470		}
9471		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9472			// Keywords associated with the document.
9473			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9474		}
9475		if (!TCPDF_STATIC::empty_string($this->creator)) {
9476			// 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.
9477			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9478		}
9479		// restore previous isunicode value
9480		$this->isunicode = $prev_isunicode;
9481		// default producer
9482		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9483		// The date and time the document was created, in human-readable form
9484		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9485		// The date and time the document was most recently modified, in human-readable form
9486		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9487		// A name object indicating whether the document has been modified to include trapping information
9488		$out .= ' /Trapped /False';
9489		$out .= ' >>';
9490		$out .= "\n".'endobj';
9491		$this->_out($out);
9492		return $oid;
9493	}
9494
9495	/**
9496	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9497	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9498	 * @param $xmp (string) Custom XMP data.
9499	 * @since 5.9.128 (2011-10-06)
9500	 * @public
9501	 */
9502	public function setExtraXMP($xmp) {
9503		$this->custom_xmp = $xmp;
9504	}
9505
9506	/**
9507	 * Put XMP data object and return ID.
9508	 * @return (int) The object ID.
9509	 * @since 5.9.121 (2011-09-28)
9510	 * @protected
9511	 */
9512	protected function _putXMP() {
9513		$oid = $this->_newobj();
9514		// store current isunicode value
9515		$prev_isunicode = $this->isunicode;
9516		$this->isunicode = true;
9517		$prev_encrypted = $this->encrypted;
9518		$this->encrypted = false;
9519		// set XMP data
9520		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9521		$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";
9522		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9523		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9524		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9525		$xmp .= "\t\t\t".'<dc:title>'."\n";
9526		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9527		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9528		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9529		$xmp .= "\t\t\t".'</dc:title>'."\n";
9530		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9531		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9532		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9533		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9534		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9535		$xmp .= "\t\t\t".'<dc:description>'."\n";
9536		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9537		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9538		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9539		$xmp .= "\t\t\t".'</dc:description>'."\n";
9540		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9541		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9542		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9543		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9544		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9545		$xmp .= "\t\t".'</rdf:Description>'."\n";
9546		// convert doc creation date format
9547		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9548		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9549		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9550		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9551		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9552		// convert doc modification date format
9553		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9554		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9555		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9556		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9557		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9558		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9559		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9560		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9561		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9562		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9563		$xmp .= "\t\t".'</rdf:Description>'."\n";
9564		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9565		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9566		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9567		$xmp .= "\t\t".'</rdf:Description>'."\n";
9568		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9569		$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);
9570		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9571		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9572		$xmp .= "\t\t".'</rdf:Description>'."\n";
9573		if ($this->pdfa_mode) {
9574			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9575			$xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9576			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9577			$xmp .= "\t\t".'</rdf:Description>'."\n";
9578		}
9579		// XMP extension schemas
9580		$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";
9581		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9582		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9583		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9584		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9585		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9586		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9587		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9588		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9589		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9590		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9591		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9592		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9593		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9594		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9595		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9596		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9597		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9598		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9599		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9600		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9601		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9602		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9603		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9604		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9605		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9606		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9607		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9608		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9609		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9610		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9611		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9612		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9613		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9614		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9615		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9616		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9617		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9618		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9619		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9620		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9621		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9622		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9623		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9624		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9625		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9626		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9627		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9628		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9629		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9630		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9631		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9632		$xmp .= "\t\t".'</rdf:Description>'."\n";
9633		$xmp .= "\t".'</rdf:RDF>'."\n";
9634		$xmp .= $this->custom_xmp;
9635		$xmp .= '</x:xmpmeta>'."\n";
9636		$xmp .= '<?xpacket end="w"?>';
9637		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9638		// restore previous isunicode value
9639		$this->isunicode = $prev_isunicode;
9640		$this->encrypted = $prev_encrypted;
9641		$this->_out($out);
9642		return $oid;
9643	}
9644
9645	/**
9646	 * Output Catalog.
9647	 * @return int object id
9648	 * @protected
9649	 */
9650	protected function _putcatalog() {
9651		// put XMP
9652		$xmpobj = $this->_putXMP();
9653		// if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9654		if ($this->pdfa_mode OR $this->force_srgb) {
9655			$iccobj = $this->_newobj();
9656			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9657			$filter = '';
9658			if ($this->compress) {
9659				$filter = ' /Filter /FlateDecode';
9660				$icc = gzcompress($icc);
9661			}
9662			$icc = $this->_getrawstream($icc);
9663			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9664		}
9665		// start catalog
9666		$oid = $this->_newobj();
9667		$out = '<< /Type /Catalog';
9668		$out .= ' /Version /'.$this->PDFVersion;
9669		//$out .= ' /Extensions <<>>';
9670		$out .= ' /Pages 1 0 R';
9671		//$out .= ' /PageLabels ' //...;
9672		$out .= ' /Names <<';
9673		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9674			$out .= ' /JavaScript '.$this->n_js;
9675		}
9676		if (!empty($this->efnames)) {
9677			$out .= ' /EmbeddedFiles <</Names [';
9678			foreach ($this->efnames AS $fn => $fref) {
9679				$out .= ' '.$this->_datastring($fn).' '.$fref;
9680			}
9681			$out .= ' ]>>';
9682		}
9683		$out .= ' >>';
9684		if (!empty($this->dests)) {
9685			$out .= ' /Dests '.($this->n_dests).' 0 R';
9686		}
9687		$out .= $this->_putviewerpreferences();
9688		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9689			$out .= ' /PageLayout /'.$this->LayoutMode;
9690		}
9691		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9692			$out .= ' /PageMode /'.$this->PageMode;
9693		}
9694		if (count($this->outlines) > 0) {
9695			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9696			$out .= ' /PageMode /UseOutlines';
9697		}
9698		//$out .= ' /Threads []';
9699		if ($this->ZoomMode == 'fullpage') {
9700			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9701		} elseif ($this->ZoomMode == 'fullwidth') {
9702			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9703		} elseif ($this->ZoomMode == 'real') {
9704			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9705		} elseif (!is_string($this->ZoomMode)) {
9706			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9707		}
9708		//$out .= ' /AA <<>>';
9709		//$out .= ' /URI <<>>';
9710		$out .= ' /Metadata '.$xmpobj.' 0 R';
9711		//$out .= ' /StructTreeRoot <<>>';
9712		//$out .= ' /MarkInfo <<>>';
9713		if (isset($this->l['a_meta_language'])) {
9714			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9715		}
9716		//$out .= ' /SpiderInfo <<>>';
9717		// set OutputIntent to sRGB IEC61966-2.1 if required
9718		if ($this->pdfa_mode OR $this->force_srgb) {
9719			$out .= ' /OutputIntents [<<';
9720			$out .= ' /Type /OutputIntent';
9721			$out .= ' /S /GTS_PDFA1';
9722			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9723			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9724			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9725			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9726			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9727			$out .= ' >>]';
9728		}
9729		//$out .= ' /PieceInfo <<>>';
9730		if (!empty($this->pdflayers)) {
9731			$lyrobjs = '';
9732			$lyrobjs_off = '';
9733			$lyrobjs_lock = '';
9734			foreach ($this->pdflayers as $layer) {
9735				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9736				$lyrobjs .= $layer_obj_ref;
9737				if ($layer['view'] === false) {
9738					$lyrobjs_off .= $layer_obj_ref;
9739				}
9740				if ($layer['lock']) {
9741					$lyrobjs_lock .= $layer_obj_ref;
9742				}
9743			}
9744			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9745			$out .= ' /D <<';
9746			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9747			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9748			$out .= ' /BaseState /ON';
9749			$out .= ' /OFF ['.$lyrobjs_off.']';
9750			$out .= ' /Locked ['.$lyrobjs_lock.']';
9751			$out .= ' /Intent /View';
9752			$out .= ' /AS [';
9753			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9754			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9755			$out .= ' ]';
9756			$out .= ' /Order ['.$lyrobjs.']';
9757			$out .= ' /ListMode /AllPages';
9758			//$out .= ' /RBGroups ['..']';
9759			//$out .= ' /Locked ['..']';
9760			$out .= ' >>';
9761			$out .= ' >>';
9762		}
9763		// AcroForm
9764		if (!empty($this->form_obj_id)
9765			OR ($this->sign AND isset($this->signature_data['cert_type']))
9766			OR !empty($this->empty_signature_appearance)) {
9767			$out .= ' /AcroForm <<';
9768			$objrefs = '';
9769			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9770				// set reference for signature object
9771				$objrefs .= $this->sig_obj_id.' 0 R';
9772			}
9773			if (!empty($this->empty_signature_appearance)) {
9774				foreach ($this->empty_signature_appearance as $esa) {
9775					// set reference for empty signature objects
9776					$objrefs .= ' '.$esa['objid'].' 0 R';
9777				}
9778			}
9779			if (!empty($this->form_obj_id)) {
9780				foreach($this->form_obj_id as $objid) {
9781					$objrefs .= ' '.$objid.' 0 R';
9782				}
9783			}
9784			$out .= ' /Fields ['.$objrefs.']';
9785			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9786			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9787				$out .= ' /NeedAppearances false';
9788			}
9789			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9790				if ($this->signature_data['cert_type'] > 0) {
9791					$out .= ' /SigFlags 3';
9792				} else {
9793					$out .= ' /SigFlags 1';
9794				}
9795			}
9796			//$out .= ' /CO ';
9797			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9798				$out .= ' /DR <<';
9799				$out .= ' /Font <<';
9800				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9801					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9802				}
9803				$out .= ' >> >>';
9804			}
9805			$font = $this->getFontBuffer('helvetica');
9806			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9807			$out .= ' /Q '.(($this->rtl)?'2':'0');
9808			//$out .= ' /XFA ';
9809			$out .= ' >>';
9810			// signatures
9811			if ($this->sign AND isset($this->signature_data['cert_type'])
9812				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9813				if ($this->signature_data['cert_type'] > 0) {
9814					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9815				} else {
9816					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9817				}
9818			}
9819		}
9820		//$out .= ' /Legal <<>>';
9821		//$out .= ' /Requirements []';
9822		//$out .= ' /Collection <<>>';
9823		//$out .= ' /NeedsRendering true';
9824		$out .= ' >>';
9825		$out .= "\n".'endobj';
9826		$this->_out($out);
9827		return $oid;
9828	}
9829
9830	/**
9831	 * Output viewer preferences.
9832	 * @return string for viewer preferences
9833	 * @author Nicola asuni
9834	 * @since 3.1.000 (2008-06-09)
9835	 * @protected
9836	 */
9837	protected function _putviewerpreferences() {
9838		$vp = $this->viewer_preferences;
9839		$out = ' /ViewerPreferences <<';
9840		if ($this->rtl) {
9841			$out .= ' /Direction /R2L';
9842		} else {
9843			$out .= ' /Direction /L2R';
9844		}
9845		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9846			$out .= ' /HideToolbar true';
9847		}
9848		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9849			$out .= ' /HideMenubar true';
9850		}
9851		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9852			$out .= ' /HideWindowUI true';
9853		}
9854		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9855			$out .= ' /FitWindow true';
9856		}
9857		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9858			$out .= ' /CenterWindow true';
9859		}
9860		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9861			$out .= ' /DisplayDocTitle true';
9862		}
9863		if (isset($vp['NonFullScreenPageMode'])) {
9864			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9865		}
9866		if (isset($vp['ViewArea'])) {
9867			$out .= ' /ViewArea /'.$vp['ViewArea'];
9868		}
9869		if (isset($vp['ViewClip'])) {
9870			$out .= ' /ViewClip /'.$vp['ViewClip'];
9871		}
9872		if (isset($vp['PrintArea'])) {
9873			$out .= ' /PrintArea /'.$vp['PrintArea'];
9874		}
9875		if (isset($vp['PrintClip'])) {
9876			$out .= ' /PrintClip /'.$vp['PrintClip'];
9877		}
9878		if (isset($vp['PrintScaling'])) {
9879			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
9880		}
9881		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9882			$out .= ' /Duplex /'.$vp['Duplex'];
9883		}
9884		if (isset($vp['PickTrayByPDFSize'])) {
9885			if ($vp['PickTrayByPDFSize']) {
9886				$out .= ' /PickTrayByPDFSize true';
9887			} else {
9888				$out .= ' /PickTrayByPDFSize false';
9889			}
9890		}
9891		if (isset($vp['PrintPageRange'])) {
9892			$PrintPageRangeNum = '';
9893			foreach ($vp['PrintPageRange'] as $k => $v) {
9894				$PrintPageRangeNum .= ' '.($v - 1).'';
9895			}
9896			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9897		}
9898		if (isset($vp['NumCopies'])) {
9899			$out .= ' /NumCopies '.intval($vp['NumCopies']);
9900		}
9901		$out .= ' >>';
9902		return $out;
9903	}
9904
9905	/**
9906	 * Output PDF File Header (7.5.2).
9907	 * @protected
9908	 */
9909	protected function _putheader() {
9910		$this->_out('%PDF-'.$this->PDFVersion);
9911		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9912	}
9913
9914	/**
9915	 * Output end of document (EOF).
9916	 * @protected
9917	 */
9918	protected function _enddoc() {
9919		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9920			// save subset chars of the previous font
9921			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9922		}
9923		$this->state = 1;
9924		$this->_putheader();
9925		$this->_putpages();
9926		$this->_putresources();
9927		// empty signature fields
9928		if (!empty($this->empty_signature_appearance)) {
9929			foreach ($this->empty_signature_appearance as $key => $esa) {
9930				// widget annotation for empty signature
9931				$out = $this->_getobj($esa['objid'])."\n";
9932				$out .= '<< /Type /Annot';
9933				$out .= ' /Subtype /Widget';
9934				$out .= ' /Rect ['.$esa['rect'].']';
9935				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
9936				$out .= ' /F 4';
9937				$out .= ' /FT /Sig';
9938				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
9939				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
9940				$out .= ' /Ff 0';
9941				$out .= ' >>';
9942				$out .= "\n".'endobj';
9943				$this->_out($out);
9944			}
9945		}
9946		// Signature
9947		if ($this->sign AND isset($this->signature_data['cert_type'])) {
9948			// widget annotation for signature
9949			$out = $this->_getobj($this->sig_obj_id)."\n";
9950			$out .= '<< /Type /Annot';
9951			$out .= ' /Subtype /Widget';
9952			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
9953			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
9954			$out .= ' /F 4';
9955			$out .= ' /FT /Sig';
9956			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
9957			$out .= ' /Ff 0';
9958			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
9959			$out .= ' >>';
9960			$out .= "\n".'endobj';
9961			$this->_out($out);
9962			// signature
9963			$this->_putsignature();
9964		}
9965		// Info
9966		$objid_info = $this->_putinfo();
9967		// Catalog
9968		$objid_catalog = $this->_putcatalog();
9969		// Cross-ref
9970		$o = $this->bufferlen;
9971		// XREF section
9972		$this->_out('xref');
9973		$this->_out('0 '.($this->n + 1));
9974		$this->_out('0000000000 65535 f ');
9975		$freegen = ($this->n + 2);
9976		for ($i=1; $i <= $this->n; ++$i) {
9977			if (!isset($this->offsets[$i]) AND ($i > 1)) {
9978				$this->_out(sprintf('0000000000 %05d f ', $freegen));
9979				++$freegen;
9980			} else {
9981				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
9982			}
9983		}
9984		// TRAILER
9985		$out = 'trailer'."\n";
9986		$out .= '<<';
9987		$out .= ' /Size '.($this->n + 1);
9988		$out .= ' /Root '.$objid_catalog.' 0 R';
9989		$out .= ' /Info '.$objid_info.' 0 R';
9990		if ($this->encrypted) {
9991			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
9992		}
9993		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
9994		$out .= ' >>';
9995		$this->_out($out);
9996		$this->_out('startxref');
9997		$this->_out($o);
9998		$this->_out('%%EOF');
9999		$this->state = 3; // end-of-doc
10000	}
10001
10002	/**
10003	 * Initialize a new page.
10004	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10005	 * @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().
10006	 * @protected
10007	 * @see getPageSizeFromFormat(), setPageFormat()
10008	 */
10009	protected function _beginpage($orientation='', $format='') {
10010		++$this->page;
10011		$this->pageobjects[$this->page] = array();
10012		$this->setPageBuffer($this->page, '');
10013		// initialize array for graphics tranformation positions inside a page buffer
10014		$this->transfmrk[$this->page] = array();
10015		$this->state = 2;
10016		if (TCPDF_STATIC::empty_string($orientation)) {
10017			if (isset($this->CurOrientation)) {
10018				$orientation = $this->CurOrientation;
10019			} elseif ($this->fwPt > $this->fhPt) {
10020				// landscape
10021				$orientation = 'L';
10022			} else {
10023				// portrait
10024				$orientation = 'P';
10025			}
10026		}
10027		if (TCPDF_STATIC::empty_string($format)) {
10028			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10029			$this->setPageOrientation($orientation);
10030		} else {
10031			$this->setPageFormat($format, $orientation);
10032		}
10033		if ($this->rtl) {
10034			$this->x = $this->w - $this->rMargin;
10035		} else {
10036			$this->x = $this->lMargin;
10037		}
10038		$this->y = $this->tMargin;
10039		if (isset($this->newpagegroup[$this->page])) {
10040			// start a new group
10041			$this->currpagegroup = $this->newpagegroup[$this->page];
10042			$this->pagegroups[$this->currpagegroup] = 1;
10043		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10044			++$this->pagegroups[$this->currpagegroup];
10045		}
10046	}
10047
10048	/**
10049	 * Mark end of page.
10050	 * @protected
10051	 */
10052	protected function _endpage() {
10053		$this->setVisibility('all');
10054		$this->state = 1;
10055	}
10056
10057	/**
10058	 * Begin a new object and return the object number.
10059	 * @return int object number
10060	 * @protected
10061	 */
10062	protected function _newobj() {
10063		$this->_out($this->_getobj());
10064		return $this->n;
10065	}
10066
10067	/**
10068	 * Return the starting object string for the selected object ID.
10069	 * @param $objid (int) Object ID (leave empty to get a new ID).
10070	 * @return string the starting object string
10071	 * @protected
10072	 * @since 5.8.009 (2010-08-20)
10073	 */
10074	protected function _getobj($objid='') {
10075		if ($objid === '') {
10076			++$this->n;
10077			$objid = $this->n;
10078		}
10079		$this->offsets[$objid] = $this->bufferlen;
10080		$this->pageobjects[$this->page][] = $objid;
10081		return $objid.' 0 obj';
10082	}
10083
10084	/**
10085	 * Underline text.
10086	 * @param $x (int) X coordinate
10087	 * @param $y (int) Y coordinate
10088	 * @param $txt (string) text to underline
10089	 * @protected
10090	 */
10091	protected function _dounderline($x, $y, $txt) {
10092		$w = $this->GetStringWidth($txt);
10093		return $this->_dounderlinew($x, $y, $w);
10094	}
10095
10096	/**
10097	 * Underline for rectangular text area.
10098	 * @param $x (int) X coordinate
10099	 * @param $y (int) Y coordinate
10100	 * @param $w (int) width to underline
10101	 * @protected
10102	 * @since 4.8.008 (2009-09-29)
10103	 */
10104	protected function _dounderlinew($x, $y, $w) {
10105		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10106		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10107	}
10108
10109	/**
10110	 * Line through text.
10111	 * @param $x (int) X coordinate
10112	 * @param $y (int) Y coordinate
10113	 * @param $txt (string) text to linethrough
10114	 * @protected
10115	 */
10116	protected function _dolinethrough($x, $y, $txt) {
10117		$w = $this->GetStringWidth($txt);
10118		return $this->_dolinethroughw($x, $y, $w);
10119	}
10120
10121	/**
10122	 * Line through for rectangular text area.
10123	 * @param $x (int) X coordinate
10124	 * @param $y (int) Y coordinate
10125	 * @param $w (int) line length (width)
10126	 * @protected
10127	 * @since 4.9.008 (2009-09-29)
10128	 */
10129	protected function _dolinethroughw($x, $y, $w) {
10130		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10131		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10132	}
10133
10134	/**
10135	 * Overline text.
10136	 * @param $x (int) X coordinate
10137	 * @param $y (int) Y coordinate
10138	 * @param $txt (string) text to overline
10139	 * @protected
10140	 * @since 4.9.015 (2010-04-19)
10141	 */
10142	protected function _dooverline($x, $y, $txt) {
10143		$w = $this->GetStringWidth($txt);
10144		return $this->_dooverlinew($x, $y, $w);
10145	}
10146
10147	/**
10148	 * Overline for rectangular text area.
10149	 * @param $x (int) X coordinate
10150	 * @param $y (int) Y coordinate
10151	 * @param $w (int) width to overline
10152	 * @protected
10153	 * @since 4.9.015 (2010-04-19)
10154	 */
10155	protected function _dooverlinew($x, $y, $w) {
10156		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10157		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10158
10159	}
10160
10161	/**
10162	 * Format a data string for meta information
10163	 * @param $s (string) data string to escape.
10164	 * @param $n (int) object ID
10165	 * @return string escaped string.
10166	 * @protected
10167	 */
10168	protected function _datastring($s, $n=0) {
10169		if ($n == 0) {
10170			$n = $this->n;
10171		}
10172		$s = $this->_encrypt_data($n, $s);
10173		return '('. TCPDF_STATIC::_escape($s).')';
10174	}
10175
10176	/**
10177	 * Set the document creation timestamp
10178	 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10179	 * @public
10180	 * @since 5.9.152 (2012-03-23)
10181	 */
10182	public function setDocCreationTimestamp($time) {
10183		if (is_string($time)) {
10184			$time = TCPDF_STATIC::getTimestamp($time);
10185		}
10186		$this->doc_creation_timestamp = intval($time);
10187	}
10188
10189	/**
10190	 * Set the document modification timestamp
10191	 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10192	 * @public
10193	 * @since 5.9.152 (2012-03-23)
10194	 */
10195	public function setDocModificationTimestamp($time) {
10196		if (is_string($time)) {
10197			$time = TCPDF_STATIC::getTimestamp($time);
10198		}
10199		$this->doc_modification_timestamp = intval($time);
10200	}
10201
10202	/**
10203	 * Returns document creation timestamp in seconds.
10204	 * @return (int) Creation timestamp in seconds.
10205	 * @public
10206	 * @since 5.9.152 (2012-03-23)
10207	 */
10208	public function getDocCreationTimestamp() {
10209		return $this->doc_creation_timestamp;
10210	}
10211
10212	/**
10213	 * Returns document modification timestamp in seconds.
10214	 * @return (int) Modfication timestamp in seconds.
10215	 * @public
10216	 * @since 5.9.152 (2012-03-23)
10217	 */
10218	public function getDocModificationTimestamp() {
10219		return $this->doc_modification_timestamp;
10220	}
10221
10222	/**
10223	 * Returns a formatted date for meta information
10224	 * @param $n (int) Object ID.
10225	 * @param $timestamp (int) Timestamp to convert.
10226	 * @return string escaped date string.
10227	 * @protected
10228	 * @since 4.6.028 (2009-08-25)
10229	 */
10230	protected function _datestring($n=0, $timestamp=0) {
10231		if ((empty($timestamp)) OR ($timestamp < 0)) {
10232			$timestamp = $this->doc_creation_timestamp;
10233		}
10234		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10235	}
10236
10237	/**
10238	 * Format a text string for meta information
10239	 * @param $s (string) string to escape.
10240	 * @param $n (int) object ID
10241	 * @return string escaped string.
10242	 * @protected
10243	 */
10244	protected function _textstring($s, $n=0) {
10245		if ($this->isunicode) {
10246			//Convert string to UTF-16BE
10247			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10248		}
10249		return $this->_datastring($s, $n);
10250	}
10251
10252	/**
10253	 * get raw output stream.
10254	 * @param $s (string) string to output.
10255	 * @param $n (int) object reference for encryption mode
10256	 * @protected
10257	 * @author Nicola Asuni
10258	 * @since 5.5.000 (2010-06-22)
10259	 */
10260	protected function _getrawstream($s, $n=0) {
10261		if ($n <= 0) {
10262			// default to current object
10263			$n = $this->n;
10264		}
10265		return $this->_encrypt_data($n, $s);
10266	}
10267
10268	/**
10269	 * Output a string to the document.
10270	 * @param $s (string) string to output.
10271	 * @protected
10272	 */
10273	protected function _out($s) {
10274		if ($this->state == 2) {
10275			if ($this->inxobj) {
10276				// we are inside an XObject template
10277				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10278			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10279				// puts data before page footer
10280				$pagebuff = $this->getPageBuffer($this->page);
10281				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10282				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10283				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10284				// update footer position
10285				$this->footerpos[$this->page] += strlen($s."\n");
10286			} else {
10287				// set page data
10288				$this->setPageBuffer($this->page, $s."\n", true);
10289			}
10290		} elseif ($this->state > 0) {
10291			// set general data
10292			$this->setBuffer($s."\n");
10293		}
10294	}
10295
10296	/**
10297	 * Set header font.
10298	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10299	 * @public
10300	 * @since 1.1
10301	 */
10302	public function setHeaderFont($font) {
10303		$this->header_font = $font;
10304	}
10305
10306	/**
10307	 * Get header font.
10308	 * @return array() Array describing the basic font parameters: (family, style, size).
10309	 * @public
10310	 * @since 4.0.012 (2008-07-24)
10311	 */
10312	public function getHeaderFont() {
10313		return $this->header_font;
10314	}
10315
10316	/**
10317	 * Set footer font.
10318	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10319	 * @public
10320	 * @since 1.1
10321	 */
10322	public function setFooterFont($font) {
10323		$this->footer_font = $font;
10324	}
10325
10326	/**
10327	 * Get Footer font.
10328	 * @return array() Array describing the basic font parameters: (family, style, size).
10329	 * @public
10330	 * @since 4.0.012 (2008-07-24)
10331	 */
10332	public function getFooterFont() {
10333		return $this->footer_font;
10334	}
10335
10336	/**
10337	 * Set language array.
10338	 * @param $language (array)
10339	 * @public
10340	 * @since 1.1
10341	 */
10342	public function setLanguageArray($language) {
10343		$this->l = $language;
10344		if (isset($this->l['a_meta_dir'])) {
10345			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10346		} else {
10347			$this->rtl = false;
10348		}
10349	}
10350
10351	/**
10352	 * Returns the PDF data.
10353	 * @public
10354	 */
10355	public function getPDFData() {
10356		if ($this->state < 3) {
10357			$this->Close();
10358		}
10359		return $this->buffer;
10360	}
10361
10362	/**
10363	 * Output anchor link.
10364	 * @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;)
10365	 * @param $name (string) link name
10366	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10367	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10368	 * @param $color (array) array of RGB text color
10369	 * @param $style (string) font style (U, D, B, I)
10370	 * @param $firstblock (boolean) if true the string is the starting of a line.
10371	 * @return the number of cells used or the remaining text if $firstline = true;
10372	 * @public
10373	 */
10374	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10375		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10376			// convert url to internal link
10377			$lnkdata = explode(',', $url);
10378			if (isset($lnkdata[0]) ) {
10379				$page = substr($lnkdata[0], 1);
10380				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10381					$lnky = floatval($lnkdata[1]);
10382				} else {
10383					$lnky = 0;
10384				}
10385				$url = $this->AddLink();
10386				$this->SetLink($url, $lnky, $page);
10387			}
10388		}
10389		// store current settings
10390		$prevcolor = $this->fgcolor;
10391		$prevstyle = $this->FontStyle;
10392		if (empty($color)) {
10393			$this->SetTextColorArray($this->htmlLinkColorArray);
10394		} else {
10395			$this->SetTextColorArray($color);
10396		}
10397		if ($style == -1) {
10398			$this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10399		} else {
10400			$this->SetFont('', $this->FontStyle.$style);
10401		}
10402		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10403		// restore settings
10404		$this->SetFont('', $prevstyle);
10405		$this->SetTextColorArray($prevcolor);
10406		return $ret;
10407	}
10408
10409	/**
10410	 * Converts pixels to User's Units.
10411	 * @param $px (int) pixels
10412	 * @return float value in user's unit
10413	 * @public
10414	 * @see setImageScale(), getImageScale()
10415	 */
10416	public function pixelsToUnits($px) {
10417		return ($px / ($this->imgscale * $this->k));
10418	}
10419
10420	/**
10421	 * Reverse function for htmlentities.
10422	 * Convert entities in UTF-8.
10423	 * @param $text_to_convert (string) Text to convert.
10424	 * @return string converted text string
10425	 * @public
10426	 */
10427	public function unhtmlentities($text_to_convert) {
10428		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10429	}
10430
10431	// ENCRYPTION METHODS ----------------------------------
10432
10433	/**
10434	 * Compute encryption key depending on object number where the encrypted data is stored.
10435	 * This is used for all strings and streams without crypt filter specifier.
10436	 * @param $n (int) object number
10437	 * @return int object key
10438	 * @protected
10439	 * @author Nicola Asuni
10440	 * @since 2.0.000 (2008-01-02)
10441	 */
10442	protected function _objectkey($n) {
10443		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10444		if ($this->encryptdata['mode'] == 2) { // AES-128
10445			// AES padding
10446			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10447		}
10448		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10449		$objkey = substr($objkey, 0, 16);
10450		return $objkey;
10451	}
10452
10453	/**
10454	 * Encrypt the input string.
10455	 * @param $n (int) object number
10456	 * @param $s (string) data string to encrypt
10457	 * @return encrypted string
10458	 * @protected
10459	 * @author Nicola Asuni
10460	 * @since 5.0.005 (2010-05-11)
10461	 */
10462	protected function _encrypt_data($n, $s) {
10463		if (!$this->encrypted) {
10464			return $s;
10465		}
10466		switch ($this->encryptdata['mode']) {
10467			case 0:   // RC4-40
10468			case 1: { // RC4-128
10469				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10470				break;
10471			}
10472			case 2: { // AES-128
10473				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10474				break;
10475			}
10476			case 3: { // AES-256
10477				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10478				break;
10479			}
10480		}
10481		return $s;
10482	}
10483
10484	/**
10485	 * Put encryption on PDF document.
10486	 * @protected
10487	 * @author Nicola Asuni
10488	 * @since 2.0.000 (2008-01-02)
10489	 */
10490	protected function _putencryption() {
10491		if (!$this->encrypted) {
10492			return;
10493		}
10494		$this->encryptdata['objid'] = $this->_newobj();
10495		$out = '<<';
10496		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10497			$this->encryptdata['Filter'] = 'Standard';
10498		}
10499		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10500		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10501			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10502		}
10503		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10504			$this->encryptdata['V'] = 1;
10505		}
10506		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10507		$out .= ' /V '.$this->encryptdata['V'];
10508		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10509			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10510			$out .= ' /Length '.$this->encryptdata['Length'];
10511		} else {
10512			$out .= ' /Length 40';
10513		}
10514		if ($this->encryptdata['V'] >= 4) {
10515			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10516				$this->encryptdata['StmF'] = 'Identity';
10517			}
10518			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10519				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10520				$this->encryptdata['StrF'] = 'Identity';
10521			}
10522			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10523			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10524				$out .= ' /CF <<';
10525				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10526				$out .= ' /Type /CryptFilter';
10527				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10528					// The method used
10529					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10530					if ($this->encryptdata['pubkey']) {
10531						$out .= ' /Recipients [';
10532						foreach ($this->encryptdata['Recipients'] as $rec) {
10533							$out .= ' <'.$rec.'>';
10534						}
10535						$out .= ' ]';
10536						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10537							$out .= ' /EncryptMetadata false';
10538						} else {
10539							$out .= ' /EncryptMetadata true';
10540						}
10541					}
10542				} else {
10543					$out .= ' /CFM /None';
10544				}
10545				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10546					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10547					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10548				} else {
10549					$out .= ' /AuthEvent /DocOpen';
10550				}
10551				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10552					// The bit length of the encryption key.
10553					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10554				}
10555				$out .= ' >> >>';
10556			}
10557			// The name of the crypt filter that shall be used by default when decrypting streams.
10558			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10559			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10560			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10561			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10562				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10563				$out .= ' /EFF /'.$this->encryptdata[''];
10564			}
10565		}
10566		// Additional encryption dictionary entries for the standard security handler
10567		if ($this->encryptdata['pubkey']) {
10568			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10569				$out .= ' /Recipients [';
10570				foreach ($this->encryptdata['Recipients'] as $rec) {
10571					$out .= ' <'.$rec.'>';
10572				}
10573				$out .= ' ]';
10574			}
10575		} else {
10576			$out .= ' /R';
10577			if ($this->encryptdata['V'] == 5) { // AES-256
10578				$out .= ' 5';
10579				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10580				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10581				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10582			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10583				$out .= ' 4';
10584			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10585				$out .= ' 2';
10586			} else { // RC-128
10587				$out .= ' 3';
10588			}
10589			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10590			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10591			$out .= ' /P '.$this->encryptdata['P'];
10592			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10593				$out .= ' /EncryptMetadata false';
10594			} else {
10595				$out .= ' /EncryptMetadata true';
10596			}
10597		}
10598		$out .= ' >>';
10599		$out .= "\n".'endobj';
10600		$this->_out($out);
10601	}
10602
10603	/**
10604	 * Compute U value (used for encryption)
10605	 * @return string U value
10606	 * @protected
10607	 * @since 2.0.000 (2008-01-02)
10608	 * @author Nicola Asuni
10609	 */
10610	protected function _Uvalue() {
10611		if ($this->encryptdata['mode'] == 0) { // RC4-40
10612			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10613		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10614			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10615			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10616			$len = strlen($tmp);
10617			for ($i = 1; $i <= 19; ++$i) {
10618				$ek = '';
10619				for ($j = 0; $j < $len; ++$j) {
10620					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10621				}
10622				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10623			}
10624			$enc .= str_repeat("\x00", 16);
10625			return substr($enc, 0, 32);
10626		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10627			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10628			// User Validation Salt
10629			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10630			// User Key Salt
10631			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10632			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10633		}
10634	}
10635
10636	/**
10637	 * Compute UE value (used for encryption)
10638	 * @return string UE value
10639	 * @protected
10640	 * @since 5.9.006 (2010-10-19)
10641	 * @author Nicola Asuni
10642	 */
10643	protected function _UEvalue() {
10644		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10645		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10646	}
10647
10648	/**
10649	 * Compute O value (used for encryption)
10650	 * @return string O value
10651	 * @protected
10652	 * @since 2.0.000 (2008-01-02)
10653	 * @author Nicola Asuni
10654	 */
10655	protected function _Ovalue() {
10656		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10657			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10658			if ($this->encryptdata['mode'] > 0) {
10659				for ($i = 0; $i < 50; ++$i) {
10660					$tmp = TCPDF_STATIC::_md5_16($tmp);
10661				}
10662			}
10663			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10664			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10665			if ($this->encryptdata['mode'] > 0) {
10666				$len = strlen($owner_key);
10667				for ($i = 1; $i <= 19; ++$i) {
10668					$ek = '';
10669					for ($j = 0; $j < $len; ++$j) {
10670						$ek .= chr(ord($owner_key[$j]) ^ $i);
10671					}
10672					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10673				}
10674			}
10675			return $enc;
10676		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10677			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10678			// Owner Validation Salt
10679			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10680			// Owner Key Salt
10681			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10682			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10683		}
10684	}
10685
10686	/**
10687	 * Compute OE value (used for encryption)
10688	 * @return string OE value
10689	 * @protected
10690	 * @since 5.9.006 (2010-10-19)
10691	 * @author Nicola Asuni
10692	 */
10693	protected function _OEvalue() {
10694		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10695		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10696	}
10697
10698	/**
10699	 * Convert password for AES-256 encryption mode
10700	 * @param $password (string) password
10701	 * @return string password
10702	 * @protected
10703	 * @since 5.9.006 (2010-10-19)
10704	 * @author Nicola Asuni
10705	 */
10706	protected function _fixAES256Password($password) {
10707		$psw = ''; // password to be returned
10708		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10709		foreach ($psw_array as $c) {
10710			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10711		}
10712		return substr($psw, 0, 127);
10713	}
10714
10715	/**
10716	 * Compute encryption key
10717	 * @protected
10718	 * @since 2.0.000 (2008-01-02)
10719	 * @author Nicola Asuni
10720	 */
10721	protected function _generateencryptionkey() {
10722		$keybytelen = ($this->encryptdata['Length'] / 8);
10723		if (!$this->encryptdata['pubkey']) { // standard mode
10724			if ($this->encryptdata['mode'] == 3) { // AES-256
10725				// generate 256 bit random key
10726				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10727				// truncate passwords
10728				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10729				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10730				// Compute U value
10731				$this->encryptdata['U'] = $this->_Uvalue();
10732				// Compute UE value
10733				$this->encryptdata['UE'] = $this->_UEvalue();
10734				// Compute O value
10735				$this->encryptdata['O'] = $this->_Ovalue();
10736				// Compute OE value
10737				$this->encryptdata['OE'] = $this->_OEvalue();
10738				// Compute P value
10739				$this->encryptdata['P'] = $this->encryptdata['protection'];
10740				// Computing the encryption dictionary's Perms (permissions) value
10741				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10742				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10743				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10744					$perms .= 'F';
10745				} else {
10746					$perms .= 'T';
10747				}
10748				$perms .= 'adb'; // bytes 9-11
10749				$perms .= 'nick'; // bytes 12-15
10750				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10751			} else { // RC4-40, RC4-128, AES-128
10752				// Pad passwords
10753				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10754				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10755				// Compute O value
10756				$this->encryptdata['O'] = $this->_Ovalue();
10757				// get default permissions (reverse byte order)
10758				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10759				// Compute encryption key
10760				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10761				if ($this->encryptdata['mode'] > 0) {
10762					for ($i = 0; $i < 50; ++$i) {
10763						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10764					}
10765				}
10766				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10767				// Compute U value
10768				$this->encryptdata['U'] = $this->_Uvalue();
10769				// Compute P value
10770				$this->encryptdata['P'] = $this->encryptdata['protection'];
10771			}
10772		} else { // Public-Key mode
10773			// random 20-byte seed
10774			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10775			$recipient_bytes = '';
10776			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10777				// for each public certificate
10778				if (isset($pubkey['p'])) {
10779					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10780				} else {
10781					$pkprotection = $this->encryptdata['protection'];
10782				}
10783				// get default permissions (reverse byte order)
10784				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10785				// envelope data
10786				$envelope = $seed.$pkpermissions;
10787				// write the envelope data to a temporary file
10788				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10789				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10790				if (!$f) {
10791					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10792				}
10793				$envelope_length = strlen($envelope);
10794				fwrite($f, $envelope, $envelope_length);
10795				fclose($f);
10796				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10797				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10798					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10799				}
10800				// read encryption signature
10801				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10802				// extract signature
10803				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10804				$tmparr = explode("\n\n", $signature);
10805				$signature = trim($tmparr[1]);
10806				unset($tmparr);
10807				// decode signature
10808				$signature = base64_decode($signature);
10809				// convert signature to hex
10810				$hexsignature = current(unpack('H*', $signature));
10811				// store signature on recipients array
10812				$this->encryptdata['Recipients'][] = $hexsignature;
10813				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10814				$recipient_bytes .= $signature;
10815			}
10816			// calculate encryption key
10817			if ($this->encryptdata['mode'] == 3) { // AES-256
10818				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10819			} else { // RC4-40, RC4-128, AES-128
10820				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10821			}
10822		}
10823	}
10824
10825	/**
10826	 * Set document protection
10827	 * Remark: the protection against modification is for people who have the full Acrobat product.
10828	 * 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.
10829	 * 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.
10830	 * @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>
10831	 * @param $user_pass (String) user password. Empty by default.
10832	 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10833	 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10834	 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10835	 * @public
10836	 * @since 2.0.000 (2008-01-02)
10837	 * @author Nicola Asuni
10838	 */
10839	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) {
10840		if ($this->pdfa_mode) {
10841			// encryption is not allowed in PDF/A mode
10842			return;
10843		}
10844		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10845		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10846			// public-key mode
10847			$this->encryptdata['pubkeys'] = $pubkeys;
10848			if ($mode == 0) {
10849				// public-Key Security requires at least 128 bit
10850				$mode = 1;
10851			}
10852			if (!function_exists('openssl_pkcs7_encrypt')) {
10853				$this->Error('Public-Key Security requires openssl library.');
10854			}
10855			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10856			$this->encryptdata['pubkey'] = true;
10857			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10858			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
10859			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
10860		} else {
10861			// standard mode (password mode)
10862			$this->encryptdata['pubkey'] = false;
10863			$this->encryptdata['Filter'] = 'Standard';
10864			$this->encryptdata['StmF'] = 'StdCF';
10865			$this->encryptdata['StrF'] = 'StdCF';
10866		}
10867		if ($mode > 1) { // AES
10868			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10869				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10870			}
10871			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10872				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10873			}
10874			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10875				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10876			}
10877			if (($mode == 3) AND !function_exists('hash')) {
10878				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10879				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10880			}
10881		}
10882		if ($owner_pass === null) {
10883			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10884		}
10885		$this->encryptdata['user_password'] = $user_pass;
10886		$this->encryptdata['owner_password'] = $owner_pass;
10887		$this->encryptdata['mode'] = $mode;
10888		switch ($mode) {
10889			case 0: { // RC4 40 bit
10890				$this->encryptdata['V'] = 1;
10891				$this->encryptdata['Length'] = 40;
10892				$this->encryptdata['CF']['CFM'] = 'V2';
10893				break;
10894			}
10895			case 1: { // RC4 128 bit
10896				$this->encryptdata['V'] = 2;
10897				$this->encryptdata['Length'] = 128;
10898				$this->encryptdata['CF']['CFM'] = 'V2';
10899				if ($this->encryptdata['pubkey']) {
10900					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10901					$this->encryptdata['Recipients'] = array();
10902				}
10903				break;
10904			}
10905			case 2: { // AES 128 bit
10906				$this->encryptdata['V'] = 4;
10907				$this->encryptdata['Length'] = 128;
10908				$this->encryptdata['CF']['CFM'] = 'AESV2';
10909				$this->encryptdata['CF']['Length'] = 128;
10910				if ($this->encryptdata['pubkey']) {
10911					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10912					$this->encryptdata['Recipients'] = array();
10913				}
10914				break;
10915			}
10916			case 3: { // AES 256 bit
10917				$this->encryptdata['V'] = 5;
10918				$this->encryptdata['Length'] = 256;
10919				$this->encryptdata['CF']['CFM'] = 'AESV3';
10920				$this->encryptdata['CF']['Length'] = 256;
10921				if ($this->encryptdata['pubkey']) {
10922					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10923					$this->encryptdata['Recipients'] = array();
10924				}
10925				break;
10926			}
10927		}
10928		$this->encrypted = true;
10929		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10930		$this->_generateencryptionkey();
10931	}
10932
10933	// END OF ENCRYPTION FUNCTIONS -------------------------
10934
10935	// START TRANSFORMATIONS SECTION -----------------------
10936
10937	/**
10938	 * Starts a 2D tranformation saving current graphic state.
10939	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
10940	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10941	 * @public
10942	 * @since 2.1.000 (2008-01-07)
10943	 * @see StartTransform(), StopTransform()
10944	 */
10945	public function StartTransform() {
10946		if ($this->state != 2) {
10947			return;
10948		}
10949		$this->_outSaveGraphicsState();
10950		if ($this->inxobj) {
10951			// we are inside an XObject template
10952			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
10953		} else {
10954			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
10955		}
10956		++$this->transfmatrix_key;
10957		$this->transfmatrix[$this->transfmatrix_key] = array();
10958	}
10959
10960	/**
10961	 * Stops a 2D tranformation restoring previous graphic state.
10962	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
10963	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10964	 * @public
10965	 * @since 2.1.000 (2008-01-07)
10966	 * @see StartTransform(), StopTransform()
10967	 */
10968	public function StopTransform() {
10969		if ($this->state != 2) {
10970			return;
10971		}
10972		$this->_outRestoreGraphicsState();
10973		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
10974			array_pop($this->transfmatrix[$this->transfmatrix_key]);
10975			--$this->transfmatrix_key;
10976		}
10977		if ($this->inxobj) {
10978			// we are inside an XObject template
10979			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
10980		} else {
10981			array_pop($this->transfmrk[$this->page]);
10982		}
10983	}
10984	/**
10985	 * Horizontal Scaling.
10986	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
10987	 * @param $x (int) abscissa of the scaling center. Default is current x position
10988	 * @param $y (int) ordinate of the scaling center. Default is current y position
10989	 * @public
10990	 * @since 2.1.000 (2008-01-07)
10991	 * @see StartTransform(), StopTransform()
10992	 */
10993	public function ScaleX($s_x, $x='', $y='') {
10994		$this->Scale($s_x, 100, $x, $y);
10995	}
10996
10997	/**
10998	 * Vertical Scaling.
10999	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11000	 * @param $x (int) abscissa of the scaling center. Default is current x position
11001	 * @param $y (int) ordinate of the scaling center. Default is current y position
11002	 * @public
11003	 * @since 2.1.000 (2008-01-07)
11004	 * @see StartTransform(), StopTransform()
11005	 */
11006	public function ScaleY($s_y, $x='', $y='') {
11007		$this->Scale(100, $s_y, $x, $y);
11008	}
11009
11010	/**
11011	 * Vertical and horizontal proportional Scaling.
11012	 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11013	 * @param $x (int) abscissa of the scaling center. Default is current x position
11014	 * @param $y (int) ordinate of the scaling center. Default is current y position
11015	 * @public
11016	 * @since 2.1.000 (2008-01-07)
11017	 * @see StartTransform(), StopTransform()
11018	 */
11019	public function ScaleXY($s, $x='', $y='') {
11020		$this->Scale($s, $s, $x, $y);
11021	}
11022
11023	/**
11024	 * Vertical and horizontal non-proportional Scaling.
11025	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11026	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11027	 * @param $x (int) abscissa of the scaling center. Default is current x position
11028	 * @param $y (int) ordinate of the scaling center. Default is current y position
11029	 * @public
11030	 * @since 2.1.000 (2008-01-07)
11031	 * @see StartTransform(), StopTransform()
11032	 */
11033	public function Scale($s_x, $s_y, $x='', $y='') {
11034		if ($x === '') {
11035			$x = $this->x;
11036		}
11037		if ($y === '') {
11038			$y = $this->y;
11039		}
11040		if (($s_x == 0) OR ($s_y == 0)) {
11041			$this->Error('Please do not use values equal to zero for scaling');
11042		}
11043		$y = ($this->h - $y) * $this->k;
11044		$x *= $this->k;
11045		//calculate elements of transformation matrix
11046		$s_x /= 100;
11047		$s_y /= 100;
11048		$tm = array();
11049		$tm[0] = $s_x;
11050		$tm[1] = 0;
11051		$tm[2] = 0;
11052		$tm[3] = $s_y;
11053		$tm[4] = $x * (1 - $s_x);
11054		$tm[5] = $y * (1 - $s_y);
11055		//scale the coordinate system
11056		$this->Transform($tm);
11057	}
11058
11059	/**
11060	 * Horizontal Mirroring.
11061	 * @param $x (int) abscissa of the point. Default is current x position
11062	 * @public
11063	 * @since 2.1.000 (2008-01-07)
11064	 * @see StartTransform(), StopTransform()
11065	 */
11066	public function MirrorH($x='') {
11067		$this->Scale(-100, 100, $x);
11068	}
11069
11070	/**
11071	 * Verical Mirroring.
11072	 * @param $y (int) ordinate of the point. Default is current y position
11073	 * @public
11074	 * @since 2.1.000 (2008-01-07)
11075	 * @see StartTransform(), StopTransform()
11076	 */
11077	public function MirrorV($y='') {
11078		$this->Scale(100, -100, '', $y);
11079	}
11080
11081	/**
11082	 * Point reflection mirroring.
11083	 * @param $x (int) abscissa of the point. Default is current x position
11084	 * @param $y (int) ordinate of the point. Default is current y position
11085	 * @public
11086	 * @since 2.1.000 (2008-01-07)
11087	 * @see StartTransform(), StopTransform()
11088	 */
11089	public function MirrorP($x='',$y='') {
11090		$this->Scale(-100, -100, $x, $y);
11091	}
11092
11093	/**
11094	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11095	 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11096	 * @param $x (int) abscissa of the point. Default is current x position
11097	 * @param $y (int) ordinate of the point. Default is current y position
11098	 * @public
11099	 * @since 2.1.000 (2008-01-07)
11100	 * @see StartTransform(), StopTransform()
11101	 */
11102	public function MirrorL($angle=0, $x='',$y='') {
11103		$this->Scale(-100, 100, $x, $y);
11104		$this->Rotate(-2*($angle-90), $x, $y);
11105	}
11106
11107	/**
11108	 * Translate graphic object horizontally.
11109	 * @param $t_x (int) movement to the right (or left for RTL)
11110	 * @public
11111	 * @since 2.1.000 (2008-01-07)
11112	 * @see StartTransform(), StopTransform()
11113	 */
11114	public function TranslateX($t_x) {
11115		$this->Translate($t_x, 0);
11116	}
11117
11118	/**
11119	 * Translate graphic object vertically.
11120	 * @param $t_y (int) movement to the bottom
11121	 * @public
11122	 * @since 2.1.000 (2008-01-07)
11123	 * @see StartTransform(), StopTransform()
11124	 */
11125	public function TranslateY($t_y) {
11126		$this->Translate(0, $t_y);
11127	}
11128
11129	/**
11130	 * Translate graphic object horizontally and vertically.
11131	 * @param $t_x (int) movement to the right
11132	 * @param $t_y (int) movement to the bottom
11133	 * @public
11134	 * @since 2.1.000 (2008-01-07)
11135	 * @see StartTransform(), StopTransform()
11136	 */
11137	public function Translate($t_x, $t_y) {
11138		//calculate elements of transformation matrix
11139		$tm = array();
11140		$tm[0] = 1;
11141		$tm[1] = 0;
11142		$tm[2] = 0;
11143		$tm[3] = 1;
11144		$tm[4] = $t_x * $this->k;
11145		$tm[5] = -$t_y * $this->k;
11146		//translate the coordinate system
11147		$this->Transform($tm);
11148	}
11149
11150	/**
11151	 * Rotate object.
11152	 * @param $angle (float) angle in degrees for counter-clockwise rotation
11153	 * @param $x (int) abscissa of the rotation center. Default is current x position
11154	 * @param $y (int) ordinate of the rotation center. Default is current y position
11155	 * @public
11156	 * @since 2.1.000 (2008-01-07)
11157	 * @see StartTransform(), StopTransform()
11158	 */
11159	public function Rotate($angle, $x='', $y='') {
11160		if ($x === '') {
11161			$x = $this->x;
11162		}
11163		if ($y === '') {
11164			$y = $this->y;
11165		}
11166		$y = ($this->h - $y) * $this->k;
11167		$x *= $this->k;
11168		//calculate elements of transformation matrix
11169		$tm = array();
11170		$tm[0] = cos(deg2rad($angle));
11171		$tm[1] = sin(deg2rad($angle));
11172		$tm[2] = -$tm[1];
11173		$tm[3] = $tm[0];
11174		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11175		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11176		//rotate the coordinate system around ($x,$y)
11177		$this->Transform($tm);
11178	}
11179
11180	/**
11181	 * Skew horizontally.
11182	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11183	 * @param $x (int) abscissa of the skewing center. default is current x position
11184	 * @param $y (int) ordinate of the skewing center. default is current y position
11185	 * @public
11186	 * @since 2.1.000 (2008-01-07)
11187	 * @see StartTransform(), StopTransform()
11188	 */
11189	public function SkewX($angle_x, $x='', $y='') {
11190		$this->Skew($angle_x, 0, $x, $y);
11191	}
11192
11193	/**
11194	 * Skew vertically.
11195	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11196	 * @param $x (int) abscissa of the skewing center. default is current x position
11197	 * @param $y (int) ordinate of the skewing center. default is current y position
11198	 * @public
11199	 * @since 2.1.000 (2008-01-07)
11200	 * @see StartTransform(), StopTransform()
11201	 */
11202	public function SkewY($angle_y, $x='', $y='') {
11203		$this->Skew(0, $angle_y, $x, $y);
11204	}
11205
11206	/**
11207	 * Skew.
11208	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11209	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11210	 * @param $x (int) abscissa of the skewing center. default is current x position
11211	 * @param $y (int) ordinate of the skewing center. default is current y position
11212	 * @public
11213	 * @since 2.1.000 (2008-01-07)
11214	 * @see StartTransform(), StopTransform()
11215	 */
11216	public function Skew($angle_x, $angle_y, $x='', $y='') {
11217		if ($x === '') {
11218			$x = $this->x;
11219		}
11220		if ($y === '') {
11221			$y = $this->y;
11222		}
11223		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11224			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11225		}
11226		$x *= $this->k;
11227		$y = ($this->h - $y) * $this->k;
11228		//calculate elements of transformation matrix
11229		$tm = array();
11230		$tm[0] = 1;
11231		$tm[1] = tan(deg2rad($angle_y));
11232		$tm[2] = tan(deg2rad($angle_x));
11233		$tm[3] = 1;
11234		$tm[4] = -$tm[2] * $y;
11235		$tm[5] = -$tm[1] * $x;
11236		//skew the coordinate system
11237		$this->Transform($tm);
11238	}
11239
11240	/**
11241	 * Apply graphic transformations.
11242	 * @param $tm (array) transformation matrix
11243	 * @protected
11244	 * @since 2.1.000 (2008-01-07)
11245	 * @see StartTransform(), StopTransform()
11246	 */
11247	protected function Transform($tm) {
11248		if ($this->state != 2) {
11249			return;
11250		}
11251		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11252		// add tranformation matrix
11253		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11254		// update transformation mark
11255		if ($this->inxobj) {
11256			// we are inside an XObject template
11257			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11258				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11259				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11260			}
11261		} elseif (end($this->transfmrk[$this->page]) !== false) {
11262			$key = key($this->transfmrk[$this->page]);
11263			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11264		}
11265	}
11266
11267	// END TRANSFORMATIONS SECTION -------------------------
11268
11269	// START GRAPHIC FUNCTIONS SECTION ---------------------
11270	// The following section is based on the code provided by David Hernandez Sanz
11271
11272	/**
11273	 * 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.
11274	 * @param $width (float) The width.
11275	 * @public
11276	 * @since 1.0
11277	 * @see Line(), Rect(), Cell(), MultiCell()
11278	 */
11279	public function SetLineWidth($width) {
11280		//Set line width
11281		$this->LineWidth = $width;
11282		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11283		if ($this->state == 2) {
11284			$this->_out($this->linestyleWidth);
11285		}
11286	}
11287
11288	/**
11289	 * Returns the current the line width.
11290	 * @return int Line width
11291	 * @public
11292	 * @since 2.1.000 (2008-01-07)
11293	 * @see Line(), SetLineWidth()
11294	 */
11295	public function GetLineWidth() {
11296		return $this->LineWidth;
11297	}
11298
11299	/**
11300	 * Set line style.
11301	 * @param $style (array) Line style. Array with keys among the following:
11302	 * <ul>
11303	 *	 <li>width (float): Width of the line in user units.</li>
11304	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11305	 * butt, round, square. The difference between "square" and "butt" is that
11306	 * "square" projects a flat end past the end of the line.</li>
11307	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11308	 * bevel.</li>
11309	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11310	 * series of length values, which are the lengths of the on and off dashes.
11311	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11312	 * 1 off, 2 on, 1 off, ...</li>
11313	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11314	 * the point at which the pattern starts.</li>
11315	 *	 <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>
11316	 * </ul>
11317	 * @param $ret (boolean) if true do not send the command.
11318	 * @return string the PDF command
11319	 * @public
11320	 * @since 2.1.000 (2008-01-08)
11321	 */
11322	public function SetLineStyle($style, $ret=false) {
11323		$s = ''; // string to be returned
11324		if (!is_array($style)) {
11325			return;
11326		}
11327		if (isset($style['width'])) {
11328			$this->LineWidth = $style['width'];
11329			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11330			$s .= $this->linestyleWidth.' ';
11331		}
11332		if (isset($style['cap'])) {
11333			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11334			if (isset($ca[$style['cap']])) {
11335				$this->linestyleCap = $ca[$style['cap']].' J';
11336				$s .= $this->linestyleCap.' ';
11337			}
11338		}
11339		if (isset($style['join'])) {
11340			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11341			if (isset($ja[$style['join']])) {
11342				$this->linestyleJoin = $ja[$style['join']].' j';
11343				$s .= $this->linestyleJoin.' ';
11344			}
11345		}
11346		if (isset($style['dash'])) {
11347			$dash_string = '';
11348			if ($style['dash']) {
11349				if (preg_match('/^.+,/', $style['dash']) > 0) {
11350					$tab = explode(',', $style['dash']);
11351				} else {
11352					$tab = array($style['dash']);
11353				}
11354				$dash_string = '';
11355				foreach ($tab as $i => $v) {
11356					if ($i) {
11357						$dash_string .= ' ';
11358					}
11359					$dash_string .= sprintf('%F', $v);
11360				}
11361			}
11362			if (!isset($style['phase']) OR !$style['dash']) {
11363				$style['phase'] = 0;
11364			}
11365			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11366			$s .= $this->linestyleDash.' ';
11367		}
11368		if (isset($style['color'])) {
11369			$s .= $this->SetDrawColorArray($style['color'], true).' ';
11370		}
11371		if (!$ret AND ($this->state == 2)) {
11372			$this->_out($s);
11373		}
11374		return $s;
11375	}
11376
11377	/**
11378	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11379	 * @param $x (float) Abscissa of point.
11380	 * @param $y (float) Ordinate of point.
11381	 * @protected
11382	 * @since 2.1.000 (2008-01-08)
11383	 */
11384	protected function _outPoint($x, $y) {
11385		if ($this->state == 2) {
11386			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11387		}
11388	}
11389
11390	/**
11391	 * Append a straight line segment from the current point to the point (x, y).
11392	 * The new current point shall be (x, y).
11393	 * @param $x (float) Abscissa of end point.
11394	 * @param $y (float) Ordinate of end point.
11395	 * @protected
11396	 * @since 2.1.000 (2008-01-08)
11397	 */
11398	protected function _outLine($x, $y) {
11399		if ($this->state == 2) {
11400			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11401		}
11402	}
11403
11404	/**
11405	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11406	 * @param $x (float) Abscissa of upper-left corner.
11407	 * @param $y (float) Ordinate of upper-left corner.
11408	 * @param $w (float) Width.
11409	 * @param $h (float) Height.
11410	 * @param $op (string) options
11411	 * @protected
11412	 * @since 2.1.000 (2008-01-08)
11413	 */
11414	protected function _outRect($x, $y, $w, $h, $op) {
11415		if ($this->state == 2) {
11416			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11417		}
11418	}
11419
11420	/**
11421	 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points.
11422	 * The new current point shall be (x3, y3).
11423	 * @param $x1 (float) Abscissa of control point 1.
11424	 * @param $y1 (float) Ordinate of control point 1.
11425	 * @param $x2 (float) Abscissa of control point 2.
11426	 * @param $y2 (float) Ordinate of control point 2.
11427	 * @param $x3 (float) Abscissa of end point.
11428	 * @param $y3 (float) Ordinate of end point.
11429	 * @protected
11430	 * @since 2.1.000 (2008-01-08)
11431	 */
11432	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11433		if ($this->state == 2) {
11434			$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)));
11435		}
11436	}
11437
11438	/**
11439	 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points.
11440	 * The new current point shall be (x3, y3).
11441	 * @param $x2 (float) Abscissa of control point 2.
11442	 * @param $y2 (float) Ordinate of control point 2.
11443	 * @param $x3 (float) Abscissa of end point.
11444	 * @param $y3 (float) Ordinate of end point.
11445	 * @protected
11446	 * @since 4.9.019 (2010-04-26)
11447	 */
11448	protected function _outCurveV($x2, $y2, $x3, $y3) {
11449		if ($this->state == 2) {
11450			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11451		}
11452	}
11453
11454	/**
11455	 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points.
11456	 * The new current point shall be (x3, y3).
11457	 * @param $x1 (float) Abscissa of control point 1.
11458	 * @param $y1 (float) Ordinate of control point 1.
11459	 * @param $x3 (float) Abscissa of end point.
11460	 * @param $y3 (float) Ordinate of end point.
11461	 * @protected
11462	 * @since 2.1.000 (2008-01-08)
11463	 */
11464	protected function _outCurveY($x1, $y1, $x3, $y3) {
11465		if ($this->state == 2) {
11466			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11467		}
11468	}
11469
11470	/**
11471	 * Draws a line between two points.
11472	 * @param $x1 (float) Abscissa of first point.
11473	 * @param $y1 (float) Ordinate of first point.
11474	 * @param $x2 (float) Abscissa of second point.
11475	 * @param $y2 (float) Ordinate of second point.
11476	 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11477	 * @public
11478	 * @since 1.0
11479	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11480	 */
11481	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11482		if ($this->state != 2) {
11483			return;
11484		}
11485		if (is_array($style)) {
11486			$this->SetLineStyle($style);
11487		}
11488		$this->_outPoint($x1, $y1);
11489		$this->_outLine($x2, $y2);
11490		$this->_out('S');
11491	}
11492
11493	/**
11494	 * Draws a rectangle.
11495	 * @param $x (float) Abscissa of upper-left corner.
11496	 * @param $y (float) Ordinate of upper-left corner.
11497	 * @param $w (float) Width.
11498	 * @param $h (float) Height.
11499	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11500	 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11501	 * <ul>
11502	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11503	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11504	 * </ul>
11505	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11506	 * @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).
11507	 * @public
11508	 * @since 1.0
11509	 * @see SetLineStyle()
11510	 */
11511	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11512		if ($this->state != 2) {
11513			return;
11514		}
11515		if (empty($style)) {
11516			$style = 'S';
11517		}
11518		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11519			// set background color
11520			$this->SetFillColorArray($fill_color);
11521		}
11522		if (!empty($border_style)) {
11523			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11524				//set global style for border
11525				$this->SetLineStyle($border_style['all']);
11526				$border_style = array();
11527			} else {
11528				// remove stroke operator from style
11529				$opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
11530				if (isset($opnostroke[$style])) {
11531					$style = $opnostroke[$style];
11532				}
11533			}
11534		}
11535		if (!empty($style)) {
11536			$op = TCPDF_STATIC::getPathPaintOperator($style);
11537			$this->_outRect($x, $y, $w, $h, $op);
11538		}
11539		if (!empty($border_style)) {
11540			$border_style2 = array();
11541			foreach ($border_style as $line => $value) {
11542				$length = strlen($line);
11543				for ($i = 0; $i < $length; ++$i) {
11544					$border_style2[$line[$i]] = $value;
11545				}
11546			}
11547			$border_style = $border_style2;
11548			if (isset($border_style['L']) AND $border_style['L']) {
11549				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11550			}
11551			if (isset($border_style['T']) AND $border_style['T']) {
11552				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11553			}
11554			if (isset($border_style['R']) AND $border_style['R']) {
11555				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11556			}
11557			if (isset($border_style['B']) AND $border_style['B']) {
11558				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11559			}
11560		}
11561	}
11562
11563	/**
11564	 * Draws a Bezier curve.
11565	 * The Bezier curve is a tangent to the line between the control points at
11566	 * either end of the curve.
11567	 * @param $x0 (float) Abscissa of start point.
11568	 * @param $y0 (float) Ordinate of start point.
11569	 * @param $x1 (float) Abscissa of control point 1.
11570	 * @param $y1 (float) Ordinate of control point 1.
11571	 * @param $x2 (float) Abscissa of control point 2.
11572	 * @param $y2 (float) Ordinate of control point 2.
11573	 * @param $x3 (float) Abscissa of end point.
11574	 * @param $y3 (float) Ordinate of end point.
11575	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11576	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11577	 * @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).
11578	 * @public
11579	 * @see SetLineStyle()
11580	 * @since 2.1.000 (2008-01-08)
11581	 */
11582	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11583		if ($this->state != 2) {
11584			return;
11585		}
11586		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11587			$this->SetFillColorArray($fill_color);
11588		}
11589		$op = TCPDF_STATIC::getPathPaintOperator($style);
11590		if ($line_style) {
11591			$this->SetLineStyle($line_style);
11592		}
11593		$this->_outPoint($x0, $y0);
11594		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11595		$this->_out($op);
11596	}
11597
11598	/**
11599	 * Draws a poly-Bezier curve.
11600	 * Each Bezier curve segment is a tangent to the line between the control points at
11601	 * either end of the curve.
11602	 * @param $x0 (float) Abscissa of start point.
11603	 * @param $y0 (float) Ordinate of start point.
11604	 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11605	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11606	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11607	 * @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).
11608	 * @public
11609	 * @see SetLineStyle()
11610	 * @since 3.0008 (2008-05-12)
11611	 */
11612	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11613		if ($this->state != 2) {
11614			return;
11615		}
11616		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11617			$this->SetFillColorArray($fill_color);
11618		}
11619		$op = TCPDF_STATIC::getPathPaintOperator($style);
11620		if ($op == 'f') {
11621			$line_style = array();
11622		}
11623		if ($line_style) {
11624			$this->SetLineStyle($line_style);
11625		}
11626		$this->_outPoint($x0, $y0);
11627		foreach ($segments as $segment) {
11628			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11629			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11630		}
11631		$this->_out($op);
11632	}
11633
11634	/**
11635	 * Draws an ellipse.
11636	 * An ellipse is formed from n Bezier curves.
11637	 * @param $x0 (float) Abscissa of center point.
11638	 * @param $y0 (float) Ordinate of center point.
11639	 * @param $rx (float) Horizontal radius.
11640	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11641	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11642	 * @param $astart: (float) Angle start of draw line. Default value: 0.
11643	 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11644	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11645	 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11646	 * @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).
11647	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11648	 * @author Nicola Asuni
11649	 * @public
11650	 * @since 2.1.000 (2008-01-08)
11651	 */
11652	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11653		if ($this->state != 2) {
11654			return;
11655		}
11656		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11657			$ry = $rx;
11658		}
11659		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11660			$this->SetFillColorArray($fill_color);
11661		}
11662		$op = TCPDF_STATIC::getPathPaintOperator($style);
11663		if ($op == 'f') {
11664			$line_style = array();
11665		}
11666		if ($line_style) {
11667			$this->SetLineStyle($line_style);
11668		}
11669		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11670		$this->_out($op);
11671	}
11672
11673	/**
11674	 * Append an elliptical arc to the current path.
11675	 * An ellipse is formed from n Bezier curves.
11676	 * @param $xc (float) Abscissa of center point.
11677	 * @param $yc (float) Ordinate of center point.
11678	 * @param $rx (float) Horizontal radius.
11679	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11680	 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11681	 * @param $angs: (float) Angle start of draw line. Default value: 0.
11682	 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11683	 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11684	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11685	 * @param $startpoint (boolean) if true output a starting point.
11686	 * @param $ccw (boolean) if true draws in counter-clockwise.
11687	 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11688	 * @return array bounding box coordinates (x min, y min, x max, y max)
11689	 * @author Nicola Asuni
11690	 * @protected
11691	 * @since 4.9.019 (2010-04-26)
11692	 */
11693	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11694		if (($rx <= 0) OR ($ry < 0)) {
11695			return;
11696		}
11697		$k = $this->k;
11698		if ($nc < 2) {
11699			$nc = 2;
11700		}
11701		$xmin = 2147483647;
11702		$ymin = 2147483647;
11703		$xmax = 0;
11704		$ymax = 0;
11705		if ($pie) {
11706			// center of the arc
11707			$this->_outPoint($xc, $yc);
11708		}
11709		$xang = deg2rad((float) $xang);
11710		$angs = deg2rad((float) $angs);
11711		$angf = deg2rad((float) $angf);
11712		if ($svg) {
11713			$as = $angs;
11714			$af = $angf;
11715		} else {
11716			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11717			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11718		}
11719		if ($as < 0) {
11720			$as += (2 * M_PI);
11721		}
11722		if ($af < 0) {
11723			$af += (2 * M_PI);
11724		}
11725		if ($ccw AND ($as > $af)) {
11726			// reverse rotation
11727			$as -= (2 * M_PI);
11728		} elseif (!$ccw AND ($as < $af)) {
11729			// reverse rotation
11730			$af -= (2 * M_PI);
11731		}
11732		$total_angle = ($af - $as);
11733		if ($nc < 2) {
11734			$nc = 2;
11735		}
11736		// total arcs to draw
11737		$nc *= (2 * abs($total_angle) / M_PI);
11738		$nc = round($nc) + 1;
11739		// angle of each arc
11740		$arcang = ($total_angle / $nc);
11741		// center point in PDF coordinates
11742		$x0 = $xc;
11743		$y0 = ($this->h - $yc);
11744		// starting angle
11745		$ang = $as;
11746		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11747		$cos_xang = cos($xang);
11748		$sin_xang = sin($xang);
11749		$cos_ang = cos($ang);
11750		$sin_ang = sin($ang);
11751		// first arc point
11752		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11753		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11754		// first Bezier control point
11755		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11756		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11757		if ($pie) {
11758			// line from center to arc starting point
11759			$this->_outLine($px1, $this->h - $py1);
11760		} elseif ($startpoint) {
11761			// arc starting point
11762			$this->_outPoint($px1, $this->h - $py1);
11763		}
11764		// draw arcs
11765		for ($i = 1; $i <= $nc; ++$i) {
11766			// starting angle
11767			$ang = $as + ($i * $arcang);
11768			if ($i == $nc) {
11769				$ang = $af;
11770			}
11771			$cos_ang = cos($ang);
11772			$sin_ang = sin($ang);
11773			// second arc point
11774			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11775			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11776			// second Bezier control point
11777			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11778			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11779			// draw arc
11780			$cx1 = ($px1 + $qx1);
11781			$cy1 = ($this->h - ($py1 + $qy1));
11782			$cx2 = ($px2 - $qx2);
11783			$cy2 = ($this->h - ($py2 - $qy2));
11784			$cx3 = $px2;
11785			$cy3 = ($this->h - $py2);
11786			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11787			// get bounding box coordinates
11788			$xmin = min($xmin, $cx1, $cx2, $cx3);
11789			$ymin = min($ymin, $cy1, $cy2, $cy3);
11790			$xmax = max($xmax, $cx1, $cx2, $cx3);
11791			$ymax = max($ymax, $cy1, $cy2, $cy3);
11792			// move to next point
11793			$px1 = $px2;
11794			$py1 = $py2;
11795			$qx1 = $qx2;
11796			$qy1 = $qy2;
11797		}
11798		if ($pie) {
11799			$this->_outLine($xc, $yc);
11800			// get bounding box coordinates
11801			$xmin = min($xmin, $xc);
11802			$ymin = min($ymin, $yc);
11803			$xmax = max($xmax, $xc);
11804			$ymax = max($ymax, $yc);
11805		}
11806		return array($xmin, $ymin, $xmax, $ymax);
11807	}
11808
11809	/**
11810	 * Draws a circle.
11811	 * A circle is formed from n Bezier curves.
11812	 * @param $x0 (float) Abscissa of center point.
11813	 * @param $y0 (float) Ordinate of center point.
11814	 * @param $r (float) Radius.
11815	 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11816	 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11817	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11818	 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11819	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11820	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11821	 * @public
11822	 * @since 2.1.000 (2008-01-08)
11823	 */
11824	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11825		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11826	}
11827
11828	/**
11829	 * Draws a polygonal line
11830	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11831	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11832	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11833	 * <ul>
11834	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11835	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11836	 * </ul>
11837	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11838	 * @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).
11839	 * @since 4.8.003 (2009-09-15)
11840	 * @public
11841	 */
11842	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11843		$this->Polygon($p, $style, $line_style, $fill_color, false);
11844	}
11845
11846	/**
11847	 * Draws a polygon.
11848	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11849	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11850	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11851	 * <ul>
11852	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11853	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11854	 * </ul>
11855	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11856	 * @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).
11857	 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11858	 * @public
11859	 * @since 2.1.000 (2008-01-08)
11860	 */
11861	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11862		if ($this->state != 2) {
11863			return;
11864		}
11865		$nc = count($p); // number of coordinates
11866		$np = $nc / 2; // number of points
11867		if ($closed) {
11868			// close polygon by adding the first 2 points at the end (one line)
11869			for ($i = 0; $i < 4; ++$i) {
11870				$p[$nc + $i] = $p[$i];
11871			}
11872			// copy style for the last added line
11873			if (isset($line_style[0])) {
11874				$line_style[$np] = $line_style[0];
11875			}
11876			$nc += 4;
11877		}
11878		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11879			$this->SetFillColorArray($fill_color);
11880		}
11881		$op = TCPDF_STATIC::getPathPaintOperator($style);
11882		if ($op == 'f') {
11883			$line_style = array();
11884		}
11885		$draw = true;
11886		if ($line_style) {
11887			if (isset($line_style['all'])) {
11888				$this->SetLineStyle($line_style['all']);
11889			} else {
11890				$draw = false;
11891				if ($op == 'B') {
11892					// draw fill
11893					$op = 'f';
11894					$this->_outPoint($p[0], $p[1]);
11895					for ($i = 2; $i < $nc; $i = $i + 2) {
11896						$this->_outLine($p[$i], $p[$i + 1]);
11897					}
11898					$this->_out($op);
11899				}
11900				// draw outline
11901				$this->_outPoint($p[0], $p[1]);
11902				for ($i = 2; $i < $nc; $i = $i + 2) {
11903					$line_num = ($i / 2) - 1;
11904					if (isset($line_style[$line_num])) {
11905						if ($line_style[$line_num] != 0) {
11906							if (is_array($line_style[$line_num])) {
11907								$this->_out('S');
11908								$this->SetLineStyle($line_style[$line_num]);
11909								$this->_outPoint($p[$i - 2], $p[$i - 1]);
11910								$this->_outLine($p[$i], $p[$i + 1]);
11911								$this->_out('S');
11912								$this->_outPoint($p[$i], $p[$i + 1]);
11913							} else {
11914								$this->_outLine($p[$i], $p[$i + 1]);
11915							}
11916						}
11917					} else {
11918						$this->_outLine($p[$i], $p[$i + 1]);
11919					}
11920				}
11921				$this->_out($op);
11922			}
11923		}
11924		if ($draw) {
11925			$this->_outPoint($p[0], $p[1]);
11926			for ($i = 2; $i < $nc; $i = $i + 2) {
11927				$this->_outLine($p[$i], $p[$i + 1]);
11928			}
11929			$this->_out($op);
11930		}
11931	}
11932
11933	/**
11934	 * Draws a regular polygon.
11935	 * @param $x0 (float) Abscissa of center point.
11936	 * @param $y0 (float) Ordinate of center point.
11937	 * @param $r: (float) Radius of inscribed circle.
11938	 * @param $ns (integer) Number of sides.
11939	 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
11940	 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
11941	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11942	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11943	 * <ul>
11944	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
11945	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
11946	 * </ul>
11947	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11948	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11949	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11950	 * <ul>
11951	 *	 <li>D or empty string: Draw (default).</li>
11952	 *	 <li>F: Fill.</li>
11953	 *	 <li>DF or FD: Draw and fill.</li>
11954	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11955	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11956	 * </ul>
11957	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11958	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
11959	 * @public
11960	 * @since 2.1.000 (2008-01-08)
11961	 */
11962	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()) {
11963		if (3 > $ns) {
11964			$ns = 3;
11965		}
11966		if ($draw_circle) {
11967			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
11968		}
11969		$p = array();
11970		for ($i = 0; $i < $ns; ++$i) {
11971			$a = $angle + ($i * 360 / $ns);
11972			$a_rad = deg2rad((float) $a);
11973			$p[] = $x0 + ($r * sin($a_rad));
11974			$p[] = $y0 + ($r * cos($a_rad));
11975		}
11976		$this->Polygon($p, $style, $line_style, $fill_color);
11977	}
11978
11979	/**
11980	 * Draws a star polygon
11981	 * @param $x0 (float) Abscissa of center point.
11982	 * @param $y0 (float) Ordinate of center point.
11983	 * @param $r (float) Radius of inscribed circle.
11984	 * @param $nv (integer) Number of vertices.
11985	 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
11986	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11987	 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
11988	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11989	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11990	 * <ul>
11991	 *	 <li>all: Line style of all sides. Array like for
11992	 * SetLineStyle().</li>
11993	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
11994	 * </ul>
11995	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11996	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11997	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11998	 * <ul>
11999	 *	 <li>D or empty string: Draw (default).</li>
12000	 *	 <li>F: Fill.</li>
12001	 *	 <li>DF or FD: Draw and fill.</li>
12002	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12003	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12004	 * </ul>
12005	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12006	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12007	 * @public
12008	 * @since 2.1.000 (2008-01-08)
12009	 */
12010	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()) {
12011		if ($nv < 2) {
12012			$nv = 2;
12013		}
12014		if ($draw_circle) {
12015			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12016		}
12017		$p2 = array();
12018		$visited = array();
12019		for ($i = 0; $i < $nv; ++$i) {
12020			$a = $angle + ($i * 360 / $nv);
12021			$a_rad = deg2rad((float) $a);
12022			$p2[] = $x0 + ($r * sin($a_rad));
12023			$p2[] = $y0 + ($r * cos($a_rad));
12024			$visited[] = false;
12025		}
12026		$p = array();
12027		$i = 0;
12028		do {
12029			$p[] = $p2[$i * 2];
12030			$p[] = $p2[($i * 2) + 1];
12031			$visited[$i] = true;
12032			$i += $ng;
12033			$i %= $nv;
12034		} while (!$visited[$i]);
12035		$this->Polygon($p, $style, $line_style, $fill_color);
12036	}
12037
12038	/**
12039	 * Draws a rounded rectangle.
12040	 * @param $x (float) Abscissa of upper-left corner.
12041	 * @param $y (float) Ordinate of upper-left corner.
12042	 * @param $w (float) Width.
12043	 * @param $h (float) Height.
12044	 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12045	 * @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").
12046	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12047	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12048	 * @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).
12049	 * @public
12050	 * @since 2.1.000 (2008-01-08)
12051	 */
12052	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12053		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12054	}
12055
12056	/**
12057	 * Draws a rounded rectangle.
12058	 * @param $x (float) Abscissa of upper-left corner.
12059	 * @param $y (float) Ordinate of upper-left corner.
12060	 * @param $w (float) Width.
12061	 * @param $h (float) Height.
12062	 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12063	 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12064	 * @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").
12065	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12066	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12067	 * @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).
12068	 * @public
12069	 * @since 4.9.019 (2010-04-22)
12070	 */
12071	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12072		if ($this->state != 2) {
12073			return;
12074		}
12075		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12076			// Not rounded
12077			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12078			return;
12079		}
12080		// Rounded
12081		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12082			$this->SetFillColorArray($fill_color);
12083		}
12084		$op = TCPDF_STATIC::getPathPaintOperator($style);
12085		if ($op == 'f') {
12086			$border_style = array();
12087		}
12088		if ($border_style) {
12089			$this->SetLineStyle($border_style);
12090		}
12091		$MyArc = 4 / 3 * (sqrt(2) - 1);
12092		$this->_outPoint($x + $rx, $y);
12093		$xc = $x + $w - $rx;
12094		$yc = $y + $ry;
12095		$this->_outLine($xc, $y);
12096		if ($round_corner[0]) {
12097			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12098		} else {
12099			$this->_outLine($x + $w, $y);
12100		}
12101		$xc = $x + $w - $rx;
12102		$yc = $y + $h - $ry;
12103		$this->_outLine($x + $w, $yc);
12104		if ($round_corner[1]) {
12105			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12106		} else {
12107			$this->_outLine($x + $w, $y + $h);
12108		}
12109		$xc = $x + $rx;
12110		$yc = $y + $h - $ry;
12111		$this->_outLine($xc, $y + $h);
12112		if ($round_corner[2]) {
12113			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12114		} else {
12115			$this->_outLine($x, $y + $h);
12116		}
12117		$xc = $x + $rx;
12118		$yc = $y + $ry;
12119		$this->_outLine($x, $yc);
12120		if ($round_corner[3]) {
12121			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12122		} else {
12123			$this->_outLine($x, $y);
12124			$this->_outLine($x + $rx, $y);
12125		}
12126		$this->_out($op);
12127	}
12128
12129	/**
12130	 * Draws a grahic arrow.
12131	 * @param $x0 (float) Abscissa of first point.
12132	 * @param $y0 (float) Ordinate of first point.
12133	 * @param $x1 (float) Abscissa of second point.
12134	 * @param $y1 (float) Ordinate of second point.
12135	 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12136	 * @param $arm_size (float) length of arrowhead arms
12137	 * @param $arm_angle (int) angle between an arm and the shaft
12138	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12139	 * @since 4.6.018 (2009-07-10)
12140	 */
12141	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12142		// getting arrow direction angle
12143		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12144		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12145		if ($dir_angle < 0) {
12146			$dir_angle += (2 * M_PI);
12147		}
12148		$arm_angle = deg2rad($arm_angle);
12149		$sx1 = $x1;
12150		$sy1 = $y1;
12151		if ($head_style > 0) {
12152			// calculate the stopping point for the arrow shaft
12153			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12154			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12155		}
12156		// main arrow line / shaft
12157		$this->Line($x0, $y0, $sx1, $sy1);
12158		// left arrowhead arm tip
12159		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12160		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12161		// right arrowhead arm tip
12162		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12163		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12164		$mode = 'D';
12165		$style = array();
12166		switch ($head_style) {
12167			case 0: {
12168				// draw only arrowhead arms
12169				$mode = 'D';
12170				$style = array(1, 1, 0);
12171				break;
12172			}
12173			case 1: {
12174				// draw closed arrowhead, but no fill
12175				$mode = 'D';
12176				break;
12177			}
12178			case 2: {
12179				// closed and filled arrowhead
12180				$mode = 'DF';
12181				break;
12182			}
12183			case 3: {
12184				// filled arrowhead
12185				$mode = 'F';
12186				break;
12187			}
12188		}
12189		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12190	}
12191
12192	// END GRAPHIC FUNCTIONS SECTION -----------------------
12193
12194	/**
12195	 * Add a Named Destination.
12196	 * NOTE: destination names are unique, so only last entry will be saved.
12197	 * @param $name (string) Destination name.
12198	 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12199	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12200	 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12201	 * @return (string) Stripped named destination identifier or false in case of error.
12202	 * @public
12203	 * @author Christian Deligant, Nicola Asuni
12204	 * @since 5.9.097 (2011-06-23)
12205	 */
12206	public function setDestination($name, $y=-1, $page='', $x=-1) {
12207		// remove unsupported characters
12208		$name = TCPDF_STATIC::encodeNameObject($name);
12209		if (TCPDF_STATIC::empty_string($name)) {
12210			return false;
12211		}
12212		if ($y == -1) {
12213			$y = $this->GetY();
12214		} elseif ($y < 0) {
12215			$y = 0;
12216		} elseif ($y > $this->h) {
12217			$y = $this->h;
12218		}
12219		if ($x == -1) {
12220			$x = $this->GetX();
12221		} elseif ($x < 0) {
12222			$x = 0;
12223		} elseif ($x > $this->w) {
12224			$x = $this->w;
12225		}
12226		$fixed = false;
12227		if (!empty($page) AND ($page[0] == '*')) {
12228			$page = intval(substr($page, 1));
12229			// this page number will not be changed when moving/add/deleting pages
12230			$fixed = true;
12231		}
12232		if (empty($page)) {
12233			$page = $this->PageNo();
12234			if (empty($page)) {
12235				return;
12236			}
12237		}
12238		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12239		return $name;
12240	}
12241
12242	/**
12243	 * Return the Named Destination array.
12244	 * @return (array) Named Destination array.
12245	 * @public
12246	 * @author Nicola Asuni
12247	 * @since 5.9.097 (2011-06-23)
12248	 */
12249	public function getDestination() {
12250		return $this->dests;
12251	}
12252
12253	/**
12254	 * Insert Named Destinations.
12255	 * @protected
12256	 * @author Johannes G\FCntert, Nicola Asuni
12257	 * @since 5.9.098 (2011-06-23)
12258	 */
12259	protected function _putdests() {
12260		if (empty($this->dests)) {
12261			return;
12262		}
12263		$this->n_dests = $this->_newobj();
12264		$out = ' <<';
12265		foreach($this->dests as $name => $o) {
12266			$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)));
12267		}
12268		$out .= ' >>';
12269		$out .= "\n".'endobj';
12270		$this->_out($out);
12271	}
12272
12273	/**
12274	 * Adds a bookmark - alias for Bookmark().
12275	 * @param $txt (string) Bookmark description.
12276	 * @param $level (int) Bookmark level (minimum value is 0).
12277	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12278	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12279	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12280	 * @param $color (array) RGB color array (values from 0 to 255).
12281	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12282	 * @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).
12283	 * @public
12284	 */
12285	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12286		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12287	}
12288
12289	/**
12290	 * Adds a bookmark.
12291	 * @param $txt (string) Bookmark description.
12292	 * @param $level (int) Bookmark level (minimum value is 0).
12293	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12294	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12295	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12296	 * @param $color (array) RGB color array (values from 0 to 255).
12297	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12298	 * @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).
12299	 * @public
12300	 * @since 2.1.002 (2008-02-12)
12301	 */
12302	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12303		if ($level < 0) {
12304			$level = 0;
12305		}
12306		if (isset($this->outlines[0])) {
12307			$lastoutline = end($this->outlines);
12308			$maxlevel = $lastoutline['l'] + 1;
12309		} else {
12310			$maxlevel = 0;
12311		}
12312		if ($level > $maxlevel) {
12313			$level = $maxlevel;
12314		}
12315		if ($y == -1) {
12316			$y = $this->GetY();
12317		} elseif ($y < 0) {
12318			$y = 0;
12319		} elseif ($y > $this->h) {
12320			$y = $this->h;
12321		}
12322		if ($x == -1) {
12323			$x = $this->GetX();
12324		} elseif ($x < 0) {
12325			$x = 0;
12326		} elseif ($x > $this->w) {
12327			$x = $this->w;
12328		}
12329		$fixed = false;
12330		if (!empty($page) AND ($page[0] == '*')) {
12331			$page = intval(substr($page, 1));
12332			// this page number will not be changed when moving/add/deleting pages
12333			$fixed = true;
12334		}
12335		if (empty($page)) {
12336			$page = $this->PageNo();
12337			if (empty($page)) {
12338				return;
12339			}
12340		}
12341		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12342	}
12343
12344	/**
12345	 * Sort bookmarks for page and key.
12346	 * @protected
12347	 * @since 5.9.119 (2011-09-19)
12348	 */
12349	protected function sortBookmarks() {
12350		// get sorting columns
12351		$outline_p = array();
12352		$outline_y = array();
12353		foreach ($this->outlines as $key => $row) {
12354			$outline_p[$key] = $row['p'];
12355			$outline_k[$key] = $key;
12356		}
12357		// sort outlines by page and original position
12358		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12359	}
12360
12361	/**
12362	 * Create a bookmark PDF string.
12363	 * @protected
12364	 * @author Olivier Plathey, Nicola Asuni
12365	 * @since 2.1.002 (2008-02-12)
12366	 */
12367	protected function _putbookmarks() {
12368		$nb = count($this->outlines);
12369		if ($nb == 0) {
12370			return;
12371		}
12372		// sort bookmarks
12373		$this->sortBookmarks();
12374		$lru = array();
12375		$level = 0;
12376		foreach ($this->outlines as $i => $o) {
12377			if ($o['l'] > 0) {
12378				$parent = $lru[($o['l'] - 1)];
12379				//Set parent and last pointers
12380				$this->outlines[$i]['parent'] = $parent;
12381				$this->outlines[$parent]['last'] = $i;
12382				if ($o['l'] > $level) {
12383					//Level increasing: set first pointer
12384					$this->outlines[$parent]['first'] = $i;
12385				}
12386			} else {
12387				$this->outlines[$i]['parent'] = $nb;
12388			}
12389			if (($o['l'] <= $level) AND ($i > 0)) {
12390				//Set prev and next pointers
12391				$prev = $lru[$o['l']];
12392				$this->outlines[$prev]['next'] = $i;
12393				$this->outlines[$i]['prev'] = $prev;
12394			}
12395			$lru[$o['l']] = $i;
12396			$level = $o['l'];
12397		}
12398		//Outline items
12399		$n = $this->n + 1;
12400		$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';
12401		foreach ($this->outlines as $i => $o) {
12402			$oid = $this->_newobj();
12403			// covert HTML title to string
12404			$title = preg_replace($nltags, "\n", $o['t']);
12405			$title = preg_replace("/[\r]+/si", '', $title);
12406			$title = preg_replace("/[\n]+/si", "\n", $title);
12407			$title = strip_tags($title);
12408			$title = $this->stringTrim($title);
12409			$out = '<</Title '.$this->_textstring($title, $oid);
12410			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12411			if (isset($o['prev'])) {
12412				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12413			}
12414			if (isset($o['next'])) {
12415				$out .= ' /Next '.($n + $o['next']).' 0 R';
12416			}
12417			if (isset($o['first'])) {
12418				$out .= ' /First '.($n + $o['first']).' 0 R';
12419			}
12420			if (isset($o['last'])) {
12421				$out .= ' /Last '.($n + $o['last']).' 0 R';
12422			}
12423			if (isset($o['u']) AND !empty($o['u'])) {
12424				// link
12425				if (is_string($o['u'])) {
12426					if ($o['u'][0] == '#') {
12427						// internal destination
12428						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12429					} elseif ($o['u'][0] == '%') {
12430						// embedded PDF file
12431						$filename = basename(substr($o['u'], 1));
12432						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12433					} elseif ($o['u'][0] == '*') {
12434						// embedded generic file
12435						$filename = basename(substr($o['u'], 1));
12436						$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});';
12437						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12438					} else {
12439						// external URI link
12440						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12441					}
12442				} elseif (isset($this->links[$o['u']])) {
12443					// internal link ID
12444					$l = $this->links[$o['u']];
12445					if (isset($this->page_obj_id[($l['p'])])) {
12446						$out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
12447					}
12448				}
12449			} elseif (isset($this->page_obj_id[($o['p'])])) {
12450				// link to a page
12451				$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)));
12452			}
12453			// set font style
12454			$style = 0;
12455			if (!empty($o['s'])) {
12456				// bold
12457				if (strpos($o['s'], 'B') !== false) {
12458					$style |= 2;
12459				}
12460				// oblique
12461				if (strpos($o['s'], 'I') !== false) {
12462					$style |= 1;
12463				}
12464			}
12465			$out .= sprintf(' /F %d', $style);
12466			// set bookmark color
12467			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12468				$color = array_values($o['c']);
12469				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12470			} else {
12471				// black
12472				$out .= ' /C [0.0 0.0 0.0]';
12473			}
12474			$out .= ' /Count 0'; // normally closed item
12475			$out .= ' >>';
12476			$out .= "\n".'endobj';
12477			$this->_out($out);
12478		}
12479		//Outline root
12480		$this->OutlineRoot = $this->_newobj();
12481		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12482	}
12483
12484	// --- JAVASCRIPT ------------------------------------------------------
12485
12486	/**
12487	 * Adds a javascript
12488	 * @param $script (string) Javascript code
12489	 * @public
12490	 * @author Johannes G\FCntert, Nicola Asuni
12491	 * @since 2.1.002 (2008-02-12)
12492	 */
12493	public function IncludeJS($script) {
12494		$this->javascript .= $script;
12495	}
12496
12497	/**
12498	 * Adds a javascript object and return object ID
12499	 * @param $script (string) Javascript code
12500	 * @param $onload (boolean) if true executes this object when opening the document
12501	 * @return int internal object ID
12502	 * @public
12503	 * @author Nicola Asuni
12504	 * @since 4.8.000 (2009-09-07)
12505	 */
12506	public function addJavascriptObject($script, $onload=false) {
12507		if ($this->pdfa_mode) {
12508			// javascript is not allowed in PDF/A mode
12509			return false;
12510		}
12511		++$this->n;
12512		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12513		return $this->n;
12514	}
12515
12516	/**
12517	 * Create a javascript PDF string.
12518	 * @protected
12519	 * @author Johannes G\FCntert, Nicola Asuni
12520	 * @since 2.1.002 (2008-02-12)
12521	 */
12522	protected function _putjavascript() {
12523		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12524			return;
12525		}
12526		if (strpos($this->javascript, 'this.addField') > 0) {
12527			if (!$this->ur['enabled']) {
12528				//$this->setUserRights();
12529			}
12530			// the following two lines are used to avoid form fields duplication after saving
12531			// The addField method only works when releasing user rights (UR3)
12532			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12533			$jsb = "getField('tcpdfdocsaved').value='saved';";
12534			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12535		}
12536		// name tree for javascript
12537		$this->n_js = '<< /Names [';
12538		if (!empty($this->javascript)) {
12539			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12540		}
12541		if (!empty($this->js_objects)) {
12542			foreach ($this->js_objects as $key => $val) {
12543				if ($val['onload']) {
12544					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12545				}
12546			}
12547		}
12548		$this->n_js .= ' ] >>';
12549		// default Javascript object
12550		if (!empty($this->javascript)) {
12551			$obj_id = $this->_newobj();
12552			$out = '<< /S /JavaScript';
12553			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12554			$out .= ' >>';
12555			$out .= "\n".'endobj';
12556			$this->_out($out);
12557		}
12558		// additional Javascript objects
12559		if (!empty($this->js_objects)) {
12560			foreach ($this->js_objects as $key => $val) {
12561				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12562				$this->_out($out);
12563			}
12564		}
12565	}
12566
12567	/**
12568	 * Adds a javascript form field.
12569	 * @param $type (string) field type
12570	 * @param $name (string) field name
12571	 * @param $x (int) horizontal position
12572	 * @param $y (int) vertical position
12573	 * @param $w (int) width
12574	 * @param $h (int) height
12575	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12576	 * @protected
12577	 * @author Denis Van Nuffelen, Nicola Asuni
12578	 * @since 2.1.002 (2008-02-12)
12579	 */
12580	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12581		if ($this->rtl) {
12582			$x = $x - $w;
12583		}
12584		// the followind avoid fields duplication after saving the document
12585		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12586		$k = $this->k;
12587		$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";
12588		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12589		while (list($key, $val) = each($prop)) {
12590			if (strcmp(substr($key, -5), 'Color') == 0) {
12591				$val = TCPDF_COLORS::_JScolor($val);
12592			} else {
12593				$val = "'".$val."'";
12594			}
12595			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12596		}
12597		if ($this->rtl) {
12598			$this->x -= $w;
12599		} else {
12600			$this->x += $w;
12601		}
12602		$this->javascript .= '}';
12603	}
12604
12605	// --- FORM FIELDS -----------------------------------------------------
12606
12607
12608
12609	/**
12610	 * Set default properties for form fields.
12611	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12612	 * @public
12613	 * @author Nicola Asuni
12614	 * @since 4.8.000 (2009-09-06)
12615	 */
12616	public function setFormDefaultProp($prop=array()) {
12617		$this->default_form_prop = $prop;
12618	}
12619
12620	/**
12621	 * Return the default properties for form fields.
12622	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12623	 * @public
12624	 * @author Nicola Asuni
12625	 * @since 4.8.000 (2009-09-06)
12626	 */
12627	public function getFormDefaultProp() {
12628		return $this->default_form_prop;
12629	}
12630
12631	/**
12632	 * Creates a text field
12633	 * @param $name (string) field name
12634	 * @param $w (float) Width of the rectangle
12635	 * @param $h (float) Height of the rectangle
12636	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12637	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12638	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12639	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12640	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12641	 * @public
12642	 * @author Nicola Asuni
12643	 * @since 4.8.000 (2009-09-07)
12644	 */
12645	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12646		if ($x === '') {
12647			$x = $this->x;
12648		}
12649		if ($y === '') {
12650			$y = $this->y;
12651		}
12652		// check page for no-write regions and adapt page margins if necessary
12653		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12654		if ($js) {
12655			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12656			return;
12657		}
12658		// get default style
12659		$prop = array_merge($this->getFormDefaultProp(), $prop);
12660		// get annotation data
12661		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12662		// set default appearance stream
12663		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12664		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12665		$popt['da'] = $fontstyle;
12666		// build appearance stream
12667		$popt['ap'] = array();
12668		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12669		$text = '';
12670		if (isset($prop['value']) AND !empty($prop['value'])) {
12671			$text = $prop['value'];
12672		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12673			$text = $opt['v'];
12674		}
12675		$tmpid = $this->startTemplate($w, $h, false);
12676		$align = '';
12677		if (isset($popt['q'])) {
12678			switch ($popt['q']) {
12679				case 0: {
12680					$align = 'L';
12681					break;
12682				}
12683				case 1: {
12684					$align = 'C';
12685					break;
12686				}
12687				case 2: {
12688					$align = 'R';
12689					break;
12690				}
12691				default: {
12692					$align = '';
12693					break;
12694				}
12695			}
12696		}
12697		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12698		$this->endTemplate();
12699		--$this->n;
12700		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12701		unset($this->xobjects[$tmpid]);
12702		$popt['ap']['n'] .= 'Q EMC';
12703		// merge options
12704		$opt = array_merge($popt, $opt);
12705		// remove some conflicting options
12706		unset($opt['bs']);
12707		// set remaining annotation data
12708		$opt['Subtype'] = 'Widget';
12709		$opt['ft'] = 'Tx';
12710		$opt['t'] = $name;
12711		// Additional annotation's parameters (check _putannotsobj() method):
12712		//$opt['f']
12713		//$opt['as']
12714		//$opt['bs']
12715		//$opt['be']
12716		//$opt['c']
12717		//$opt['border']
12718		//$opt['h']
12719		//$opt['mk'];
12720		//$opt['mk']['r']
12721		//$opt['mk']['bc'];
12722		//$opt['mk']['bg'];
12723		unset($opt['mk']['ca']);
12724		unset($opt['mk']['rc']);
12725		unset($opt['mk']['ac']);
12726		unset($opt['mk']['i']);
12727		unset($opt['mk']['ri']);
12728		unset($opt['mk']['ix']);
12729		unset($opt['mk']['if']);
12730		//$opt['mk']['if']['sw'];
12731		//$opt['mk']['if']['s'];
12732		//$opt['mk']['if']['a'];
12733		//$opt['mk']['if']['fb'];
12734		unset($opt['mk']['tp']);
12735		//$opt['tu']
12736		//$opt['tm']
12737		//$opt['ff']
12738		//$opt['v']
12739		//$opt['dv']
12740		//$opt['a']
12741		//$opt['aa']
12742		//$opt['q']
12743		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12744		if ($this->rtl) {
12745			$this->x -= $w;
12746		} else {
12747			$this->x += $w;
12748		}
12749	}
12750
12751	/**
12752	 * Creates a RadioButton field.
12753	 * @param $name (string) Field name.
12754	 * @param $w (int) Width of the radio button.
12755	 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12756	 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12757	 * @param $onvalue (string) Value to be returned if selected.
12758	 * @param $checked (boolean) Define the initial state.
12759	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12760	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12761	 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12762	 * @public
12763	 * @author Nicola Asuni
12764	 * @since 4.8.000 (2009-09-07)
12765	 */
12766	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12767		if ($x === '') {
12768			$x = $this->x;
12769		}
12770		if ($y === '') {
12771			$y = $this->y;
12772		}
12773		// check page for no-write regions and adapt page margins if necessary
12774		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12775		if ($js) {
12776			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12777			return;
12778		}
12779		if (TCPDF_STATIC::empty_string($onvalue)) {
12780			$onvalue = 'On';
12781		}
12782		if ($checked) {
12783			$defval = $onvalue;
12784		} else {
12785			$defval = 'Off';
12786		}
12787		// set font
12788		$font = 'zapfdingbats';
12789		if ($this->pdfa_mode) {
12790			// all fonts must be embedded
12791			$font = 'pdfa'.$font;
12792		}
12793		$this->AddFont($font);
12794		$tmpfont = $this->getFontBuffer($font);
12795		// set data for parent group
12796		if (!isset($this->radiobutton_groups[$this->page])) {
12797			$this->radiobutton_groups[$this->page] = array();
12798		}
12799		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12800			$this->radiobutton_groups[$this->page][$name] = array();
12801			++$this->n;
12802			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12803			$this->radio_groups[] = $this->n;
12804		}
12805		$kid = ($this->n + 1);
12806		// save object ID to be added on Kids entry on parent object
12807		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12808		// get default style
12809		$prop = array_merge($this->getFormDefaultProp(), $prop);
12810		$prop['NoToggleToOff'] = 'true';
12811		$prop['Radio'] = 'true';
12812		$prop['borderStyle'] = 'inset';
12813		// get annotation data
12814		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12815		// set additional default options
12816		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12817		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12818		$popt['da'] = $fontstyle;
12819		// build appearance stream
12820		$popt['ap'] = array();
12821		$popt['ap']['n'] = array();
12822		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12823		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12824		$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);
12825		$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);
12826		if (!isset($popt['mk'])) {
12827			$popt['mk'] = array();
12828		}
12829		$popt['mk']['ca'] = '(l)';
12830		// merge options
12831		$opt = array_merge($popt, $opt);
12832		// set remaining annotation data
12833		$opt['Subtype'] = 'Widget';
12834		$opt['ft'] = 'Btn';
12835		if ($checked) {
12836			$opt['v'] = array('/'.$onvalue);
12837			$opt['as'] = $onvalue;
12838		} else {
12839			$opt['as'] = 'Off';
12840		}
12841		// store readonly flag
12842		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12843			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12844		}
12845		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12846		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12847		if ($this->rtl) {
12848			$this->x -= $w;
12849		} else {
12850			$this->x += $w;
12851		}
12852	}
12853
12854	/**
12855	 * Creates a List-box field
12856	 * @param $name (string) field name
12857	 * @param $w (int) width
12858	 * @param $h (int) height
12859	 * @param $values (array) array containing the list of values.
12860	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12861	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12862	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12863	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12864	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12865	 * @public
12866	 * @author Nicola Asuni
12867	 * @since 4.8.000 (2009-09-07)
12868	 */
12869	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12870		if ($x === '') {
12871			$x = $this->x;
12872		}
12873		if ($y === '') {
12874			$y = $this->y;
12875		}
12876		// check page for no-write regions and adapt page margins if necessary
12877		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12878		if ($js) {
12879			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12880			$s = '';
12881			foreach ($values as $value) {
12882				if (is_array($value)) {
12883					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12884				} else {
12885					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12886				}
12887			}
12888			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12889			return;
12890		}
12891		// get default style
12892		$prop = array_merge($this->getFormDefaultProp(), $prop);
12893		// get annotation data
12894		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12895		// set additional default values
12896		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12897		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12898		$popt['da'] = $fontstyle;
12899		// build appearance stream
12900		$popt['ap'] = array();
12901		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12902		$text = '';
12903		foreach($values as $item) {
12904			if (is_array($item)) {
12905				$text .= $item[1]."\n";
12906			} else {
12907				$text .= $item."\n";
12908			}
12909		}
12910		$tmpid = $this->startTemplate($w, $h, false);
12911		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12912		$this->endTemplate();
12913		--$this->n;
12914		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12915		unset($this->xobjects[$tmpid]);
12916		$popt['ap']['n'] .= 'Q EMC';
12917		// merge options
12918		$opt = array_merge($popt, $opt);
12919		// set remaining annotation data
12920		$opt['Subtype'] = 'Widget';
12921		$opt['ft'] = 'Ch';
12922		$opt['t'] = $name;
12923		$opt['opt'] = $values;
12924		unset($opt['mk']['ca']);
12925		unset($opt['mk']['rc']);
12926		unset($opt['mk']['ac']);
12927		unset($opt['mk']['i']);
12928		unset($opt['mk']['ri']);
12929		unset($opt['mk']['ix']);
12930		unset($opt['mk']['if']);
12931		unset($opt['mk']['tp']);
12932		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12933		if ($this->rtl) {
12934			$this->x -= $w;
12935		} else {
12936			$this->x += $w;
12937		}
12938	}
12939
12940	/**
12941	 * Creates a Combo-box field
12942	 * @param $name (string) field name
12943	 * @param $w (int) width
12944	 * @param $h (int) height
12945	 * @param $values (array) array containing the list of values.
12946	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12947	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12948	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12949	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12950	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12951	 * @public
12952	 * @author Nicola Asuni
12953	 * @since 4.8.000 (2009-09-07)
12954	 */
12955	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12956		if ($x === '') {
12957			$x = $this->x;
12958		}
12959		if ($y === '') {
12960			$y = $this->y;
12961		}
12962		// check page for no-write regions and adapt page margins if necessary
12963		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12964		if ($js) {
12965			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
12966			$s = '';
12967			foreach ($values as $value) {
12968				if (is_array($value)) {
12969					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12970				} else {
12971					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12972				}
12973			}
12974			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12975			return;
12976		}
12977		// get default style
12978		$prop = array_merge($this->getFormDefaultProp(), $prop);
12979		$prop['Combo'] = true;
12980		// get annotation data
12981		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12982		// set additional default options
12983		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12984		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12985		$popt['da'] = $fontstyle;
12986		// build appearance stream
12987		$popt['ap'] = array();
12988		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12989		$text = '';
12990		foreach($values as $item) {
12991			if (is_array($item)) {
12992				$text .= $item[1]."\n";
12993			} else {
12994				$text .= $item."\n";
12995			}
12996		}
12997		$tmpid = $this->startTemplate($w, $h, false);
12998		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12999		$this->endTemplate();
13000		--$this->n;
13001		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13002		unset($this->xobjects[$tmpid]);
13003		$popt['ap']['n'] .= 'Q EMC';
13004		// merge options
13005		$opt = array_merge($popt, $opt);
13006		// set remaining annotation data
13007		$opt['Subtype'] = 'Widget';
13008		$opt['ft'] = 'Ch';
13009		$opt['t'] = $name;
13010		$opt['opt'] = $values;
13011		unset($opt['mk']['ca']);
13012		unset($opt['mk']['rc']);
13013		unset($opt['mk']['ac']);
13014		unset($opt['mk']['i']);
13015		unset($opt['mk']['ri']);
13016		unset($opt['mk']['ix']);
13017		unset($opt['mk']['if']);
13018		unset($opt['mk']['tp']);
13019		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13020		if ($this->rtl) {
13021			$this->x -= $w;
13022		} else {
13023			$this->x += $w;
13024		}
13025	}
13026
13027	/**
13028	 * Creates a CheckBox field
13029	 * @param $name (string) field name
13030	 * @param $w (int) width
13031	 * @param $checked (boolean) define the initial state.
13032	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13033	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13034	 * @param $onvalue (string) value to be returned if selected.
13035	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13036	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13037	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13038	 * @public
13039	 * @author Nicola Asuni
13040	 * @since 4.8.000 (2009-09-07)
13041	 */
13042	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13043		if ($x === '') {
13044			$x = $this->x;
13045		}
13046		if ($y === '') {
13047			$y = $this->y;
13048		}
13049		// check page for no-write regions and adapt page margins if necessary
13050		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13051		if ($js) {
13052			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13053			return;
13054		}
13055		if (!isset($prop['value'])) {
13056			$prop['value'] = array('Yes');
13057		}
13058		// get default style
13059		$prop = array_merge($this->getFormDefaultProp(), $prop);
13060		$prop['borderStyle'] = 'inset';
13061		// get annotation data
13062		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13063		// set additional default options
13064		$font = 'zapfdingbats';
13065		if ($this->pdfa_mode) {
13066			// all fonts must be embedded
13067			$font = 'pdfa'.$font;
13068		}
13069		$this->AddFont($font);
13070		$tmpfont = $this->getFontBuffer($font);
13071		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13072		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13073		$popt['da'] = $fontstyle;
13074		// build appearance stream
13075		$popt['ap'] = array();
13076		$popt['ap']['n'] = array();
13077		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13078		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13079		$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);
13080		$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);
13081		// merge options
13082		$opt = array_merge($popt, $opt);
13083		// set remaining annotation data
13084		$opt['Subtype'] = 'Widget';
13085		$opt['ft'] = 'Btn';
13086		$opt['t'] = $name;
13087		if (TCPDF_STATIC::empty_string($onvalue)) {
13088			$onvalue = 'Yes';
13089		}
13090		$opt['opt'] = array($onvalue);
13091		if ($checked) {
13092			$opt['v'] = array('/Yes');
13093			$opt['as'] = 'Yes';
13094		} else {
13095			$opt['v'] = array('/Off');
13096			$opt['as'] = 'Off';
13097		}
13098		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13099		if ($this->rtl) {
13100			$this->x -= $w;
13101		} else {
13102			$this->x += $w;
13103		}
13104	}
13105
13106	/**
13107	 * Creates a button field
13108	 * @param $name (string) field name
13109	 * @param $w (int) width
13110	 * @param $h (int) height
13111	 * @param $caption (string) caption.
13112	 * @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.
13113	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13114	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13115	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13116	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13117	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13118	 * @public
13119	 * @author Nicola Asuni
13120	 * @since 4.8.000 (2009-09-07)
13121	 */
13122	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13123		if ($x === '') {
13124			$x = $this->x;
13125		}
13126		if ($y === '') {
13127			$y = $this->y;
13128		}
13129		// check page for no-write regions and adapt page margins if necessary
13130		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13131		if ($js) {
13132			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13133			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13134			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13135			$this->javascript .= 'f'.$name.".highlight='push';\n";
13136			$this->javascript .= 'f'.$name.".print=false;\n";
13137			return;
13138		}
13139		// get default style
13140		$prop = array_merge($this->getFormDefaultProp(), $prop);
13141		$prop['Pushbutton'] = 'true';
13142		$prop['highlight'] = 'push';
13143		$prop['display'] = 'display.noPrint';
13144		// get annotation data
13145		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13146		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13147		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13148		$popt['da'] = $fontstyle;
13149		// build appearance stream
13150		$popt['ap'] = array();
13151		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13152		$tmpid = $this->startTemplate($w, $h, false);
13153		$bw = (2 / $this->k); // border width
13154		$border = array(
13155			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13156			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13157			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13158			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13159		$this->SetFillColor(204);
13160		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13161		$this->endTemplate();
13162		--$this->n;
13163		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13164		unset($this->xobjects[$tmpid]);
13165		$popt['ap']['n'] .= 'Q EMC';
13166		// set additional default options
13167		if (!isset($popt['mk'])) {
13168			$popt['mk'] = array();
13169		}
13170		$ann_obj_id = ($this->n + 1);
13171		if (!empty($action) AND !is_array($action)) {
13172			$ann_obj_id = ($this->n + 2);
13173		}
13174		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13175		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13176		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13177		// merge options
13178		$opt = array_merge($popt, $opt);
13179		// set remaining annotation data
13180		$opt['Subtype'] = 'Widget';
13181		$opt['ft'] = 'Btn';
13182		$opt['t'] = $caption;
13183		$opt['v'] = $name;
13184		if (!empty($action)) {
13185			if (is_array($action)) {
13186				// form action options as on section 12.7.5 of PDF32000_2008.
13187				$opt['aa'] = '/D <<';
13188				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13189				foreach ($action AS $key => $val) {
13190					if (($key == 'S') AND in_array($val, $bmode)) {
13191						$opt['aa'] .= ' /S /'.$val;
13192					} elseif (($key == 'F') AND (!empty($val))) {
13193						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13194					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13195						$opt['aa'] .= ' /Fields [';
13196						foreach ($val AS $field) {
13197							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13198						}
13199						$opt['aa'] .= ']';
13200					} elseif (($key == 'Flags')) {
13201						$ff = 0;
13202						if (is_array($val)) {
13203							foreach ($val AS $flag) {
13204								switch ($flag) {
13205									case 'Include/Exclude': {
13206										$ff += 1 << 0;
13207										break;
13208									}
13209									case 'IncludeNoValueFields': {
13210										$ff += 1 << 1;
13211										break;
13212									}
13213									case 'ExportFormat': {
13214										$ff += 1 << 2;
13215										break;
13216									}
13217									case 'GetMethod': {
13218										$ff += 1 << 3;
13219										break;
13220									}
13221									case 'SubmitCoordinates': {
13222										$ff += 1 << 4;
13223										break;
13224									}
13225									case 'XFDF': {
13226										$ff += 1 << 5;
13227										break;
13228									}
13229									case 'IncludeAppendSaves': {
13230										$ff += 1 << 6;
13231										break;
13232									}
13233									case 'IncludeAnnotations': {
13234										$ff += 1 << 7;
13235										break;
13236									}
13237									case 'SubmitPDF': {
13238										$ff += 1 << 8;
13239										break;
13240									}
13241									case 'CanonicalFormat': {
13242										$ff += 1 << 9;
13243										break;
13244									}
13245									case 'ExclNonUserAnnots': {
13246										$ff += 1 << 10;
13247										break;
13248									}
13249									case 'ExclFKey': {
13250										$ff += 1 << 11;
13251										break;
13252									}
13253									case 'EmbedForm': {
13254										$ff += 1 << 13;
13255										break;
13256									}
13257								}
13258							}
13259						} else {
13260							$ff = intval($val);
13261						}
13262						$opt['aa'] .= ' /Flags '.$ff;
13263					}
13264				}
13265				$opt['aa'] .= ' >>';
13266			} else {
13267				// Javascript action or raw action command
13268				$js_obj_id = $this->addJavascriptObject($action);
13269				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13270			}
13271		}
13272		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13273		if ($this->rtl) {
13274			$this->x -= $w;
13275		} else {
13276			$this->x += $w;
13277		}
13278	}
13279
13280	// --- END FORMS FIELDS ------------------------------------------------
13281
13282	/**
13283	 * Add certification signature (DocMDP or UR3)
13284	 * You can set only one signature type
13285	 * @protected
13286	 * @author Nicola Asuni
13287	 * @since 4.6.008 (2009-05-07)
13288	 */
13289	protected function _putsignature() {
13290		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13291			return;
13292		}
13293		$sigobjid = ($this->sig_obj_id + 1);
13294		$out = $this->_getobj($sigobjid)."\n";
13295		$out .= '<< /Type /Sig';
13296		$out .= ' /Filter /Adobe.PPKLite';
13297		$out .= ' /SubFilter /adbe.pkcs7.detached';
13298		$out .= ' '.TCPDF_STATIC::$byterange_string;
13299		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13300		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13301			$out .= ' /Reference ['; // array of signature reference dictionaries
13302			$out .= ' << /Type /SigRef';
13303			if ($this->signature_data['cert_type'] > 0) {
13304				$out .= ' /TransformMethod /DocMDP';
13305				$out .= ' /TransformParams <<';
13306				$out .= ' /Type /TransformParams';
13307				$out .= ' /P '.$this->signature_data['cert_type'];
13308				$out .= ' /V /1.2';
13309			} else {
13310				$out .= ' /TransformMethod /UR3';
13311				$out .= ' /TransformParams <<';
13312				$out .= ' /Type /TransformParams';
13313				$out .= ' /V /2.2';
13314				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13315					$out .= ' /Document['.$this->ur['document'].']';
13316				}
13317				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13318					$out .= ' /Form['.$this->ur['form'].']';
13319				}
13320				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13321					$out .= ' /Signature['.$this->ur['signature'].']';
13322				}
13323				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13324					$out .= ' /Annots['.$this->ur['annots'].']';
13325				}
13326				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13327					$out .= ' /EF['.$this->ur['ef'].']';
13328				}
13329				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13330					$out .= ' /FormEX['.$this->ur['formex'].']';
13331				}
13332			}
13333			$out .= ' >>'; // close TransformParams
13334			// optional digest data (values must be calculated and replaced later)
13335			//$out .= ' /Data ********** 0 R';
13336			//$out .= ' /DigestMethod/MD5';
13337			//$out .= ' /DigestLocation[********** 34]';
13338			//$out .= ' /DigestValue<********************************>';
13339			$out .= ' >>';
13340			$out .= ' ]'; // end of reference
13341		}
13342		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13343			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13344		}
13345		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13346			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13347		}
13348		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13349			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13350		}
13351		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13352			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13353		}
13354		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13355		$out .= ' >>';
13356		$out .= "\n".'endobj';
13357		$this->_out($out);
13358	}
13359
13360	/**
13361	 * Set User's Rights for PDF Reader
13362	 * WARNING: This is experimental and currently do not work.
13363	 * Check the PDF Reference 8.7.1 Transform Methods,
13364	 * Table 8.105 Entries in the UR transform parameters dictionary
13365	 * @param $enable (boolean) if true enable user's rights on PDF reader
13366	 * @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.
13367	 * @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.
13368	 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13369	 * @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.
13370	 * @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
13371	 Names specifying additional embedded-files-related usage rights for the document.
13372	 * @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.
13373	 * @public
13374	 * @author Nicola Asuni
13375	 * @since 2.9.000 (2008-03-26)
13376	 */
13377	public function setUserRights(
13378			$enable=true,
13379			$document='/FullSave',
13380			$annots='/Create/Delete/Modify/Copy/Import/Export',
13381			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13382			$signature='/Modify',
13383			$ef='/Create/Delete/Modify/Import',
13384			$formex='') {
13385		$this->ur['enabled'] = $enable;
13386		$this->ur['document'] = $document;
13387		$this->ur['annots'] = $annots;
13388		$this->ur['form'] = $form;
13389		$this->ur['signature'] = $signature;
13390		$this->ur['ef'] = $ef;
13391		$this->ur['formex'] = $formex;
13392		if (!$this->sign) {
13393			$this->setSignature('', '', '', '', 0, array());
13394		}
13395	}
13396
13397	/**
13398	 * Enable document signature (requires the OpenSSL Library).
13399	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13400	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13401	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13402	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13403	 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13404	 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13405	 * @param $private_key_password (string) password
13406	 * @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.
13407	 * @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.
13408	 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13409	 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13410	 * @public
13411	 * @author Nicola Asuni
13412	 * @since 4.6.005 (2009-04-24)
13413	 */
13414	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13415		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13416		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13417		// to convert pfx certificate to pem: openssl
13418		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13419		$this->sign = true;
13420		++$this->n;
13421		$this->sig_obj_id = $this->n; // signature widget
13422		++$this->n; // signature object ($this->sig_obj_id + 1)
13423		$this->signature_data = array();
13424		if (strlen($signing_cert) == 0) {
13425			$this->Error('Please provide a certificate file and password!');
13426		}
13427		if (strlen($private_key) == 0) {
13428			$private_key = $signing_cert;
13429		}
13430		$this->signature_data['signcert'] = $signing_cert;
13431		$this->signature_data['privkey'] = $private_key;
13432		$this->signature_data['password'] = $private_key_password;
13433		$this->signature_data['extracerts'] = $extracerts;
13434		$this->signature_data['cert_type'] = $cert_type;
13435		$this->signature_data['info'] = $info;
13436		$this->signature_data['approval'] = $approval;
13437	}
13438
13439	/**
13440	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13441	 * @param $x (float) Abscissa of the upper-left corner.
13442	 * @param $y (float) Ordinate of the upper-left corner.
13443	 * @param $w (float) Width of the signature area.
13444	 * @param $h (float) Height of the signature area.
13445	 * @param $page (int) option page number (if < 0 the current page is used).
13446	 * @param $name (string) Name of the signature.
13447	 * @public
13448	 * @author Nicola Asuni
13449	 * @since 5.3.011 (2010-06-17)
13450	 */
13451	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13452		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13453	}
13454
13455	/**
13456	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13457	 * @param $x (float) Abscissa of the upper-left corner.
13458	 * @param $y (float) Ordinate of the upper-left corner.
13459	 * @param $w (float) Width of the signature area.
13460	 * @param $h (float) Height of the signature area.
13461	 * @param $page (int) option page number (if < 0 the current page is used).
13462	 * @param $name (string) Name of the signature.
13463	 * @public
13464	 * @author Nicola Asuni
13465	 * @since 5.9.101 (2011-07-06)
13466	 */
13467	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13468		++$this->n;
13469		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13470	}
13471
13472	/**
13473	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13474	 * @param $x (float) Abscissa of the upper-left corner.
13475	 * @param $y (float) Ordinate of the upper-left corner.
13476	 * @param $w (float) Width of the signature area.
13477	 * @param $h (float) Height of the signature area.
13478	 * @param $page (int) option page number (if < 0 the current page is used).
13479	 * @param $name (string) Name of the signature.
13480	 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13481	 * @protected
13482	 * @author Nicola Asuni
13483	 * @since 5.9.101 (2011-07-06)
13484	 */
13485	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13486		$sigapp = array();
13487		if (($page < 1) OR ($page > $this->numpages)) {
13488			$sigapp['page'] = $this->page;
13489		} else {
13490			$sigapp['page'] = intval($page);
13491		}
13492		if (empty($name)) {
13493			$sigapp['name'] = 'Signature';
13494		} else {
13495			$sigapp['name'] = $name;
13496		}
13497		$a = $x * $this->k;
13498		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13499		$c = $w * $this->k;
13500		$d = $h * $this->k;
13501		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13502		return $sigapp;
13503	}
13504
13505	/**
13506	 * Enable document timestamping (requires the OpenSSL Library).
13507	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13508	 * Use with digital signature only!
13509	 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13510	 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13511	 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13512	 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13513	 * @public
13514	 * @author Richard Stockinger
13515	 * @since 6.0.090 (2014-06-16)
13516	 */
13517	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13518		$this->tsa_data = array();
13519		if (!function_exists('curl_init')) {
13520			$this->Error('Please enable cURL PHP extension!');
13521		}
13522		if (strlen($tsa_host) == 0) {
13523			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13524		}
13525		$this->tsa_data['tsa_host'] = $tsa_host;
13526		if (is_file($tsa_username)) {
13527			$this->tsa_data['tsa_auth'] = $tsa_username;
13528		} else {
13529			$this->tsa_data['tsa_username'] = $tsa_username;
13530		}
13531		$this->tsa_data['tsa_password'] = $tsa_password;
13532		$this->tsa_data['tsa_cert'] = $tsa_cert;
13533		$this->tsa_timestamp = true;
13534	}
13535
13536	/**
13537	 * NOT YET IMPLEMENTED
13538	 * Request TSA for a timestamp
13539	 * @param $signature (string) Digital signature as binary string
13540	 * @return (string) Timestamped digital signature
13541	 * @protected
13542	 * @author Richard Stockinger
13543	 * @since 6.0.090 (2014-06-16)
13544	 */
13545	protected function applyTSA($signature) {
13546		if (!$this->tsa_timestamp) {
13547			return $signature;
13548		}
13549		//@TODO: implement this feature
13550		return $signature;
13551	}
13552
13553	/**
13554	 * Create a new page group.
13555	 * NOTE: call this function before calling AddPage()
13556	 * @param $page (int) starting group page (leave empty for next page).
13557	 * @public
13558	 * @since 3.0.000 (2008-03-27)
13559	 */
13560	public function startPageGroup($page='') {
13561		if (empty($page)) {
13562			$page = $this->page + 1;
13563		}
13564		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13565	}
13566
13567	/**
13568	 * Set the starting page number.
13569	 * @param $num (int) Starting page number.
13570	 * @since 5.9.093 (2011-06-16)
13571	 * @public
13572	 */
13573	public function setStartingPageNumber($num=1) {
13574		$this->starting_page_number = max(0, intval($num));
13575	}
13576
13577	/**
13578	 * Returns the string alias used right align page numbers.
13579	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13580	 * @return string
13581	 * @since 5.9.099 (2011-06-27)
13582	 * @public
13583	 */
13584	public function getAliasRightShift() {
13585		// calculate aproximatively the ratio between widths of aliases and replacements.
13586		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13587		$rep = str_repeat(' ', $this->GetNumChars($ref));
13588		$wrep = $this->GetStringWidth($rep);
13589		if ($wrep > 0) {
13590			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13591		} else {
13592			$wdiff = 1;
13593		}
13594		$sdiff = sprintf('%F', $wdiff);
13595		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13596		if ($this->isUnicodeFont()) {
13597			$alias = '{'.$alias;
13598		}
13599		return $alias;
13600	}
13601
13602	/**
13603	 * Returns the string alias used for the total number of pages.
13604	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13605	 * This alias will be replaced by the total number of pages in the document.
13606	 * @return string
13607	 * @since 4.0.018 (2008-08-08)
13608	 * @public
13609	 */
13610	public function getAliasNbPages() {
13611		if ($this->isUnicodeFont()) {
13612			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13613		}
13614		return TCPDF_STATIC::$alias_tot_pages;
13615	}
13616
13617	/**
13618	 * Returns the string alias used for the page number.
13619	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13620	 * This alias will be replaced by the page number.
13621	 * @return string
13622	 * @since 4.5.000 (2009-01-02)
13623	 * @public
13624	 */
13625	public function getAliasNumPage() {
13626		if ($this->isUnicodeFont()) {
13627			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13628		}
13629		return TCPDF_STATIC::$alias_num_page;
13630	}
13631
13632	/**
13633	 * Return the alias for the total number of pages in the current page group.
13634	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13635	 * This alias will be replaced by the total number of pages in this group.
13636	 * @return alias of the current page group
13637	 * @public
13638	 * @since 3.0.000 (2008-03-27)
13639	 */
13640	public function getPageGroupAlias() {
13641		if ($this->isUnicodeFont()) {
13642			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13643		}
13644		return TCPDF_STATIC::$alias_group_tot_pages;
13645	}
13646
13647	/**
13648	 * Return the alias for the page number on the current page group.
13649	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13650	 * This alias will be replaced by the page number (relative to the belonging group).
13651	 * @return alias of the current page group
13652	 * @public
13653	 * @since 4.5.000 (2009-01-02)
13654	 */
13655	public function getPageNumGroupAlias() {
13656		if ($this->isUnicodeFont()) {
13657			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13658		}
13659		return TCPDF_STATIC::$alias_group_num_page;
13660	}
13661
13662	/**
13663	 * Return the current page in the group.
13664	 * @return current page in the group
13665	 * @public
13666	 * @since 3.0.000 (2008-03-27)
13667	 */
13668	public function getGroupPageNo() {
13669		return $this->pagegroups[$this->currpagegroup];
13670	}
13671
13672	/**
13673	 * Returns the current group page number formatted as a string.
13674	 * @public
13675	 * @since 4.3.003 (2008-11-18)
13676	 * @see PaneNo(), formatPageNumber()
13677	 */
13678	public function getGroupPageNoFormatted() {
13679		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13680	}
13681
13682	/**
13683	 * Returns the current page number formatted as a string.
13684	 * @public
13685	 * @since 4.2.005 (2008-11-06)
13686	 * @see PaneNo(), formatPageNumber()
13687	 */
13688	public function PageNoFormatted() {
13689		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13690	}
13691
13692	/**
13693	 * Put pdf layers.
13694	 * @protected
13695	 * @since 3.0.000 (2008-03-27)
13696	 */
13697	protected function _putocg() {
13698		if (empty($this->pdflayers)) {
13699			return;
13700		}
13701		foreach ($this->pdflayers as $key => $layer) {
13702			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13703			 $out = '<< /Type /OCG';
13704			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13705			 $out .= ' /Usage <<';
13706			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13707				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13708			 }
13709			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13710			 $out .= ' >> >>';
13711			 $out .= "\n".'endobj';
13712			 $this->_out($out);
13713		}
13714	}
13715
13716	/**
13717	 * Start a new pdf layer.
13718	 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13719	 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13720	 * @param $view (boolean) Set to true to view this layer.
13721	 * @param $lock (boolean) If true lock the layer
13722	 * @public
13723	 * @since 5.9.102 (2011-07-13)
13724	 */
13725	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13726		if ($this->state != 2) {
13727			return;
13728		}
13729		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13730		if (empty($name)) {
13731			$name = $layer;
13732		} else {
13733			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13734		}
13735		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13736		$this->openMarkedContent = true;
13737		$this->_out('/OC /'.$layer.' BDC');
13738	}
13739
13740	/**
13741	 * End the current PDF layer.
13742	 * @public
13743	 * @since 5.9.102 (2011-07-13)
13744	 */
13745	public function endLayer() {
13746		if ($this->state != 2) {
13747			return;
13748		}
13749		if ($this->openMarkedContent) {
13750			// close existing open marked-content layer
13751			$this->_out('EMC');
13752			$this->openMarkedContent = false;
13753		}
13754	}
13755
13756	/**
13757	 * Set the visibility of the successive elements.
13758	 * This can be useful, for instance, to put a background
13759	 * image or color that will show on screen but won't print.
13760	 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13761	 * @public
13762	 * @since 3.0.000 (2008-03-27)
13763	 */
13764	public function setVisibility($v) {
13765		if ($this->state != 2) {
13766			return;
13767		}
13768		$this->endLayer();
13769		switch($v) {
13770			case 'print': {
13771				$this->startLayer('Print', true, false);
13772				break;
13773			}
13774			case 'view':
13775			case 'screen': {
13776				$this->startLayer('View', false, true);
13777				break;
13778			}
13779			case 'all': {
13780				$this->_out('');
13781				break;
13782			}
13783			default: {
13784				$this->Error('Incorrect visibility: '.$v);
13785				break;
13786			}
13787		}
13788	}
13789
13790	/**
13791	 * Add transparency parameters to the current extgstate
13792	 * @param $parms (array) parameters
13793	 * @return the number of extgstates
13794	 * @protected
13795	 * @since 3.0.000 (2008-03-27)
13796	 */
13797	protected function addExtGState($parms) {
13798		if ($this->pdfa_mode) {
13799			// transparencies are not allowed in PDF/A mode
13800			return;
13801		}
13802		// check if this ExtGState already exist
13803		foreach ($this->extgstates as $i => $ext) {
13804			if ($ext['parms'] == $parms) {
13805				if ($this->inxobj) {
13806					// we are inside an XObject template
13807					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13808				}
13809				// return reference to existing ExtGState
13810				return $i;
13811			}
13812		}
13813		$n = (count($this->extgstates) + 1);
13814		$this->extgstates[$n] = array('parms' => $parms);
13815		if ($this->inxobj) {
13816			// we are inside an XObject template
13817			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13818		}
13819		return $n;
13820	}
13821
13822	/**
13823	 * Add an extgstate
13824	 * @param $gs (array) extgstate
13825	 * @protected
13826	 * @since 3.0.000 (2008-03-27)
13827	 */
13828	protected function setExtGState($gs) {
13829		if ($this->pdfa_mode OR ($this->state != 2)) {
13830			// transparency is not allowed in PDF/A mode
13831			return;
13832		}
13833		$this->_out(sprintf('/GS%d gs', $gs));
13834	}
13835
13836	/**
13837	 * Put extgstates for object transparency
13838	 * @protected
13839	 * @since 3.0.000 (2008-03-27)
13840	 */
13841	protected function _putextgstates() {
13842		foreach ($this->extgstates as $i => $ext) {
13843			$this->extgstates[$i]['n'] = $this->_newobj();
13844			$out = '<< /Type /ExtGState';
13845			foreach ($ext['parms'] as $k => $v) {
13846				if (is_float($v)) {
13847					$v = sprintf('%F', $v);
13848				} elseif ($v === true) {
13849					$v = 'true';
13850				} elseif ($v === false) {
13851					$v = 'false';
13852				}
13853				$out .= ' /'.$k.' '.$v;
13854			}
13855			$out .= ' >>';
13856			$out .= "\n".'endobj';
13857			$this->_out($out);
13858		}
13859	}
13860
13861	/**
13862	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13863	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13864	 * @param $stroking (boolean) If true apply overprint for stroking operations.
13865	 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13866	 * @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).
13867	 * @public
13868	 * @since 5.9.152 (2012-03-23)
13869	 */
13870	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13871		if ($this->state != 2) {
13872			return;
13873		}
13874		$stroking = $stroking ? true : false;
13875		if (TCPDF_STATIC::empty_string($nonstroking)) {
13876			// default value if not set
13877			$nonstroking = $stroking;
13878		} else {
13879			$nonstroking = $nonstroking ? true : false;
13880		}
13881		if (($mode != 0) AND ($mode != 1)) {
13882			$mode = 0;
13883		}
13884		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13885		$gs = $this->addExtGState($this->overprint);
13886		$this->setExtGState($gs);
13887	}
13888
13889	/**
13890	 * Get the overprint mode array (OP, op, OPM).
13891	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13892	 * @return array.
13893	 * @public
13894	 * @since 5.9.152 (2012-03-23)
13895	 */
13896	public function getOverprint() {
13897		return $this->overprint;
13898	}
13899
13900	/**
13901	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13902	 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13903	 * @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
13904	 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13905	 * @param $ais (boolean)
13906	 * @public
13907	 * @since 3.0.000 (2008-03-27)
13908	 */
13909	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13910		if ($this->pdfa_mode) {
13911			// transparency is not allowed in PDF/A mode
13912			return;
13913		}
13914		$stroking = floatval($stroking);
13915		if (TCPDF_STATIC::empty_string($nonstroking)) {
13916			// default value if not set
13917			$nonstroking = $stroking;
13918		} else {
13919			$nonstroking = floatval($nonstroking);
13920		}
13921		if ($bm[0] == '/') {
13922			// remove trailing slash
13923			$bm = substr($bm, 1);
13924		}
13925		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13926			$bm = 'Normal';
13927		}
13928		$ais = $ais ? true : false;
13929		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13930		$gs = $this->addExtGState($this->alpha);
13931		$this->setExtGState($gs);
13932	}
13933
13934	/**
13935	 * Get the alpha mode array (CA, ca, BM, AIS).
13936	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13937	 * @return array.
13938	 * @public
13939	 * @since 5.9.152 (2012-03-23)
13940	 */
13941	public function getAlpha() {
13942		return $this->alpha;
13943	}
13944
13945	/**
13946	 * Set the default JPEG compression quality (1-100)
13947	 * @param $quality (int) JPEG quality, integer between 1 and 100
13948	 * @public
13949	 * @since 3.0.000 (2008-03-27)
13950	 */
13951	public function setJPEGQuality($quality) {
13952		if (($quality < 1) OR ($quality > 100)) {
13953			$quality = 75;
13954		}
13955		$this->jpeg_quality = intval($quality);
13956	}
13957
13958	/**
13959	 * Set the default number of columns in a row for HTML tables.
13960	 * @param $cols (int) number of columns
13961	 * @public
13962	 * @since 3.0.014 (2008-06-04)
13963	 */
13964	public function setDefaultTableColumns($cols=4) {
13965		$this->default_table_columns = intval($cols);
13966	}
13967
13968	/**
13969	 * Set the height of the cell (line height) respect the font height.
13970	 * @param $h (int) cell proportion respect font height (typical value = 1.25).
13971	 * @public
13972	 * @since 3.0.014 (2008-06-04)
13973	 */
13974	public function setCellHeightRatio($h) {
13975		$this->cell_height_ratio = $h;
13976	}
13977
13978	/**
13979	 * return the height of cell repect font height.
13980	 * @public
13981	 * @since 4.0.012 (2008-07-24)
13982	 */
13983	public function getCellHeightRatio() {
13984		return $this->cell_height_ratio;
13985	}
13986
13987	/**
13988	 * Set the PDF version (check PDF reference for valid values).
13989	 * @param $version (string) PDF document version.
13990	 * @public
13991	 * @since 3.1.000 (2008-06-09)
13992	 */
13993	public function setPDFVersion($version='1.7') {
13994		if ($this->pdfa_mode) {
13995			// PDF/A mode
13996			$this->PDFVersion = '1.4';
13997		} else {
13998			$this->PDFVersion = $version;
13999		}
14000	}
14001
14002	/**
14003	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14004	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14005	 * <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>
14006	 * @param $preferences (array) array of options.
14007	 * @author Nicola Asuni
14008	 * @public
14009	 * @since 3.1.000 (2008-06-09)
14010	 */
14011	public function setViewerPreferences($preferences) {
14012		$this->viewer_preferences = $preferences;
14013	}
14014
14015	/**
14016	 * Paints color transition registration bars
14017	 * @param $x (float) abscissa of the top left corner of the rectangle.
14018	 * @param $y (float) ordinate of the top left corner of the rectangle.
14019	 * @param $w (float) width of the rectangle.
14020	 * @param $h (float) height of the rectangle.
14021	 * @param $transition (boolean) if true prints tcolor transitions to white.
14022	 * @param $vertical (boolean) if true prints bar vertically.
14023	 * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14024	 * @author Nicola Asuni
14025	 * @since 4.9.000 (2010-03-26)
14026	 * @public
14027	 */
14028	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14029		if (strpos($colors, 'ALLSPOT') !== false) {
14030			// expand spot colors
14031			$spot_colors = '';
14032			foreach ($this->spot_colors as $spot_color_name => $v) {
14033				$spot_colors .= ','.$spot_color_name;
14034			}
14035			if (!empty($spot_colors)) {
14036				$spot_colors = substr($spot_colors, 1);
14037				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14038			} else {
14039				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14040			}
14041		}
14042		$bars = explode(',', $colors);
14043		$numbars = count($bars); // number of bars to print
14044		if ($numbars <= 0) {
14045			return;
14046		}
14047		// set bar measures
14048		if ($vertical) {
14049			$coords = array(0, 0, 0, 1);
14050			$wb = $w / $numbars; // bar width
14051			$hb = $h; // bar height
14052			$xd = $wb; // delta x
14053			$yd = 0; // delta y
14054		} else {
14055			$coords = array(1, 0, 0, 0);
14056			$wb = $w; // bar width
14057			$hb = $h / $numbars; // bar height
14058			$xd = 0; // delta x
14059			$yd = $hb; // delta y
14060		}
14061		$xb = $x;
14062		$yb = $y;
14063		foreach ($bars as $col) {
14064			switch ($col) {
14065				// set transition colors
14066				case 'A': { // BLACK (GRAYSCALE)
14067					$col_a = array(255);
14068					$col_b = array(0);
14069					break;
14070				}
14071				case 'W': { // WHITE (GRAYSCALE)
14072					$col_a = array(0);
14073					$col_b = array(255);
14074					break;
14075				}
14076				case 'R': { // RED (RGB)
14077					$col_a = array(255,255,255);
14078					$col_b = array(255,0,0);
14079					break;
14080				}
14081				case 'G': { // GREEN (RGB)
14082					$col_a = array(255,255,255);
14083					$col_b = array(0,255,0);
14084					break;
14085				}
14086				case 'B': { // BLUE (RGB)
14087					$col_a = array(255,255,255);
14088					$col_b = array(0,0,255);
14089					break;
14090				}
14091				case 'C': { // CYAN (CMYK)
14092					$col_a = array(0,0,0,0);
14093					$col_b = array(100,0,0,0);
14094					break;
14095				}
14096				case 'M': { // MAGENTA (CMYK)
14097					$col_a = array(0,0,0,0);
14098					$col_b = array(0,100,0,0);
14099					break;
14100				}
14101				case 'Y': { // YELLOW (CMYK)
14102					$col_a = array(0,0,0,0);
14103					$col_b = array(0,0,100,0);
14104					break;
14105				}
14106				case 'K': { // KEY - BLACK (CMYK)
14107					$col_a = array(0,0,0,0);
14108					$col_b = array(0,0,0,100);
14109					break;
14110				}
14111				case 'RGB': { // BLACK REGISTRATION (RGB)
14112					$col_a = array(255,255,255);
14113					$col_b = array(0,0,0);
14114					break;
14115				}
14116				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14117					$col_a = array(0,0,0,0);
14118					$col_b = array(100,100,100,100);
14119					break;
14120				}
14121				case 'ALL': { // SPOT COLOR REGISTRATION
14122					$col_a = array(0,0,0,0,'None');
14123					$col_b = array(100,100,100,100,'All');
14124					break;
14125				}
14126				case 'NONE': { // SKIP THIS COLOR
14127					$col_a = array(0,0,0,0,'None');
14128					$col_b = array(0,0,0,0,'None');
14129					break;
14130				}
14131				default: { // SPECIFIC SPOT COLOR NAME
14132					$col_a = array(0,0,0,0,'None');
14133					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14134					if ($col_b === false) {
14135						// in case of error defaults to the registration color
14136						$col_b = array(100,100,100,100,'All');
14137					}
14138					break;
14139				}
14140			}
14141			if ($col != 'NONE') {
14142				if ($transition) {
14143					// color gradient
14144					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14145				} else {
14146					$this->SetFillColorArray($col_b);
14147					// colored rectangle
14148					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14149				}
14150				$xb += $xd;
14151				$yb += $yd;
14152			}
14153		}
14154	}
14155
14156	/**
14157	 * Paints crop marks.
14158	 * @param $x (float) abscissa of the crop mark center.
14159	 * @param $y (float) ordinate of the crop mark center.
14160	 * @param $w (float) width of the crop mark.
14161	 * @param $h (float) height of the crop mark.
14162	 * @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.
14163	 * @param $color (array) crop mark color (default spot registration color).
14164	 * @author Nicola Asuni
14165	 * @since 4.9.000 (2010-03-26)
14166	 * @public
14167	 */
14168	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14169		$this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14170		$type = strtoupper($type);
14171		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14172		// split type in single components
14173		$type = str_replace('-', ',', $type);
14174		$type = str_replace('TL', 'T,L', $type);
14175		$type = str_replace('TR', 'T,R', $type);
14176		$type = str_replace('BL', 'F,L', $type);
14177		$type = str_replace('BR', 'F,R', $type);
14178		$type = str_replace('A', 'T,L', $type);
14179		$type = str_replace('B', 'T,R', $type);
14180		$type = str_replace('T,RO', 'BO', $type);
14181		$type = str_replace('C', 'F,L', $type);
14182		$type = str_replace('D', 'F,R', $type);
14183		$crops = explode(',', strtoupper($type));
14184		// remove duplicates
14185		$crops = array_unique($crops);
14186		$dw = ($w / 4); // horizontal space to leave before the intersection point
14187		$dh = ($h / 4); // vertical space to leave before the intersection point
14188		foreach ($crops as $crop) {
14189			switch ($crop) {
14190				case 'T':
14191				case 'TOP': {
14192					$x1 = $x;
14193					$y1 = ($y - $h);
14194					$x2 = $x;
14195					$y2 = ($y - $dh);
14196					break;
14197				}
14198				case 'F':
14199				case 'BOTTOM': {
14200					$x1 = $x;
14201					$y1 = ($y + $dh);
14202					$x2 = $x;
14203					$y2 = ($y + $h);
14204					break;
14205				}
14206				case 'L':
14207				case 'LEFT': {
14208					$x1 = ($x - $w);
14209					$y1 = $y;
14210					$x2 = ($x - $dw);
14211					$y2 = $y;
14212					break;
14213				}
14214				case 'R':
14215				case 'RIGHT': {
14216					$x1 = ($x + $dw);
14217					$y1 = $y;
14218					$x2 = ($x + $w);
14219					$y2 = $y;
14220					break;
14221				}
14222			}
14223			$this->Line($x1, $y1, $x2, $y2);
14224		}
14225	}
14226
14227	/**
14228	 * Paints a registration mark
14229	 * @param $x (float) abscissa of the registration mark center.
14230	 * @param $y (float) ordinate of the registration mark center.
14231	 * @param $r (float) radius of the crop mark.
14232	 * @param $double (boolean) if true print two concentric crop marks.
14233	 * @param $cola (array) crop mark color (default spot registration color 'All').
14234	 * @param $colb (array) second crop mark color (default spot registration color 'None').
14235	 * @author Nicola Asuni
14236	 * @since 4.9.000 (2010-03-26)
14237	 * @public
14238	 */
14239	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14240		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14241		$this->SetFillColorArray($cola);
14242		$this->PieSector($x, $y, $r, 90, 180, 'F');
14243		$this->PieSector($x, $y, $r, 270, 360, 'F');
14244		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14245		if ($double) {
14246			$ri = $r * 0.5;
14247			$this->SetFillColorArray($colb);
14248			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14249			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14250			$this->SetFillColorArray($cola);
14251			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14252			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14253			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14254		}
14255	}
14256
14257	/**
14258	 * Paints a CMYK registration mark
14259	 * @param $x (float) abscissa of the registration mark center.
14260	 * @param $y (float) ordinate of the registration mark center.
14261	 * @param $r (float) radius of the crop mark.
14262	 * @author Nicola Asuni
14263	 * @since 6.0.038 (2013-09-30)
14264	 * @public
14265	 */
14266	public function registrationMarkCMYK($x, $y, $r) {
14267		// line width
14268		$lw = max((0.5 / $this->k),($r / 8));
14269		// internal radius
14270		$ri = ($r * 0.6);
14271		// external radius
14272		$re = ($r * 1.3);
14273		// Cyan
14274		$this->SetFillColorArray(array(100,0,0,0));
14275		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14276		// Magenta
14277		$this->SetFillColorArray(array(0,100,0,0));
14278		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14279		// Yellow
14280		$this->SetFillColorArray(array(0,0,100,0));
14281		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14282		// Key - black
14283		$this->SetFillColorArray(array(0,0,0,100));
14284		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14285		// registration color
14286		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14287		$this->SetFillColorArray(array(100,100,100,100,'All'));
14288		// external circle
14289		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14290		// cross lines
14291		$this->Line($x, ($y - $re), $x, ($y - $ri));
14292		$this->Line($x, ($y + $ri), $x, ($y + $re));
14293		$this->Line(($x - $re), $y, ($x - $ri), $y);
14294		$this->Line(($x + $ri), $y, ($x + $re), $y);
14295	}
14296
14297	/**
14298	 * Paints a linear colour gradient.
14299	 * @param $x (float) abscissa of the top left corner of the rectangle.
14300	 * @param $y (float) ordinate of the top left corner of the rectangle.
14301	 * @param $w (float) width of the rectangle.
14302	 * @param $h (float) height of the rectangle.
14303	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14304	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14305	 * @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).
14306	 * @author Andreas W\FCrmser, Nicola Asuni
14307	 * @since 3.1.000 (2008-06-09)
14308	 * @public
14309	 */
14310	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14311		$this->Clip($x, $y, $w, $h);
14312		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14313	}
14314
14315	/**
14316	 * Paints a radial colour gradient.
14317	 * @param $x (float) abscissa of the top left corner of the rectangle.
14318	 * @param $y (float) ordinate of the top left corner of the rectangle.
14319	 * @param $w (float) width of the rectangle.
14320	 * @param $h (float) height of the rectangle.
14321	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14322	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14323	 * @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.
14324	 * @author Andreas W\FCrmser, Nicola Asuni
14325	 * @since 3.1.000 (2008-06-09)
14326	 * @public
14327	 */
14328	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14329		$this->Clip($x, $y, $w, $h);
14330		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14331	}
14332
14333	/**
14334	 * Paints a coons patch mesh.
14335	 * @param $x (float) abscissa of the top left corner of the rectangle.
14336	 * @param $y (float) ordinate of the top left corner of the rectangle.
14337	 * @param $w (float) width of the rectangle.
14338	 * @param $h (float) height of the rectangle.
14339	 * @param $col1 (array) first color (lower left corner) (RGB components).
14340	 * @param $col2 (array) second color (lower right corner) (RGB components).
14341	 * @param $col3 (array) third color (upper right corner) (RGB components).
14342	 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14343	 * @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>
14344	 * @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
14345	 * @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
14346	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14347	 * @author Andreas W\FCrmser, Nicola Asuni
14348	 * @since 3.1.000 (2008-06-09)
14349	 * @public
14350	 */
14351	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) {
14352		if ($this->pdfa_mode OR ($this->state != 2)) {
14353			return;
14354		}
14355		$this->Clip($x, $y, $w, $h);
14356		$n = count($this->gradients) + 1;
14357		$this->gradients[$n] = array();
14358		$this->gradients[$n]['type'] = 6; //coons patch mesh
14359		$this->gradients[$n]['coords'] = array();
14360		$this->gradients[$n]['antialias'] = $antialias;
14361		$this->gradients[$n]['colors'] = array();
14362		$this->gradients[$n]['transparency'] = false;
14363		//check the coords array if it is the simple array or the multi patch array
14364		if (!isset($coords[0]['f'])) {
14365			//simple array -> convert to multi patch array
14366			if (!isset($col1[1])) {
14367				$col1[1] = $col1[2] = $col1[0];
14368			}
14369			if (!isset($col2[1])) {
14370				$col2[1] = $col2[2] = $col2[0];
14371			}
14372			if (!isset($col3[1])) {
14373				$col3[1] = $col3[2] = $col3[0];
14374			}
14375			if (!isset($col4[1])) {
14376				$col4[1] = $col4[2] = $col4[0];
14377			}
14378			$patch_array[0]['f'] = 0;
14379			$patch_array[0]['points'] = $coords;
14380			$patch_array[0]['colors'][0]['r'] = $col1[0];
14381			$patch_array[0]['colors'][0]['g'] = $col1[1];
14382			$patch_array[0]['colors'][0]['b'] = $col1[2];
14383			$patch_array[0]['colors'][1]['r'] = $col2[0];
14384			$patch_array[0]['colors'][1]['g'] = $col2[1];
14385			$patch_array[0]['colors'][1]['b'] = $col2[2];
14386			$patch_array[0]['colors'][2]['r'] = $col3[0];
14387			$patch_array[0]['colors'][2]['g'] = $col3[1];
14388			$patch_array[0]['colors'][2]['b'] = $col3[2];
14389			$patch_array[0]['colors'][3]['r'] = $col4[0];
14390			$patch_array[0]['colors'][3]['g'] = $col4[1];
14391			$patch_array[0]['colors'][3]['b'] = $col4[2];
14392		} else {
14393			//multi patch array
14394			$patch_array = $coords;
14395		}
14396		$bpcd = 65535; //16 bits per coordinate
14397		//build the data stream
14398		$this->gradients[$n]['stream'] = '';
14399		$count_patch = count($patch_array);
14400		for ($i=0; $i < $count_patch; ++$i) {
14401			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14402			$count_points = count($patch_array[$i]['points']);
14403			for ($j=0; $j < $count_points; ++$j) {
14404				//each point as 16 bit
14405				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14406				if ($patch_array[$i]['points'][$j] < 0) {
14407					$patch_array[$i]['points'][$j] = 0;
14408				}
14409				if ($patch_array[$i]['points'][$j] > $bpcd) {
14410					$patch_array[$i]['points'][$j] = $bpcd;
14411				}
14412				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14413				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14414			}
14415			$count_cols = count($patch_array[$i]['colors']);
14416			for ($j=0; $j < $count_cols; ++$j) {
14417				//each color component as 8 bit
14418				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14419				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14420				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14421			}
14422		}
14423		//paint the gradient
14424		$this->_out('/Sh'.$n.' sh');
14425		//restore previous Graphic State
14426		$this->_outRestoreGraphicsState();
14427		if ($this->inxobj) {
14428			// we are inside an XObject template
14429			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14430		}
14431	}
14432
14433	/**
14434	 * Set a rectangular clipping area.
14435	 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14436	 * @param $y (float) ordinate of the top left corner of the rectangle.
14437	 * @param $w (float) width of the rectangle.
14438	 * @param $h (float) height of the rectangle.
14439	 * @author Andreas W\FCrmser, Nicola Asuni
14440	 * @since 3.1.000 (2008-06-09)
14441	 * @protected
14442	 */
14443	protected function Clip($x, $y, $w, $h) {
14444		if ($this->state != 2) {
14445			 return;
14446		}
14447		if ($this->rtl) {
14448			$x = $this->w - $x - $w;
14449		}
14450		//save current Graphic State
14451		$s = 'q';
14452		//set clipping area
14453		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14454		//set up transformation matrix for gradient
14455		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14456		$this->_out($s);
14457	}
14458
14459	/**
14460	 * Output gradient.
14461	 * @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)
14462	 * @param $coords (array) array of coordinates.
14463	 * @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).
14464	 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14465	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14466	 * @author Nicola Asuni
14467	 * @since 3.1.000 (2008-06-09)
14468	 * @public
14469	 */
14470	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14471		if ($this->pdfa_mode OR ($this->state != 2)) {
14472			return;
14473		}
14474		$n = count($this->gradients) + 1;
14475		$this->gradients[$n] = array();
14476		$this->gradients[$n]['type'] = $type;
14477		$this->gradients[$n]['coords'] = $coords;
14478		$this->gradients[$n]['antialias'] = $antialias;
14479		$this->gradients[$n]['colors'] = array();
14480		$this->gradients[$n]['transparency'] = false;
14481		// color space
14482		$numcolspace = count($stops[0]['color']);
14483		$bcolor = array_values($background);
14484		switch($numcolspace) {
14485			case 5:   // SPOT
14486			case 4: { // CMYK
14487				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14488				if (!empty($background)) {
14489					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14490				}
14491				break;
14492			}
14493			case 3: { // RGB
14494				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14495				if (!empty($background)) {
14496					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14497				}
14498				break;
14499			}
14500			case 1: { // GRAY SCALE
14501				$this->gradients[$n]['colspace'] = 'DeviceGray';
14502				if (!empty($background)) {
14503					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14504				}
14505				break;
14506			}
14507		}
14508		$num_stops = count($stops);
14509		$last_stop_id = $num_stops - 1;
14510		foreach ($stops as $key => $stop) {
14511			$this->gradients[$n]['colors'][$key] = array();
14512			// offset represents a location along the gradient vector
14513			if (isset($stop['offset'])) {
14514				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14515			} else {
14516				if ($key == 0) {
14517					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14518				} elseif ($key == $last_stop_id) {
14519					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14520				} else {
14521					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14522					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14523				}
14524			}
14525			if (isset($stop['opacity'])) {
14526				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14527				if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14528					$this->gradients[$n]['transparency'] = true;
14529				}
14530			} else {
14531				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14532			}
14533			// exponent for the exponential interpolation function
14534			if (isset($stop['exponent'])) {
14535				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14536			} else {
14537				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14538			}
14539			// set colors
14540			$color = array_values($stop['color']);
14541			switch($numcolspace) {
14542				case 5:   // SPOT
14543				case 4: { // CMYK
14544					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14545					break;
14546				}
14547				case 3: { // RGB
14548					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14549					break;
14550				}
14551				case 1: { // GRAY SCALE
14552					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14553					break;
14554				}
14555			}
14556		}
14557		if ($this->gradients[$n]['transparency']) {
14558			// paint luminosity gradient
14559			$this->_out('/TGS'.$n.' gs');
14560		}
14561		//paint the gradient
14562		$this->_out('/Sh'.$n.' sh');
14563		//restore previous Graphic State
14564		$this->_outRestoreGraphicsState();
14565		if ($this->inxobj) {
14566			// we are inside an XObject template
14567			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14568		}
14569	}
14570
14571	/**
14572	 * Output gradient shaders.
14573	 * @author Nicola Asuni
14574	 * @since 3.1.000 (2008-06-09)
14575	 * @protected
14576	 */
14577	function _putshaders() {
14578		if ($this->pdfa_mode) {
14579			return;
14580		}
14581		$idt = count($this->gradients); //index for transparency gradients
14582		foreach ($this->gradients as $id => $grad) {
14583			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14584				$fc = $this->_newobj();
14585				$out = '<<';
14586				$out .= ' /FunctionType 3';
14587				$out .= ' /Domain [0 1]';
14588				$functions = '';
14589				$bounds = '';
14590				$encode = '';
14591				$i = 1;
14592				$num_cols = count($grad['colors']);
14593				$lastcols = $num_cols - 1;
14594				for ($i = 1; $i < $num_cols; ++$i) {
14595					$functions .= ($fc + $i).' 0 R ';
14596					if ($i < $lastcols) {
14597						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14598					}
14599					$encode .= '0 1 ';
14600				}
14601				$out .= ' /Functions ['.trim($functions).']';
14602				$out .= ' /Bounds ['.trim($bounds).']';
14603				$out .= ' /Encode ['.trim($encode).']';
14604				$out .= ' >>';
14605				$out .= "\n".'endobj';
14606				$this->_out($out);
14607				for ($i = 1; $i < $num_cols; ++$i) {
14608					$this->_newobj();
14609					$out = '<<';
14610					$out .= ' /FunctionType 2';
14611					$out .= ' /Domain [0 1]';
14612					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14613					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14614					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14615					$out .= ' >>';
14616					$out .= "\n".'endobj';
14617					$this->_out($out);
14618				}
14619				// set transparency functions
14620				if ($grad['transparency']) {
14621					$ft = $this->_newobj();
14622					$out = '<<';
14623					$out .= ' /FunctionType 3';
14624					$out .= ' /Domain [0 1]';
14625					$functions = '';
14626					$i = 1;
14627					$num_cols = count($grad['colors']);
14628					for ($i = 1; $i < $num_cols; ++$i) {
14629						$functions .= ($ft + $i).' 0 R ';
14630					}
14631					$out .= ' /Functions ['.trim($functions).']';
14632					$out .= ' /Bounds ['.trim($bounds).']';
14633					$out .= ' /Encode ['.trim($encode).']';
14634					$out .= ' >>';
14635					$out .= "\n".'endobj';
14636					$this->_out($out);
14637					for ($i = 1; $i < $num_cols; ++$i) {
14638						$this->_newobj();
14639						$out = '<<';
14640						$out .= ' /FunctionType 2';
14641						$out .= ' /Domain [0 1]';
14642						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14643						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14644						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14645						$out .= ' >>';
14646						$out .= "\n".'endobj';
14647						$this->_out($out);
14648					}
14649				}
14650			}
14651			// set shading object
14652			$this->_newobj();
14653			$out = '<< /ShadingType '.$grad['type'];
14654			if (isset($grad['colspace'])) {
14655				$out .= ' /ColorSpace /'.$grad['colspace'];
14656			} else {
14657				$out .= ' /ColorSpace /DeviceRGB';
14658			}
14659			if (isset($grad['background']) AND !empty($grad['background'])) {
14660				$out .= ' /Background ['.$grad['background'].']';
14661			}
14662			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14663				$out .= ' /AntiAlias true';
14664			}
14665			if ($grad['type'] == 2) {
14666				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14667				$out .= ' /Domain [0 1]';
14668				$out .= ' /Function '.$fc.' 0 R';
14669				$out .= ' /Extend [true true]';
14670				$out .= ' >>';
14671			} elseif ($grad['type'] == 3) {
14672				//x0, y0, r0, x1, y1, r1
14673				//at this this time radius of inner circle is 0
14674				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14675				$out .= ' /Domain [0 1]';
14676				$out .= ' /Function '.$fc.' 0 R';
14677				$out .= ' /Extend [true true]';
14678				$out .= ' >>';
14679			} elseif ($grad['type'] == 6) {
14680				$out .= ' /BitsPerCoordinate 16';
14681				$out .= ' /BitsPerComponent 8';
14682				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14683				$out .= ' /BitsPerFlag 8';
14684				$stream = $this->_getrawstream($grad['stream']);
14685				$out .= ' /Length '.strlen($stream);
14686				$out .= ' >>';
14687				$out .= ' stream'."\n".$stream."\n".'endstream';
14688			}
14689			$out .= "\n".'endobj';
14690			$this->_out($out);
14691			if ($grad['transparency']) {
14692				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14693				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14694			}
14695			$this->gradients[$id]['id'] = $this->n;
14696			// set pattern object
14697			$this->_newobj();
14698			$out = '<< /Type /Pattern /PatternType 2';
14699			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14700			$out .= ' >>';
14701			$out .= "\n".'endobj';
14702			$this->_out($out);
14703			$this->gradients[$id]['pattern'] = $this->n;
14704			// set shading and pattern for transparency mask
14705			if ($grad['transparency']) {
14706				// luminosity pattern
14707				$idgs = $id + $idt;
14708				$this->_newobj();
14709				$this->_out($shading_transparency);
14710				$this->gradients[$idgs]['id'] = $this->n;
14711				$this->_newobj();
14712				$out = '<< /Type /Pattern /PatternType 2';
14713				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14714				$out .= ' >>';
14715				$out .= "\n".'endobj';
14716				$this->_out($out);
14717				$this->gradients[$idgs]['pattern'] = $this->n;
14718				// luminosity XObject
14719				$oid = $this->_newobj();
14720				$this->xobjects['LX'.$oid] = array('n' => $oid);
14721				$filter = '';
14722				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14723				if ($this->compress) {
14724					$filter = ' /Filter /FlateDecode';
14725					$stream = gzcompress($stream);
14726				}
14727				$stream = $this->_getrawstream($stream);
14728				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14729				$out .= ' /Length '.strlen($stream);
14730				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14731				$out .= ' /BBox [0 0 '.$rect.']';
14732				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14733				$out .= ' /Resources <<';
14734				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14735				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14736				$out .= ' >>';
14737				$out .= ' >> ';
14738				$out .= ' stream'."\n".$stream."\n".'endstream';
14739				$out .= "\n".'endobj';
14740				$this->_out($out);
14741				// SMask
14742				$this->_newobj();
14743				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14744				$this->_out($out);
14745				// ExtGState
14746				$this->_newobj();
14747				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14748				$this->_out($out);
14749				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14750			}
14751		}
14752	}
14753
14754	/**
14755	 * Draw the sector of a circle.
14756	 * It can be used for instance to render pie charts.
14757	 * @param $xc (float) abscissa of the center.
14758	 * @param $yc (float) ordinate of the center.
14759	 * @param $r (float) radius.
14760	 * @param $a (float) start angle (in degrees).
14761	 * @param $b (float) end angle (in degrees).
14762	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14763	 * @param $cw: (float) indicates whether to go clockwise (default: true).
14764	 * @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.
14765	 * @author Maxime Delorme, Nicola Asuni
14766	 * @since 3.1.000 (2008-06-09)
14767	 * @public
14768	 */
14769	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14770		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14771	}
14772
14773	/**
14774	 * Draw the sector of an ellipse.
14775	 * It can be used for instance to render pie charts.
14776	 * @param $xc (float) abscissa of the center.
14777	 * @param $yc (float) ordinate of the center.
14778	 * @param $rx (float) the x-axis radius.
14779	 * @param $ry (float) the y-axis radius.
14780	 * @param $a (float) start angle (in degrees).
14781	 * @param $b (float) end angle (in degrees).
14782	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14783	 * @param $cw: (float) indicates whether to go clockwise.
14784	 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14785	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14786	 * @author Maxime Delorme, Nicola Asuni
14787	 * @since 3.1.000 (2008-06-09)
14788	 * @public
14789	 */
14790	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14791		if ($this->state != 2) {
14792			 return;
14793		}
14794		if ($this->rtl) {
14795			$xc = ($this->w - $xc);
14796		}
14797		$op = TCPDF_STATIC::getPathPaintOperator($style);
14798		if ($op == 'f') {
14799			$line_style = array();
14800		}
14801		if ($cw) {
14802			$d = $b;
14803			$b = (360 - $a + $o);
14804			$a = (360 - $d + $o);
14805		} else {
14806			$b += $o;
14807			$a += $o;
14808		}
14809		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14810		$this->_out($op);
14811	}
14812
14813	/**
14814	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14815	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14816	 * Only vector drawing is supported, not text or bitmap.
14817	 * 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).
14818	 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14819	 * @param $x (float) Abscissa of the upper-left corner.
14820	 * @param $y (float) Ordinate of the upper-left corner.
14821	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14822	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14823	 * @param $link (mixed) URL or identifier returned by AddLink().
14824	 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14825	 * @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>
14826	 * @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>
14827	 * @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)))
14828	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14829	 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14830	 * @author Valentin Schmidt, Nicola Asuni
14831	 * @since 3.1.000 (2008-06-09)
14832	 * @public
14833	 */
14834	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14835		if ($this->state != 2) {
14836			 return;
14837		}
14838		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14839			// convert EPS to raster image using GD or ImageMagick libraries
14840			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14841		}
14842		if ($x === '') {
14843			$x = $this->x;
14844		}
14845		if ($y === '') {
14846			$y = $this->y;
14847		}
14848		// check page for no-write regions and adapt page margins if necessary
14849		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14850		$k = $this->k;
14851		if ($file[0] === '@') { // image from string
14852			$data = substr($file, 1);
14853		} else { // EPS/AI file
14854			$data = TCPDF_STATIC::fileGetContents($file);
14855		}
14856		if ($data === FALSE) {
14857			$this->Error('EPS file not found: '.$file);
14858		}
14859		$regs = array();
14860		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14861		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14862		if (count($regs) > 1) {
14863			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14864			if (strpos($version_str, 'Adobe Illustrator') !== false) {
14865				$versexp = explode(' ', $version_str);
14866				$version = (float)array_pop($versexp);
14867				if ($version >= 9) {
14868					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14869				}
14870			}
14871		}
14872		// strip binary bytes in front of PS-header
14873		$start = strpos($data, '%!PS-Adobe');
14874		if ($start > 0) {
14875			$data = substr($data, $start);
14876		}
14877		// find BoundingBox params
14878		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14879		if (count($regs) > 1) {
14880			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14881		} else {
14882			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
14883		}
14884		$start = strpos($data, '%%EndSetup');
14885		if ($start === false) {
14886			$start = strpos($data, '%%EndProlog');
14887		}
14888		if ($start === false) {
14889			$start = strpos($data, '%%BoundingBox');
14890		}
14891		$data = substr($data, $start);
14892		$end = strpos($data, '%%PageTrailer');
14893		if ($end===false) {
14894			$end = strpos($data, 'showpage');
14895		}
14896		if ($end) {
14897			$data = substr($data, 0, $end);
14898		}
14899		// calculate image width and height on document
14900		if (($w <= 0) AND ($h <= 0)) {
14901			$w = ($x2 - $x1) / $k;
14902			$h = ($y2 - $y1) / $k;
14903		} elseif ($w <= 0) {
14904			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14905		} elseif ($h <= 0) {
14906			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14907		}
14908		// fit the image on available space
14909		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14910		if ($this->rasterize_vector_images) {
14911			// convert EPS to raster image using GD or ImageMagick libraries
14912			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14913		}
14914		// set scaling factors
14915		$scale_x = $w / (($x2 - $x1) / $k);
14916		$scale_y = $h / (($y2 - $y1) / $k);
14917		// set alignment
14918		$this->img_rb_y = $y + $h;
14919		// set alignment
14920		if ($this->rtl) {
14921			if ($palign == 'L') {
14922				$ximg = $this->lMargin;
14923			} elseif ($palign == 'C') {
14924				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14925			} elseif ($palign == 'R') {
14926				$ximg = $this->w - $this->rMargin - $w;
14927			} else {
14928				$ximg = $x - $w;
14929			}
14930			$this->img_rb_x = $ximg;
14931		} else {
14932			if ($palign == 'L') {
14933				$ximg = $this->lMargin;
14934			} elseif ($palign == 'C') {
14935				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14936			} elseif ($palign == 'R') {
14937				$ximg = $this->w - $this->rMargin - $w;
14938			} else {
14939				$ximg = $x;
14940			}
14941			$this->img_rb_x = $ximg + $w;
14942		}
14943		if ($useBoundingBox) {
14944			$dx = $ximg * $k - $x1;
14945			$dy = $y * $k - $y1;
14946		} else {
14947			$dx = $ximg * $k;
14948			$dy = $y * $k;
14949		}
14950		// save the current graphic state
14951		$this->_out('q'.$this->epsmarker);
14952		// translate
14953		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
14954		// scale
14955		if (isset($scale_x)) {
14956			$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
14957		}
14958		// handle pc/unix/mac line endings
14959		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
14960		$u=0;
14961		$cnt = count($lines);
14962		for ($i=0; $i < $cnt; ++$i) {
14963			$line = $lines[$i];
14964			if (($line == '') OR ($line[0] == '%')) {
14965				continue;
14966			}
14967			$len = strlen($line);
14968			// check for spot color names
14969			$color_name = '';
14970			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
14971				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
14972					// extract spot color name
14973					$color_name = $matches[0];
14974					// remove color name from string
14975					$line = str_replace(' '.$color_name, '', $line);
14976					// remove pharentesis from color name
14977					$color_name = substr($color_name, 1, -1);
14978				}
14979			}
14980			$chunks = explode(' ', $line);
14981			$cmd = trim(array_pop($chunks));
14982			// RGB
14983			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
14984				$b = array_pop($chunks);
14985				$g = array_pop($chunks);
14986				$r = array_pop($chunks);
14987				$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!
14988				continue;
14989			}
14990			$skip = false;
14991			if ($fixoutvals) {
14992				// check for values outside the bounding box
14993				switch ($cmd) {
14994					case 'm':
14995					case 'l':
14996					case 'L': {
14997						// skip values outside bounding box
14998						foreach ($chunks as $key => $val) {
14999							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15000								$skip = true;
15001							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15002								$skip = true;
15003							}
15004						}
15005					}
15006				}
15007			}
15008			switch ($cmd) {
15009				case 'm':
15010				case 'l':
15011				case 'v':
15012				case 'y':
15013				case 'c':
15014				case 'k':
15015				case 'K':
15016				case 'g':
15017				case 'G':
15018				case 's':
15019				case 'S':
15020				case 'J':
15021				case 'j':
15022				case 'w':
15023				case 'M':
15024				case 'd':
15025				case 'n': {
15026					if ($skip) {
15027						break;
15028					}
15029					$this->_out($line);
15030					break;
15031				}
15032				case 'x': {// custom fill color
15033					if (empty($color_name)) {
15034						// CMYK color
15035						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15036						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15037					} else {
15038						// Spot Color (CMYK + tint)
15039						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15040						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15041						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15042						$this->_out($color_cmd);
15043					}
15044					break;
15045				}
15046				case 'X': { // custom stroke color
15047					if (empty($color_name)) {
15048						// CMYK color
15049						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15050						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15051					} else {
15052						// Spot Color (CMYK + tint)
15053						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15054						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15055						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15056						$this->_out($color_cmd);
15057					}
15058					break;
15059				}
15060				case 'Y':
15061				case 'N':
15062				case 'V':
15063				case 'L':
15064				case 'C': {
15065					if ($skip) {
15066						break;
15067					}
15068					$line[($len - 1)] = strtolower($cmd);
15069					$this->_out($line);
15070					break;
15071				}
15072				case 'b':
15073				case 'B': {
15074					$this->_out($cmd . '*');
15075					break;
15076				}
15077				case 'f':
15078				case 'F': {
15079					if ($u > 0) {
15080						$isU = false;
15081						$max = min(($i + 5), $cnt);
15082						for ($j = ($i + 1); $j < $max; ++$j) {
15083							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15084						}
15085						if ($isU) {
15086							$this->_out('f*');
15087						}
15088					} else {
15089						$this->_out('f*');
15090					}
15091					break;
15092				}
15093				case '*u': {
15094					++$u;
15095					break;
15096				}
15097				case '*U': {
15098					--$u;
15099					break;
15100				}
15101			}
15102		}
15103		// restore previous graphic state
15104		$this->_out($this->epsmarker.'Q');
15105		if (!empty($border)) {
15106			$bx = $this->x;
15107			$by = $this->y;
15108			$this->x = $ximg;
15109			if ($this->rtl) {
15110				$this->x += $w;
15111			}
15112			$this->y = $y;
15113			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15114			$this->x = $bx;
15115			$this->y = $by;
15116		}
15117		if ($link) {
15118			$this->Link($ximg, $y, $w, $h, $link, 0);
15119		}
15120		// set pointer to align the next text/objects
15121		switch($align) {
15122			case 'T':{
15123				$this->y = $y;
15124				$this->x = $this->img_rb_x;
15125				break;
15126			}
15127			case 'M':{
15128				$this->y = $y + round($h/2);
15129				$this->x = $this->img_rb_x;
15130				break;
15131			}
15132			case 'B':{
15133				$this->y = $this->img_rb_y;
15134				$this->x = $this->img_rb_x;
15135				break;
15136			}
15137			case 'N':{
15138				$this->SetY($this->img_rb_y);
15139				break;
15140			}
15141			default:{
15142				break;
15143			}
15144		}
15145		$this->endlinex = $this->img_rb_x;
15146	}
15147
15148	/**
15149	 * Set document barcode.
15150	 * @param $bc (string) barcode
15151	 * @public
15152	 */
15153	public function setBarcode($bc='') {
15154		$this->barcode = $bc;
15155	}
15156
15157	/**
15158	 * Get current barcode.
15159	 * @return string
15160	 * @public
15161	 * @since 4.0.012 (2008-07-24)
15162	 */
15163	public function getBarcode() {
15164		return $this->barcode;
15165	}
15166
15167	/**
15168	 * Print a Linear Barcode.
15169	 * @param $code (string) code to print
15170	 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15171	 * @param $x (int) x position in user units (empty string = current x position)
15172	 * @param $y (int) y position in user units (empty string = current y position)
15173	 * @param $w (int) width in user units (empty string = remaining page width)
15174	 * @param $h (int) height in user units (empty string = remaining page height)
15175	 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15176	 * @param $style (array) array of options:<ul>
15177	 * <li>boolean $style['border'] if true prints a border</li>
15178	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15179	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15180	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15181	 * <li>array $style['fgcolor'] color array for bars and text</li>
15182	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15183	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15184	 * <li>string $style['label'] override default label</li>
15185	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15186	 * <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>
15187	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15188	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15189	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15190	 * <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>
15191	 * <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>
15192	 * @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>
15193	 * @author Nicola Asuni
15194	 * @since 3.1.000 (2008-06-09)
15195	 * @public
15196	 */
15197	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
15198		if (TCPDF_STATIC::empty_string(trim($code))) {
15199			return;
15200		}
15201		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15202		// save current graphic settings
15203		$gvars = $this->getGraphicVars();
15204		// create new barcode object
15205		$barcodeobj = new TCPDFBarcode($code, $type);
15206		$arrcode = $barcodeobj->getBarcodeArray();
15207		if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15208			$this->Error('Error in 1D barcode string');
15209		}
15210		if ($arrcode['maxh'] <= 0) {
15211			$arrcode['maxh'] = 1;
15212		}
15213		// set default values
15214		if (!isset($style['position'])) {
15215			$style['position'] = '';
15216		} elseif ($style['position'] == 'S') {
15217			// keep this for backward compatibility
15218			$style['position'] = '';
15219			$style['stretch'] = true;
15220		}
15221		if (!isset($style['fitwidth'])) {
15222			if (!isset($style['stretch'])) {
15223				$style['fitwidth'] = true;
15224			} else {
15225				$style['fitwidth'] = false;
15226			}
15227		}
15228		if ($style['fitwidth']) {
15229			// disable stretch
15230			$style['stretch'] = false;
15231		}
15232		if (!isset($style['stretch'])) {
15233			if (($w === '') OR ($w <= 0)) {
15234				$style['stretch'] = false;
15235			} else {
15236				$style['stretch'] = true;
15237			}
15238		}
15239		if (!isset($style['fgcolor'])) {
15240			$style['fgcolor'] = array(0,0,0); // default black
15241		}
15242		if (!isset($style['bgcolor'])) {
15243			$style['bgcolor'] = false; // default transparent
15244		}
15245		if (!isset($style['border'])) {
15246			$style['border'] = false;
15247		}
15248		$fontsize = 0;
15249		if (!isset($style['text'])) {
15250			$style['text'] = false;
15251		}
15252		if ($style['text'] AND isset($style['font'])) {
15253			if (isset($style['fontsize'])) {
15254				$fontsize = $style['fontsize'];
15255			}
15256			$this->SetFont($style['font'], '', $fontsize);
15257		}
15258		if (!isset($style['stretchtext'])) {
15259			$style['stretchtext'] = 4;
15260		}
15261		if ($x === '') {
15262			$x = $this->x;
15263		}
15264		if ($y === '') {
15265			$y = $this->y;
15266		}
15267		// check page for no-write regions and adapt page margins if necessary
15268		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15269		if (($w === '') OR ($w <= 0)) {
15270			if ($this->rtl) {
15271				$w = $x - $this->lMargin;
15272			} else {
15273				$w = $this->w - $this->rMargin - $x;
15274			}
15275		}
15276		// padding
15277		if (!isset($style['padding'])) {
15278			$padding = 0;
15279		} elseif ($style['padding'] === 'auto') {
15280			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15281		} else {
15282			$padding = floatval($style['padding']);
15283		}
15284		// horizontal padding
15285		if (!isset($style['hpadding'])) {
15286			$hpadding = $padding;
15287		} elseif ($style['hpadding'] === 'auto') {
15288			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15289		} else {
15290			$hpadding = floatval($style['hpadding']);
15291		}
15292		// vertical padding
15293		if (!isset($style['vpadding'])) {
15294			$vpadding = $padding;
15295		} elseif ($style['vpadding'] === 'auto') {
15296			$vpadding = ($hpadding / 2);
15297		} else {
15298			$vpadding = floatval($style['vpadding']);
15299		}
15300		// calculate xres (single bar width)
15301		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15302		if ($style['stretch']) {
15303			$xres = $max_xres;
15304		} else {
15305			if (TCPDF_STATIC::empty_string($xres)) {
15306				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15307			}
15308			if ($xres > $max_xres) {
15309				// correct xres to fit on $w
15310				$xres = $max_xres;
15311			}
15312			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15313				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15314				$hpadding = 10 * $xres;
15315				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15316					$vpadding = ($hpadding / 2);
15317				}
15318			}
15319		}
15320		if ($style['fitwidth']) {
15321			$wold = $w;
15322			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15323			if (isset($style['cellfitalign'])) {
15324				switch ($style['cellfitalign']) {
15325					case 'L': {
15326						if ($this->rtl) {
15327							$x -= ($wold - $w);
15328						}
15329						break;
15330					}
15331					case 'R': {
15332						if (!$this->rtl) {
15333							$x += ($wold - $w);
15334						}
15335						break;
15336					}
15337					case 'C': {
15338						if ($this->rtl) {
15339							$x -= (($wold - $w) / 2);
15340						} else {
15341							$x += (($wold - $w) / 2);
15342						}
15343						break;
15344					}
15345					default : {
15346						break;
15347					}
15348				}
15349			}
15350		}
15351		$text_height = $this->getCellHeight($fontsize / $this->k);
15352		// height
15353		if (($h === '') OR ($h <= 0)) {
15354			// set default height
15355			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15356		}
15357		$barh = $h - $text_height - (2 * $vpadding);
15358		if ($barh <=0) {
15359			// try to reduce font or padding to fit barcode on available height
15360			if ($text_height > $h) {
15361				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15362				$text_height = $this->getCellHeight($fontsize / $this->k);
15363				$this->SetFont($style['font'], '', $fontsize);
15364			}
15365			if ($vpadding > 0) {
15366				$vpadding = (($h - $text_height) / 4);
15367			}
15368			$barh = $h - $text_height - (2 * $vpadding);
15369		}
15370		// fit the barcode on available space
15371		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15372		// set alignment
15373		$this->img_rb_y = $y + $h;
15374		// set alignment
15375		if ($this->rtl) {
15376			if ($style['position'] == 'L') {
15377				$xpos = $this->lMargin;
15378			} elseif ($style['position'] == 'C') {
15379				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15380			} elseif ($style['position'] == 'R') {
15381				$xpos = $this->w - $this->rMargin - $w;
15382			} else {
15383				$xpos = $x - $w;
15384			}
15385			$this->img_rb_x = $xpos;
15386		} else {
15387			if ($style['position'] == 'L') {
15388				$xpos = $this->lMargin;
15389			} elseif ($style['position'] == 'C') {
15390				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15391			} elseif ($style['position'] == 'R') {
15392				$xpos = $this->w - $this->rMargin - $w;
15393			} else {
15394				$xpos = $x;
15395			}
15396			$this->img_rb_x = $xpos + $w;
15397		}
15398		$xpos_rect = $xpos;
15399		if (!isset($style['align'])) {
15400			$style['align'] = 'C';
15401		}
15402		switch ($style['align']) {
15403			case 'L': {
15404				$xpos = $xpos_rect + $hpadding;
15405				break;
15406			}
15407			case 'R': {
15408				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15409				break;
15410			}
15411			case 'C':
15412			default : {
15413				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15414				break;
15415			}
15416		}
15417		$xpos_text = $xpos;
15418		// barcode is always printed in LTR direction
15419		$tempRTL = $this->rtl;
15420		$this->rtl = false;
15421		// print background color
15422		if ($style['bgcolor']) {
15423			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15424		} elseif ($style['border']) {
15425			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15426		}
15427		// set foreground color
15428		$this->SetDrawColorArray($style['fgcolor']);
15429		$this->SetTextColorArray($style['fgcolor']);
15430		// print bars
15431		foreach ($arrcode['bcode'] as $k => $v) {
15432			$bw = ($v['w'] * $xres);
15433			if ($v['t']) {
15434				// draw a vertical bar
15435				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15436				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15437			}
15438			$xpos += $bw;
15439		}
15440		// print text
15441		if ($style['text']) {
15442			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15443				$label = $style['label'];
15444			} else {
15445				$label = $code;
15446			}
15447			$txtwidth = ($arrcode['maxw'] * $xres);
15448			if ($this->GetStringWidth($label) > $txtwidth) {
15449				$style['stretchtext'] = 2;
15450			}
15451			// print text
15452			$this->x = $xpos_text;
15453			$this->y = $y + $vpadding + $barh;
15454			$cellpadding = $this->cell_padding;
15455			$this->SetCellPadding(0);
15456			$this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15457			$this->cell_padding = $cellpadding;
15458		}
15459		// restore original direction
15460		$this->rtl = $tempRTL;
15461		// restore previous settings
15462		$this->setGraphicVars($gvars);
15463		// set pointer to align the next text/objects
15464		switch($align) {
15465			case 'T':{
15466				$this->y = $y;
15467				$this->x = $this->img_rb_x;
15468				break;
15469			}
15470			case 'M':{
15471				$this->y = $y + round($h / 2);
15472				$this->x = $this->img_rb_x;
15473				break;
15474			}
15475			case 'B':{
15476				$this->y = $this->img_rb_y;
15477				$this->x = $this->img_rb_x;
15478				break;
15479			}
15480			case 'N':{
15481				$this->SetY($this->img_rb_y);
15482				break;
15483			}
15484			default:{
15485				break;
15486			}
15487		}
15488		$this->endlinex = $this->img_rb_x;
15489	}
15490
15491	/**
15492	 * Print 2D Barcode.
15493	 * @param $code (string) code to print
15494	 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15495	 * @param $x (int) x position in user units
15496	 * @param $y (int) y position in user units
15497	 * @param $w (int) width in user units
15498	 * @param $h (int) height in user units
15499	 * @param $style (array) array of options:<ul>
15500	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15501	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15502	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15503	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15504	 * <li>int $style['module_width'] width of a single module in points</li>
15505	 * <li>int $style['module_height'] height of a single module in points</li>
15506	 * <li>array $style['fgcolor'] color array for bars and text</li>
15507	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15508	 * <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>
15509	 * <li>$style['module_height'] height of a single module in points</li></ul>
15510	 * @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>
15511	 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15512	 * @author Nicola Asuni
15513	 * @since 4.5.037 (2009-04-07)
15514	 * @public
15515	 */
15516	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
15517		if (TCPDF_STATIC::empty_string(trim($code))) {
15518			return;
15519		}
15520		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15521		// save current graphic settings
15522		$gvars = $this->getGraphicVars();
15523		// create new barcode object
15524		$barcodeobj = new TCPDF2DBarcode($code, $type);
15525		$arrcode = $barcodeobj->getBarcodeArray();
15526		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)) {
15527			$this->Error('Error in 2D barcode string');
15528		}
15529		// set default values
15530		if (!isset($style['position'])) {
15531			$style['position'] = '';
15532		}
15533		if (!isset($style['fgcolor'])) {
15534			$style['fgcolor'] = array(0,0,0); // default black
15535		}
15536		if (!isset($style['bgcolor'])) {
15537			$style['bgcolor'] = false; // default transparent
15538		}
15539		if (!isset($style['border'])) {
15540			$style['border'] = false;
15541		}
15542		// padding
15543		if (!isset($style['padding'])) {
15544			$style['padding'] = 0;
15545		} elseif ($style['padding'] === 'auto') {
15546			$style['padding'] = 4;
15547		}
15548		if (!isset($style['hpadding'])) {
15549			$style['hpadding'] = $style['padding'];
15550		} elseif ($style['hpadding'] === 'auto') {
15551			$style['hpadding'] = 4;
15552		}
15553		if (!isset($style['vpadding'])) {
15554			$style['vpadding'] = $style['padding'];
15555		} elseif ($style['vpadding'] === 'auto') {
15556			$style['vpadding'] = 4;
15557		}
15558		$hpad = (2 * $style['hpadding']);
15559		$vpad = (2 * $style['vpadding']);
15560		// cell (module) dimension
15561		if (!isset($style['module_width'])) {
15562			$style['module_width'] = 1; // width of a single module in points
15563		}
15564		if (!isset($style['module_height'])) {
15565			$style['module_height'] = 1; // height of a single module in points
15566		}
15567		if ($x === '') {
15568			$x = $this->x;
15569		}
15570		if ($y === '') {
15571			$y = $this->y;
15572		}
15573		// check page for no-write regions and adapt page margins if necessary
15574		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15575		// number of barcode columns and rows
15576		$rows = $arrcode['num_rows'];
15577		$cols = $arrcode['num_cols'];
15578		if (($rows <= 0) || ($cols <= 0)){
15579			$this->Error('Error in 2D barcode string');
15580		}
15581		// module width and height
15582		$mw = $style['module_width'];
15583		$mh = $style['module_height'];
15584		if (($mw <= 0) OR ($mh <= 0)) {
15585			$this->Error('Error in 2D barcode string');
15586		}
15587		// get max dimensions
15588		if ($this->rtl) {
15589			$maxw = $x - $this->lMargin;
15590		} else {
15591			$maxw = $this->w - $this->rMargin - $x;
15592		}
15593		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15594		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15595		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15596		if (!$distort) {
15597			if (($maxw * $ratioHW) > $maxh) {
15598				$maxw = $maxh * $ratioWH;
15599			}
15600			if (($maxh * $ratioWH) > $maxw) {
15601				$maxh = $maxw * $ratioHW;
15602			}
15603		}
15604		// set maximum dimensions
15605		if ($w > $maxw) {
15606			$w = $maxw;
15607		}
15608		if ($h > $maxh) {
15609			$h = $maxh;
15610		}
15611		// set dimensions
15612		if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15613			$w = ($cols + $hpad) * ($mw / $this->k);
15614			$h = ($rows + $vpad) * ($mh / $this->k);
15615		} elseif (($w === '') OR ($w <= 0)) {
15616			$w = $h * $ratioWH;
15617		} elseif (($h === '') OR ($h <= 0)) {
15618			$h = $w * $ratioHW;
15619		}
15620		// barcode size (excluding padding)
15621		$bw = ($w * $cols) / ($cols + $hpad);
15622		$bh = ($h * $rows) / ($rows + $vpad);
15623		// dimension of single barcode cell unit
15624		$cw = $bw / $cols;
15625		$ch = $bh / $rows;
15626		if (!$distort) {
15627			if (($cw / $ch) > ($mw / $mh)) {
15628				// correct horizontal distortion
15629				$cw = $ch * $mw / $mh;
15630				$bw = $cw * $cols;
15631				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15632			} else {
15633				// correct vertical distortion
15634				$ch = $cw * $mh / $mw;
15635				$bh = $ch * $rows;
15636				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15637			}
15638		}
15639		// fit the barcode on available space
15640		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15641		// set alignment
15642		$this->img_rb_y = $y + $h;
15643		// set alignment
15644		if ($this->rtl) {
15645			if ($style['position'] == 'L') {
15646				$xpos = $this->lMargin;
15647			} elseif ($style['position'] == 'C') {
15648				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15649			} elseif ($style['position'] == 'R') {
15650				$xpos = $this->w - $this->rMargin - $w;
15651			} else {
15652				$xpos = $x - $w;
15653			}
15654			$this->img_rb_x = $xpos;
15655		} else {
15656			if ($style['position'] == 'L') {
15657				$xpos = $this->lMargin;
15658			} elseif ($style['position'] == 'C') {
15659				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15660			} elseif ($style['position'] == 'R') {
15661				$xpos = $this->w - $this->rMargin - $w;
15662			} else {
15663				$xpos = $x;
15664			}
15665			$this->img_rb_x = $xpos + $w;
15666		}
15667		$xstart = $xpos + ($style['hpadding'] * $cw);
15668		$ystart = $y + ($style['vpadding'] * $ch);
15669		// barcode is always printed in LTR direction
15670		$tempRTL = $this->rtl;
15671		$this->rtl = false;
15672		// print background color
15673		if ($style['bgcolor']) {
15674			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15675		} elseif ($style['border']) {
15676			$this->Rect($xpos, $y, $w, $h, 'D');
15677		}
15678		// set foreground color
15679		$this->SetDrawColorArray($style['fgcolor']);
15680		// print barcode cells
15681		// for each row
15682		for ($r = 0; $r < $rows; ++$r) {
15683			$xr = $xstart;
15684			// for each column
15685			for ($c = 0; $c < $cols; ++$c) {
15686				if ($arrcode['bcode'][$r][$c] == 1) {
15687					// draw a single barcode cell
15688					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15689				}
15690				$xr += $cw;
15691			}
15692			$ystart += $ch;
15693		}
15694		// restore original direction
15695		$this->rtl = $tempRTL;
15696		// restore previous settings
15697		$this->setGraphicVars($gvars);
15698		// set pointer to align the next text/objects
15699		switch($align) {
15700			case 'T':{
15701				$this->y = $y;
15702				$this->x = $this->img_rb_x;
15703				break;
15704			}
15705			case 'M':{
15706				$this->y = $y + round($h/2);
15707				$this->x = $this->img_rb_x;
15708				break;
15709			}
15710			case 'B':{
15711				$this->y = $this->img_rb_y;
15712				$this->x = $this->img_rb_x;
15713				break;
15714			}
15715			case 'N':{
15716				$this->SetY($this->img_rb_y);
15717				break;
15718			}
15719			default:{
15720				break;
15721			}
15722		}
15723		$this->endlinex = $this->img_rb_x;
15724	}
15725
15726	/**
15727	 * Returns an array containing current margins:
15728	 * <ul>
15729			<li>$ret['left'] = left margin</li>
15730			<li>$ret['right'] = right margin</li>
15731			<li>$ret['top'] = top margin</li>
15732			<li>$ret['bottom'] = bottom margin</li>
15733			<li>$ret['header'] = header margin</li>
15734			<li>$ret['footer'] = footer margin</li>
15735			<li>$ret['cell'] = cell padding array</li>
15736			<li>$ret['padding_left'] = cell left padding</li>
15737			<li>$ret['padding_top'] = cell top padding</li>
15738			<li>$ret['padding_right'] = cell right padding</li>
15739			<li>$ret['padding_bottom'] = cell bottom padding</li>
15740	 * </ul>
15741	 * @return array containing all margins measures
15742	 * @public
15743	 * @since 3.2.000 (2008-06-23)
15744	 */
15745	public function getMargins() {
15746		$ret = array(
15747			'left' => $this->lMargin,
15748			'right' => $this->rMargin,
15749			'top' => $this->tMargin,
15750			'bottom' => $this->bMargin,
15751			'header' => $this->header_margin,
15752			'footer' => $this->footer_margin,
15753			'cell' => $this->cell_padding,
15754			'padding_left' => $this->cell_padding['L'],
15755			'padding_top' => $this->cell_padding['T'],
15756			'padding_right' => $this->cell_padding['R'],
15757			'padding_bottom' => $this->cell_padding['B']
15758		);
15759		return $ret;
15760	}
15761
15762	/**
15763	 * Returns an array containing original margins:
15764	 * <ul>
15765			<li>$ret['left'] = left margin</li>
15766			<li>$ret['right'] = right margin</li>
15767	 * </ul>
15768	 * @return array containing all margins measures
15769	 * @public
15770	 * @since 4.0.012 (2008-07-24)
15771	 */
15772	public function getOriginalMargins() {
15773		$ret = array(
15774			'left' => $this->original_lMargin,
15775			'right' => $this->original_rMargin
15776		);
15777		return $ret;
15778	}
15779
15780	/**
15781	 * Returns the current font size.
15782	 * @return current font size
15783	 * @public
15784	 * @since 3.2.000 (2008-06-23)
15785	 */
15786	public function getFontSize() {
15787		return $this->FontSize;
15788	}
15789
15790	/**
15791	 * Returns the current font size in points unit.
15792	 * @return current font size in points unit
15793	 * @public
15794	 * @since 3.2.000 (2008-06-23)
15795	 */
15796	public function getFontSizePt() {
15797		return $this->FontSizePt;
15798	}
15799
15800	/**
15801	 * Returns the current font family name.
15802	 * @return string current font family name
15803	 * @public
15804	 * @since 4.3.008 (2008-12-05)
15805	 */
15806	public function getFontFamily() {
15807		return $this->FontFamily;
15808	}
15809
15810	/**
15811	 * Returns the current font style.
15812	 * @return string current font style
15813	 * @public
15814	 * @since 4.3.008 (2008-12-05)
15815	 */
15816	public function getFontStyle() {
15817		return $this->FontStyle;
15818	}
15819
15820	/**
15821	 * Cleanup HTML code (requires HTML Tidy library).
15822	 * @param $html (string) htmlcode to fix
15823	 * @param $default_css (string) CSS commands to add
15824	 * @param $tagvs (array) parameters for setHtmlVSpace method
15825	 * @param $tidy_options (array) options for tidy_parse_string function
15826	 * @return string XHTML code cleaned up
15827	 * @author Nicola Asuni
15828	 * @public
15829	 * @since 5.9.017 (2010-11-16)
15830	 * @see setHtmlVSpace()
15831	 */
15832	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15833		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15834	}
15835
15836	/**
15837	 * Returns the border width from CSS property
15838	 * @param $width (string) border width
15839	 * @return int with in user units
15840	 * @protected
15841	 * @since 5.7.000 (2010-08-02)
15842	 */
15843	protected function getCSSBorderWidth($width) {
15844		if ($width == 'thin') {
15845			$width = (2 / $this->k);
15846		} elseif ($width == 'medium') {
15847			$width = (4 / $this->k);
15848		} elseif ($width == 'thick') {
15849			$width = (6 / $this->k);
15850		} else {
15851			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15852		}
15853		return $width;
15854	}
15855
15856	/**
15857	 * Returns the border dash style from CSS property
15858	 * @param $style (string) border style to convert
15859	 * @return int sash style (return -1 in case of none or hidden border)
15860	 * @protected
15861	 * @since 5.7.000 (2010-08-02)
15862	 */
15863	protected function getCSSBorderDashStyle($style) {
15864		switch (strtolower($style)) {
15865			case 'none':
15866			case 'hidden': {
15867				$dash = -1;
15868				break;
15869			}
15870			case 'dotted': {
15871				$dash = 1;
15872				break;
15873			}
15874			case 'dashed': {
15875				$dash = 3;
15876				break;
15877			}
15878			case 'double':
15879			case 'groove':
15880			case 'ridge':
15881			case 'inset':
15882			case 'outset':
15883			case 'solid':
15884			default: {
15885				$dash = 0;
15886				break;
15887			}
15888		}
15889		return $dash;
15890	}
15891
15892	/**
15893	 * Returns the border style array from CSS border properties
15894	 * @param $cssborder (string) border properties
15895	 * @return array containing border properties
15896	 * @protected
15897	 * @since 5.7.000 (2010-08-02)
15898	 */
15899	protected function getCSSBorderStyle($cssborder) {
15900		$bprop = preg_split('/[\s]+/', trim($cssborder));
15901		$border = array(); // value to be returned
15902		switch (count($bprop)) {
15903			case 3: {
15904				$width = $bprop[0];
15905				$style = $bprop[1];
15906				$color = $bprop[2];
15907				break;
15908			}
15909			case 2: {
15910				$width = 'medium';
15911				$style = $bprop[0];
15912				$color = $bprop[1];
15913				break;
15914			}
15915			case 1: {
15916				$width = 'medium';
15917				$style = $bprop[0];
15918				$color = 'black';
15919				break;
15920			}
15921			default: {
15922				$width = 'medium';
15923				$style = 'solid';
15924				$color = 'black';
15925				break;
15926			}
15927		}
15928		if ($style == 'none') {
15929			return array();
15930		}
15931		$border['cap'] = 'square';
15932		$border['join'] = 'miter';
15933		$border['dash'] = $this->getCSSBorderDashStyle($style);
15934		if ($border['dash'] < 0) {
15935			return array();
15936		}
15937		$border['width'] = $this->getCSSBorderWidth($width);
15938		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
15939		return $border;
15940	}
15941
15942	/**
15943	 * Get the internal Cell padding from CSS attribute.
15944	 * @param $csspadding (string) padding properties
15945	 * @param $width (float) width of the containing element
15946	 * @return array of cell paddings
15947	 * @public
15948	 * @since 5.9.000 (2010-10-04)
15949	 */
15950	public function getCSSPadding($csspadding, $width=0) {
15951		$padding = preg_split('/[\s]+/', trim($csspadding));
15952		$cell_padding = array(); // value to be returned
15953		switch (count($padding)) {
15954			case 4: {
15955				$cell_padding['T'] = $padding[0];
15956				$cell_padding['R'] = $padding[1];
15957				$cell_padding['B'] = $padding[2];
15958				$cell_padding['L'] = $padding[3];
15959				break;
15960			}
15961			case 3: {
15962				$cell_padding['T'] = $padding[0];
15963				$cell_padding['R'] = $padding[1];
15964				$cell_padding['B'] = $padding[2];
15965				$cell_padding['L'] = $padding[1];
15966				break;
15967			}
15968			case 2: {
15969				$cell_padding['T'] = $padding[0];
15970				$cell_padding['R'] = $padding[1];
15971				$cell_padding['B'] = $padding[0];
15972				$cell_padding['L'] = $padding[1];
15973				break;
15974			}
15975			case 1: {
15976				$cell_padding['T'] = $padding[0];
15977				$cell_padding['R'] = $padding[0];
15978				$cell_padding['B'] = $padding[0];
15979				$cell_padding['L'] = $padding[0];
15980				break;
15981			}
15982			default: {
15983				return $this->cell_padding;
15984			}
15985		}
15986		if ($width == 0) {
15987			$width = $this->w - $this->lMargin - $this->rMargin;
15988		}
15989		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
15990		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
15991		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
15992		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
15993		return $cell_padding;
15994	}
15995
15996	/**
15997	 * Get the internal Cell margin from CSS attribute.
15998	 * @param $cssmargin (string) margin properties
15999	 * @param $width (float) width of the containing element
16000	 * @return array of cell margins
16001	 * @public
16002	 * @since 5.9.000 (2010-10-04)
16003	 */
16004	public function getCSSMargin($cssmargin, $width=0) {
16005		$margin = preg_split('/[\s]+/', trim($cssmargin));
16006		$cell_margin = array(); // value to be returned
16007		switch (count($margin)) {
16008			case 4: {
16009				$cell_margin['T'] = $margin[0];
16010				$cell_margin['R'] = $margin[1];
16011				$cell_margin['B'] = $margin[2];
16012				$cell_margin['L'] = $margin[3];
16013				break;
16014			}
16015			case 3: {
16016				$cell_margin['T'] = $margin[0];
16017				$cell_margin['R'] = $margin[1];
16018				$cell_margin['B'] = $margin[2];
16019				$cell_margin['L'] = $margin[1];
16020				break;
16021			}
16022			case 2: {
16023				$cell_margin['T'] = $margin[0];
16024				$cell_margin['R'] = $margin[1];
16025				$cell_margin['B'] = $margin[0];
16026				$cell_margin['L'] = $margin[1];
16027				break;
16028			}
16029			case 1: {
16030				$cell_margin['T'] = $margin[0];
16031				$cell_margin['R'] = $margin[0];
16032				$cell_margin['B'] = $margin[0];
16033				$cell_margin['L'] = $margin[0];
16034				break;
16035			}
16036			default: {
16037				return $this->cell_margin;
16038			}
16039		}
16040		if ($width == 0) {
16041			$width = $this->w - $this->lMargin - $this->rMargin;
16042		}
16043		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16044		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16045		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16046		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16047		return $cell_margin;
16048	}
16049
16050	/**
16051	 * Get the border-spacing from CSS attribute.
16052	 * @param $cssbspace (string) border-spacing CSS properties
16053	 * @param $width (float) width of the containing element
16054	 * @return array of border spacings
16055	 * @public
16056	 * @since 5.9.010 (2010-10-27)
16057	 */
16058	public function getCSSBorderMargin($cssbspace, $width=0) {
16059		$space = preg_split('/[\s]+/', trim($cssbspace));
16060		$border_spacing = array(); // value to be returned
16061		switch (count($space)) {
16062			case 2: {
16063				$border_spacing['H'] = $space[0];
16064				$border_spacing['V'] = $space[1];
16065				break;
16066			}
16067			case 1: {
16068				$border_spacing['H'] = $space[0];
16069				$border_spacing['V'] = $space[0];
16070				break;
16071			}
16072			default: {
16073				return array('H' => 0, 'V' => 0);
16074			}
16075		}
16076		if ($width == 0) {
16077			$width = $this->w - $this->lMargin - $this->rMargin;
16078		}
16079		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16080		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16081		return $border_spacing;
16082	}
16083
16084	/**
16085	 * Returns the letter-spacing value from CSS value
16086	 * @param $spacing (string) letter-spacing value
16087	 * @param $parent (float) font spacing (tracking) value of the parent element
16088	 * @return float quantity to increases or decreases the space between characters in a text.
16089	 * @protected
16090	 * @since 5.9.000 (2010-10-02)
16091	 */
16092	protected function getCSSFontSpacing($spacing, $parent=0) {
16093		$val = 0; // value to be returned
16094		$spacing = trim($spacing);
16095		switch ($spacing) {
16096			case 'normal': {
16097				$val = 0;
16098				break;
16099			}
16100			case 'inherit': {
16101				if ($parent == 'normal') {
16102					$val = 0;
16103				} else {
16104					$val = $parent;
16105				}
16106				break;
16107			}
16108			default: {
16109				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16110			}
16111		}
16112		return $val;
16113	}
16114
16115	/**
16116	 * Returns the percentage of font stretching from CSS value
16117	 * @param $stretch (string) stretch mode
16118	 * @param $parent (float) stretch value of the parent element
16119	 * @return float font stretching percentage
16120	 * @protected
16121	 * @since 5.9.000 (2010-10-02)
16122	 */
16123	protected function getCSSFontStretching($stretch, $parent=100) {
16124		$val = 100; // value to be returned
16125		$stretch = trim($stretch);
16126		switch ($stretch) {
16127			case 'ultra-condensed': {
16128				$val = 40;
16129				break;
16130			}
16131			case 'extra-condensed': {
16132				$val = 55;
16133				break;
16134			}
16135			case 'condensed': {
16136				$val = 70;
16137				break;
16138			}
16139			case 'semi-condensed': {
16140				$val = 85;
16141				break;
16142			}
16143			case 'normal': {
16144				$val = 100;
16145				break;
16146			}
16147			case 'semi-expanded': {
16148				$val = 115;
16149				break;
16150			}
16151			case 'expanded': {
16152				$val = 130;
16153				break;
16154			}
16155			case 'extra-expanded': {
16156				$val = 145;
16157				break;
16158			}
16159			case 'ultra-expanded': {
16160				$val = 160;
16161				break;
16162			}
16163			case 'wider': {
16164				$val = ($parent + 10);
16165				break;
16166			}
16167			case 'narrower': {
16168				$val = ($parent - 10);
16169				break;
16170			}
16171			case 'inherit': {
16172				if ($parent == 'normal') {
16173					$val = 100;
16174				} else {
16175					$val = $parent;
16176				}
16177				break;
16178			}
16179			default: {
16180				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16181			}
16182		}
16183		return $val;
16184	}
16185
16186	/**
16187	 * Convert HTML string containing font size value to points
16188	 * @param $val (string) String containing font size value and unit.
16189	 * @param $refsize (float) Reference font size in points.
16190	 * @param $parent_size (float) Parent font size in points.
16191	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16192	 * @return float value in points
16193	 * @public
16194	 */
16195	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16196		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16197		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16198		switch ($val) {
16199			case 'xx-small': {
16200				$size = ($refsize - 4);
16201				break;
16202			}
16203			case 'x-small': {
16204				$size = ($refsize - 3);
16205				break;
16206			}
16207			case 'small': {
16208				$size = ($refsize - 2);
16209				break;
16210			}
16211			case 'medium': {
16212				$size = $refsize;
16213				break;
16214			}
16215			case 'large': {
16216				$size = ($refsize + 2);
16217				break;
16218			}
16219			case 'x-large': {
16220				$size = ($refsize + 4);
16221				break;
16222			}
16223			case 'xx-large': {
16224				$size = ($refsize + 6);
16225				break;
16226			}
16227			case 'smaller': {
16228				$size = ($parent_size - 3);
16229				break;
16230			}
16231			case 'larger': {
16232				$size = ($parent_size + 3);
16233				break;
16234			}
16235			default: {
16236				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16237			}
16238		}
16239		return $size;
16240	}
16241
16242	/**
16243	 * Returns the HTML DOM array.
16244	 * @param $html (string) html code
16245	 * @return array
16246	 * @protected
16247	 * @since 3.2.000 (2008-06-20)
16248	 */
16249	protected function getHtmlDomArray($html) {
16250		// array of CSS styles ( selector => properties).
16251		$css = array();
16252		// get CSS array defined at previous call
16253		$matches = array();
16254		if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16255			if (isset($matches[1][0])) {
16256				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16257			}
16258			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16259		}
16260		// extract external CSS files
16261		$matches = array();
16262		if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16263			foreach ($matches[1] as $key => $link) {
16264				$type = array();
16265				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16266					$type = array();
16267					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16268					// get 'all' and 'print' media, other media types are discarded
16269					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16270					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16271						$type = array();
16272						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16273							// read CSS data file
16274							$cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16275							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16276								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16277							}
16278						}
16279					}
16280				}
16281			}
16282		}
16283		// extract style tags
16284		$matches = array();
16285		if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16286			foreach ($matches[1] as $key => $media) {
16287				$type = array();
16288				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16289				// get 'all' and 'print' media, other media types are discarded
16290				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16291				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16292					$cssdata = $matches[2][$key];
16293					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16294				}
16295			}
16296		}
16297		// create a special tag to contain the CSS array (used for table content)
16298		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16299		// remove head and style blocks
16300		$html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16301		$html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16302		// define block tags
16303		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16304		// define self-closing tags
16305		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16306		// remove all unsupported tags (the line below lists all supported tags)
16307		$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>');
16308		//replace some blank characters
16309		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16310		$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);
16311		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16312		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16313		$html = strtr($html, $repTable);
16314		$offset = 0;
16315		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16316			$html_a = substr($html, 0, $offset);
16317			$html_b = substr($html, $offset, ($pos - $offset + 6));
16318			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16319				// preserve newlines on <pre> tag
16320				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16321			}
16322			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16323				// preserve spaces on <pre> tag
16324				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16325			}
16326			$html = $html_a.$html_b.substr($html, $pos + 6);
16327			$offset = strlen($html_a.$html_b);
16328		}
16329		$offset = 0;
16330		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16331			$html_a = substr($html, 0, $offset);
16332			$html_b = substr($html, $offset, ($pos - $offset + 11));
16333			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16334				// preserve newlines on <textarea> tag
16335				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16336				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16337			}
16338			$html = $html_a.$html_b.substr($html, $pos + 11);
16339			$offset = strlen($html_a.$html_b);
16340		}
16341		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16342		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16343		$offset = 0;
16344		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16345			$html_a = substr($html, 0, $offset);
16346			$html_b = substr($html, $offset, ($pos - $offset + 9));
16347			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16348				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16349				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16350			}
16351			$html = $html_a.$html_b.substr($html, $pos + 9);
16352			$offset = strlen($html_a.$html_b);
16353		}
16354		if (preg_match("'</select'si", $html)) {
16355			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16356			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16357		}
16358		$html = str_replace("\n", ' ', $html);
16359		// restore textarea newlines
16360		$html = str_replace('<TBR>', "\n", $html);
16361		// remove extra spaces from code
16362		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16363		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16364		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16365		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16366		$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);
16367		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16368		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16369		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16370		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16371		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16372		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16373		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16374		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16375		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16376		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16377		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16378		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16379		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16380		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16381		// trim string
16382		$html = $this->stringTrim($html);
16383		// fix br tag after li
16384		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16385		// fix first image tag alignment
16386		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16387		// pattern for generic tag
16388		$tagpattern = '/(<[^>]+>)/';
16389		// explodes the string
16390		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16391		// count elements
16392		$maxel = count($a);
16393		$elkey = 0;
16394		$key = 0;
16395		// create an array of elements
16396		$dom = array();
16397		$dom[$key] = array();
16398		// set inheritable properties fot the first void element
16399		// 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
16400		$dom[$key]['tag'] = false;
16401		$dom[$key]['block'] = false;
16402		$dom[$key]['value'] = '';
16403		$dom[$key]['parent'] = 0;
16404		$dom[$key]['hide'] = false;
16405		$dom[$key]['fontname'] = $this->FontFamily;
16406		$dom[$key]['fontstyle'] = $this->FontStyle;
16407		$dom[$key]['fontsize'] = $this->FontSizePt;
16408		$dom[$key]['font-stretch'] = $this->font_stretching;
16409		$dom[$key]['letter-spacing'] = $this->font_spacing;
16410		$dom[$key]['stroke'] = $this->textstrokewidth;
16411		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16412		$dom[$key]['clip'] = ($this->textrendermode > 3);
16413		$dom[$key]['line-height'] = $this->cell_height_ratio;
16414		$dom[$key]['bgcolor'] = false;
16415		$dom[$key]['fgcolor'] = $this->fgcolor; // color
16416		$dom[$key]['strokecolor'] = $this->strokecolor;
16417		$dom[$key]['align'] = '';
16418		$dom[$key]['listtype'] = '';
16419		$dom[$key]['text-indent'] = 0;
16420		$dom[$key]['text-transform'] = '';
16421		$dom[$key]['border'] = array();
16422		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16423		$thead = false; // true when we are inside the THEAD tag
16424		++$key;
16425		$level = array();
16426		array_push($level, 0); // root
16427		while ($elkey < $maxel) {
16428			$dom[$key] = array();
16429			$element = $a[$elkey];
16430			$dom[$key]['elkey'] = $elkey;
16431			if (preg_match($tagpattern, $element)) {
16432				// html tag
16433				$element = substr($element, 1, -1);
16434				// get tag name
16435				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16436				$tagname = strtolower($tag[1]);
16437				// check if we are inside a table header
16438				if ($tagname == 'thead') {
16439					if ($element[0] == '/') {
16440						$thead = false;
16441					} else {
16442						$thead = true;
16443					}
16444					++$elkey;
16445					continue;
16446				}
16447				$dom[$key]['tag'] = true;
16448				$dom[$key]['value'] = $tagname;
16449				if (in_array($dom[$key]['value'], $blocktags)) {
16450					$dom[$key]['block'] = true;
16451				} else {
16452					$dom[$key]['block'] = false;
16453				}
16454				if ($element[0] == '/') {
16455					// *** closing html tag
16456					$dom[$key]['opening'] = false;
16457					$dom[$key]['parent'] = end($level);
16458					array_pop($level);
16459					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16460					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16461					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16462					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16463					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16464					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16465					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16466					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16467					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16468					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16469					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16470					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16471					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16472					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16473					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16474					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16475					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16476						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16477					}
16478					// set the number of columns in table tag
16479					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16480						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16481					}
16482					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16483						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16484						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16485							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16486						}
16487						$key = $i;
16488						// mark nested tables
16489						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16490						// remove thead sections from nested tables
16491						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16492						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16493					}
16494					// store header rows on a new table
16495					if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16496						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16497							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16498						}
16499						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16500							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16501						}
16502						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16503							$dom[($dom[$key]['parent'])]['attribute'] = array();
16504						}
16505						// header elements must be always contained in a single page
16506						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16507					}
16508					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16509						// remove the nobr attributes from the table header
16510						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16511						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16512					}
16513				} else {
16514					// *** opening or self-closing html tag
16515					$dom[$key]['opening'] = true;
16516					$dom[$key]['parent'] = end($level);
16517					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16518						// self-closing tag
16519						$dom[$key]['self'] = true;
16520					} else {
16521						// opening tag
16522						array_push($level, $key);
16523						$dom[$key]['self'] = false;
16524					}
16525					// copy some values from parent
16526					$parentkey = 0;
16527					if ($key > 0) {
16528						$parentkey = $dom[$key]['parent'];
16529						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16530						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16531						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16532						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16533						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16534						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16535						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16536						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16537						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16538						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16539						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16540						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16541						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16542						$dom[$key]['align'] = $dom[$parentkey]['align'];
16543						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16544						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16545						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16546						$dom[$key]['border'] = array();
16547						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16548					}
16549					// get attributes
16550					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16551					$dom[$key]['attribute'] = array(); // reset attribute array
16552					while (list($id, $name) = each($attr_array[1])) {
16553						$dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16554					}
16555					if (!empty($css)) {
16556						// merge CSS style to current style
16557						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16558						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16559					}
16560					// split style attributes
16561					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16562						// get style attributes
16563						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16564						$dom[$key]['style'] = array(); // reset style attribute array
16565						while (list($id, $name) = each($style_array[1])) {
16566							// in case of duplicate attribute the last replace the previous
16567							$dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16568						}
16569						// --- get some style attributes ---
16570						// text direction
16571						if (isset($dom[$key]['style']['direction'])) {
16572							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16573						}
16574						// display
16575						if (isset($dom[$key]['style']['display'])) {
16576							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16577						}
16578						// font family
16579						if (isset($dom[$key]['style']['font-family'])) {
16580							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16581						}
16582						// list-style-type
16583						if (isset($dom[$key]['style']['list-style-type'])) {
16584							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16585							if ($dom[$key]['listtype'] == 'inherit') {
16586								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16587							}
16588						}
16589						// text-indent
16590						if (isset($dom[$key]['style']['text-indent'])) {
16591							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16592							if ($dom[$key]['text-indent'] == 'inherit') {
16593								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16594							}
16595						}
16596						// text-transform
16597						if (isset($dom[$key]['style']['text-transform'])) {
16598							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16599						}
16600						// font size
16601						if (isset($dom[$key]['style']['font-size'])) {
16602							$fsize = trim($dom[$key]['style']['font-size']);
16603							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16604						}
16605						// font-stretch
16606						if (isset($dom[$key]['style']['font-stretch'])) {
16607							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16608						}
16609						// letter-spacing
16610						if (isset($dom[$key]['style']['letter-spacing'])) {
16611							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16612						}
16613						// line-height (internally is the cell height ratio)
16614						if (isset($dom[$key]['style']['line-height'])) {
16615							$lineheight = trim($dom[$key]['style']['line-height']);
16616							switch ($lineheight) {
16617								// A normal line height. This is default
16618								case 'normal': {
16619									$dom[$key]['line-height'] = $dom[0]['line-height'];
16620									break;
16621								}
16622								case 'inherit': {
16623									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16624								}
16625								default: {
16626									if (is_numeric($lineheight)) {
16627										// convert to percentage of font height
16628										$lineheight = ($lineheight * 100).'%';
16629									}
16630									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16631									if (substr($lineheight, -1) !== '%') {
16632										if ($dom[$key]['fontsize'] <= 0) {
16633											$dom[$key]['line-height'] = 1;
16634										} else {
16635											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16636										}
16637									}
16638								}
16639							}
16640						}
16641						// font style
16642						if (isset($dom[$key]['style']['font-weight'])) {
16643							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16644								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16645									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16646								}
16647							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16648								$dom[$key]['fontstyle'] .= 'B';
16649							}
16650						}
16651						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16652							$dom[$key]['fontstyle'] .= 'I';
16653						}
16654						// font color
16655						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16656							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16657						} elseif ($dom[$key]['value'] == 'a') {
16658							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16659						}
16660						// background color
16661						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16662							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16663						}
16664						// text-decoration
16665						if (isset($dom[$key]['style']['text-decoration'])) {
16666							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16667							foreach ($decors as $dec) {
16668								$dec = trim($dec);
16669								if (!TCPDF_STATIC::empty_string($dec)) {
16670									if ($dec[0] == 'u') {
16671										// underline
16672										$dom[$key]['fontstyle'] .= 'U';
16673									} elseif ($dec[0] == 'l') {
16674										// line-through
16675										$dom[$key]['fontstyle'] .= 'D';
16676									} elseif ($dec[0] == 'o') {
16677										// overline
16678										$dom[$key]['fontstyle'] .= 'O';
16679									}
16680								}
16681							}
16682						} elseif ($dom[$key]['value'] == 'a') {
16683							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16684						}
16685						// check for width attribute
16686						if (isset($dom[$key]['style']['width'])) {
16687							$dom[$key]['width'] = $dom[$key]['style']['width'];
16688						}
16689						// check for height attribute
16690						if (isset($dom[$key]['style']['height'])) {
16691							$dom[$key]['height'] = $dom[$key]['style']['height'];
16692						}
16693						// check for text alignment
16694						if (isset($dom[$key]['style']['text-align'])) {
16695							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16696						}
16697						// check for CSS border properties
16698						if (isset($dom[$key]['style']['border'])) {
16699							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16700							if (!empty($borderstyle)) {
16701								$dom[$key]['border']['LTRB'] = $borderstyle;
16702							}
16703						}
16704						if (isset($dom[$key]['style']['border-color'])) {
16705							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16706							if (isset($brd_colors[3])) {
16707								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16708							}
16709							if (isset($brd_colors[1])) {
16710								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16711							}
16712							if (isset($brd_colors[0])) {
16713								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16714							}
16715							if (isset($brd_colors[2])) {
16716								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16717							}
16718						}
16719						if (isset($dom[$key]['style']['border-width'])) {
16720							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16721							if (isset($brd_widths[3])) {
16722								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16723							}
16724							if (isset($brd_widths[1])) {
16725								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16726							}
16727							if (isset($brd_widths[0])) {
16728								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16729							}
16730							if (isset($brd_widths[2])) {
16731								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16732							}
16733						}
16734						if (isset($dom[$key]['style']['border-style'])) {
16735							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16736							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16737								$dom[$key]['border']['L']['cap'] = 'square';
16738								$dom[$key]['border']['L']['join'] = 'miter';
16739								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16740								if ($dom[$key]['border']['L']['dash'] < 0) {
16741									$dom[$key]['border']['L'] = array();
16742								}
16743							}
16744							if (isset($brd_styles[1])) {
16745								$dom[$key]['border']['R']['cap'] = 'square';
16746								$dom[$key]['border']['R']['join'] = 'miter';
16747								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16748								if ($dom[$key]['border']['R']['dash'] < 0) {
16749									$dom[$key]['border']['R'] = array();
16750								}
16751							}
16752							if (isset($brd_styles[0])) {
16753								$dom[$key]['border']['T']['cap'] = 'square';
16754								$dom[$key]['border']['T']['join'] = 'miter';
16755								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16756								if ($dom[$key]['border']['T']['dash'] < 0) {
16757									$dom[$key]['border']['T'] = array();
16758								}
16759							}
16760							if (isset($brd_styles[2])) {
16761								$dom[$key]['border']['B']['cap'] = 'square';
16762								$dom[$key]['border']['B']['join'] = 'miter';
16763								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16764								if ($dom[$key]['border']['B']['dash'] < 0) {
16765									$dom[$key]['border']['B'] = array();
16766								}
16767							}
16768						}
16769						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16770						foreach ($cellside as $bsk => $bsv) {
16771							if (isset($dom[$key]['style']['border-'.$bsv])) {
16772								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16773								if (!empty($borderstyle)) {
16774									$dom[$key]['border'][$bsk] = $borderstyle;
16775								}
16776							}
16777							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16778								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16779							}
16780							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16781								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16782							}
16783							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16784								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16785								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16786									$dom[$key]['border'][$bsk] = array();
16787								}
16788							}
16789						}
16790						// check for CSS padding properties
16791						if (isset($dom[$key]['style']['padding'])) {
16792							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16793						} else {
16794							$dom[$key]['padding'] = $this->cell_padding;
16795						}
16796						foreach ($cellside as $psk => $psv) {
16797							if (isset($dom[$key]['style']['padding-'.$psv])) {
16798								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16799							}
16800						}
16801						// check for CSS margin properties
16802						if (isset($dom[$key]['style']['margin'])) {
16803							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16804						} else {
16805							$dom[$key]['margin'] = $this->cell_margin;
16806						}
16807						foreach ($cellside as $psk => $psv) {
16808							if (isset($dom[$key]['style']['margin-'.$psv])) {
16809								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16810							}
16811						}
16812						// check for CSS border-spacing properties
16813						if (isset($dom[$key]['style']['border-spacing'])) {
16814							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16815						}
16816						// page-break-inside
16817						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16818							$dom[$key]['attribute']['nobr'] = 'true';
16819						}
16820						// page-break-before
16821						if (isset($dom[$key]['style']['page-break-before'])) {
16822							if ($dom[$key]['style']['page-break-before'] == 'always') {
16823								$dom[$key]['attribute']['pagebreak'] = 'true';
16824							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16825								$dom[$key]['attribute']['pagebreak'] = 'left';
16826							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16827								$dom[$key]['attribute']['pagebreak'] = 'right';
16828							}
16829						}
16830						// page-break-after
16831						if (isset($dom[$key]['style']['page-break-after'])) {
16832							if ($dom[$key]['style']['page-break-after'] == 'always') {
16833								$dom[$key]['attribute']['pagebreakafter'] = 'true';
16834							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16835								$dom[$key]['attribute']['pagebreakafter'] = 'left';
16836							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16837								$dom[$key]['attribute']['pagebreakafter'] = 'right';
16838							}
16839						}
16840					}
16841					if (isset($dom[$key]['attribute']['display'])) {
16842						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16843					}
16844					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16845						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16846						if (!empty($borderstyle)) {
16847							$dom[$key]['border']['LTRB'] = $borderstyle;
16848						}
16849					}
16850					// check for font tag
16851					if ($dom[$key]['value'] == 'font') {
16852						// font family
16853						if (isset($dom[$key]['attribute']['face'])) {
16854							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16855						}
16856						// font size
16857						if (isset($dom[$key]['attribute']['size'])) {
16858							if ($key > 0) {
16859								if ($dom[$key]['attribute']['size'][0] == '+') {
16860									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16861								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
16862									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16863								} else {
16864									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16865								}
16866							} else {
16867								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16868							}
16869						}
16870					}
16871					// force natural alignment for lists
16872					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16873						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16874						if ($this->rtl) {
16875							$dom[$key]['align'] = 'R';
16876						} else {
16877							$dom[$key]['align'] = 'L';
16878						}
16879					}
16880					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16881						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16882							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16883						}
16884					}
16885					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16886						$dom[$key]['fontstyle'] .= 'B';
16887					}
16888					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16889						$dom[$key]['fontstyle'] .= 'I';
16890					}
16891					if ($dom[$key]['value'] == 'u') {
16892						$dom[$key]['fontstyle'] .= 'U';
16893					}
16894					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16895						$dom[$key]['fontstyle'] .= 'D';
16896					}
16897					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16898						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16899					}
16900					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16901						$dom[$key]['fontname'] = $this->default_monospaced_font;
16902					}
16903					if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
16904						// headings h1, h2, h3, h4, h5, h6
16905						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16906							$headsize = (4 - intval($dom[$key]['value']{1})) * 2;
16907							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16908						}
16909						if (!isset($dom[$key]['style']['font-weight'])) {
16910							$dom[$key]['fontstyle'] .= 'B';
16911						}
16912					}
16913					if (($dom[$key]['value'] == 'table')) {
16914						$dom[$key]['rows'] = 0; // number of rows
16915						$dom[$key]['trids'] = array(); // IDs of TR elements
16916						$dom[$key]['thead'] = ''; // table header rows
16917					}
16918					if (($dom[$key]['value'] == 'tr')) {
16919						$dom[$key]['cols'] = 0;
16920						if ($thead) {
16921							$dom[$key]['thead'] = true;
16922							// rows on thead block are printed as a separate table
16923						} else {
16924							$dom[$key]['thead'] = false;
16925							// store the number of rows on table element
16926							++$dom[($dom[$key]['parent'])]['rows'];
16927							// store the TR elements IDs on table element
16928							array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16929						}
16930					}
16931					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16932						if (isset($dom[$key]['attribute']['colspan'])) {
16933							$colspan = intval($dom[$key]['attribute']['colspan']);
16934						} else {
16935							$colspan = 1;
16936						}
16937						$dom[$key]['attribute']['colspan'] = $colspan;
16938						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
16939					}
16940					// text direction
16941					if (isset($dom[$key]['attribute']['dir'])) {
16942						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
16943					}
16944					// set foreground color attribute
16945					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
16946						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
16947					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
16948						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16949					}
16950					// set background color attribute
16951					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
16952						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
16953					}
16954					// set stroke color attribute
16955					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
16956						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
16957					}
16958					// check for width attribute
16959					if (isset($dom[$key]['attribute']['width'])) {
16960						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
16961					}
16962					// check for height attribute
16963					if (isset($dom[$key]['attribute']['height'])) {
16964						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
16965					}
16966					// check for text alignment
16967					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
16968						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
16969					}
16970					// check for text rendering mode (the following attributes do not exist in HTML)
16971					if (isset($dom[$key]['attribute']['stroke'])) {
16972						// font stroke width
16973						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
16974					}
16975					if (isset($dom[$key]['attribute']['fill'])) {
16976						// font fill
16977						if ($dom[$key]['attribute']['fill'] == 'true') {
16978							$dom[$key]['fill'] = true;
16979						} else {
16980							$dom[$key]['fill'] = false;
16981						}
16982					}
16983					if (isset($dom[$key]['attribute']['clip'])) {
16984						// clipping mode
16985						if ($dom[$key]['attribute']['clip'] == 'true') {
16986							$dom[$key]['clip'] = true;
16987						} else {
16988							$dom[$key]['clip'] = false;
16989						}
16990					}
16991				} // end opening tag
16992			} else {
16993				// text
16994				$dom[$key]['tag'] = false;
16995				$dom[$key]['block'] = false;
16996				$dom[$key]['parent'] = end($level);
16997				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
16998				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
16999					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17000					if (function_exists('mb_convert_case')) {
17001						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17002						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17003							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17004						}
17005					} elseif (!$this->isunicode) {
17006						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17007							case 'capitalize': {
17008								$element = ucwords(strtolower($element));
17009								break;
17010							}
17011							case 'uppercase': {
17012								$element = strtoupper($element);
17013								break;
17014							}
17015							case 'lowercase': {
17016								$element = strtolower($element);
17017								break;
17018							}
17019						}
17020					}
17021				}
17022				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17023			}
17024			++$elkey;
17025			++$key;
17026		}
17027		return $dom;
17028	}
17029
17030	/**
17031	 * Returns the string used to find spaces
17032	 * @return string
17033	 * @protected
17034	 * @author Nicola Asuni
17035	 * @since 4.8.024 (2010-01-15)
17036	 */
17037	protected function getSpaceString() {
17038		$spacestr = chr(32);
17039		if ($this->isUnicodeFont()) {
17040			$spacestr = chr(0).chr(32);
17041		}
17042		return $spacestr;
17043	}
17044
17045	/**
17046	 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17047	 * @param $data (string) serialized data
17048	 * @return string
17049	 * @public static
17050	 */
17051	protected function getHashForTCPDFtagParams($data) {
17052		return md5(strlen($data).$this->file_id.$data);
17053	}
17054
17055	/**
17056	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17057	 * @param $data (array) parameters array
17058	 * @return string containing serialized data
17059	 * @public static
17060	 */
17061	public function serializeTCPDFtagParameters($data) {
17062		$encoded = urlencode(json_encode($data));
17063		return $this->getHashForTCPDFtagParams($encoded).$encoded;
17064	}
17065
17066	/**
17067	 * Unserialize parameters to be used with TCPDF tag in HTML code.
17068	 * @param $data (string) serialized data
17069	 * @return array containing unserialized data
17070	 * @protected static
17071	 */
17072	protected function unserializeTCPDFtagParameters($data) {
17073		$hash = substr($data, 0, 32);
17074		$encoded = substr($data, 32);
17075		if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17076			$this->Error('Invalid parameters');
17077		}
17078		return json_decode(urldecode($encoded), true);
17079	}
17080
17081	/**
17082	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17083	 * 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 />
17084	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17085	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17086	 * 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
17087	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17088	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17089	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17090	 * @param $x (float) upper-left corner X coordinate
17091	 * @param $y (float) upper-left corner Y coordinate
17092	 * @param $html (string) html text to print. Default value: empty string.
17093	 * @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)))
17094	 * @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>
17095Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17096	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17097	 * @param $reseth (boolean) if true reset the last cell height (default true).
17098	 * @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>
17099	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17100	 * @see Multicell(), writeHTML()
17101	 * @public
17102	 */
17103	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17104		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17105	}
17106
17107	/**
17108	 * Allows to preserve some HTML formatting (limited support).<br />
17109	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17110	 * 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
17111	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17112	 * @param $html (string) text to display
17113	 * @param $ln (boolean) if true add a new line after text (default = true)
17114	 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17115	 * @param $reseth (boolean) if true reset the last cell height (default false).
17116	 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17117	 * @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>
17118	 * @public
17119	 */
17120	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17121		$gvars = $this->getGraphicVars();
17122		// store current values
17123		$prev_cell_margin = $this->cell_margin;
17124		$prev_cell_padding = $this->cell_padding;
17125		$prevPage = $this->page;
17126		$prevlMargin = $this->lMargin;
17127		$prevrMargin = $this->rMargin;
17128		$curfontname = $this->FontFamily;
17129		$curfontstyle = $this->FontStyle;
17130		$curfontsize = $this->FontSizePt;
17131		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17132		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17133		$curfontstretcing = $this->font_stretching;
17134		$curfonttracking = $this->font_spacing;
17135		$this->newline = true;
17136		$newline = true;
17137		$startlinepage = $this->page;
17138		$minstartliney = $this->y;
17139		$maxbottomliney = 0;
17140		$startlinex = $this->x;
17141		$startliney = $this->y;
17142		$yshift = 0;
17143		$loop = 0;
17144		$curpos = 0;
17145		$this_method_vars = array();
17146		$undo = false;
17147		$fontaligned = false;
17148		$reverse_dir = false; // true when the text direction is reversed
17149		$this->premode = false;
17150		if ($this->inxobj) {
17151			// we are inside an XObject template
17152			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17153		} elseif (isset($this->PageAnnots[$this->page])) {
17154			$pask = count($this->PageAnnots[$this->page]);
17155		} else {
17156			$pask = 0;
17157		}
17158		if ($this->inxobj) {
17159			// we are inside an XObject template
17160			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17161		} elseif (!$this->InFooter) {
17162			if (isset($this->footerlen[$this->page])) {
17163				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17164			} else {
17165				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17166			}
17167			$startlinepos = $this->footerpos[$this->page];
17168		} else {
17169			// we are inside the footer
17170			$startlinepos = $this->pagelen[$this->page];
17171		}
17172		$lalign = $align;
17173		$plalign = $align;
17174		if ($this->rtl) {
17175			$w = $this->x - $this->lMargin;
17176		} else {
17177			$w = $this->w - $this->rMargin - $this->x;
17178		}
17179		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17180		if ($cell) {
17181			if ($this->rtl) {
17182				$this->x -= $this->cell_padding['R'];
17183				$this->lMargin += $this->cell_padding['R'];
17184			} else {
17185				$this->x += $this->cell_padding['L'];
17186				$this->rMargin += $this->cell_padding['L'];
17187			}
17188		}
17189		if ($this->customlistindent >= 0) {
17190			$this->listindent = $this->customlistindent;
17191		} else {
17192			$this->listindent = $this->GetStringWidth('000000');
17193		}
17194		$this->listindentlevel = 0;
17195		// save previous states
17196		$prev_cell_height_ratio = $this->cell_height_ratio;
17197		$prev_listnum = $this->listnum;
17198		$prev_listordered = $this->listordered;
17199		$prev_listcount = $this->listcount;
17200		$prev_lispacer = $this->lispacer;
17201		$this->listnum = 0;
17202		$this->listordered = array();
17203		$this->listcount = array();
17204		$this->lispacer = '';
17205		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17206			// reset row height
17207			$this->resetLastH();
17208		}
17209		$dom = $this->getHtmlDomArray($html);
17210		$maxel = count($dom);
17211		$key = 0;
17212		while ($key < $maxel) {
17213			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17214				// store the node key
17215				$hidden_node_key = $key;
17216				if ($dom[$key]['self']) {
17217					// skip just this self-closing tag
17218					++$key;
17219				} else {
17220					// skip this and all children tags
17221					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17222						// skip hidden objects
17223						++$key;
17224					}
17225					++$key;
17226				}
17227			}
17228			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17229				// check for pagebreak
17230				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17231					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17232					$this->checkPageBreak($this->PageBreakTrigger + 1);
17233					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17234				}
17235				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17236					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17237					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17238					$this->checkPageBreak($this->PageBreakTrigger + 1);
17239					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17240				}
17241			}
17242			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17243				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17244					$dom[$key]['attribute']['nobr'] = false;
17245				} else {
17246					// store current object
17247					$this->startTransaction();
17248					// save this method vars
17249					$this_method_vars['html'] = $html;
17250					$this_method_vars['ln'] = $ln;
17251					$this_method_vars['fill'] = $fill;
17252					$this_method_vars['reseth'] = $reseth;
17253					$this_method_vars['cell'] = $cell;
17254					$this_method_vars['align'] = $align;
17255					$this_method_vars['gvars'] = $gvars;
17256					$this_method_vars['prevPage'] = $prevPage;
17257					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17258					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17259					$this_method_vars['prevlMargin'] = $prevlMargin;
17260					$this_method_vars['prevrMargin'] = $prevrMargin;
17261					$this_method_vars['curfontname'] = $curfontname;
17262					$this_method_vars['curfontstyle'] = $curfontstyle;
17263					$this_method_vars['curfontsize'] = $curfontsize;
17264					$this_method_vars['curfontascent'] = $curfontascent;
17265					$this_method_vars['curfontdescent'] = $curfontdescent;
17266					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17267					$this_method_vars['curfonttracking'] = $curfonttracking;
17268					$this_method_vars['minstartliney'] = $minstartliney;
17269					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17270					$this_method_vars['yshift'] = $yshift;
17271					$this_method_vars['startlinepage'] = $startlinepage;
17272					$this_method_vars['startlinepos'] = $startlinepos;
17273					$this_method_vars['startlinex'] = $startlinex;
17274					$this_method_vars['startliney'] = $startliney;
17275					$this_method_vars['newline'] = $newline;
17276					$this_method_vars['loop'] = $loop;
17277					$this_method_vars['curpos'] = $curpos;
17278					$this_method_vars['pask'] = $pask;
17279					$this_method_vars['lalign'] = $lalign;
17280					$this_method_vars['plalign'] = $plalign;
17281					$this_method_vars['w'] = $w;
17282					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17283					$this_method_vars['prev_listnum'] = $prev_listnum;
17284					$this_method_vars['prev_listordered'] = $prev_listordered;
17285					$this_method_vars['prev_listcount'] = $prev_listcount;
17286					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17287					$this_method_vars['fontaligned'] = $fontaligned;
17288					$this_method_vars['key'] = $key;
17289					$this_method_vars['dom'] = $dom;
17290				}
17291			}
17292			// print THEAD block
17293			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17294				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17295					$this->inthead = true;
17296					// print table header (thead)
17297					$this->writeHTML($this->thead, false, false, false, false, '');
17298					// check if we are on a new page or on a new column
17299					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17300						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17301						// restore previous object
17302						$this->rollbackTransaction(true);
17303						// restore previous values
17304						foreach ($this_method_vars as $vkey => $vval) {
17305							$$vkey = $vval;
17306						}
17307						// disable table header
17308						$tmp_thead = $this->thead;
17309						$this->thead = '';
17310						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17311						$pre_y = $this->y;
17312						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17313							// fix for multicolumn mode
17314							$startliney = $this->y;
17315						}
17316						$this->start_transaction_page = $this->page;
17317						$this->start_transaction_y = $this->y;
17318						// restore table header
17319						$this->thead = $tmp_thead;
17320						// fix table border properties
17321						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17322							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17323						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17324							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17325						} else {
17326							$tmp_cellspacing = 0;
17327						}
17328						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17329						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17330						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17331						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17332						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17333						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17334						// print table header (thead)
17335						$this->writeHTML($this->thead, false, false, false, false, '');
17336					}
17337				}
17338				// move $key index forward to skip THEAD block
17339				while ( ($key < $maxel) AND (!(
17340					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17341					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17342					++$key;
17343				}
17344			}
17345			if ($dom[$key]['tag'] OR ($key == 0)) {
17346				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17347					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17348				}
17349				// vertically align image in line
17350				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17351					// get image height
17352					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17353					$autolinebreak = false;
17354					if (!empty($dom[$key]['width'])) {
17355						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17356						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17357							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17358							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17359							// add automatic line break
17360							$autolinebreak = true;
17361							$this->Ln('', $cell);
17362							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17363								// go back to evaluate this line break
17364								--$key;
17365							}
17366						}
17367					}
17368					if (!$autolinebreak) {
17369						if ($this->inPageBody()) {
17370							$pre_y = $this->y;
17371							// check for page break
17372							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17373								// fix for multicolumn mode
17374								$startliney = $this->y;
17375							}
17376						}
17377						if ($this->page > $startlinepage) {
17378							// fix line splitted over two pages
17379							if (isset($this->footerlen[$startlinepage])) {
17380								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17381							}
17382							// line to be moved one page forward
17383							$pagebuff = $this->getPageBuffer($startlinepage);
17384							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17385							$tstart = substr($pagebuff, 0, $startlinepos);
17386							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17387							// remove line from previous page
17388							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17389							$pagebuff = $this->getPageBuffer($this->page);
17390							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17391							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17392							// add line start to current page
17393							$yshift = ($minstartliney - $this->y);
17394							if ($fontaligned) {
17395								$yshift += ($curfontsize / $this->k);
17396							}
17397							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17398							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17399							// shift the annotations and links
17400							if (isset($this->PageAnnots[$this->page])) {
17401								$next_pask = count($this->PageAnnots[$this->page]);
17402							} else {
17403								$next_pask = 0;
17404							}
17405							if (isset($this->PageAnnots[$startlinepage])) {
17406								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17407									if ($pak >= $pask) {
17408										$this->PageAnnots[$this->page][] = $pac;
17409										unset($this->PageAnnots[$startlinepage][$pak]);
17410										$npak = count($this->PageAnnots[$this->page]) - 1;
17411										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17412									}
17413								}
17414							}
17415							$pask = $next_pask;
17416							$startlinepos = $this->cntmrk[$this->page];
17417							$startlinepage = $this->page;
17418							$startliney = $this->y;
17419							$this->newline = false;
17420						}
17421						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17422						$minstartliney = min($this->y, $minstartliney);
17423						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17424					}
17425				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17426					// account for different font size
17427					$pfontname = $curfontname;
17428					$pfontstyle = $curfontstyle;
17429					$pfontsize = $curfontsize;
17430					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17431					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17432					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17433					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17434					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17435					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17436						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17437						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17438						if (($key < ($maxel - 1)) AND (
17439								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17440								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17441								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17442								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17443								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17444							)) {
17445							if ($this->page > $startlinepage) {
17446								// fix lines splitted over two pages
17447								if (isset($this->footerlen[$startlinepage])) {
17448									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17449								}
17450								// line to be moved one page forward
17451								$pagebuff = $this->getPageBuffer($startlinepage);
17452								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17453								$tstart = substr($pagebuff, 0, $startlinepos);
17454								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17455								// remove line start from previous page
17456								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17457								$pagebuff = $this->getPageBuffer($this->page);
17458								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17459								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17460								// add line start to current page
17461								$yshift = ($minstartliney - $this->y);
17462								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17463								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17464								// shift the annotations and links
17465								if (isset($this->PageAnnots[$this->page])) {
17466									$next_pask = count($this->PageAnnots[$this->page]);
17467								} else {
17468									$next_pask = 0;
17469								}
17470								if (isset($this->PageAnnots[$startlinepage])) {
17471									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17472										if ($pak >= $pask) {
17473											$this->PageAnnots[$this->page][] = $pac;
17474											unset($this->PageAnnots[$startlinepage][$pak]);
17475											$npak = count($this->PageAnnots[$this->page]) - 1;
17476											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17477										}
17478									}
17479								}
17480								$pask = $next_pask;
17481								$startlinepos = $this->cntmrk[$this->page];
17482								$startlinepage = $this->page;
17483								$startliney = $this->y;
17484							}
17485							if (!isset($dom[$key]['line-height'])) {
17486								$dom[$key]['line-height'] = $this->cell_height_ratio;
17487							}
17488							if (!$dom[$key]['block']) {
17489								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']))) {
17490									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17491								}
17492								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17493									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17494									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)))) {
17495										$minstartliney = min($this->y, $line_align_data[1]);
17496										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17497									} else {
17498										$minstartliney = min($this->y, $minstartliney);
17499										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17500									}
17501									$line_align_data = $current_line_align_data;
17502								}
17503							}
17504							$this->cell_height_ratio = $dom[$key]['line-height'];
17505							$fontaligned = true;
17506						}
17507						$this->SetFont($fontname, $fontstyle, $fontsize);
17508						// reset row height
17509						$this->resetLastH();
17510						$curfontname = $fontname;
17511						$curfontstyle = $fontstyle;
17512						$curfontsize = $fontsize;
17513						$curfontascent = $fontascent;
17514						$curfontdescent = $fontdescent;
17515					}
17516				}
17517				// set text rendering mode
17518				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17519				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17520				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17521				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17522				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17523					$this->setFontStretching($dom[$key]['font-stretch']);
17524				}
17525				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17526					$this->setFontSpacing($dom[$key]['letter-spacing']);
17527				}
17528				if (($plalign == 'J') AND $dom[$key]['block']) {
17529					$plalign = '';
17530				}
17531				// get current position on page buffer
17532				$curpos = $this->pagelen[$startlinepage];
17533				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17534					$this->SetFillColorArray($dom[$key]['bgcolor']);
17535					$wfill = true;
17536				} else {
17537					$wfill = $fill | false;
17538				}
17539				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17540					$this->SetTextColorArray($dom[$key]['fgcolor']);
17541				}
17542				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17543					$this->SetDrawColorArray($dom[$key]['strokecolor']);
17544				}
17545				if (isset($dom[$key]['align'])) {
17546					$lalign = $dom[$key]['align'];
17547				}
17548				if (TCPDF_STATIC::empty_string($lalign)) {
17549					$lalign = $align;
17550				}
17551			}
17552			// align lines
17553			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17554				$newline = true;
17555				$fontaligned = false;
17556				// we are at the beginning of a new line
17557				if (isset($startlinex)) {
17558					$yshift = ($minstartliney - $startliney);
17559					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17560						$yshift = 0;
17561					}
17562					$t_x = 0;
17563					// the last line must be shifted to be aligned as requested
17564					$linew = abs($this->endlinex - $startlinex);
17565					if ($this->inxobj) {
17566						// we are inside an XObject template
17567						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17568						if (isset($opentagpos)) {
17569							$midpos = $opentagpos;
17570						} else {
17571							$midpos = 0;
17572						}
17573						if ($midpos > 0) {
17574							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17575							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17576						} else {
17577							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17578							$pend = '';
17579						}
17580					} else {
17581						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17582						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17583							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17584							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17585						} elseif (isset($opentagpos)) {
17586							$midpos = $opentagpos;
17587						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17588							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17589							$midpos = $this->footerpos[$startlinepage];
17590						} else {
17591							$midpos = 0;
17592						}
17593						if ($midpos > 0) {
17594							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17595							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17596						} else {
17597							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17598							$pend = '';
17599						}
17600					}
17601					if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17602						// calculate shifting amount
17603						$tw = $w;
17604						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17605							$tw += $this->cell_padding['R'];
17606						}
17607						if ($this->lMargin != $prevlMargin) {
17608							$tw += ($prevlMargin - $this->lMargin);
17609						}
17610						if ($this->rMargin != $prevrMargin) {
17611							$tw += ($prevrMargin - $this->rMargin);
17612						}
17613						$one_space_width = $this->GetStringWidth(chr(32));
17614						$no = 0; // number of spaces on a line contained on a single block
17615						if ($this->isRTLTextDir()) { // RTL
17616							// remove left space if exist
17617							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17618							if ($pos1 > 0) {
17619								$pos1 = intval($pos1);
17620								if ($this->isUnicodeFont()) {
17621									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17622									$spacelen = 2;
17623								} else {
17624									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17625									$spacelen = 1;
17626								}
17627								if ($pos1 == $pos2) {
17628									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17629									if (substr($pmid, $pos1, 4) == '[()]') {
17630										$linew -= $one_space_width;
17631									} elseif ($pos1 == strpos($pmid, '[(')) {
17632										$no = 1;
17633									}
17634								}
17635							}
17636						} else { // LTR
17637							// remove right space if exist
17638							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17639							if ($pos1 > 0) {
17640								$pos1 = intval($pos1);
17641								if ($this->isUnicodeFont()) {
17642									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17643									$spacelen = 2;
17644								} else {
17645									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17646									$spacelen = 1;
17647								}
17648								if ($pos1 == $pos2) {
17649									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17650									$linew -= $one_space_width;
17651								}
17652							}
17653						}
17654						$mdiff = ($tw - $linew);
17655						if ($plalign == 'C') {
17656							if ($this->rtl) {
17657								$t_x = -($mdiff / 2);
17658							} else {
17659								$t_x = ($mdiff / 2);
17660							}
17661						} elseif ($plalign == 'R') {
17662							// right alignment on LTR document
17663							$t_x = $mdiff;
17664						} elseif ($plalign == 'L') {
17665							// left alignment on RTL document
17666							$t_x = -$mdiff;
17667						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17668							// Justification
17669							if ($this->isRTLTextDir()) {
17670								// align text on the left
17671								$t_x = -$mdiff;
17672							}
17673							$ns = 0; // number of spaces
17674							$pmidtemp = $pmid;
17675							// escape special characters
17676							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17677							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17678							// search spaces
17679							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17680								$spacestr = $this->getSpaceString();
17681								$maxkk = count($lnstring[1]) - 1;
17682								for ($kk=0; $kk <= $maxkk; ++$kk) {
17683									// restore special characters
17684									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17685									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17686									// store number of spaces on the strings
17687									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17688									// count total spaces on line
17689									$ns += $lnstring[2][$kk];
17690									$lnstring[3][$kk] = $ns;
17691								}
17692								if ($ns == 0) {
17693									$ns = 1;
17694								}
17695								// calculate additional space to add to each existing space
17696								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17697								if ($this->FontSize <= 0) {
17698									$this->FontSize = 1;
17699								}
17700								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17701								if ($this->font_spacing != 0) {
17702									// fixed spacing mode
17703									$osw = -1000 * $this->font_spacing / $this->FontSize;
17704									$spacewidthu += $osw;
17705								}
17706								$nsmax = $ns;
17707								$ns = 0;
17708								reset($lnstring);
17709								$offset = 0;
17710								$strcount = 0;
17711								$prev_epsposbeg = 0;
17712								$textpos = 0;
17713								if ($this->isRTLTextDir()) {
17714									$textpos = $this->wPt;
17715								}
17716								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17717									// check if we are inside a string section '[( ... )]'
17718									$stroffset = strpos($pmid, '[(', $offset);
17719									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17720										// set offset to the end of string section
17721										$offset = strpos($pmid, ')]', $stroffset);
17722										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17723											$offset = strpos($pmid, ')]', ($offset + 1));
17724										}
17725										if ($offset === false) {
17726											$this->Error('HTML Justification: malformed PDF code.');
17727										}
17728										continue;
17729									}
17730									if ($this->isRTLTextDir()) {
17731										$spacew = ($spacewidth * ($nsmax - $ns));
17732									} else {
17733										$spacew = ($spacewidth * $ns);
17734									}
17735									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17736									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17737									if ($epsposend !== null) {
17738										$epsposend += strlen($this->epsmarker.'Q');
17739										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17740										if ($epsposbeg === null) {
17741											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17742											$prev_epsposbeg = $epsposbeg;
17743										}
17744										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17745											// shift EPS images
17746											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17747											$pmid_b = substr($pmid, 0, $epsposbeg);
17748											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17749											$pmid_e = substr($pmid, $epsposend);
17750											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17751											$offset = $epsposend;
17752											continue;
17753										}
17754									}
17755									$currentxpos = 0;
17756									// shift blocks of code
17757									switch ($strpiece[2][0]) {
17758										case 'Td':
17759										case 'cm':
17760										case 'm':
17761										case 'l': {
17762											// get current X position
17763											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17764											if (!isset($xmatches[1])) {
17765												break;
17766											}
17767											$currentxpos = $xmatches[1];
17768											$textpos = $currentxpos;
17769											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17770												$ns = $lnstring[3][$strcount];
17771												if ($this->isRTLTextDir()) {
17772													$spacew = ($spacewidth * ($nsmax - $ns));
17773												}
17774												++$strcount;
17775											}
17776											// justify block
17777											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17778												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17779												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17780												unset($pmatch, $newpmid);
17781											}
17782											break;
17783										}
17784										case 're': {
17785											// justify block
17786											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17787												$this->lispacer = '';
17788												continue;
17789											}
17790											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17791											if (!isset($xmatches[1])) {
17792												break;
17793											}
17794											$currentxpos = $xmatches[1];
17795											$x_diff = 0;
17796											$w_diff = 0;
17797											if ($this->isRTLTextDir()) { // RTL
17798												if ($currentxpos < $textpos) {
17799													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17800													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17801												} else {
17802													if ($strcount > 0) {
17803														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17804														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17805													}
17806												}
17807											} else { // LTR
17808												if ($currentxpos > $textpos) {
17809													if ($strcount > 0) {
17810														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17811													}
17812													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17813												} else {
17814													if ($strcount > 1) {
17815														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17816													}
17817													if ($strcount > 0) {
17818														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17819													}
17820												}
17821											}
17822											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17823												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17824												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17825												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17826												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17827												unset($pmatch, $newpmid, $newx, $neww);
17828											}
17829											break;
17830										}
17831										case 'c': {
17832											// get current X position
17833											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);
17834											if (!isset($xmatches[1])) {
17835												break;
17836											}
17837											$currentxpos = $xmatches[1];
17838											// justify block
17839											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $pmatch) == 1) {
17840												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17841												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17842												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17843												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17844												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17845												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17846											}
17847											break;
17848										}
17849									}
17850									// shift the annotations and links
17851									$cxpos = ($currentxpos / $this->k);
17852									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17853									if ($this->inxobj) {
17854										// we are inside an XObject template
17855										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17856											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17857												if ($cxpos > $lmpos) {
17858													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17859													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17860												} else {
17861													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17862												}
17863												break;
17864											}
17865										}
17866									} elseif (isset($this->PageAnnots[$this->page])) {
17867										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17868											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17869												if ($cxpos > $lmpos) {
17870													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17871													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17872												} else {
17873													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17874												}
17875												break;
17876											}
17877										}
17878									}
17879								} // end of while
17880								// remove markers
17881								$pmid = str_replace('x*#!#*x', '', $pmid);
17882								if ($this->isUnicodeFont()) {
17883									// multibyte characters
17884									$spacew = $spacewidthu;
17885									if ($this->font_stretching != 100) {
17886										// word spacing is affected by stretching
17887										$spacew /= ($this->font_stretching / 100);
17888									}
17889									// escape special characters
17890									$pos = 0;
17891									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17892									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17893									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17894										foreach($pamatch[0] as $pk => $pmatch) {
17895											$replace = $pamatch[1][$pk];
17896											$replace = str_replace('#!#OP#!#', '(', $replace);
17897											$replace = str_replace('#!#CP#!#', ')', $replace);
17898											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17899											$pos = strpos($pmid, $pmatch, $pos);
17900											if ($pos !== FALSE) {
17901												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17902											}
17903											++$pos;
17904										}
17905										unset($pamatch);
17906									}
17907									if ($this->inxobj) {
17908										// we are inside an XObject template
17909										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17910									} else {
17911										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17912									}
17913									$endlinepos = strlen($pstart."\n".$pmid."\n");
17914								} else {
17915									// non-unicode (single-byte characters)
17916									if ($this->font_stretching != 100) {
17917										// word spacing (Tw) is affected by stretching
17918										$spacewidth /= ($this->font_stretching / 100);
17919									}
17920									$rs = sprintf('%F Tw', $spacewidth);
17921									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17922									if ($this->inxobj) {
17923										// we are inside an XObject template
17924										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17925									} else {
17926										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17927									}
17928									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17929								}
17930							}
17931						} // end of J
17932					} // end if $startlinex
17933					if (($t_x != 0) OR ($yshift < 0)) {
17934						// shift the line
17935						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
17936						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
17937						$endlinepos = strlen($pstart);
17938						if ($this->inxobj) {
17939							// we are inside an XObject template
17940							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
17941							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17942								if ($pak >= $pask) {
17943									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
17944									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
17945								}
17946							}
17947						} else {
17948							$this->setPageBuffer($startlinepage, $pstart.$pend);
17949							// shift the annotations and links
17950							if (isset($this->PageAnnots[$this->page])) {
17951								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17952									if ($pak >= $pask) {
17953										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
17954										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
17955									}
17956								}
17957							}
17958						}
17959						$this->y -= $yshift;
17960					}
17961				}
17962				$pbrk = $this->checkPageBreak($this->lasth);
17963				$this->newline = false;
17964				$startlinex = $this->x;
17965				$startliney = $this->y;
17966				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
17967					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
17968				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
17969					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
17970				} else {
17971					$minstartliney = $startliney;
17972					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
17973				}
17974				$startlinepage = $this->page;
17975				if (isset($endlinepos) AND (!$pbrk)) {
17976					$startlinepos = $endlinepos;
17977				} else {
17978					if ($this->inxobj) {
17979						// we are inside an XObject template
17980						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17981					} elseif (!$this->InFooter) {
17982						if (isset($this->footerlen[$this->page])) {
17983							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17984						} else {
17985							$this->footerpos[$this->page] = $this->pagelen[$this->page];
17986						}
17987						$startlinepos = $this->footerpos[$this->page];
17988					} else {
17989						$startlinepos = $this->pagelen[$this->page];
17990					}
17991				}
17992				unset($endlinepos);
17993				$plalign = $lalign;
17994				if (isset($this->PageAnnots[$this->page])) {
17995					$pask = count($this->PageAnnots[$this->page]);
17996				} else {
17997					$pask = 0;
17998				}
17999				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18000					AND (isset($this->emptypagemrk[$this->page]))
18001					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18002					$this->SetFont($fontname, $fontstyle, $fontsize);
18003					if ($wfill) {
18004						$this->SetFillColorArray($this->bgcolor);
18005					}
18006				}
18007			} // end newline
18008			if (isset($opentagpos)) {
18009				unset($opentagpos);
18010			}
18011			if ($dom[$key]['tag']) {
18012				if ($dom[$key]['opening']) {
18013					// get text indentation (if any)
18014					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18015						$this->textindent = $dom[$key]['text-indent'];
18016						$this->newline = true;
18017					}
18018					// table
18019					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18020						// available page width
18021						if ($this->rtl) {
18022							$wtmp = $this->x - $this->lMargin;
18023						} else {
18024							$wtmp = $this->w - $this->rMargin - $this->x;
18025						}
18026						// get cell spacing
18027						if (isset($dom[$key]['attribute']['cellspacing'])) {
18028							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18029							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18030						} elseif (isset($dom[$key]['border-spacing'])) {
18031							$cellspacing = $dom[$key]['border-spacing'];
18032						} else {
18033							$cellspacing = array('H' => 0, 'V' => 0);
18034						}
18035						// table width
18036						if (isset($dom[$key]['width'])) {
18037							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18038						} else {
18039							$table_width = $wtmp;
18040						}
18041						$table_width -= (2 * $cellspacing['H']);
18042						if (!$this->inthead) {
18043							$this->y += $cellspacing['V'];
18044						}
18045						if ($this->rtl) {
18046							$cellspacingx = -$cellspacing['H'];
18047						} else {
18048							$cellspacingx = $cellspacing['H'];
18049						}
18050						// total table width without cellspaces
18051						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18052						// minimum column width
18053						// tcpdf-patch: begin
18054						if ($table_columns_width == 0 || $dom[$key]['cols'] == 0)
18055						{
18056							$table_min_column_width = 1;
18057						}
18058						else
18059						{
18060							$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18061						}
18062						if ($dom[$key]['cols'] != 0)
18063						{
18064							// array of custom column widths
18065							$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18066						}
18067						// tcpdf-patch: end
18068					}
18069					// table row
18070					if ($dom[$key]['value'] == 'tr') {
18071						// reset column counter
18072						$colid = 0;
18073					}
18074					// table cell
18075					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18076						$trid = $dom[$key]['parent'];
18077						$table_el = $dom[$trid]['parent'];
18078						if (!isset($dom[$table_el]['cols'])) {
18079							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18080						}
18081						// store border info
18082						$tdborder = 0;
18083						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18084							$tdborder = $dom[$key]['border'];
18085						}
18086						$colspan = intval($dom[$key]['attribute']['colspan']);
18087						if ($colspan <= 0) {
18088							$colspan = 1;
18089						}
18090						$old_cell_padding = $this->cell_padding;
18091						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18092							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18093							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18094						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18095							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18096						} else {
18097							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18098						}
18099						$this->cell_padding = $current_cell_padding;
18100						if (isset($dom[$key]['height'])) {
18101							// minimum cell height
18102							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18103						} else {
18104							$cellh = 0;
18105						}
18106						if (isset($dom[$key]['content'])) {
18107							$cell_content = $dom[$key]['content'];
18108						} else {
18109							$cell_content = '&nbsp;';
18110						}
18111						$tagtype = $dom[$key]['value'];
18112						$parentid = $key;
18113						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18114							// move $key index forward
18115							++$key;
18116						}
18117						if (!isset($dom[$trid]['startpage'])) {
18118							$dom[$trid]['startpage'] = $this->page;
18119						} else {
18120							$this->setPage($dom[$trid]['startpage']);
18121						}
18122						if (!isset($dom[$trid]['startcolumn'])) {
18123							$dom[$trid]['startcolumn'] = $this->current_column;
18124						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18125							$tmpx = $this->x;
18126							$this->selectColumn($dom[$trid]['startcolumn']);
18127							$this->x = $tmpx;
18128						}
18129						if (!isset($dom[$trid]['starty'])) {
18130							$dom[$trid]['starty'] = $this->y;
18131						} else {
18132							$this->y = $dom[$trid]['starty'];
18133						}
18134						if (!isset($dom[$trid]['startx'])) {
18135							$dom[$trid]['startx'] = $this->x;
18136							$this->x += $cellspacingx;
18137						} else {
18138							$this->x += ($cellspacingx / 2);
18139						}
18140						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18141							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18142						} else {
18143							$rowspan = 1;
18144						}
18145						// skip row-spanned cells started on the previous rows
18146						if (isset($dom[$table_el]['rowspans'])) {
18147							$rsk = 0;
18148							$rskmax = count($dom[$table_el]['rowspans']);
18149							while ($rsk < $rskmax) {
18150								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18151								$rsstartx = $trwsp['startx'];
18152								$rsendx = $trwsp['endx'];
18153								// account for margin changes
18154								if ($trwsp['startpage'] < $this->page) {
18155									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18156										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18157										$rsstartx -= $dl;
18158										$rsendx -= $dl;
18159									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18160										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18161										$rsstartx += $dl;
18162										$rsendx += $dl;
18163									}
18164								}
18165								if (($trwsp['rowspan'] > 0)
18166									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18167									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18168									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18169									// set the starting X position of the current cell
18170									$this->x = $rsendx + $cellspacingx;
18171									// increment column indicator
18172									$colid += $trwsp['colspan'];
18173									if (($trwsp['rowspan'] == 1)
18174										AND (isset($dom[$trid]['endy']))
18175										AND (isset($dom[$trid]['endpage']))
18176										AND (isset($dom[$trid]['endcolumn']))
18177										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18178										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18179										// set ending Y position for row
18180										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18181										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18182									}
18183									$rsk = 0;
18184								} else {
18185									++$rsk;
18186								}
18187							}
18188						}
18189						if (isset($dom[$parentid]['width'])) {
18190							// user specified width
18191							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18192							$tmpcw = ($cellw / $colspan);
18193							for ($i = 0; $i < $colspan; ++$i) {
18194								$table_colwidths[($colid + $i)] = $tmpcw;
18195							}
18196						} else {
18197							// inherit column width
18198							$cellw = 0;
18199							for ($i = 0; $i < $colspan; ++$i) {
18200								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18201							}
18202						}
18203						$cellw += (($colspan - 1) * $cellspacing['H']);
18204						// increment column indicator
18205						$colid += $colspan;
18206						// add rowspan information to table element
18207						if ($rowspan > 1) {
18208							$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));
18209						}
18210						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18211						if ($rowspan > 1) {
18212							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18213						}
18214						// push background colors
18215						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18216							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18217						}
18218						// store border info
18219						if (isset($tdborder) AND !empty($tdborder)) {
18220							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18221						}
18222						$prevLastH = $this->lasth;
18223						// store some info for multicolumn mode
18224						if ($this->rtl) {
18225							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18226						} else {
18227							$this->colxshift['x'] = $this->x - $this->lMargin;
18228						}
18229						$this->colxshift['s'] = $cellspacing;
18230						$this->colxshift['p'] = $current_cell_padding;
18231						// ****** write the cell content ******
18232						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18233						// restore some values
18234						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18235						$this->lasth = $prevLastH;
18236						$this->cell_padding = $old_cell_padding;
18237						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18238						// update the end of row position
18239						if ($rowspan <= 1) {
18240							if (isset($dom[$trid]['endy'])) {
18241								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18242									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18243								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18244									$dom[$trid]['endy'] = $this->y;
18245								}
18246							} else {
18247								$dom[$trid]['endy'] = $this->y;
18248							}
18249							if (isset($dom[$trid]['endpage'])) {
18250								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18251							} else {
18252								$dom[$trid]['endpage'] = $this->page;
18253							}
18254							if (isset($dom[$trid]['endcolumn'])) {
18255								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18256							} else {
18257								$dom[$trid]['endcolumn'] = $this->current_column;
18258							}
18259						} else {
18260							// account for row-spanned cells
18261							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18262							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18263							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18264							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18265						}
18266						if (isset($dom[$table_el]['rowspans'])) {
18267							// update endy and endpage on rowspanned cells
18268							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18269								if ($trwsp['rowspan'] > 0) {
18270									if (isset($dom[$trid]['endpage'])) {
18271										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18272											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18273										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18274											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18275											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18276											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18277										} else {
18278											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18279										}
18280									}
18281								}
18282							}
18283						}
18284						$this->x += ($cellspacingx / 2);
18285					} else {
18286						// opening tag (or self-closing tag)
18287						if (!isset($opentagpos)) {
18288							if ($this->inxobj) {
18289								// we are inside an XObject template
18290								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18291							} elseif (!$this->InFooter) {
18292								if (isset($this->footerlen[$this->page])) {
18293									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18294								} else {
18295									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18296								}
18297								$opentagpos = $this->footerpos[$this->page];
18298							}
18299						}
18300						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18301					}
18302				} else { // closing tag
18303					$prev_numpages = $this->numpages;
18304					$old_bordermrk = $this->bordermrk[$this->page];
18305					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18306					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18307						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18308					}
18309					if ($prev_numpages > $this->numpages) {
18310						$startlinepage = $this->page;
18311					}
18312				}
18313			} elseif (strlen($dom[$key]['value']) > 0) {
18314				// print list-item
18315				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18316					$this->SetFont($pfontname, $pfontstyle, $pfontsize);
18317					$this->resetLastH();
18318					$minstartliney = $this->y;
18319					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18320					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18321						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18322					}
18323					$this->SetFont($curfontname, $curfontstyle, $curfontsize);
18324					$this->resetLastH();
18325					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18326						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18327						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18328						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18329						$minstartliney = min($this->y, $minstartliney);
18330						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18331					}
18332				}
18333				// text
18334				$this->htmlvspace = 0;
18335				if ((!$this->premode) AND $this->isRTLTextDir()) {
18336					// reverse spaces order
18337					$lsp = ''; // left spaces
18338					$rsp = ''; // right spaces
18339					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18340						$lsp = $matches[1];
18341					}
18342					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18343						$rsp = $matches[1];
18344					}
18345					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18346				}
18347				if ($newline) {
18348					if (!$this->premode) {
18349						$prelen = strlen($dom[$key]['value']);
18350						if ($this->isRTLTextDir()) {
18351							// right trim except non-breaking space
18352							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18353						} else {
18354							// left trim except non-breaking space
18355							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18356						}
18357						$postlen = strlen($dom[$key]['value']);
18358						if (($postlen == 0) AND ($prelen > 0)) {
18359							$dom[$key]['trimmed_space'] = true;
18360						}
18361					}
18362					$newline = false;
18363					$firstblock = true;
18364				} else {
18365					$firstblock = false;
18366					// replace empty multiple spaces string with a single space
18367					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18368				}
18369				$strrest = '';
18370				if ($this->rtl) {
18371					$this->x -= $this->textindent;
18372				} else {
18373					$this->x += $this->textindent;
18374				}
18375				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18376					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18377					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18378						// HTML <a> Link
18379						$hrefcolor = '';
18380						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18381							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18382						}
18383						$hrefstyle = -1;
18384						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18385							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18386						}
18387						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18388					} else {
18389						$wadj = 0; // space to leave for block continuity
18390						if ($this->rtl) {
18391							$cwa = ($this->x - $this->lMargin);
18392						} else {
18393							$cwa = ($this->w - $this->rMargin - $this->x);
18394						}
18395						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18396							// check the next text blocks for continuity
18397							$nkey = ($key + 1);
18398							$write_block = true;
18399							$same_textdir = true;
18400							$tmp_fontname = $this->FontFamily;
18401							$tmp_fontstyle = $this->FontStyle;
18402							$tmp_fontsize = $this->FontSizePt;
18403							while ($write_block AND isset($dom[$nkey])) {
18404								if ($dom[$nkey]['tag']) {
18405									if ($dom[$nkey]['block']) {
18406										// end of block
18407										$write_block = false;
18408									}
18409									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18410									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18411									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18412									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18413								} else {
18414									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18415									if (isset($nextstr[0]) AND $same_textdir) {
18416										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18417										if (isset($nextstr[1])) {
18418											$write_block = false;
18419										}
18420									}
18421								}
18422								++$nkey;
18423							}
18424						}
18425						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18426							$wadj = 0;
18427							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18428							$numblks = count($nextstr);
18429							if ($numblks > 1) {
18430								// try to split on blank spaces
18431								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18432							} else {
18433								// set the entire block on new line
18434								$wadj = $this->GetStringWidth($nextstr[0]);
18435							}
18436						}
18437						// check for reversed text direction
18438						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18439							// LTR text on RTL direction or RTL text on LTR direction
18440							$reverse_dir = true;
18441							$this->rtl = !$this->rtl;
18442							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18443							if ($this->rtl) {
18444								$this->x += $revshift;
18445							} else {
18446								$this->x -= $revshift;
18447							}
18448							$xws = $this->x;
18449						}
18450						// ****** write only until the end of the line and get the rest ******
18451						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18452						// restore default direction
18453						if ($reverse_dir AND ($wadj == 0)) {
18454							$this->x = $xws;
18455							$this->rtl = !$this->rtl;
18456							$reverse_dir = false;
18457						}
18458					}
18459				}
18460				$this->textindent = 0;
18461				if (strlen($strrest) > 0) {
18462					// store the remaining string on the previous $key position
18463					$this->newline = true;
18464					if ($strrest == $dom[$key]['value']) {
18465						// used to avoid infinite loop
18466						++$loop;
18467					} else {
18468						$loop = 0;
18469					}
18470					$dom[$key]['value'] = $strrest;
18471					if ($cell) {
18472						if ($this->rtl) {
18473							$this->x -= $this->cell_padding['R'];
18474						} else {
18475							$this->x += $this->cell_padding['L'];
18476						}
18477					}
18478					if ($loop < 3) {
18479						--$key;
18480					}
18481				} else {
18482					$loop = 0;
18483					// add the positive font spacing of the last character (if any)
18484					 if ($this->font_spacing > 0) {
18485					 	if ($this->rtl) {
18486							$this->x -= $this->font_spacing;
18487						} else {
18488							$this->x += $this->font_spacing;
18489						}
18490					}
18491				}
18492			}
18493			++$key;
18494			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')) {
18495				// check if we are on a new page or on a new column
18496				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18497					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18498					// restore previous object
18499					$this->rollbackTransaction(true);
18500					// restore previous values
18501					foreach ($this_method_vars as $vkey => $vval) {
18502						$$vkey = $vval;
18503					}
18504					if (!empty($dom[$key]['thead'])) {
18505						$this->inthead = true;
18506					}
18507					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18508					$pre_y = $this->y;
18509					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18510						$startliney = $this->y;
18511					}
18512					$undo = true; // avoid infinite loop
18513				} else {
18514					$undo = false;
18515				}
18516			}
18517		} // end for each $key
18518		// align the last line
18519		if (isset($startlinex)) {
18520			$yshift = ($minstartliney - $startliney);
18521			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18522				$yshift = 0;
18523			}
18524			$t_x = 0;
18525			// the last line must be shifted to be aligned as requested
18526			$linew = abs($this->endlinex - $startlinex);
18527			if ($this->inxobj) {
18528				// we are inside an XObject template
18529				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18530				if (isset($opentagpos)) {
18531					$midpos = $opentagpos;
18532				} else {
18533					$midpos = 0;
18534				}
18535				if ($midpos > 0) {
18536					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18537					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18538				} else {
18539					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18540					$pend = '';
18541				}
18542			} else {
18543				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18544				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18545					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18546					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18547				} elseif (isset($opentagpos)) {
18548					$midpos = $opentagpos;
18549				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18550					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18551					$midpos = $this->footerpos[$startlinepage];
18552				} else {
18553					$midpos = 0;
18554				}
18555				if ($midpos > 0) {
18556					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18557					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18558				} else {
18559					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18560					$pend = '';
18561				}
18562			}
18563			if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18564				// calculate shifting amount
18565				$tw = $w;
18566				if ($this->lMargin != $prevlMargin) {
18567					$tw += ($prevlMargin - $this->lMargin);
18568				}
18569				if ($this->rMargin != $prevrMargin) {
18570					$tw += ($prevrMargin - $this->rMargin);
18571				}
18572				$one_space_width = $this->GetStringWidth(chr(32));
18573				$no = 0; // number of spaces on a line contained on a single block
18574				if ($this->isRTLTextDir()) { // RTL
18575					// remove left space if exist
18576					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18577					if ($pos1 > 0) {
18578						$pos1 = intval($pos1);
18579						if ($this->isUnicodeFont()) {
18580							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18581							$spacelen = 2;
18582						} else {
18583							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18584							$spacelen = 1;
18585						}
18586						if ($pos1 == $pos2) {
18587							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18588							if (substr($pmid, $pos1, 4) == '[()]') {
18589								$linew -= $one_space_width;
18590							} elseif ($pos1 == strpos($pmid, '[(')) {
18591								$no = 1;
18592							}
18593						}
18594					}
18595				} else { // LTR
18596					// remove right space if exist
18597					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18598					if ($pos1 > 0) {
18599						$pos1 = intval($pos1);
18600						if ($this->isUnicodeFont()) {
18601							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18602							$spacelen = 2;
18603						} else {
18604							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18605							$spacelen = 1;
18606						}
18607						if ($pos1 == $pos2) {
18608							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18609							$linew -= $one_space_width;
18610						}
18611					}
18612				}
18613				$mdiff = ($tw - $linew);
18614				if ($plalign == 'C') {
18615					if ($this->rtl) {
18616						$t_x = -($mdiff / 2);
18617					} else {
18618						$t_x = ($mdiff / 2);
18619					}
18620				} elseif ($plalign == 'R') {
18621					// right alignment on LTR document
18622					$t_x = $mdiff;
18623				} elseif ($plalign == 'L') {
18624					// left alignment on RTL document
18625					$t_x = -$mdiff;
18626				}
18627			} // end if startlinex
18628			if (($t_x != 0) OR ($yshift < 0)) {
18629				// shift the line
18630				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18631				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18632				$endlinepos = strlen($pstart);
18633				if ($this->inxobj) {
18634					// we are inside an XObject template
18635					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18636					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18637						if ($pak >= $pask) {
18638							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18639							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18640						}
18641					}
18642				} else {
18643					$this->setPageBuffer($startlinepage, $pstart.$pend);
18644					// shift the annotations and links
18645					if (isset($this->PageAnnots[$this->page])) {
18646						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18647							if ($pak >= $pask) {
18648								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18649								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18650							}
18651						}
18652					}
18653				}
18654				$this->y -= $yshift;
18655				$yshift = 0;
18656			}
18657		}
18658		// restore previous values
18659		$this->setGraphicVars($gvars);
18660		if ($this->num_columns > 1) {
18661			$this->selectColumn();
18662		} elseif ($this->page > $prevPage) {
18663			$this->lMargin = $this->pagedim[$this->page]['olm'];
18664			$this->rMargin = $this->pagedim[$this->page]['orm'];
18665		}
18666		// restore previous list state
18667		$this->cell_height_ratio = $prev_cell_height_ratio;
18668		$this->listnum = $prev_listnum;
18669		$this->listordered = $prev_listordered;
18670		$this->listcount = $prev_listcount;
18671		$this->lispacer = $prev_lispacer;
18672		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18673			$this->Ln($this->lasth);
18674			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18675				$this->y = $maxbottomliney;
18676			}
18677		}
18678		unset($dom);
18679	}
18680
18681	/**
18682	 * Process opening tags.
18683	 * @param $dom (array) html dom array
18684	 * @param $key (int) current element id
18685	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18686	 * @return $dom array
18687	 * @protected
18688	 */
18689	protected function openHTMLTagHandler($dom, $key, $cell) {
18690		$tag = $dom[$key];
18691		$parent = $dom[($dom[$key]['parent'])];
18692		$firsttag = ($key == 1);
18693		// check for text direction attribute
18694		if (isset($tag['dir'])) {
18695			$this->setTempRTL($tag['dir']);
18696		} else {
18697			$this->tmprtl = false;
18698		}
18699		if ($tag['block']) {
18700			$hbz = 0; // distance from y to line bottom
18701			$hb = 0; // vertical space between block tags
18702			// calculate vertical space for block tags
18703			if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18704				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18705			} elseif (isset($tag['fontsize'])) {
18706				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18707			} else {
18708				$cur_h = $this->getCellHeight($this->FontSize);
18709			}
18710			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18711				$on = $this->tagvspaces[$tag['value']][0]['n'];
18712			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18713				$on = 0.6;
18714			} else {
18715				$on = 1;
18716			}
18717			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18718				$hb = 0;
18719			} else {
18720				$hb = ($on * $cur_h);
18721			}
18722			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18723				if (isset($parent['fontsize'])) {
18724					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18725				} else {
18726					$hbz = $this->getCellHeight($this->FontSize);
18727				}
18728			}
18729			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18730				// fix vertical space after table
18731				$hbz = 0;
18732			}
18733			// closing vertical space
18734			$hbc = 0;
18735			if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18736				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18737			} elseif (isset($parent['fontsize'])) {
18738				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18739			} else {
18740				$pre_h = $this->getCellHeight($this->FontSize);
18741			}
18742			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18743				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18744			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18745				$cn = 0.6;
18746			} else {
18747				$cn = 1;
18748			}
18749			if (isset($this->tagvspaces[$tag['value']][1])) {
18750				$hbc = ($cn * $pre_h);
18751			}
18752		}
18753		// Opening tag
18754		switch($tag['value']) {
18755			case 'table': {
18756				$cp = 0;
18757				$cs = 0;
18758				$dom[$key]['rowspans'] = array();
18759				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18760					$this->htmlvspace = 0;
18761					// set table header
18762					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18763						// set table header
18764						$this->thead = $dom[$key]['thead'];
18765						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18766							$this->theadMargins = array();
18767							$this->theadMargins['cell_padding'] = $this->cell_padding;
18768							$this->theadMargins['lmargin'] = $this->lMargin;
18769							$this->theadMargins['rmargin'] = $this->rMargin;
18770							$this->theadMargins['page'] = $this->page;
18771							$this->theadMargins['cell'] = $cell;
18772							$this->theadMargins['gvars'] = $this->getGraphicVars();
18773						}
18774					}
18775				}
18776				// store current margins and page
18777				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18778				if (isset($tag['attribute']['cellpadding'])) {
18779					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18780					$this->SetCellPadding($pad);
18781				} elseif (isset($tag['padding'])) {
18782					$this->cell_padding = $tag['padding'];
18783				}
18784				if (isset($tag['attribute']['cellspacing'])) {
18785					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18786				} elseif (isset($tag['border-spacing'])) {
18787					$cs = $tag['border-spacing']['V'];
18788				}
18789				$prev_y = $this->y;
18790				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18791					$this->inthead = true;
18792					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18793					$this->checkPageBreak($this->PageBreakTrigger + 1);
18794				}
18795				break;
18796			}
18797			case 'tr': {
18798				// array of columns positions
18799				$dom[$key]['cellpos'] = array();
18800				break;
18801			}
18802			case 'hr': {
18803				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18804					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18805				} else {
18806					$hrHeight = $this->GetLineWidth();
18807				}
18808				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18809				$x = $this->GetX();
18810				$y = $this->GetY();
18811				$wtmp = $this->w - $this->lMargin - $this->rMargin;
18812				if ($cell) {
18813					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18814				}
18815				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18816					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18817				} else {
18818					$hrWidth = $wtmp;
18819				}
18820				$prevlinewidth = $this->GetLineWidth();
18821				$this->SetLineWidth($hrHeight);
18822				$this->Line($x, $y, $x + $hrWidth, $y);
18823				$this->SetLineWidth($prevlinewidth);
18824				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18825				break;
18826			}
18827			case 'a': {
18828				if (array_key_exists('href', $tag['attribute'])) {
18829					$this->HREF['url'] = $tag['attribute']['href'];
18830				}
18831				break;
18832			}
18833			case 'img': {
18834				if (!empty($tag['attribute']['src'])) {
18835					if ($tag['attribute']['src'][0] === '@') {
18836						// data stream
18837						$tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18838						$type = '';
18839					} else {
18840						// get image type
18841						$type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18842					}
18843					if (!isset($tag['width'])) {
18844						$tag['width'] = 0;
18845					}
18846					if (!isset($tag['height'])) {
18847						$tag['height'] = 0;
18848					}
18849					//if (!isset($tag['attribute']['align'])) {
18850						// the only alignment supported is "bottom"
18851						// further development is required for other modes.
18852						$tag['attribute']['align'] = 'bottom';
18853					//}
18854					switch($tag['attribute']['align']) {
18855						case 'top': {
18856							$align = 'T';
18857							break;
18858						}
18859						case 'middle': {
18860							$align = 'M';
18861							break;
18862						}
18863						case 'bottom': {
18864							$align = 'B';
18865							break;
18866						}
18867						default: {
18868							$align = 'B';
18869							break;
18870						}
18871					}
18872					$prevy = $this->y;
18873					$xpos = $this->x;
18874					$imglink = '';
18875					if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18876						$imglink = $this->HREF['url'];
18877						if ($imglink[0] == '#') {
18878							// convert url to internal link
18879							$lnkdata = explode(',', $imglink);
18880							if (isset($lnkdata[0])) {
18881								$page = intval(substr($lnkdata[0], 1));
18882								if (empty($page) OR ($page <= 0)) {
18883									$page = $this->page;
18884								}
18885								if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18886									$lnky = floatval($lnkdata[1]);
18887								} else {
18888									$lnky = 0;
18889								}
18890								$imglink = $this->AddLink();
18891								$this->SetLink($imglink, $lnky, $page);
18892							}
18893						}
18894					}
18895					$border = 0;
18896					if (isset($tag['border']) AND !empty($tag['border'])) {
18897						// currently only support 1 (frame) or a combination of 'LTRB'
18898						$border = $tag['border'];
18899					}
18900					$iw = '';
18901					if (isset($tag['width'])) {
18902						$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18903					}
18904					$ih = '';
18905					if (isset($tag['height'])) {
18906						$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18907					}
18908					if (($type == 'eps') OR ($type == 'ai')) {
18909						$this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18910					} elseif ($type == 'svg') {
18911						$this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18912					} else {
18913						$this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18914					}
18915					switch($align) {
18916						case 'T': {
18917							$this->y = $prevy;
18918							break;
18919						}
18920						case 'M': {
18921							$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18922							break;
18923						}
18924						case 'B': {
18925							$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
18926							break;
18927						}
18928					}
18929				}
18930				break;
18931			}
18932			case 'dl': {
18933				++$this->listnum;
18934				if ($this->listnum == 1) {
18935					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18936				} else {
18937					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18938				}
18939				break;
18940			}
18941			case 'dt': {
18942				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18943				break;
18944			}
18945			case 'dd': {
18946				if ($this->rtl) {
18947					$this->rMargin += $this->listindent;
18948				} else {
18949					$this->lMargin += $this->listindent;
18950				}
18951				++$this->listindentlevel;
18952				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18953				break;
18954			}
18955			case 'ul':
18956			case 'ol': {
18957				++$this->listnum;
18958				if ($tag['value'] == 'ol') {
18959					$this->listordered[$this->listnum] = true;
18960				} else {
18961					$this->listordered[$this->listnum] = false;
18962				}
18963				if (isset($tag['attribute']['start'])) {
18964					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
18965				} else {
18966					$this->listcount[$this->listnum] = 0;
18967				}
18968				if ($this->rtl) {
18969					$this->rMargin += $this->listindent;
18970					$this->x -= $this->listindent;
18971				} else {
18972					$this->lMargin += $this->listindent;
18973					$this->x += $this->listindent;
18974				}
18975				++$this->listindentlevel;
18976				if ($this->listnum == 1) {
18977					if ($key > 1) {
18978						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18979					}
18980				} else {
18981					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18982				}
18983				break;
18984			}
18985			case 'li': {
18986				if ($key > 2) {
18987					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18988				}
18989				if ($this->listordered[$this->listnum]) {
18990					// ordered item
18991					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18992						$this->lispacer = $parent['attribute']['type'];
18993					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18994						$this->lispacer = $parent['listtype'];
18995					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18996						$this->lispacer = $this->lisymbol;
18997					} else {
18998						$this->lispacer = '#';
18999					}
19000					++$this->listcount[$this->listnum];
19001					if (isset($tag['attribute']['value'])) {
19002						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19003					}
19004				} else {
19005					// unordered item
19006					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19007						$this->lispacer = $parent['attribute']['type'];
19008					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19009						$this->lispacer = $parent['listtype'];
19010					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19011						$this->lispacer = $this->lisymbol;
19012					} else {
19013						$this->lispacer = '!';
19014					}
19015				}
19016				break;
19017			}
19018			case 'blockquote': {
19019				if ($this->rtl) {
19020					$this->rMargin += $this->listindent;
19021				} else {
19022					$this->lMargin += $this->listindent;
19023				}
19024				++$this->listindentlevel;
19025				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19026				break;
19027			}
19028			case 'br': {
19029				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19030				break;
19031			}
19032			case 'div': {
19033				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19034				break;
19035			}
19036			case 'p': {
19037				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19038				break;
19039			}
19040			case 'pre': {
19041				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19042				$this->premode = true;
19043				break;
19044			}
19045			case 'sup': {
19046				$this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19047				break;
19048			}
19049			case 'sub': {
19050				$this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19051				break;
19052			}
19053			case 'h1':
19054			case 'h2':
19055			case 'h3':
19056			case 'h4':
19057			case 'h5':
19058			case 'h6': {
19059				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19060				break;
19061			}
19062			// Form fields (since 4.8.000 - 2009-09-07)
19063			case 'form': {
19064				if (isset($tag['attribute']['action'])) {
19065					$this->form_action = $tag['attribute']['action'];
19066				} else {
19067					$this->Error('Please explicitly set action attribute path!');
19068				}
19069				if (isset($tag['attribute']['enctype'])) {
19070					$this->form_enctype = $tag['attribute']['enctype'];
19071				} else {
19072					$this->form_enctype = 'application/x-www-form-urlencoded';
19073				}
19074				if (isset($tag['attribute']['method'])) {
19075					$this->form_mode = $tag['attribute']['method'];
19076				} else {
19077					$this->form_mode = 'post';
19078				}
19079				break;
19080			}
19081			case 'input': {
19082				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19083					$name = $tag['attribute']['name'];
19084				} else {
19085					break;
19086				}
19087				$prop = array();
19088				$opt = array();
19089				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19090					$prop['readonly'] = true;
19091				}
19092				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19093					$value = $tag['attribute']['value'];
19094				}
19095				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19096					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19097				}
19098				$h = $this->getCellHeight($this->FontSize);
19099				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19100					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19101				} else {
19102					$w = $h;
19103				}
19104				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19105					$checked = true;
19106				} else {
19107					$checked = false;
19108				}
19109				if (isset($tag['align'])) {
19110					switch ($tag['align']) {
19111						case 'C': {
19112							$opt['q'] = 1;
19113							break;
19114						}
19115						case 'R': {
19116							$opt['q'] = 2;
19117							break;
19118						}
19119						case 'L':
19120						default: {
19121							break;
19122						}
19123					}
19124				}
19125				switch ($tag['attribute']['type']) {
19126					case 'text': {
19127						if (isset($value)) {
19128							$opt['v'] = $value;
19129						}
19130						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19131						break;
19132					}
19133					case 'password': {
19134						if (isset($value)) {
19135							$opt['v'] = $value;
19136						}
19137						$prop['password'] = 'true';
19138						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19139						break;
19140					}
19141					case 'checkbox': {
19142						if (!isset($value)) {
19143							break;
19144						}
19145						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19146						break;
19147					}
19148					case 'radio': {
19149						if (!isset($value)) {
19150							break;
19151						}
19152						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19153						break;
19154					}
19155					case 'submit': {
19156						if (!isset($value)) {
19157							$value = 'submit';
19158						}
19159						$w = $this->GetStringWidth($value) * 1.5;
19160						$h *= 1.6;
19161						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19162						$action = array();
19163						$action['S'] = 'SubmitForm';
19164						$action['F'] = $this->form_action;
19165						if ($this->form_enctype != 'FDF') {
19166							$action['Flags'] = array('ExportFormat');
19167						}
19168						if ($this->form_mode == 'get') {
19169							$action['Flags'] = array('GetMethod');
19170						}
19171						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19172						break;
19173					}
19174					case 'reset': {
19175						if (!isset($value)) {
19176							$value = 'reset';
19177						}
19178						$w = $this->GetStringWidth($value) * 1.5;
19179						$h *= 1.6;
19180						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19181						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19182						break;
19183					}
19184					case 'file': {
19185						$prop['fileSelect'] = 'true';
19186						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19187						if (!isset($value)) {
19188							$value = '*';
19189						}
19190						$w = $this->GetStringWidth($value) * 2;
19191						$h *= 1.2;
19192						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19193						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19194						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19195						break;
19196					}
19197					case 'hidden': {
19198						if (isset($value)) {
19199							$opt['v'] = $value;
19200						}
19201						$opt['f'] = array('invisible', 'hidden');
19202						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19203						break;
19204					}
19205					case 'image': {
19206						// THIS TYPE MUST BE FIXED
19207						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19208							$img = $tag['attribute']['src'];
19209						} else {
19210							break;
19211						}
19212						$value = 'img';
19213						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19214						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19215							$jsaction = $tag['attribute']['onclick'];
19216						} else {
19217							$jsaction = '';
19218						}
19219						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19220						break;
19221					}
19222					case 'button': {
19223						if (!isset($value)) {
19224							$value = ' ';
19225						}
19226						$w = $this->GetStringWidth($value) * 1.5;
19227						$h *= 1.6;
19228						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19229						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19230							$jsaction = $tag['attribute']['onclick'];
19231						} else {
19232							$jsaction = '';
19233						}
19234						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19235						break;
19236					}
19237				}
19238				break;
19239			}
19240			case 'textarea': {
19241				$prop = array();
19242				$opt = array();
19243				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19244					$prop['readonly'] = true;
19245				}
19246				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19247					$name = $tag['attribute']['name'];
19248				} else {
19249					break;
19250				}
19251				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19252					$opt['v'] = $tag['attribute']['value'];
19253				}
19254				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19255					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19256				} else {
19257					$w = 40;
19258				}
19259				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19260					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19261				} else {
19262					$h = 10;
19263				}
19264				$prop['multiline'] = 'true';
19265				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19266				break;
19267			}
19268			case 'select': {
19269				$h = $this->getCellHeight($this->FontSize);
19270				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19271					$h *= ($tag['attribute']['size'] + 1);
19272				}
19273				$prop = array();
19274				$opt = array();
19275				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19276					$name = $tag['attribute']['name'];
19277				} else {
19278					break;
19279				}
19280				$w = 0;
19281				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19282					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19283					$values = array();
19284					foreach ($options as $val) {
19285						if (strpos($val, '#!TaB!#') !== false) {
19286							$opts = explode('#!TaB!#', $val);
19287							$values[] = $opts;
19288							$w = max($w, $this->GetStringWidth($opts[1]));
19289						} else {
19290							$values[] = $val;
19291							$w = max($w, $this->GetStringWidth($val));
19292						}
19293					}
19294				} else {
19295					break;
19296				}
19297				$w *= 2;
19298				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19299					$prop['multipleSelection'] = 'true';
19300					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19301				} else {
19302					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19303				}
19304				break;
19305			}
19306			case 'tcpdf': {
19307				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19308					// Special tag used to call TCPDF methods
19309					if (isset($tag['attribute']['method'])) {
19310						$tcpdf_method = $tag['attribute']['method'];
19311						if (method_exists($this, $tcpdf_method)) {
19312							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19313								$params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19314								call_user_func_array(array($this, $tcpdf_method), $params);
19315							} else {
19316								$this->$tcpdf_method();
19317							}
19318							$this->newline = true;
19319						}
19320					}
19321				}
19322				break;
19323			}
19324			default: {
19325				break;
19326			}
19327		}
19328		// define tags that support borders and background colors
19329		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19330		if (in_array($tag['value'], $bordertags)) {
19331			// set border
19332			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19333		}
19334		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19335			$pba = $dom[$key]['attribute']['pagebreakafter'];
19336			// check for pagebreak
19337			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19338				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19339				$this->checkPageBreak($this->PageBreakTrigger + 1);
19340			}
19341			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19342				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19343				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19344				$this->checkPageBreak($this->PageBreakTrigger + 1);
19345			}
19346		}
19347		return $dom;
19348	}
19349
19350	/**
19351	 * Process closing tags.
19352	 * @param $dom (array) html dom array
19353	 * @param $key (int) current element id
19354	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19355	 * @param $maxbottomliney (int) maximum y value of current line
19356	 * @return $dom array
19357	 * @protected
19358	 */
19359	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19360		$tag = $dom[$key];
19361		$parent = $dom[($dom[$key]['parent'])];
19362		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19363		$in_table_head = false;
19364		// maximum x position (used to draw borders)
19365		if ($this->rtl) {
19366			$xmax = $this->w;
19367		} else {
19368			$xmax = 0;
19369		}
19370		if ($tag['block']) {
19371			$hbz = 0; // distance from y to line bottom
19372			$hb = 0; // vertical space between block tags
19373			// calculate vertical space for block tags
19374			if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19375				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19376			} elseif (isset($parent['fontsize'])) {
19377				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19378			} else {
19379				$pre_h = $this->getCellHeight($this->FontSize);
19380			}
19381			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19382				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19383			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19384				$cn = 0.6;
19385			} else {
19386				$cn = 1;
19387			}
19388			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19389				$hb = 0;
19390			} else {
19391				$hb = ($cn * $pre_h);
19392			}
19393			if ($maxbottomliney > $this->PageBreakTrigger) {
19394				$hbz = $this->getCellHeight($this->FontSize);
19395			} elseif ($this->y < $maxbottomliney) {
19396				$hbz = ($maxbottomliney - $this->y);
19397			}
19398		}
19399		// Closing tag
19400		switch($tag['value']) {
19401			case 'tr': {
19402				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19403				if (!isset($parent['endy'])) {
19404					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19405					$parent['endy'] = $this->y;
19406				}
19407				if (!isset($parent['endpage'])) {
19408					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19409					$parent['endpage'] = $this->page;
19410				}
19411				if (!isset($parent['endcolumn'])) {
19412					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19413					$parent['endcolumn'] = $this->current_column;
19414				}
19415				// update row-spanned cells
19416				if (isset($dom[$table_el]['rowspans'])) {
19417					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19418						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19419						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19420							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19421								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19422							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19423								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19424								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19425								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19426							}
19427						}
19428					}
19429					// report new endy and endpage to the rowspanned cells
19430					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19431						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19432							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19433							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19434							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19435							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19436							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19437							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19438						}
19439					}
19440					// update remaining rowspanned cells
19441					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19442						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19443							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19444							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19445							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19446						}
19447					}
19448				}
19449				$prev_page = $this->page;
19450				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19451				if ($this->num_columns > 1) {
19452					if (($prev_page < $this->page)
19453						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19454							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19455						// page jump
19456						$this->selectColumn(0);
19457						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19458						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19459					} else {
19460						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19461						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19462					}
19463				} else {
19464					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19465				}
19466				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19467					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19468				} elseif (isset($dom[$table_el]['border-spacing'])) {
19469					$this->y += $dom[$table_el]['border-spacing']['V'];
19470				}
19471				$this->Ln(0, $cell);
19472				if ($this->current_column == $parent['startcolumn']) {
19473					$this->x = $parent['startx'];
19474				}
19475				// account for booklet mode
19476				if ($this->page > $parent['startpage']) {
19477					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19478						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19479					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19480						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19481					}
19482				}
19483				break;
19484			}
19485			case 'tablehead':
19486				// closing tag used for the thead part
19487				$in_table_head = true;
19488				$this->inthead = false;
19489			case 'table': {
19490				$table_el = $parent;
19491				// set default border
19492				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19493					// set default border
19494					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19495				} else {
19496					$border = 0;
19497				}
19498				$default_border = $border;
19499				// fix bottom line alignment of last line before page break
19500				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19501					// update row-spanned cells
19502					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19503						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19504							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19505								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19506							}
19507							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19508								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19509							}
19510						}
19511					}
19512					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19513						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19514						$dom[$prevtrkey]['endy'] = $pgendy;
19515						// update row-spanned cells
19516						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19517							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19518								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19519									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19520									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19521								}
19522							}
19523						}
19524					}
19525					$prevtrkey = $trkey;
19526					$table_el = $dom[($dom[$key]['parent'])];
19527				}
19528				// for each row
19529				if (count($table_el['trids']) > 0) {
19530					unset($xmax);
19531				}
19532				foreach ($table_el['trids'] as $j => $trkey) {
19533					$parent = $dom[$trkey];
19534					if (!isset($xmax)) {
19535						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19536					}
19537					// for each cell on the row
19538					foreach ($parent['cellpos'] as $k => $cellpos) {
19539						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19540							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19541							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19542							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19543							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19544							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19545							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19546							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19547						} else {
19548							$endy = $parent['endy'];
19549							$startpage = $parent['startpage'];
19550							$endpage = $parent['endpage'];
19551							$startcolumn = $parent['startcolumn'];
19552							$endcolumn = $parent['endcolumn'];
19553						}
19554						if ($this->num_columns == 0) {
19555							$this->num_columns = 1;
19556						}
19557						if (isset($cellpos['border'])) {
19558							$border = $cellpos['border'];
19559						}
19560						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19561							$this->SetFillColorArray($cellpos['bgcolor']);
19562							$fill = true;
19563						} else {
19564							$fill = false;
19565						}
19566						$x = $cellpos['startx'];
19567						$y = $parent['starty'];
19568						$starty = $y;
19569						$w = abs($cellpos['endx'] - $cellpos['startx']);
19570						// get border modes
19571						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19572						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19573						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19574						// design borders around HTML cells.
19575						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19576							$ccode = '';
19577							$this->setPage($page);
19578							if ($this->num_columns < 2) {
19579								// single-column mode
19580								$this->x = $x;
19581								$this->y = $this->tMargin;
19582							}
19583							// account for margin changes
19584							if ($page > $startpage) {
19585								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19586									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19587								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19588									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19589								}
19590							}
19591							if ($startpage == $endpage) { // single page
19592								$deltacol = 0;
19593								$deltath = 0;
19594								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19595									$this->selectColumn($column);
19596									if ($startcolumn == $endcolumn) { // single column
19597										$cborder = $border;
19598										$h = $endy - $parent['starty'];
19599										$this->y = $y;
19600										$this->x = $x;
19601									} elseif ($column == $startcolumn) { // first column
19602										$cborder = $border_start;
19603										$this->y = $starty;
19604										$this->x = $x;
19605										$h = $this->h - $this->y - $this->bMargin;
19606										if ($this->rtl) {
19607											$deltacol = $this->x + $this->rMargin - $this->w;
19608										} else {
19609											$deltacol = $this->x - $this->lMargin;
19610										}
19611									} elseif ($column == $endcolumn) { // end column
19612										$cborder = $border_end;
19613										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19614											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19615										}
19616										$this->x += $deltacol;
19617										$h = $endy - $this->y;
19618									} else { // middle column
19619										$cborder = $border_middle;
19620										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19621											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19622										}
19623										$this->x += $deltacol;
19624										$h = $this->h - $this->y - $this->bMargin;
19625									}
19626									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19627								} // end for each column
19628							} elseif ($page == $startpage) { // first page
19629								$deltacol = 0;
19630								$deltath = 0;
19631								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19632									$this->selectColumn($column);
19633									if ($column == $startcolumn) { // first column
19634										$cborder = $border_start;
19635										$this->y = $starty;
19636										$this->x = $x;
19637										$h = $this->h - $this->y - $this->bMargin;
19638										if ($this->rtl) {
19639											$deltacol = $this->x + $this->rMargin - $this->w;
19640										} else {
19641											$deltacol = $this->x - $this->lMargin;
19642										}
19643									} else { // middle column
19644										$cborder = $border_middle;
19645										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19646											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19647										}
19648										$this->x += $deltacol;
19649										$h = $this->h - $this->y - $this->bMargin;
19650									}
19651									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19652								} // end for each column
19653							} elseif ($page == $endpage) { // last page
19654								$deltacol = 0;
19655								$deltath = 0;
19656								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19657									$this->selectColumn($column);
19658									if ($column == $endcolumn) { // end column
19659										$cborder = $border_end;
19660										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19661											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19662										}
19663										$this->x += $deltacol;
19664										$h = $endy - $this->y;
19665									} else { // middle column
19666										$cborder = $border_middle;
19667										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19668											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19669										}
19670										$this->x += $deltacol;
19671										$h = $this->h - $this->y - $this->bMargin;
19672									}
19673									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19674								} // end for each column
19675							} else { // middle page
19676								$deltacol = 0;
19677								$deltath = 0;
19678								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19679									$this->selectColumn($column);
19680									$cborder = $border_middle;
19681									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19682										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19683									}
19684									$this->x += $deltacol;
19685									$h = $this->h - $this->y - $this->bMargin;
19686									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19687								} // end for each column
19688							}
19689							if (!empty($cborder) OR !empty($fill)) {
19690								$offsetlen = strlen($ccode);
19691								// draw border and fill
19692								if ($this->inxobj) {
19693									// we are inside an XObject template
19694									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19695										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19696										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19697										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19698									} else {
19699										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19700										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19701									}
19702									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19703									$pstart = substr($pagebuff, 0, $pagemark);
19704									$pend = substr($pagebuff, $pagemark);
19705									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19706								} else {
19707									// draw border and fill
19708									if (end($this->transfmrk[$this->page]) !== false) {
19709										$pagemarkkey = key($this->transfmrk[$this->page]);
19710										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19711									} elseif ($this->InFooter) {
19712										$pagemark = $this->footerpos[$this->page];
19713									} else {
19714										$pagemark = $this->intmrk[$this->page];
19715									}
19716									$pagebuff = $this->getPageBuffer($this->page);
19717									$pstart = substr($pagebuff, 0, $pagemark);
19718									$pend = substr($pagebuff, $pagemark);
19719									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19720								}
19721							}
19722						} // end for each page
19723						// restore default border
19724						$border = $default_border;
19725					} // end for each cell on the row
19726					if (isset($table_el['attribute']['cellspacing'])) {
19727						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19728					} elseif (isset($table_el['border-spacing'])) {
19729						$this->y += $table_el['border-spacing']['V'];
19730					}
19731					$this->Ln(0, $cell);
19732					$this->x = $parent['startx'];
19733					if ($endpage > $startpage) {
19734						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19735							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19736						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19737							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19738						}
19739					}
19740				}
19741				if (!$in_table_head) { // we are not inside a thead section
19742					$this->cell_padding = $table_el['old_cell_padding'];
19743					// reset row height
19744					$this->resetLastH();
19745					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19746						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19747						if (($plendiff > 0) AND ($plendiff < 60)) {
19748							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19749							if (substr($pagediff, 0, 5) == 'BT /F') {
19750								// the difference is only a font setting
19751								$plendiff = 0;
19752							}
19753						}
19754						if ($plendiff == 0) {
19755							// remove last blank page
19756							$this->deletePage($this->numpages);
19757						}
19758					}
19759					if (isset($this->theadMargins['top'])) {
19760						// restore top margin
19761						$this->tMargin = $this->theadMargins['top'];
19762					}
19763					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19764						// reset main table header
19765						$this->thead = '';
19766						$this->theadMargins = array();
19767						$this->pagedim[$this->page]['tm'] = $this->tMargin;
19768					}
19769				}
19770				$parent = $table_el;
19771				break;
19772			}
19773			case 'a': {
19774				$this->HREF = '';
19775				break;
19776			}
19777			case 'sup': {
19778				$this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19779				break;
19780			}
19781			case 'sub': {
19782				$this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19783				break;
19784			}
19785			case 'div': {
19786				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19787				break;
19788			}
19789			case 'blockquote': {
19790				if ($this->rtl) {
19791					$this->rMargin -= $this->listindent;
19792				} else {
19793					$this->lMargin -= $this->listindent;
19794				}
19795				--$this->listindentlevel;
19796				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19797				break;
19798			}
19799			case 'p': {
19800				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19801				break;
19802			}
19803			case 'pre': {
19804				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19805				$this->premode = false;
19806				break;
19807			}
19808			case 'dl': {
19809				--$this->listnum;
19810				if ($this->listnum <= 0) {
19811					$this->listnum = 0;
19812					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19813				} else {
19814					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19815				}
19816				$this->resetLastH();
19817				break;
19818			}
19819			case 'dt': {
19820				$this->lispacer = '';
19821				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19822				break;
19823			}
19824			case 'dd': {
19825				$this->lispacer = '';
19826				if ($this->rtl) {
19827					$this->rMargin -= $this->listindent;
19828				} else {
19829					$this->lMargin -= $this->listindent;
19830				}
19831				--$this->listindentlevel;
19832				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19833				break;
19834			}
19835			case 'ul':
19836			case 'ol': {
19837				--$this->listnum;
19838				$this->lispacer = '';
19839				if ($this->rtl) {
19840					$this->rMargin -= $this->listindent;
19841				} else {
19842					$this->lMargin -= $this->listindent;
19843				}
19844				--$this->listindentlevel;
19845				if ($this->listnum <= 0) {
19846					$this->listnum = 0;
19847					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19848				} else {
19849					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19850				}
19851				$this->resetLastH();
19852				break;
19853			}
19854			case 'li': {
19855				$this->lispacer = '';
19856				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19857				break;
19858			}
19859			case 'h1':
19860			case 'h2':
19861			case 'h3':
19862			case 'h4':
19863			case 'h5':
19864			case 'h6': {
19865				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19866				break;
19867			}
19868			// Form fields (since 4.8.000 - 2009-09-07)
19869			case 'form': {
19870				$this->form_action = '';
19871				$this->form_enctype = 'application/x-www-form-urlencoded';
19872				break;
19873			}
19874			default : {
19875				break;
19876			}
19877		}
19878		// draw border and background (if any)
19879		$this->drawHTMLTagBorder($parent, $xmax);
19880		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19881			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19882			// check for pagebreak
19883			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19884				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19885				$this->checkPageBreak($this->PageBreakTrigger + 1);
19886			}
19887			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19888				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19889				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19890				$this->checkPageBreak($this->PageBreakTrigger + 1);
19891			}
19892		}
19893		$this->tmprtl = false;
19894		return $dom;
19895	}
19896
19897	/**
19898	 * Add vertical spaces if needed.
19899	 * @param $hbz (string) Distance between current y and line bottom.
19900	 * @param $hb (string) The height of the break.
19901	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19902	 * @param $firsttag (boolean) set to true when the tag is the first.
19903	 * @param $lasttag (boolean) set to true when the tag is the last.
19904	 * @protected
19905	 */
19906	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19907		if ($firsttag) {
19908			$this->Ln(0, $cell);
19909			$this->htmlvspace = 0;
19910			return;
19911		}
19912		if ($lasttag) {
19913			$this->Ln($hbz, $cell);
19914			$this->htmlvspace = 0;
19915			return;
19916		}
19917		if ($hb < $this->htmlvspace) {
19918			$hd = 0;
19919		} else {
19920			$hd = $hb - $this->htmlvspace;
19921			$this->htmlvspace = $hb;
19922		}
19923		$this->Ln(($hbz + $hd), $cell);
19924	}
19925
19926	/**
19927	 * Return the starting coordinates to draw an html border
19928	 * @return array containing top-left border coordinates
19929	 * @protected
19930	 * @since 5.7.000 (2010-08-03)
19931	 */
19932	protected function getBorderStartPosition() {
19933		if ($this->rtl) {
19934			$xmax = $this->lMargin;
19935		} else {
19936			$xmax = $this->w - $this->rMargin;
19937		}
19938		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
19939	}
19940
19941	/**
19942	 * Draw an HTML block border and fill
19943	 * @param $tag (array) array of tag properties.
19944	 * @param $xmax (int) end X coordinate for border.
19945	 * @protected
19946	 * @since 5.7.000 (2010-08-03)
19947	 */
19948	protected function drawHTMLTagBorder($tag, $xmax) {
19949		if (!isset($tag['borderposition'])) {
19950			// nothing to draw
19951			return;
19952		}
19953		$prev_x = $this->x;
19954		$prev_y = $this->y;
19955		$prev_lasth = $this->lasth;
19956		$border = 0;
19957		$fill = false;
19958		$this->lasth = 0;
19959		if (isset($tag['border']) AND !empty($tag['border'])) {
19960			// get border style
19961			$border = $tag['border'];
19962			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
19963				// border for table header
19964				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19965			}
19966		}
19967		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
19968			// get background color
19969			$old_bgcolor = $this->bgcolor;
19970			$this->SetFillColorArray($tag['bgcolor']);
19971			$fill = true;
19972		}
19973		if (!$border AND !$fill) {
19974			// nothing to draw
19975			return;
19976		}
19977		if (isset($tag['attribute']['cellspacing'])) {
19978			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19979			$cellspacing = array('H' => $clsp, 'V' => $clsp);
19980		} elseif (isset($tag['border-spacing'])) {
19981			$cellspacing = $tag['border-spacing'];
19982		} else {
19983			$cellspacing = array('H' => 0, 'V' => 0);
19984		}
19985		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
19986			// draw the border externally respect the sqare edge.
19987			$border['mode'] = 'ext';
19988		}
19989		if ($this->rtl) {
19990			if ($xmax >= $tag['borderposition']['x']) {
19991				$xmax = $tag['borderposition']['xmax'];
19992			}
19993			$w = ($tag['borderposition']['x'] - $xmax);
19994		} else {
19995			if ($xmax <= $tag['borderposition']['x']) {
19996				$xmax = $tag['borderposition']['xmax'];
19997			}
19998			$w = ($xmax - $tag['borderposition']['x']);
19999		}
20000		if ($w <= 0) {
20001			return;
20002		}
20003		$w += $cellspacing['H'];
20004		$startpage = $tag['borderposition']['page'];
20005		$startcolumn = $tag['borderposition']['column'];
20006		$x = $tag['borderposition']['x'];
20007		$y = $tag['borderposition']['y'];
20008		$endpage = $this->page;
20009		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20010		$currentY = $this->y;
20011		$this->x = $x;
20012		// get latest column
20013		$endcolumn = $this->current_column;
20014		if ($this->num_columns == 0) {
20015			$this->num_columns = 1;
20016		}
20017		// get border modes
20018		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20019		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20020		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20021		// temporary disable page regions
20022		$temp_page_regions = $this->page_regions;
20023		$this->page_regions = array();
20024		// design borders around HTML cells.
20025		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20026			$ccode = '';
20027			$this->setPage($page);
20028			if ($this->num_columns < 2) {
20029				// single-column mode
20030				$this->x = $x;
20031				$this->y = $this->tMargin;
20032			}
20033			// account for margin changes
20034			if ($page > $startpage) {
20035				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20036					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20037				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20038					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20039				}
20040			}
20041			if ($startpage == $endpage) {
20042				// single page
20043				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20044					$this->selectColumn($column);
20045					if ($startcolumn == $endcolumn) { // single column
20046						$cborder = $border;
20047						$h = ($currentY - $y) + $cellspacing['V'];
20048						$this->y = $starty;
20049					} elseif ($column == $startcolumn) { // first column
20050						$cborder = $border_start;
20051						$this->y = $starty;
20052						$h = $this->h - $this->y - $this->bMargin;
20053					} elseif ($column == $endcolumn) { // end column
20054						$cborder = $border_end;
20055						$h = $currentY - $this->y;
20056					} else { // middle column
20057						$cborder = $border_middle;
20058						$h = $this->h - $this->y - $this->bMargin;
20059					}
20060					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20061				} // end for each column
20062			} elseif ($page == $startpage) { // first page
20063				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20064					$this->selectColumn($column);
20065					if ($column == $startcolumn) { // first column
20066						$cborder = $border_start;
20067						$this->y = $starty;
20068						$h = $this->h - $this->y - $this->bMargin;
20069					} else { // middle column
20070						$cborder = $border_middle;
20071						$h = $this->h - $this->y - $this->bMargin;
20072					}
20073					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20074				} // end for each column
20075			} elseif ($page == $endpage) { // last page
20076				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20077					$this->selectColumn($column);
20078					if ($column == $endcolumn) {
20079						// end column
20080						$cborder = $border_end;
20081						$h = $currentY - $this->y;
20082					} else {
20083						// middle column
20084						$cborder = $border_middle;
20085						$h = $this->h - $this->y - $this->bMargin;
20086					}
20087					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20088				} // end for each column
20089			} else { // middle page
20090				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20091					$this->selectColumn($column);
20092					$cborder = $border_middle;
20093					$h = $this->h - $this->y - $this->bMargin;
20094					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20095				} // end for each column
20096			}
20097			if ($cborder OR $fill) {
20098				$offsetlen = strlen($ccode);
20099				// draw border and fill
20100				if ($this->inxobj) {
20101					// we are inside an XObject template
20102					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20103						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20104						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20105						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20106					} else {
20107						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20108						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20109					}
20110					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20111					$pstart = substr($pagebuff, 0, $pagemark);
20112					$pend = substr($pagebuff, $pagemark);
20113					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20114				} else {
20115					if (end($this->transfmrk[$this->page]) !== false) {
20116						$pagemarkkey = key($this->transfmrk[$this->page]);
20117						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20118					} elseif ($this->InFooter) {
20119						$pagemark = $this->footerpos[$this->page];
20120					} else {
20121						$pagemark = $this->intmrk[$this->page];
20122					}
20123					$pagebuff = $this->getPageBuffer($this->page);
20124					$pstart = substr($pagebuff, 0, $pagemark);
20125					$pend = substr($pagebuff, $pagemark);
20126					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20127					$this->bordermrk[$this->page] += $offsetlen;
20128					$this->cntmrk[$this->page] += $offsetlen;
20129				}
20130			}
20131		} // end for each page
20132		// restore page regions
20133		$this->page_regions = $temp_page_regions;
20134		if (isset($old_bgcolor)) {
20135			// restore background color
20136			$this->SetFillColorArray($old_bgcolor);
20137		}
20138		// restore pointer position
20139		$this->x = $prev_x;
20140		$this->y = $prev_y;
20141		$this->lasth = $prev_lasth;
20142	}
20143
20144	/**
20145	 * Set the default bullet to be used as LI bullet symbol
20146	 * @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')
20147	 * @public
20148	 * @since 4.0.028 (2008-09-26)
20149	 */
20150	public function setLIsymbol($symbol='!') {
20151		// check for custom image symbol
20152		if (substr($symbol, 0, 4) == 'img|') {
20153			$this->lisymbol = $symbol;
20154			return;
20155		}
20156		$symbol = strtolower($symbol);
20157		$valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
20158		if (in_array($symbol, $valid_symbols)) {
20159			$this->lisymbol = $symbol;
20160		} else {
20161			$this->lisymbol = '';
20162		}
20163	}
20164
20165	/**
20166	 * Set the booklet mode for double-sided pages.
20167	 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20168	 * @param $inner (float) Inner page margin.
20169	 * @param $outer (float) Outer page margin.
20170	 * @public
20171	 * @since 4.2.000 (2008-10-29)
20172	 */
20173	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20174		$this->booklet = $booklet;
20175		if ($inner >= 0) {
20176			$this->lMargin = $inner;
20177		}
20178		if ($outer >= 0) {
20179			$this->rMargin = $outer;
20180		}
20181	}
20182
20183	/**
20184	 * Swap the left and right margins.
20185	 * @param $reverse (boolean) if true swap left and right margins.
20186	 * @protected
20187	 * @since 4.2.000 (2008-10-29)
20188	 */
20189	protected function swapMargins($reverse=true) {
20190		if ($reverse) {
20191			// swap left and right margins
20192			$mtemp = $this->original_lMargin;
20193			$this->original_lMargin = $this->original_rMargin;
20194			$this->original_rMargin = $mtemp;
20195			$deltam = $this->original_lMargin - $this->original_rMargin;
20196			$this->lMargin += $deltam;
20197			$this->rMargin -= $deltam;
20198		}
20199	}
20200
20201	/**
20202	 * Set the vertical spaces for HTML tags.
20203	 * The array must have the following structure (example):
20204	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20205	 * The first array level contains the tag names,
20206	 * the second level contains 0 for opening tags or 1 for closing tags,
20207	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20208	 * If the h parameter is not specified, default values are used.
20209	 * @param $tagvs (array) array of tags and relative vertical spaces.
20210	 * @public
20211	 * @since 4.2.001 (2008-10-30)
20212	 */
20213	public function setHtmlVSpace($tagvs) {
20214		$this->tagvspaces = $tagvs;
20215	}
20216
20217	/**
20218	 * Set custom width for list indentation.
20219	 * @param $width (float) width of the indentation. Use negative value to disable it.
20220	 * @public
20221	 * @since 4.2.007 (2008-11-12)
20222	 */
20223	public function setListIndentWidth($width) {
20224		return $this->customlistindent = floatval($width);
20225	}
20226
20227	/**
20228	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20229	 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20230	 * @public
20231	 * @since 4.2.010 (2008-11-14)
20232	 */
20233	public function setOpenCell($isopen) {
20234		$this->opencell = $isopen;
20235	}
20236
20237	/**
20238	 * Set the color and font style for HTML links.
20239	 * @param $color (array) RGB array of colors
20240	 * @param $fontstyle (string) additional font styles to add
20241	 * @public
20242	 * @since 4.4.003 (2008-12-09)
20243	 */
20244	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20245		$this->htmlLinkColorArray = $color;
20246		$this->htmlLinkFontStyle = $fontstyle;
20247	}
20248
20249	/**
20250	 * Convert HTML string containing value and unit of measure to user's units or points.
20251	 * @param $htmlval (string) String containing values and unit.
20252	 * @param $refsize (string) Reference value in points.
20253	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20254	 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20255	 * @return float value in user's unit or point if $points=true
20256	 * @public
20257	 * @since 4.4.004 (2008-12-10)
20258	 */
20259	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20260		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20261		$retval = 0;
20262		$value = 0;
20263		$unit = 'px';
20264		if ($points) {
20265			$k = 1;
20266		} else {
20267			$k = $this->k;
20268		}
20269		if (in_array($defaultunit, $supportedunits)) {
20270			$unit = $defaultunit;
20271		}
20272		if (is_numeric($htmlval)) {
20273			$value = floatval($htmlval);
20274		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20275			$value = floatval($mnum[1]);
20276			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20277				if (in_array($munit[1], $supportedunits)) {
20278					$unit = $munit[1];
20279				}
20280			}
20281		}
20282		switch ($unit) {
20283			// percentage
20284			case '%': {
20285				$retval = (($value * $refsize) / 100);
20286				break;
20287			}
20288			// relative-size
20289			case 'em': {
20290				$retval = ($value * $refsize);
20291				break;
20292			}
20293			// height of lower case 'x' (about half the font-size)
20294			case 'ex': {
20295				$retval = ($value * ($refsize / 2));
20296				break;
20297			}
20298			// absolute-size
20299			case 'in': {
20300				$retval = (($value * $this->dpi) / $k);
20301				break;
20302			}
20303			// centimeters
20304			case 'cm': {
20305				$retval = (($value / 2.54 * $this->dpi) / $k);
20306				break;
20307			}
20308			// millimeters
20309			case 'mm': {
20310				$retval = (($value / 25.4 * $this->dpi) / $k);
20311				break;
20312			}
20313			// one pica is 12 points
20314			case 'pc': {
20315				$retval = (($value * 12) / $k);
20316				break;
20317			}
20318			// points
20319			case 'pt': {
20320				$retval = ($value / $k);
20321				break;
20322			}
20323			// pixels
20324			case 'px': {
20325				$retval = $this->pixelsToUnits($value);
20326				if ($points) {
20327					$retval *= $this->k;
20328				}
20329				break;
20330			}
20331		}
20332		return $retval;
20333	}
20334
20335	/**
20336	 * Output an HTML list bullet or ordered item symbol
20337	 * @param $listdepth (int) list nesting level
20338	 * @param $listtype (string) type of list
20339	 * @param $size (float) current font size
20340	 * @protected
20341	 * @since 4.4.004 (2008-12-10)
20342	 */
20343	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20344		if ($this->state != 2) {
20345			return;
20346		}
20347		$size /= $this->k;
20348		$fill = '';
20349		$bgcolor = $this->bgcolor;
20350		$color = $this->fgcolor;
20351		$strokecolor = $this->strokecolor;
20352		$width = 0;
20353		$textitem = '';
20354		$tmpx = $this->x;
20355		$lspace = $this->GetStringWidth('  ');
20356		if ($listtype == '^') {
20357			// special symbol used for avoid justification of rect bullet
20358			$this->lispacer = '';
20359			return;
20360		} elseif ($listtype == '!') {
20361			// set default list type for unordered list
20362			$deftypes = array('disc', 'circle', 'square');
20363			$listtype = $deftypes[($listdepth - 1) % 3];
20364		} elseif ($listtype == '#') {
20365			// set default list type for ordered list
20366			$listtype = 'decimal';
20367		} elseif (substr($listtype, 0, 4) == 'img|') {
20368			// custom image type ('img|type|width|height|image.ext')
20369			$img = explode('|', $listtype);
20370			$listtype = 'img';
20371		}
20372		switch ($listtype) {
20373			// unordered types
20374			case 'none': {
20375				break;
20376			}
20377			case 'disc': {
20378				$r = $size / 6;
20379				$lspace += (2 * $r);
20380				if ($this->rtl) {
20381					$this->x += $lspace;
20382				} else {
20383					$this->x -= $lspace;
20384				}
20385				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20386				break;
20387			}
20388			case 'circle': {
20389				$r = $size / 6;
20390				$lspace += (2 * $r);
20391				if ($this->rtl) {
20392					$this->x += $lspace;
20393				} else {
20394					$this->x -= $lspace;
20395				}
20396				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20397				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20398				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20399				$this->_out($prev_line_style); // restore line settings
20400				break;
20401			}
20402			case 'square': {
20403				$l = $size / 3;
20404				$lspace += $l;
20405				if ($this->rtl) {;
20406					$this->x += $lspace;
20407				} else {
20408					$this->x -= $lspace;
20409				}
20410				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20411				break;
20412			}
20413			case 'img': {
20414				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20415				$lspace += $img[2];
20416				if ($this->rtl) {;
20417					$this->x += $lspace;
20418				} else {
20419					$this->x -= $lspace;
20420				}
20421				$imgtype = strtolower($img[1]);
20422				$prev_y = $this->y;
20423				switch ($imgtype) {
20424					case 'svg': {
20425						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20426						break;
20427					}
20428					case 'ai':
20429					case 'eps': {
20430						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20431						break;
20432					}
20433					default: {
20434						$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);
20435						break;
20436					}
20437				}
20438				$this->y = $prev_y;
20439				break;
20440			}
20441			// ordered types
20442			// $this->listcount[$this->listnum];
20443			// $textitem
20444			case '1':
20445			case 'decimal': {
20446				$textitem = $this->listcount[$this->listnum];
20447				break;
20448			}
20449			case 'decimal-leading-zero': {
20450				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20451				break;
20452			}
20453			case 'i':
20454			case 'lower-roman': {
20455				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20456				break;
20457			}
20458			case 'I':
20459			case 'upper-roman': {
20460				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20461				break;
20462			}
20463			case 'a':
20464			case 'lower-alpha':
20465			case 'lower-latin': {
20466				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20467				break;
20468			}
20469			case 'A':
20470			case 'upper-alpha':
20471			case 'upper-latin': {
20472				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20473				break;
20474			}
20475			case 'lower-greek': {
20476				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20477				break;
20478			}
20479			/*
20480			// Types to be implemented (special handling)
20481			case 'hebrew': {
20482				break;
20483			}
20484			case 'armenian': {
20485				break;
20486			}
20487			case 'georgian': {
20488				break;
20489			}
20490			case 'cjk-ideographic': {
20491				break;
20492			}
20493			case 'hiragana': {
20494				break;
20495			}
20496			case 'katakana': {
20497				break;
20498			}
20499			case 'hiragana-iroha': {
20500				break;
20501			}
20502			case 'katakana-iroha': {
20503				break;
20504			}
20505			*/
20506			default: {
20507				$textitem = $this->listcount[$this->listnum];
20508			}
20509		}
20510		if (!TCPDF_STATIC::empty_string($textitem)) {
20511			// Check whether we need a new page or new column
20512			$prev_y = $this->y;
20513			$h = $this->getCellHeight($this->FontSize);
20514			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20515				$tmpx = $this->x;
20516			}
20517			// print ordered item
20518			if ($this->rtl) {
20519				$textitem = '.'.$textitem;
20520			} else {
20521				$textitem = $textitem.'.';
20522			}
20523			$lspace += $this->GetStringWidth($textitem);
20524			if ($this->rtl) {
20525				$this->x += $lspace;
20526			} else {
20527				$this->x -= $lspace;
20528			}
20529			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20530		}
20531		$this->x = $tmpx;
20532		$this->lispacer = '^';
20533		// restore colors
20534		$this->SetFillColorArray($bgcolor);
20535		$this->SetDrawColorArray($strokecolor);
20536		$this->SettextColorArray($color);
20537	}
20538
20539	/**
20540	 * Returns current graphic variables as array.
20541	 * @return array of graphic variables
20542	 * @protected
20543	 * @since 4.2.010 (2008-11-14)
20544	 */
20545	protected function getGraphicVars() {
20546		$grapvars = array(
20547			'FontFamily' => $this->FontFamily,
20548			'FontStyle' => $this->FontStyle,
20549			'FontSizePt' => $this->FontSizePt,
20550			'rMargin' => $this->rMargin,
20551			'lMargin' => $this->lMargin,
20552			'cell_padding' => $this->cell_padding,
20553			'cell_margin' => $this->cell_margin,
20554			'LineWidth' => $this->LineWidth,
20555			'linestyleWidth' => $this->linestyleWidth,
20556			'linestyleCap' => $this->linestyleCap,
20557			'linestyleJoin' => $this->linestyleJoin,
20558			'linestyleDash' => $this->linestyleDash,
20559			'textrendermode' => $this->textrendermode,
20560			'textstrokewidth' => $this->textstrokewidth,
20561			'DrawColor' => $this->DrawColor,
20562			'FillColor' => $this->FillColor,
20563			'TextColor' => $this->TextColor,
20564			'ColorFlag' => $this->ColorFlag,
20565			'bgcolor' => $this->bgcolor,
20566			'fgcolor' => $this->fgcolor,
20567			'htmlvspace' => $this->htmlvspace,
20568			'listindent' => $this->listindent,
20569			'listindentlevel' => $this->listindentlevel,
20570			'listnum' => $this->listnum,
20571			'listordered' => $this->listordered,
20572			'listcount' => $this->listcount,
20573			'lispacer' => $this->lispacer,
20574			'cell_height_ratio' => $this->cell_height_ratio,
20575			'font_stretching' => $this->font_stretching,
20576			'font_spacing' => $this->font_spacing,
20577			'alpha' => $this->alpha,
20578			// extended
20579			'lasth' => $this->lasth,
20580			'tMargin' => $this->tMargin,
20581			'bMargin' => $this->bMargin,
20582			'AutoPageBreak' => $this->AutoPageBreak,
20583			'PageBreakTrigger' => $this->PageBreakTrigger,
20584			'x' => $this->x,
20585			'y' => $this->y,
20586			'w' => $this->w,
20587			'h' => $this->h,
20588			'wPt' => $this->wPt,
20589			'hPt' => $this->hPt,
20590			'fwPt' => $this->fwPt,
20591			'fhPt' => $this->fhPt,
20592			'page' => $this->page,
20593			'current_column' => $this->current_column,
20594			'num_columns' => $this->num_columns
20595			);
20596		return $grapvars;
20597	}
20598
20599	/**
20600	 * Set graphic variables.
20601	 * @param $gvars (array) array of graphic variablesto restore
20602	 * @param $extended (boolean) if true restore extended graphic variables
20603	 * @protected
20604	 * @since 4.2.010 (2008-11-14)
20605	 */
20606	protected function setGraphicVars($gvars, $extended=false) {
20607		if ($this->state != 2) {
20608			 return;
20609		}
20610		$this->FontFamily = $gvars['FontFamily'];
20611		$this->FontStyle = $gvars['FontStyle'];
20612		$this->FontSizePt = $gvars['FontSizePt'];
20613		$this->rMargin = $gvars['rMargin'];
20614		$this->lMargin = $gvars['lMargin'];
20615		$this->cell_padding = $gvars['cell_padding'];
20616		$this->cell_margin = $gvars['cell_margin'];
20617		$this->LineWidth = $gvars['LineWidth'];
20618		$this->linestyleWidth = $gvars['linestyleWidth'];
20619		$this->linestyleCap = $gvars['linestyleCap'];
20620		$this->linestyleJoin = $gvars['linestyleJoin'];
20621		$this->linestyleDash = $gvars['linestyleDash'];
20622		$this->textrendermode = $gvars['textrendermode'];
20623		$this->textstrokewidth = $gvars['textstrokewidth'];
20624		$this->DrawColor = $gvars['DrawColor'];
20625		$this->FillColor = $gvars['FillColor'];
20626		$this->TextColor = $gvars['TextColor'];
20627		$this->ColorFlag = $gvars['ColorFlag'];
20628		$this->bgcolor = $gvars['bgcolor'];
20629		$this->fgcolor = $gvars['fgcolor'];
20630		$this->htmlvspace = $gvars['htmlvspace'];
20631		$this->listindent = $gvars['listindent'];
20632		$this->listindentlevel = $gvars['listindentlevel'];
20633		$this->listnum = $gvars['listnum'];
20634		$this->listordered = $gvars['listordered'];
20635		$this->listcount = $gvars['listcount'];
20636		$this->lispacer = $gvars['lispacer'];
20637		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20638		$this->font_stretching = $gvars['font_stretching'];
20639		$this->font_spacing = $gvars['font_spacing'];
20640		$this->alpha = $gvars['alpha'];
20641		if ($extended) {
20642			// restore extended values
20643			$this->lasth = $gvars['lasth'];
20644			$this->tMargin = $gvars['tMargin'];
20645			$this->bMargin = $gvars['bMargin'];
20646			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20647			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20648			$this->x = $gvars['x'];
20649			$this->y = $gvars['y'];
20650			$this->w = $gvars['w'];
20651			$this->h = $gvars['h'];
20652			$this->wPt = $gvars['wPt'];
20653			$this->hPt = $gvars['hPt'];
20654			$this->fwPt = $gvars['fwPt'];
20655			$this->fhPt = $gvars['fhPt'];
20656			$this->page = $gvars['page'];
20657			$this->current_column = $gvars['current_column'];
20658			$this->num_columns = $gvars['num_columns'];
20659		}
20660		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20661		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20662			$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20663		}
20664	}
20665
20666	/**
20667	 * Outputs the "save graphics state" operator 'q'
20668	 * @protected
20669	 */
20670	protected function _outSaveGraphicsState() {
20671		$this->_out('q');
20672	}
20673
20674	/**
20675	 * Outputs the "restore graphics state" operator 'Q'
20676	 * @protected
20677	 */
20678	protected function _outRestoreGraphicsState() {
20679		$this->_out('Q');
20680	}
20681
20682	/**
20683	 * Set buffer content (always append data).
20684	 * @param $data (string) data
20685	 * @protected
20686	 * @since 4.5.000 (2009-01-02)
20687	 */
20688	protected function setBuffer($data) {
20689		$this->bufferlen += strlen($data);
20690		$this->buffer .= $data;
20691	}
20692
20693	/**
20694	 * Replace the buffer content
20695	 * @param $data (string) data
20696	 * @protected
20697	 * @since 5.5.000 (2010-06-22)
20698	 */
20699	protected function replaceBuffer($data) {
20700		$this->bufferlen = strlen($data);
20701		$this->buffer = $data;
20702	}
20703
20704	/**
20705	 * Get buffer content.
20706	 * @return string buffer content
20707	 * @protected
20708	 * @since 4.5.000 (2009-01-02)
20709	 */
20710	protected function getBuffer() {
20711		return $this->buffer;
20712	}
20713
20714	/**
20715	 * Set page buffer content.
20716	 * @param $page (int) page number
20717	 * @param $data (string) page data
20718	 * @param $append (boolean) if true append data, false replace.
20719	 * @protected
20720	 * @since 4.5.000 (2008-12-31)
20721	 */
20722	protected function setPageBuffer($page, $data, $append=false) {
20723		if ($append) {
20724			$this->pages[$page] .= $data;
20725		} else {
20726			$this->pages[$page] = $data;
20727		}
20728		if ($append AND isset($this->pagelen[$page])) {
20729			$this->pagelen[$page] += strlen($data);
20730		} else {
20731			$this->pagelen[$page] = strlen($data);
20732		}
20733	}
20734
20735	/**
20736	 * Get page buffer content.
20737	 * @param $page (int) page number
20738	 * @return string page buffer content or false in case of error
20739	 * @protected
20740	 * @since 4.5.000 (2008-12-31)
20741	 */
20742	protected function getPageBuffer($page) {
20743		if (isset($this->pages[$page])) {
20744			return $this->pages[$page];
20745		}
20746		return false;
20747	}
20748
20749	/**
20750	 * Set image buffer content.
20751	 * @param $image (string) image key
20752	 * @param $data (array) image data
20753	 * @return int image index number
20754	 * @protected
20755	 * @since 4.5.000 (2008-12-31)
20756	 */
20757	protected function setImageBuffer($image, $data) {
20758		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20759			$this->imagekeys[$this->numimages] = $image;
20760			$data['i'] = $this->numimages;
20761			++$this->numimages;
20762		}
20763		$this->images[$image] = $data;
20764		return $data['i'];
20765	}
20766
20767	/**
20768	 * Set image buffer content for a specified sub-key.
20769	 * @param $image (string) image key
20770	 * @param $key (string) image sub-key
20771	 * @param $data (array) image data
20772	 * @protected
20773	 * @since 4.5.000 (2008-12-31)
20774	 */
20775	protected function setImageSubBuffer($image, $key, $data) {
20776		if (!isset($this->images[$image])) {
20777			$this->setImageBuffer($image, array());
20778		}
20779		$this->images[$image][$key] = $data;
20780	}
20781
20782	/**
20783	 * Get image buffer content.
20784	 * @param $image (string) image key
20785	 * @return string image buffer content or false in case of error
20786	 * @protected
20787	 * @since 4.5.000 (2008-12-31)
20788	 */
20789	protected function getImageBuffer($image) {
20790		if (isset($this->images[$image])) {
20791			return $this->images[$image];
20792		}
20793		return false;
20794	}
20795
20796	/**
20797	 * Set font buffer content.
20798	 * @param $font (string) font key
20799	 * @param $data (array) font data
20800	 * @protected
20801	 * @since 4.5.000 (2009-01-02)
20802	 */
20803	protected function setFontBuffer($font, $data) {
20804		$this->fonts[$font] = $data;
20805		if (!in_array($font, $this->fontkeys)) {
20806			$this->fontkeys[] = $font;
20807			// store object ID for current font
20808			++$this->n;
20809			$this->font_obj_ids[$font] = $this->n;
20810			$this->setFontSubBuffer($font, 'n', $this->n);
20811		}
20812	}
20813
20814	/**
20815	 * Set font buffer content.
20816	 * @param $font (string) font key
20817	 * @param $key (string) font sub-key
20818	 * @param $data (array) font data
20819	 * @protected
20820	 * @since 4.5.000 (2009-01-02)
20821	 */
20822	protected function setFontSubBuffer($font, $key, $data) {
20823		if (!isset($this->fonts[$font])) {
20824			$this->setFontBuffer($font, array());
20825		}
20826		$this->fonts[$font][$key] = $data;
20827	}
20828
20829	/**
20830	 * Get font buffer content.
20831	 * @param $font (string) font key
20832	 * @return string font buffer content or false in case of error
20833	 * @protected
20834	 * @since 4.5.000 (2009-01-02)
20835	 */
20836	protected function getFontBuffer($font) {
20837		if (isset($this->fonts[$font])) {
20838			return $this->fonts[$font];
20839		}
20840		return false;
20841	}
20842
20843	/**
20844	 * Move a page to a previous position.
20845	 * @param $frompage (int) number of the source page
20846	 * @param $topage (int) number of the destination page (must be less than $frompage)
20847	 * @return true in case of success, false in case of error.
20848	 * @public
20849	 * @since 4.5.000 (2009-01-02)
20850	 */
20851	public function movePage($frompage, $topage) {
20852		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20853			return false;
20854		}
20855		if ($frompage == $this->page) {
20856			// close the page before moving it
20857			$this->endPage();
20858		}
20859		// move all page-related states
20860		$tmppage = $this->getPageBuffer($frompage);
20861		$tmppagedim = $this->pagedim[$frompage];
20862		$tmppagelen = $this->pagelen[$frompage];
20863		$tmpintmrk = $this->intmrk[$frompage];
20864		$tmpbordermrk = $this->bordermrk[$frompage];
20865		$tmpcntmrk = $this->cntmrk[$frompage];
20866		$tmppageobjects = $this->pageobjects[$frompage];
20867		if (isset($this->footerpos[$frompage])) {
20868			$tmpfooterpos = $this->footerpos[$frompage];
20869		}
20870		if (isset($this->footerlen[$frompage])) {
20871			$tmpfooterlen = $this->footerlen[$frompage];
20872		}
20873		if (isset($this->transfmrk[$frompage])) {
20874			$tmptransfmrk = $this->transfmrk[$frompage];
20875		}
20876		if (isset($this->PageAnnots[$frompage])) {
20877			$tmpannots = $this->PageAnnots[$frompage];
20878		}
20879		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20880			for ($i = $frompage; $i > $topage; --$i) {
20881				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20882					--$this->pagegroups[$this->newpagegroup[$i]];
20883					break;
20884				}
20885			}
20886			for ($i = $topage; $i > 0; --$i) {
20887				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20888					++$this->pagegroups[$this->newpagegroup[$i]];
20889					break;
20890				}
20891			}
20892		}
20893		for ($i = $frompage; $i > $topage; --$i) {
20894			$j = $i - 1;
20895			// shift pages down
20896			$this->setPageBuffer($i, $this->getPageBuffer($j));
20897			$this->pagedim[$i] = $this->pagedim[$j];
20898			$this->pagelen[$i] = $this->pagelen[$j];
20899			$this->intmrk[$i] = $this->intmrk[$j];
20900			$this->bordermrk[$i] = $this->bordermrk[$j];
20901			$this->cntmrk[$i] = $this->cntmrk[$j];
20902			$this->pageobjects[$i] = $this->pageobjects[$j];
20903			if (isset($this->footerpos[$j])) {
20904				$this->footerpos[$i] = $this->footerpos[$j];
20905			} elseif (isset($this->footerpos[$i])) {
20906				unset($this->footerpos[$i]);
20907			}
20908			if (isset($this->footerlen[$j])) {
20909				$this->footerlen[$i] = $this->footerlen[$j];
20910			} elseif (isset($this->footerlen[$i])) {
20911				unset($this->footerlen[$i]);
20912			}
20913			if (isset($this->transfmrk[$j])) {
20914				$this->transfmrk[$i] = $this->transfmrk[$j];
20915			} elseif (isset($this->transfmrk[$i])) {
20916				unset($this->transfmrk[$i]);
20917			}
20918			if (isset($this->PageAnnots[$j])) {
20919				$this->PageAnnots[$i] = $this->PageAnnots[$j];
20920			} elseif (isset($this->PageAnnots[$i])) {
20921				unset($this->PageAnnots[$i]);
20922			}
20923			if (isset($this->newpagegroup[$j])) {
20924				$this->newpagegroup[$i] = $this->newpagegroup[$j];
20925				unset($this->newpagegroup[$j]);
20926			}
20927			if ($this->currpagegroup == $j) {
20928				$this->currpagegroup = $i;
20929			}
20930		}
20931		$this->setPageBuffer($topage, $tmppage);
20932		$this->pagedim[$topage] = $tmppagedim;
20933		$this->pagelen[$topage] = $tmppagelen;
20934		$this->intmrk[$topage] = $tmpintmrk;
20935		$this->bordermrk[$topage] = $tmpbordermrk;
20936		$this->cntmrk[$topage] = $tmpcntmrk;
20937		$this->pageobjects[$topage] = $tmppageobjects;
20938		if (isset($tmpfooterpos)) {
20939			$this->footerpos[$topage] = $tmpfooterpos;
20940		} elseif (isset($this->footerpos[$topage])) {
20941			unset($this->footerpos[$topage]);
20942		}
20943		if (isset($tmpfooterlen)) {
20944			$this->footerlen[$topage] = $tmpfooterlen;
20945		} elseif (isset($this->footerlen[$topage])) {
20946			unset($this->footerlen[$topage]);
20947		}
20948		if (isset($tmptransfmrk)) {
20949			$this->transfmrk[$topage] = $tmptransfmrk;
20950		} elseif (isset($this->transfmrk[$topage])) {
20951			unset($this->transfmrk[$topage]);
20952		}
20953		if (isset($tmpannots)) {
20954			$this->PageAnnots[$topage] = $tmpannots;
20955		} elseif (isset($this->PageAnnots[$topage])) {
20956			unset($this->PageAnnots[$topage]);
20957		}
20958		// adjust outlines
20959		$tmpoutlines = $this->outlines;
20960		foreach ($tmpoutlines as $key => $outline) {
20961			if (!$outline['f']) {
20962				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
20963					$this->outlines[$key]['p'] = ($outline['p'] + 1);
20964				} elseif ($outline['p'] == $frompage) {
20965					$this->outlines[$key]['p'] = $topage;
20966				}
20967			}
20968		}
20969		// adjust dests
20970		$tmpdests = $this->dests;
20971		foreach ($tmpdests as $key => $dest) {
20972			if (!$dest['f']) {
20973				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
20974					$this->dests[$key]['p'] = ($dest['p'] + 1);
20975				} elseif ($dest['p'] == $frompage) {
20976					$this->dests[$key]['p'] = $topage;
20977				}
20978			}
20979		}
20980		// adjust links
20981		$tmplinks = $this->links;
20982		foreach ($tmplinks as $key => $link) {
20983			if (!$link['f']) {
20984				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
20985					$this->links[$key]['p'] = ($link['p'] + 1);
20986				} elseif ($link['p'] == $frompage) {
20987					$this->links[$key]['p'] = $topage;
20988				}
20989			}
20990		}
20991		// adjust javascript
20992		$jfrompage = $frompage;
20993		$jtopage = $topage;
20994		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
20995			foreach($pamatch[0] as $pk => $pmatch) {
20996				$pagenum = intval($pamatch[3][$pk]) + 1;
20997				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
20998					$newpage = ($pagenum + 1);
20999				} elseif ($pagenum == $jfrompage) {
21000					$newpage = $jtopage;
21001				} else {
21002					$newpage = $pagenum;
21003				}
21004				--$newpage;
21005				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21006				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21007			}
21008			unset($pamatch);
21009		}
21010		// return to last page
21011		$this->lastPage(true);
21012		return true;
21013	}
21014
21015	/**
21016	 * Remove the specified page.
21017	 * @param $page (int) page to remove
21018	 * @return true in case of success, false in case of error.
21019	 * @public
21020	 * @since 4.6.004 (2009-04-23)
21021	 */
21022	public function deletePage($page) {
21023		if (($page < 1) OR ($page > $this->numpages)) {
21024			return false;
21025		}
21026		// delete current page
21027		unset($this->pages[$page]);
21028		unset($this->pagedim[$page]);
21029		unset($this->pagelen[$page]);
21030		unset($this->intmrk[$page]);
21031		unset($this->bordermrk[$page]);
21032		unset($this->cntmrk[$page]);
21033		foreach ($this->pageobjects[$page] as $oid) {
21034			if (isset($this->offsets[$oid])){
21035				unset($this->offsets[$oid]);
21036			}
21037		}
21038		unset($this->pageobjects[$page]);
21039		if (isset($this->footerpos[$page])) {
21040			unset($this->footerpos[$page]);
21041		}
21042		if (isset($this->footerlen[$page])) {
21043			unset($this->footerlen[$page]);
21044		}
21045		if (isset($this->transfmrk[$page])) {
21046			unset($this->transfmrk[$page]);
21047		}
21048		if (isset($this->PageAnnots[$page])) {
21049			unset($this->PageAnnots[$page]);
21050		}
21051		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21052			for ($i = $page; $i > 0; --$i) {
21053				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21054					--$this->pagegroups[$this->newpagegroup[$i]];
21055					break;
21056				}
21057			}
21058		}
21059		if (isset($this->pageopen[$page])) {
21060			unset($this->pageopen[$page]);
21061		}
21062		if ($page < $this->numpages) {
21063			// update remaining pages
21064			for ($i = $page; $i < $this->numpages; ++$i) {
21065				$j = $i + 1;
21066				// shift pages
21067				$this->setPageBuffer($i, $this->getPageBuffer($j));
21068				$this->pagedim[$i] = $this->pagedim[$j];
21069				$this->pagelen[$i] = $this->pagelen[$j];
21070				$this->intmrk[$i] = $this->intmrk[$j];
21071				$this->bordermrk[$i] = $this->bordermrk[$j];
21072				$this->cntmrk[$i] = $this->cntmrk[$j];
21073				$this->pageobjects[$i] = $this->pageobjects[$j];
21074				if (isset($this->footerpos[$j])) {
21075					$this->footerpos[$i] = $this->footerpos[$j];
21076				} elseif (isset($this->footerpos[$i])) {
21077					unset($this->footerpos[$i]);
21078				}
21079				if (isset($this->footerlen[$j])) {
21080					$this->footerlen[$i] = $this->footerlen[$j];
21081				} elseif (isset($this->footerlen[$i])) {
21082					unset($this->footerlen[$i]);
21083				}
21084				if (isset($this->transfmrk[$j])) {
21085					$this->transfmrk[$i] = $this->transfmrk[$j];
21086				} elseif (isset($this->transfmrk[$i])) {
21087					unset($this->transfmrk[$i]);
21088				}
21089				if (isset($this->PageAnnots[$j])) {
21090					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21091				} elseif (isset($this->PageAnnots[$i])) {
21092					unset($this->PageAnnots[$i]);
21093				}
21094				if (isset($this->newpagegroup[$j])) {
21095					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21096					unset($this->newpagegroup[$j]);
21097				}
21098				if ($this->currpagegroup == $j) {
21099					$this->currpagegroup = $i;
21100				}
21101				if (isset($this->pageopen[$j])) {
21102					$this->pageopen[$i] = $this->pageopen[$j];
21103				} elseif (isset($this->pageopen[$i])) {
21104					unset($this->pageopen[$i]);
21105				}
21106			}
21107			// remove last page
21108			unset($this->pages[$this->numpages]);
21109			unset($this->pagedim[$this->numpages]);
21110			unset($this->pagelen[$this->numpages]);
21111			unset($this->intmrk[$this->numpages]);
21112			unset($this->bordermrk[$this->numpages]);
21113			unset($this->cntmrk[$this->numpages]);
21114			foreach ($this->pageobjects[$this->numpages] as $oid) {
21115				if (isset($this->offsets[$oid])){
21116					unset($this->offsets[$oid]);
21117				}
21118			}
21119			unset($this->pageobjects[$this->numpages]);
21120			if (isset($this->footerpos[$this->numpages])) {
21121				unset($this->footerpos[$this->numpages]);
21122			}
21123			if (isset($this->footerlen[$this->numpages])) {
21124				unset($this->footerlen[$this->numpages]);
21125			}
21126			if (isset($this->transfmrk[$this->numpages])) {
21127				unset($this->transfmrk[$this->numpages]);
21128			}
21129			if (isset($this->PageAnnots[$this->numpages])) {
21130				unset($this->PageAnnots[$this->numpages]);
21131			}
21132			if (isset($this->newpagegroup[$this->numpages])) {
21133				unset($this->newpagegroup[$this->numpages]);
21134			}
21135			if ($this->currpagegroup == $this->numpages) {
21136				$this->currpagegroup = ($this->numpages - 1);
21137			}
21138			if (isset($this->pagegroups[$this->numpages])) {
21139				unset($this->pagegroups[$this->numpages]);
21140			}
21141			if (isset($this->pageopen[$this->numpages])) {
21142				unset($this->pageopen[$this->numpages]);
21143			}
21144		}
21145		--$this->numpages;
21146		$this->page = $this->numpages;
21147		// adjust outlines
21148		$tmpoutlines = $this->outlines;
21149		foreach ($tmpoutlines as $key => $outline) {
21150			if (!$outline['f']) {
21151				if ($outline['p'] > $page) {
21152					$this->outlines[$key]['p'] = $outline['p'] - 1;
21153				} elseif ($outline['p'] == $page) {
21154					unset($this->outlines[$key]);
21155				}
21156			}
21157		}
21158		// adjust dests
21159		$tmpdests = $this->dests;
21160		foreach ($tmpdests as $key => $dest) {
21161			if (!$dest['f']) {
21162				if ($dest['p'] > $page) {
21163					$this->dests[$key]['p'] = $dest['p'] - 1;
21164				} elseif ($dest['p'] == $page) {
21165					unset($this->dests[$key]);
21166				}
21167			}
21168		}
21169		// adjust links
21170		$tmplinks = $this->links;
21171		foreach ($tmplinks as $key => $link) {
21172			if (!$link['f']) {
21173				if ($link['p'] > $page) {
21174					$this->links[$key]['p'] = $link['p'] - 1;
21175				} elseif ($link['p'] == $page) {
21176					unset($this->links[$key]);
21177				}
21178			}
21179		}
21180		// adjust javascript
21181		$jpage = $page;
21182		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21183			foreach($pamatch[0] as $pk => $pmatch) {
21184				$pagenum = intval($pamatch[3][$pk]) + 1;
21185				if ($pagenum >= $jpage) {
21186					$newpage = ($pagenum - 1);
21187				} elseif ($pagenum == $jpage) {
21188					$newpage = 1;
21189				} else {
21190					$newpage = $pagenum;
21191				}
21192				--$newpage;
21193				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21194				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21195			}
21196			unset($pamatch);
21197		}
21198		// return to last page
21199		if ($this->numpages > 0) {
21200			$this->lastPage(true);
21201		}
21202		return true;
21203	}
21204
21205	/**
21206	 * Clone the specified page to a new page.
21207	 * @param $page (int) number of page to copy (0 = current page)
21208	 * @return true in case of success, false in case of error.
21209	 * @public
21210	 * @since 4.9.015 (2010-04-20)
21211	 */
21212	public function copyPage($page=0) {
21213		if ($page == 0) {
21214			// default value
21215			$page = $this->page;
21216		}
21217		if (($page < 1) OR ($page > $this->numpages)) {
21218			return false;
21219		}
21220		// close the last page
21221		$this->endPage();
21222		// copy all page-related states
21223		++$this->numpages;
21224		$this->page = $this->numpages;
21225		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21226		$this->pagedim[$this->page] = $this->pagedim[$page];
21227		$this->pagelen[$this->page] = $this->pagelen[$page];
21228		$this->intmrk[$this->page] = $this->intmrk[$page];
21229		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21230		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21231		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21232		$this->pageopen[$this->page] = false;
21233		if (isset($this->footerpos[$page])) {
21234			$this->footerpos[$this->page] = $this->footerpos[$page];
21235		}
21236		if (isset($this->footerlen[$page])) {
21237			$this->footerlen[$this->page] = $this->footerlen[$page];
21238		}
21239		if (isset($this->transfmrk[$page])) {
21240			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21241		}
21242		if (isset($this->PageAnnots[$page])) {
21243			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21244		}
21245		if (isset($this->newpagegroup[$page])) {
21246			// start a new group
21247			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21248			$this->currpagegroup = $this->newpagegroup[$this->page];
21249			$this->pagegroups[$this->currpagegroup] = 1;
21250		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21251			++$this->pagegroups[$this->currpagegroup];
21252		}
21253		// copy outlines
21254		$tmpoutlines = $this->outlines;
21255		foreach ($tmpoutlines as $key => $outline) {
21256			if ($outline['p'] == $page) {
21257				$this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 'f' => $outline['f'], 's' => $outline['s'], 'c' => $outline['c']);
21258			}
21259		}
21260		// copy links
21261		$tmplinks = $this->links;
21262		foreach ($tmplinks as $key => $link) {
21263			if ($link['p'] == $page) {
21264				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21265			}
21266		}
21267		// return to last page
21268		$this->lastPage(true);
21269		return true;
21270	}
21271
21272	/**
21273	 * Output a Table of Content Index (TOC).
21274	 * This method must be called after all Bookmarks were set.
21275	 * Before calling this method you have to open the page using the addTOCPage() method.
21276	 * After calling this method you have to call endTOCPage() to close the TOC page.
21277	 * You can override this method to achieve different styles.
21278	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21279	 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21280	 * @param $filler (string) string used to fill the space between text and page number.
21281	 * @param $toc_name (string) name to use for TOC bookmark.
21282	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21283	 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21284	 * @public
21285	 * @author Nicola Asuni
21286	 * @since 4.5.000 (2009-01-02)
21287	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21288	 */
21289	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21290		$fontsize = $this->FontSizePt;
21291		$fontfamily = $this->FontFamily;
21292		$fontstyle = $this->FontStyle;
21293		$w = $this->w - $this->lMargin - $this->rMargin;
21294		$spacer = $this->GetStringWidth(chr(32)) * 4;
21295		$lmargin = $this->lMargin;
21296		$rmargin = $this->rMargin;
21297		$x_start = $this->GetX();
21298		$page_first = $this->page;
21299		$current_page = $this->page;
21300		$page_fill_start = false;
21301		$page_fill_end = false;
21302		$current_column = $this->current_column;
21303		if (TCPDF_STATIC::empty_string($numbersfont)) {
21304			$numbersfont = $this->default_monospaced_font;
21305		}
21306		if (TCPDF_STATIC::empty_string($filler)) {
21307			$filler = ' ';
21308		}
21309		if (TCPDF_STATIC::empty_string($page)) {
21310			$gap = ' ';
21311		} else {
21312			$gap = '';
21313			if ($page < 1) {
21314				$page = 1;
21315			}
21316		}
21317		$this->SetFont($numbersfont, $fontstyle, $fontsize);
21318		$numwidth = $this->GetStringWidth('00000');
21319		$maxpage = 0; //used for pages on attached documents
21320		foreach ($this->outlines as $key => $outline) {
21321			// check for extra pages (used for attachments)
21322			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21323				$outline['p'] += ($this->page - $page_first);
21324			}
21325			if ($this->rtl) {
21326				$aligntext = 'R';
21327				$alignnum = 'L';
21328			} else {
21329				$aligntext = 'L';
21330				$alignnum = 'R';
21331			}
21332			if ($outline['l'] == 0) {
21333				$this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21334			} else {
21335				$this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21336			}
21337			$this->SetTextColorArray($outline['c']);
21338			// check for page break
21339			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21340			// set margins and X position
21341			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21342				$this->lMargin = $lmargin;
21343				$this->rMargin = $rmargin;
21344			} else {
21345				if ($this->current_column != $current_column) {
21346					if ($this->rtl) {
21347						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21348					} else {
21349						$x_start = $this->columns[$this->current_column]['x'];
21350					}
21351				}
21352				$lmargin = $this->lMargin;
21353				$rmargin = $this->rMargin;
21354				$current_page = $this->page;
21355				$current_column = $this->current_column;
21356			}
21357			$this->SetX($x_start);
21358			$indent = ($spacer * $outline['l']);
21359			if ($this->rtl) {
21360				$this->x -= $indent;
21361				$this->rMargin = $this->w - $this->x;
21362			} else {
21363				$this->x += $indent;
21364				$this->lMargin = $this->x;
21365			}
21366			$link = $this->AddLink();
21367			$this->SetLink($link, $outline['y'], $outline['p']);
21368			// write the text
21369			if ($this->rtl) {
21370				$txt = ' '.$outline['t'];
21371			} else {
21372				$txt = $outline['t'].' ';
21373			}
21374			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21375			if ($this->rtl) {
21376				$tw = $this->x - $this->lMargin;
21377			} else {
21378				$tw = $this->w - $this->rMargin - $this->x;
21379			}
21380			$this->SetFont($numbersfont, $fontstyle, $fontsize);
21381			if (TCPDF_STATIC::empty_string($page)) {
21382				$pagenum = $outline['p'];
21383			} else {
21384				// placemark to be replaced with the correct number
21385				$pagenum = '{#'.($outline['p']).'}';
21386				if ($this->isUnicodeFont()) {
21387					$pagenum = '{'.$pagenum.'}';
21388				}
21389				$maxpage = max($maxpage, $outline['p']);
21390			}
21391			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21392			$wfiller = $this->GetStringWidth($filler);
21393			if ($wfiller > 0) {
21394				$numfills = floor($fw / $wfiller);
21395			} else {
21396				$numfills = 0;
21397			}
21398			if ($numfills > 0) {
21399				$rowfill = str_repeat($filler, $numfills);
21400			} else {
21401				$rowfill = '';
21402			}
21403			if ($this->rtl) {
21404				$pagenum = $pagenum.$gap.$rowfill;
21405			} else {
21406				$pagenum = $rowfill.$gap.$pagenum;
21407			}
21408			// write the number
21409			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21410		}
21411		$page_last = $this->getPage();
21412		$numpages = ($page_last - $page_first + 1);
21413		// account for booklet mode
21414		if ($this->booklet) {
21415			// check if a blank page is required before TOC
21416			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21417			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21418			if ($page_fill_start) {
21419				// add a page at the end (to be moved before TOC)
21420				$this->addPage();
21421				++$page_last;
21422				++$numpages;
21423			}
21424			if ($page_fill_end) {
21425				// add a page at the end
21426				$this->addPage();
21427				++$page_last;
21428				++$numpages;
21429			}
21430		}
21431		$maxpage = max($maxpage, $page_last);
21432		if (!TCPDF_STATIC::empty_string($page)) {
21433			for ($p = $page_first; $p <= $page_last; ++$p) {
21434				// get page data
21435				$temppage = $this->getPageBuffer($p);
21436				for ($n = 1; $n <= $maxpage; ++$n) {
21437					// update page numbers
21438					$a = '{#'.$n.'}';
21439					// get page number aliases
21440					$pnalias = $this->getInternalPageNumberAliases($a);
21441					// calculate replacement number
21442					if (($n >= $page) AND ($n <= $this->numpages)) {
21443						$np = $n + $numpages;
21444					} else {
21445						$np = $n;
21446					}
21447					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21448					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21449					// replace aliases with numbers
21450					foreach ($pnalias['u'] as $u) {
21451						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21452						if ($this->rtl) {
21453							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21454						} else {
21455							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21456						}
21457						$temppage = str_replace($u, $nr, $temppage);
21458					}
21459					foreach ($pnalias['a'] as $a) {
21460						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21461						if ($this->rtl) {
21462							$nr = $na.' '.$sfill;
21463						} else {
21464							$nr = $sfill.' '.$na;
21465						}
21466						$temppage = str_replace($a, $nr, $temppage);
21467					}
21468				}
21469				// save changes
21470				$this->setPageBuffer($p, $temppage);
21471			}
21472			// move pages
21473			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21474			if ($page_fill_start) {
21475				$this->movePage($page_last, $page_first);
21476			}
21477			for ($i = 0; $i < $numpages; ++$i) {
21478				$this->movePage($page_last, $page);
21479			}
21480		}
21481	}
21482
21483	/**
21484	 * Output a Table Of Content Index (TOC) using HTML templates.
21485	 * This method must be called after all Bookmarks were set.
21486	 * Before calling this method you have to open the page using the addTOCPage() method.
21487	 * After calling this method you have to call endTOCPage() to close the TOC page.
21488	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21489	 * @param $toc_name (string) name to use for TOC bookmark.
21490	 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21491	 * @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)
21492	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21493	 * @param $color (array) RGB color array for title (values from 0 to 255).
21494	 * @public
21495	 * @author Nicola Asuni
21496	 * @since 5.0.001 (2010-05-06)
21497	 * @see addTOCPage(), endTOCPage(), addTOC()
21498	 */
21499	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21500		$filler = ' ';
21501		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21502		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21503		// set new style for link
21504		$this->htmlLinkColorArray = array();
21505		$this->htmlLinkFontStyle = '';
21506		$page_first = $this->getPage();
21507		$page_fill_start = false;
21508		$page_fill_end = false;
21509		// get the font type used for numbers in each template
21510		$current_font = $this->FontFamily;
21511		foreach ($templates as $level => $html) {
21512			$dom = $this->getHtmlDomArray($html);
21513			foreach ($dom as $key => $value) {
21514				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21515					$this->SetFont($dom[($key - 1)]['fontname']);
21516					$templates['F'.$level] = $this->isUnicodeFont();
21517				}
21518			}
21519		}
21520		$this->SetFont($current_font);
21521		$maxpage = 0; //used for pages on attached documents
21522		foreach ($this->outlines as $key => $outline) {
21523			// get HTML template
21524			$row = $templates[$outline['l']];
21525			if (TCPDF_STATIC::empty_string($page)) {
21526				$pagenum = $outline['p'];
21527			} else {
21528				// placemark to be replaced with the correct number
21529				$pagenum = '{#'.($outline['p']).'}';
21530				if ($templates['F'.$outline['l']]) {
21531					$pagenum = '{'.$pagenum.'}';
21532				}
21533				$maxpage = max($maxpage, $outline['p']);
21534			}
21535			// replace templates with current values
21536			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21537			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21538			// add link to page
21539			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21540			// write bookmark entry
21541			$this->writeHTML($row, false, false, true, false, '');
21542		}
21543		// restore link styles
21544		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21545		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21546		// move TOC page and replace numbers
21547		$page_last = $this->getPage();
21548		$numpages = ($page_last - $page_first + 1);
21549		// account for booklet mode
21550		if ($this->booklet) {
21551			// check if a blank page is required before TOC
21552			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21553			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21554			if ($page_fill_start) {
21555				// add a page at the end (to be moved before TOC)
21556				$this->addPage();
21557				++$page_last;
21558				++$numpages;
21559			}
21560			if ($page_fill_end) {
21561				// add a page at the end
21562				$this->addPage();
21563				++$page_last;
21564				++$numpages;
21565			}
21566		}
21567		$maxpage = max($maxpage, $page_last);
21568		if (!TCPDF_STATIC::empty_string($page)) {
21569			for ($p = $page_first; $p <= $page_last; ++$p) {
21570				// get page data
21571				$temppage = $this->getPageBuffer($p);
21572				for ($n = 1; $n <= $maxpage; ++$n) {
21573					// update page numbers
21574					$a = '{#'.$n.'}';
21575					// get page number aliases
21576					$pnalias = $this->getInternalPageNumberAliases($a);
21577					// calculate replacement number
21578					if ($n >= $page) {
21579						$np = $n + $numpages;
21580					} else {
21581						$np = $n;
21582					}
21583					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21584					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21585					// replace aliases with numbers
21586					foreach ($pnalias['u'] as $u) {
21587						if ($correct_align) {
21588							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21589							if ($this->rtl) {
21590								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21591							} else {
21592								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21593							}
21594						} else {
21595							$nr = $nu;
21596						}
21597						$temppage = str_replace($u, $nr, $temppage);
21598					}
21599					foreach ($pnalias['a'] as $a) {
21600						if ($correct_align) {
21601							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21602							if ($this->rtl) {
21603								$nr = $na.' '.$sfill;
21604							} else {
21605								$nr = $sfill.' '.$na;
21606							}
21607						} else {
21608							$nr = $na;
21609						}
21610						$temppage = str_replace($a, $nr, $temppage);
21611					}
21612				}
21613				// save changes
21614				$this->setPageBuffer($p, $temppage);
21615			}
21616			// move pages
21617			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21618			if ($page_fill_start) {
21619				$this->movePage($page_last, $page_first);
21620			}
21621			for ($i = 0; $i < $numpages; ++$i) {
21622				$this->movePage($page_last, $page);
21623			}
21624		}
21625	}
21626
21627	/**
21628	 * Stores a copy of the current TCPDF object used for undo operation.
21629	 * @public
21630	 * @since 4.5.029 (2009-03-19)
21631	 */
21632	public function startTransaction() {
21633		if (isset($this->objcopy)) {
21634			// remove previous copy
21635			$this->commitTransaction();
21636		}
21637		// record current page number and Y position
21638		$this->start_transaction_page = $this->page;
21639		$this->start_transaction_y = $this->y;
21640		// clone current object
21641		$this->objcopy = TCPDF_STATIC::objclone($this);
21642	}
21643
21644	/**
21645	 * Delete the copy of the current TCPDF object used for undo operation.
21646	 * @public
21647	 * @since 4.5.029 (2009-03-19)
21648	 */
21649	public function commitTransaction() {
21650		if (isset($this->objcopy)) {
21651			$this->objcopy->_destroy(true, true);
21652			unset($this->objcopy);
21653		}
21654	}
21655
21656	/**
21657	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21658	 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21659	 * @return TCPDF object.
21660	 * @public
21661	 * @since 4.5.029 (2009-03-19)
21662	 */
21663	public function rollbackTransaction($self=false) {
21664		if (isset($this->objcopy)) {
21665			$this->_destroy(true, true);
21666			if ($self) {
21667				$objvars = get_object_vars($this->objcopy);
21668				foreach ($objvars as $key => $value) {
21669					$this->$key = $value;
21670				}
21671			}
21672			return $this->objcopy;
21673		}
21674		return $this;
21675	}
21676
21677	// --- MULTI COLUMNS METHODS -----------------------
21678
21679	/**
21680	 * Set multiple columns of the same size
21681	 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21682	 * @param $width (int) column width
21683	 * @param $y (int) column starting Y position (leave empty for current Y position)
21684	 * @public
21685	 * @since 4.9.001 (2010-03-28)
21686	 */
21687	public function setEqualColumns($numcols=0, $width=0, $y='') {
21688		$this->columns = array();
21689		if ($numcols < 2) {
21690			$numcols = 0;
21691			$this->columns = array();
21692		} else {
21693			// maximum column width
21694			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21695			if (($width == 0) OR ($width > $maxwidth)) {
21696				$width = $maxwidth;
21697			}
21698			if (TCPDF_STATIC::empty_string($y)) {
21699				$y = $this->y;
21700			}
21701			// space between columns
21702			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21703			// fill the columns array (with, space, starting Y position)
21704			for ($i = 0; $i < $numcols; ++$i) {
21705				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21706			}
21707		}
21708		$this->num_columns = $numcols;
21709		$this->current_column = 0;
21710		$this->column_start_page = $this->page;
21711		$this->selectColumn(0);
21712	}
21713
21714	/**
21715	 * Remove columns and reset page margins.
21716	 * @public
21717	 * @since 5.9.072 (2011-04-26)
21718	 */
21719	public function resetColumns() {
21720		$this->lMargin = $this->original_lMargin;
21721		$this->rMargin = $this->original_rMargin;
21722		$this->setEqualColumns();
21723	}
21724
21725	/**
21726	 * Set columns array.
21727	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21728	 * @param $columns (array)
21729	 * @public
21730	 * @since 4.9.001 (2010-03-28)
21731	 */
21732	public function setColumnsArray($columns) {
21733		$this->columns = $columns;
21734		$this->num_columns = count($columns);
21735		$this->current_column = 0;
21736		$this->column_start_page = $this->page;
21737		$this->selectColumn(0);
21738	}
21739
21740	/**
21741	 * Set position at a given column
21742	 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21743	 * @public
21744	 * @since 4.9.001 (2010-03-28)
21745	 */
21746	public function selectColumn($col='') {
21747		if (is_string($col)) {
21748			$col = $this->current_column;
21749		} elseif ($col >= $this->num_columns) {
21750			$col = 0;
21751		}
21752		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21753		$enable_thead = false;
21754		if ($this->num_columns > 1) {
21755			if ($col != $this->current_column) {
21756				// move Y pointer at the top of the column
21757				if ($this->column_start_page == $this->page) {
21758					$this->y = $this->columns[$col]['y'];
21759				} else {
21760					$this->y = $this->tMargin;
21761				}
21762				// Avoid to write table headers more than once
21763				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21764					$enable_thead = true;
21765					$this->maxselcol['page'] = $this->page;
21766					$this->maxselcol['column'] = $col;
21767				}
21768			}
21769			$xshift = $this->colxshift;
21770			// set X position of the current column by case
21771			$listindent = ($this->listindentlevel * $this->listindent);
21772			// calculate column X position
21773			$colpos = 0;
21774			for ($i = 0; $i < $col; ++$i) {
21775				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21776			}
21777			if ($this->rtl) {
21778				$x = $this->w - $this->original_rMargin - $colpos;
21779				$this->rMargin = ($this->w - $x + $listindent);
21780				$this->lMargin = ($x - $this->columns[$col]['w']);
21781				$this->x = $x - $listindent;
21782			} else {
21783				$x = $this->original_lMargin + $colpos;
21784				$this->lMargin = ($x + $listindent);
21785				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21786				$this->x = $x + $listindent;
21787			}
21788			$this->columns[$col]['x'] = $x;
21789		}
21790		$this->current_column = $col;
21791		// fix for HTML mode
21792		$this->newline = true;
21793		// print HTML table header (if any)
21794		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21795			if ($enable_thead) {
21796				// print table header
21797				$this->writeHTML($this->thead, false, false, false, false, '');
21798				$this->y += $xshift['s']['V'];
21799				// store end of header position
21800				if (!isset($this->columns[$col]['th'])) {
21801					$this->columns[$col]['th'] = array();
21802				}
21803				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21804				$this->lasth = 0;
21805			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21806				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21807			}
21808		}
21809		// account for an html table cell over multiple columns
21810		if ($this->rtl) {
21811			$this->rMargin += $xshift['x'];
21812			$this->x -= ($xshift['x'] + $xshift['p']['R']);
21813		} else {
21814			$this->lMargin += $xshift['x'];
21815			$this->x += $xshift['x'] + $xshift['p']['L'];
21816		}
21817	}
21818
21819	/**
21820	 * Return the current column number
21821	 * @return int current column number
21822	 * @public
21823	 * @since 5.5.011 (2010-07-08)
21824	 */
21825	public function getColumn() {
21826		return $this->current_column;
21827	}
21828
21829	/**
21830	 * Return the current number of columns.
21831	 * @return int number of columns
21832	 * @public
21833	 * @since 5.8.018 (2010-08-25)
21834	 */
21835	public function getNumberOfColumns() {
21836		return $this->num_columns;
21837	}
21838
21839	/**
21840	 * Set Text rendering mode.
21841	 * @param $stroke (int) outline size in user units (0 = disable).
21842	 * @param $fill (boolean) if true fills the text (default).
21843	 * @param $clip (boolean) if true activate clipping mode
21844	 * @public
21845	 * @since 4.9.008 (2009-04-02)
21846	 */
21847	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21848		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21849		// convert text rendering parameters
21850		if ($stroke < 0) {
21851			$stroke = 0;
21852		}
21853		if ($fill === true) {
21854			if ($stroke > 0) {
21855				if ($clip === true) {
21856					// Fill, then stroke text and add to path for clipping
21857					$textrendermode = 6;
21858				} else {
21859					// Fill, then stroke text
21860					$textrendermode = 2;
21861				}
21862				$textstrokewidth = $stroke;
21863			} else {
21864				if ($clip === true) {
21865					// Fill text and add to path for clipping
21866					$textrendermode = 4;
21867				} else {
21868					// Fill text
21869					$textrendermode = 0;
21870				}
21871			}
21872		} else {
21873			if ($stroke > 0) {
21874				if ($clip === true) {
21875					// Stroke text and add to path for clipping
21876					$textrendermode = 5;
21877				} else {
21878					// Stroke text
21879					$textrendermode = 1;
21880				}
21881				$textstrokewidth = $stroke;
21882			} else {
21883				if ($clip === true) {
21884					// Add text to path for clipping
21885					$textrendermode = 7;
21886				} else {
21887					// Neither fill nor stroke text (invisible)
21888					$textrendermode = 3;
21889				}
21890			}
21891		}
21892		$this->textrendermode = $textrendermode;
21893		$this->textstrokewidth = $stroke;
21894	}
21895
21896	/**
21897	 * Set parameters for drop shadow effect for text.
21898	 * @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.
21899	 * @since 5.9.174 (2012-07-25)
21900	 * @public
21901	*/
21902	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21903		if (isset($params['enabled'])) {
21904			$this->txtshadow['enabled'] = $params['enabled']?true:false;
21905		} else {
21906			$this->txtshadow['enabled'] = false;
21907		}
21908		if (isset($params['depth_w'])) {
21909			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
21910		} else {
21911			$this->txtshadow['depth_w'] = 0;
21912		}
21913		if (isset($params['depth_h'])) {
21914			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
21915		} else {
21916			$this->txtshadow['depth_h'] = 0;
21917		}
21918		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21919			$this->txtshadow['color'] = $params['color'];
21920		} else {
21921			$this->txtshadow['color'] = $this->strokecolor;
21922		}
21923		if (isset($params['opacity'])) {
21924			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
21925		} else {
21926			$this->txtshadow['opacity'] = 1;
21927		}
21928		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'))) {
21929			$this->txtshadow['blend_mode'] = $params['blend_mode'];
21930		} else {
21931			$this->txtshadow['blend_mode'] = 'Normal';
21932		}
21933		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
21934			$this->txtshadow['enabled'] = false;
21935		}
21936	}
21937
21938	/**
21939	 * Return the text shadow parameters array.
21940	 * @return Array of parameters.
21941	 * @since 5.9.174 (2012-07-25)
21942	 * @public
21943	 */
21944	public function getTextShadow() {
21945		return $this->txtshadow;
21946	}
21947
21948	/**
21949	 * Returns an array of chars containing soft hyphens.
21950	 * @param $word (array) array of chars
21951	 * @param $patterns (array) Array of hypenation patterns.
21952	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
21953	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
21954	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
21955	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
21956	 * @param $charmax (int) Maximum length of broken piece of word.
21957	 * @return array text with soft hyphens
21958	 * @author Nicola Asuni
21959	 * @since 4.9.012 (2010-04-12)
21960	 * @protected
21961	 */
21962	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
21963		$hyphenword = array(); // hyphens positions
21964		$numchars = count($word);
21965		if ($numchars <= $charmin) {
21966			return $word;
21967		}
21968		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
21969		// some words will be returned as-is
21970		$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})(\]?)$/';
21971		if (preg_match($pattern, $word_string) > 0) {
21972			// email
21973			return $word;
21974		}
21975		$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})(\]?)$/';
21976		if (preg_match($pattern, $word_string) > 0) {
21977			// URL
21978			return $word;
21979		}
21980		if (isset($dictionary[$word_string])) {
21981			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
21982		}
21983		// surround word with '_' characters
21984		$tmpword = array_merge(array(46), $word, array(46));
21985		$tmpnumchars = $numchars + 2;
21986		$maxpos = $tmpnumchars - 1;
21987		for ($pos = 0; $pos < $maxpos; ++$pos) {
21988			$imax = min(($tmpnumchars - $pos), $charmax);
21989			for ($i = 1; $i <= $imax; ++$i) {
21990				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
21991				if (isset($patterns[$subword])) {
21992					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
21993					$pattern_length = count($pattern);
21994					$digits = 1;
21995					for ($j = 0; $j < $pattern_length; ++$j) {
21996						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
21997						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
21998							if ($j == 0) {
21999								$zero = $pos - 1;
22000							} else {
22001								$zero = $pos + $j - $digits;
22002							}
22003							// get hyphenation level
22004							$level = ($pattern[$j] - 48);
22005							// if two levels from two different patterns match at the same point, the higher one is selected.
22006							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22007								$hyphenword[$zero] = $level;
22008							}
22009							++$digits;
22010						}
22011					}
22012				}
22013			}
22014		}
22015		$inserted = 0;
22016		$maxpos = $numchars - $rightmin;
22017		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22018			// only odd levels indicate allowed hyphenation points
22019			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22020				// 173 = soft hyphen character
22021				array_splice($word, $i + $inserted, 0, 173);
22022				++$inserted;
22023			}
22024		}
22025		return $word;
22026	}
22027
22028	/**
22029	 * Returns text with soft hyphens.
22030	 * @param $text (string) text to process
22031	 * @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/
22032	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22033	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22034	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22035	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22036	 * @param $charmax (int) Maximum length of broken piece of word.
22037	 * @return array text with soft hyphens
22038	 * @author Nicola Asuni
22039	 * @since 4.9.012 (2010-04-12)
22040	 * @public
22041	 */
22042	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22043		$text = $this->unhtmlentities($text);
22044		$word = array(); // last word
22045		$txtarr = array(); // text to be returned
22046		$intag = false; // true if we are inside an HTML tag
22047		$skip = false; // true to skip hyphenation
22048		if (!is_array($patterns)) {
22049			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22050		}
22051		// get array of characters
22052		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22053		// for each char
22054		foreach ($unichars as $char) {
22055			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22056				// letter character
22057				$word[] = $char;
22058			} else {
22059				// other type of character
22060				if (!TCPDF_STATIC::empty_string($word)) {
22061					// hypenate the word
22062					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22063					$word = array();
22064				}
22065				$txtarr[] = $char;
22066				if (chr($char) == '<') {
22067					// we are inside an HTML tag
22068					$intag = true;
22069				} elseif ($intag AND (chr($char) == '>')) {
22070					// end of HTML tag
22071					$intag = false;
22072					// check for style tag
22073					$expected = array(115, 116, 121, 108, 101); // = 'style'
22074					$current = array_slice($txtarr, -6, 5); // last 5 chars
22075					$compare = array_diff($expected, $current);
22076					if (empty($compare)) {
22077						// check if it is a closing tag
22078						$expected = array(47); // = '/'
22079						$current = array_slice($txtarr, -7, 1);
22080						$compare = array_diff($expected, $current);
22081						if (empty($compare)) {
22082							// closing style tag
22083							$skip = false;
22084						} else {
22085							// opening style tag
22086							$skip = true;
22087						}
22088					}
22089				}
22090			}
22091		}
22092		if (!TCPDF_STATIC::empty_string($word)) {
22093			// hypenate the word
22094			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22095		}
22096		// convert char array to string and return
22097		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22098	}
22099
22100	/**
22101	 * Enable/disable rasterization of vector images using ImageMagick library.
22102	 * @param $mode (boolean) if true enable rasterization, false otherwise.
22103	 * @public
22104	 * @since 5.0.000 (2010-04-27)
22105	 */
22106	public function setRasterizeVectorImages($mode) {
22107		$this->rasterize_vector_images = $mode;
22108	}
22109
22110	/**
22111	 * Enable or disable default option for font subsetting.
22112	 * @param $enable (boolean) if true enable font subsetting by default.
22113	 * @author Nicola Asuni
22114	 * @public
22115	 * @since 5.3.002 (2010-06-07)
22116	 */
22117	public function setFontSubsetting($enable=true) {
22118		if ($this->pdfa_mode) {
22119			$this->font_subsetting = false;
22120		} else {
22121			$this->font_subsetting = $enable ? true : false;
22122		}
22123	}
22124
22125	/**
22126	 * Return the default option for font subsetting.
22127	 * @return boolean default font subsetting state.
22128	 * @author Nicola Asuni
22129	 * @public
22130	 * @since 5.3.002 (2010-06-07)
22131	 */
22132	public function getFontSubsetting() {
22133		return $this->font_subsetting;
22134	}
22135
22136	/**
22137	 * Left trim the input string
22138	 * @param $str (string) string to trim
22139	 * @param $replace (string) string that replace spaces.
22140	 * @return left trimmed string
22141	 * @author Nicola Asuni
22142	 * @public
22143	 * @since 5.8.000 (2010-08-11)
22144	 */
22145	public function stringLeftTrim($str, $replace='') {
22146		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22147	}
22148
22149	/**
22150	 * Right trim the input string
22151	 * @param $str (string) string to trim
22152	 * @param $replace (string) string that replace spaces.
22153	 * @return right trimmed string
22154	 * @author Nicola Asuni
22155	 * @public
22156	 * @since 5.8.000 (2010-08-11)
22157	 */
22158	public function stringRightTrim($str, $replace='') {
22159		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22160	}
22161
22162	/**
22163	 * Trim the input string
22164	 * @param $str (string) string to trim
22165	 * @param $replace (string) string that replace spaces.
22166	 * @return trimmed string
22167	 * @author Nicola Asuni
22168	 * @public
22169	 * @since 5.8.000 (2010-08-11)
22170	 */
22171	public function stringTrim($str, $replace='') {
22172		$str = $this->stringLeftTrim($str, $replace);
22173		$str = $this->stringRightTrim($str, $replace);
22174		return $str;
22175	}
22176
22177	/**
22178	 * Return true if the current font is unicode type.
22179	 * @return true for unicode font, false otherwise.
22180	 * @author Nicola Asuni
22181	 * @public
22182	 * @since 5.8.002 (2010-08-14)
22183	 */
22184	public function isUnicodeFont() {
22185		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22186	}
22187
22188	/**
22189	 * Return normalized font name
22190	 * @param $fontfamily (string) property string containing font family names
22191	 * @return string normalized font name
22192	 * @author Nicola Asuni
22193	 * @public
22194	 * @since 5.8.004 (2010-08-17)
22195	 */
22196	public function getFontFamilyName($fontfamily) {
22197		// remove spaces and symbols
22198		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22199		// extract all font names
22200		$fontslist = preg_split('/[,]/', $fontfamily);
22201		// find first valid font name
22202		foreach ($fontslist as $font) {
22203			// replace font variations
22204			$font = preg_replace('/regular$/', '', $font);
22205			$font = preg_replace('/italic$/', 'I', $font);
22206			$font = preg_replace('/oblique$/', 'I', $font);
22207			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22208			// replace common family names and core fonts
22209			$pattern = array();
22210			$replacement = array();
22211			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22212			$replacement[] = 'times';
22213			$pattern[] = '/^sansserif/';
22214			$replacement[] = 'helvetica';
22215			$pattern[] = '/^monospace/';
22216			$replacement[] = 'courier';
22217			$font = preg_replace($pattern, $replacement, $font);
22218			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22219				return $font;
22220			}
22221		}
22222		// return current font as default
22223		return $this->CurrentFont['fontkey'];
22224	}
22225
22226	/**
22227	 * Start a new XObject Template.
22228	 * 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).
22229	 * 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.
22230	 * Note: X,Y coordinates will be reset to 0,0.
22231	 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22232	 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22233	 * @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).
22234	 * @return int the XObject Template ID in case of success or false in case of error.
22235	 * @author Nicola Asuni
22236	 * @public
22237	 * @since 5.8.017 (2010-08-24)
22238	 * @see endTemplate(), printTemplate()
22239	 */
22240	public function startTemplate($w=0, $h=0, $group=false) {
22241		if ($this->inxobj) {
22242			// we are already inside an XObject template
22243			return false;
22244		}
22245		$this->inxobj = true;
22246		++$this->n;
22247		// XObject ID
22248		$this->xobjid = 'XT'.$this->n;
22249		// object ID
22250		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22251		// store current graphic state
22252		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22253		// initialize data
22254		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22255		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22256		$this->xobjects[$this->xobjid]['outdata'] = '';
22257		$this->xobjects[$this->xobjid]['xobjects'] = array();
22258		$this->xobjects[$this->xobjid]['images'] = array();
22259		$this->xobjects[$this->xobjid]['fonts'] = array();
22260		$this->xobjects[$this->xobjid]['annotations'] = array();
22261		$this->xobjects[$this->xobjid]['extgstates'] = array();
22262		$this->xobjects[$this->xobjid]['gradients'] = array();
22263		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22264		// set new environment
22265		$this->num_columns = 1;
22266		$this->current_column = 0;
22267		$this->SetAutoPageBreak(false);
22268		if (($w === '') OR ($w <= 0)) {
22269			$w = $this->w - $this->lMargin - $this->rMargin;
22270		}
22271		if (($h === '') OR ($h <= 0)) {
22272			$h = $this->h - $this->tMargin - $this->bMargin;
22273		}
22274		$this->xobjects[$this->xobjid]['x'] = 0;
22275		$this->xobjects[$this->xobjid]['y'] = 0;
22276		$this->xobjects[$this->xobjid]['w'] = $w;
22277		$this->xobjects[$this->xobjid]['h'] = $h;
22278		$this->w = $w;
22279		$this->h = $h;
22280		$this->wPt = $this->w * $this->k;
22281		$this->hPt = $this->h * $this->k;
22282		$this->fwPt = $this->wPt;
22283		$this->fhPt = $this->hPt;
22284		$this->x = 0;
22285		$this->y = 0;
22286		$this->lMargin = 0;
22287		$this->rMargin = 0;
22288		$this->tMargin = 0;
22289		$this->bMargin = 0;
22290		// set group mode
22291		$this->xobjects[$this->xobjid]['group'] = $group;
22292		return $this->xobjid;
22293	}
22294
22295	/**
22296	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22297	 * 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).
22298	 * 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.
22299	 * @return int the XObject Template ID in case of success or false in case of error.
22300	 * @author Nicola Asuni
22301	 * @public
22302	 * @since 5.8.017 (2010-08-24)
22303	 * @see startTemplate(), printTemplate()
22304	 */
22305	public function endTemplate() {
22306		if (!$this->inxobj) {
22307			// we are not inside a template
22308			return false;
22309		}
22310		$this->inxobj = false;
22311		// restore previous graphic state
22312		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22313		return $this->xobjid;
22314	}
22315
22316	/**
22317	 * Print an XObject Template.
22318	 * You can print an XObject Template inside the currently opened Template.
22319	 * 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).
22320	 * 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.
22321	 * @param $id (string) The ID of XObject Template to print.
22322	 * @param $x (int) X position in user units (empty string = current x position)
22323	 * @param $y (int) Y position in user units (empty string = current y position)
22324	 * @param $w (int) Width in user units (zero = remaining page width)
22325	 * @param $h (int) Height in user units (zero = remaining page height)
22326	 * @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>
22327	 * @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>
22328	 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22329	 * @author Nicola Asuni
22330	 * @public
22331	 * @since 5.8.017 (2010-08-24)
22332	 * @see startTemplate(), endTemplate()
22333	 */
22334	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22335		if ($this->state != 2) {
22336			 return;
22337		}
22338		if (!isset($this->xobjects[$id])) {
22339			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22340		}
22341		if ($this->inxobj) {
22342			if ($id == $this->xobjid) {
22343				// close current template
22344				$this->endTemplate();
22345			} else {
22346				// use the template as resource for the template currently opened
22347				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22348			}
22349		}
22350		// set default values
22351		if ($x === '') {
22352			$x = $this->x;
22353		}
22354		if ($y === '') {
22355			$y = $this->y;
22356		}
22357		// check page for no-write regions and adapt page margins if necessary
22358		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22359		$ow = $this->xobjects[$id]['w'];
22360		if ($ow <= 0) {
22361			$ow = 1;
22362		}
22363		$oh = $this->xobjects[$id]['h'];
22364		if ($oh <= 0) {
22365			$oh = 1;
22366		}
22367		// calculate template width and height on document
22368		if (($w <= 0) AND ($h <= 0)) {
22369			$w = $ow;
22370			$h = $oh;
22371		} elseif ($w <= 0) {
22372			$w = $h * $ow / $oh;
22373		} elseif ($h <= 0) {
22374			$h = $w * $oh / $ow;
22375		}
22376		// fit the template on available space
22377		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22378		// set page alignment
22379		$rb_y = $y + $h;
22380		// set alignment
22381		if ($this->rtl) {
22382			if ($palign == 'L') {
22383				$xt = $this->lMargin;
22384			} elseif ($palign == 'C') {
22385				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22386			} elseif ($palign == 'R') {
22387				$xt = $this->w - $this->rMargin - $w;
22388			} else {
22389				$xt = $x - $w;
22390			}
22391			$rb_x = $xt;
22392		} else {
22393			if ($palign == 'L') {
22394				$xt = $this->lMargin;
22395			} elseif ($palign == 'C') {
22396				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22397			} elseif ($palign == 'R') {
22398				$xt = $this->w - $this->rMargin - $w;
22399			} else {
22400				$xt = $x;
22401			}
22402			$rb_x = $xt + $w;
22403		}
22404		// print XObject Template + Transformation matrix
22405		$this->StartTransform();
22406		// translate and scale
22407		$sx = ($w / $ow);
22408		$sy = ($h / $oh);
22409		$tm = array();
22410		$tm[0] = $sx;
22411		$tm[1] = 0;
22412		$tm[2] = 0;
22413		$tm[3] = $sy;
22414		$tm[4] = $xt * $this->k;
22415		$tm[5] = ($this->h - $h - $y) * $this->k;
22416		$this->Transform($tm);
22417		// set object
22418		$this->_out('/'.$id.' Do');
22419		$this->StopTransform();
22420		// add annotations
22421		if (!empty($this->xobjects[$id]['annotations'])) {
22422			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22423				// transform original coordinates
22424				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22425				$ax = ($coordlt[4] / $this->k);
22426				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22427				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22428				$aw = ($coordrb[4] / $this->k) - $ax;
22429				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22430				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22431			}
22432		}
22433		// set pointer to align the next text/objects
22434		switch($align) {
22435			case 'T': {
22436				$this->y = $y;
22437				$this->x = $rb_x;
22438				break;
22439			}
22440			case 'M': {
22441				$this->y = $y + round($h/2);
22442				$this->x = $rb_x;
22443				break;
22444			}
22445			case 'B': {
22446				$this->y = $rb_y;
22447				$this->x = $rb_x;
22448				break;
22449			}
22450			case 'N': {
22451				$this->SetY($rb_y);
22452				break;
22453			}
22454			default:{
22455				break;
22456			}
22457		}
22458	}
22459
22460	/**
22461	 * Set the percentage of character stretching.
22462	 * @param $perc (int) percentage of stretching (100 = no stretching)
22463	 * @author Nicola Asuni
22464	 * @public
22465	 * @since 5.9.000 (2010-09-29)
22466	 */
22467	public function setFontStretching($perc=100) {
22468		$this->font_stretching = $perc;
22469	}
22470
22471	/**
22472	 * Get the percentage of character stretching.
22473	 * @return float stretching value
22474	 * @author Nicola Asuni
22475	 * @public
22476	 * @since 5.9.000 (2010-09-29)
22477	 */
22478	public function getFontStretching() {
22479		return $this->font_stretching;
22480	}
22481
22482	/**
22483	 * Set the amount to increase or decrease the space between characters in a text.
22484	 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22485	 * @author Nicola Asuni
22486	 * @public
22487	 * @since 5.9.000 (2010-09-29)
22488	 */
22489	public function setFontSpacing($spacing=0) {
22490		$this->font_spacing = $spacing;
22491	}
22492
22493	/**
22494	 * Get the amount to increase or decrease the space between characters in a text.
22495	 * @return int font spacing (tracking) value
22496	 * @author Nicola Asuni
22497	 * @public
22498	 * @since 5.9.000 (2010-09-29)
22499	 */
22500	public function getFontSpacing() {
22501		return $this->font_spacing;
22502	}
22503
22504	/**
22505	 * Return an array of no-write page regions
22506	 * @return array of no-write page regions
22507	 * @author Nicola Asuni
22508	 * @public
22509	 * @since 5.9.003 (2010-10-13)
22510	 * @see setPageRegions(), addPageRegion()
22511	 */
22512	public function getPageRegions() {
22513		return $this->page_regions;
22514	}
22515
22516	/**
22517	 * Set no-write regions on page.
22518	 * 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.
22519	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22520	 * You can set multiple regions for the same page.
22521	 * @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.
22522	 * @author Nicola Asuni
22523	 * @public
22524	 * @since 5.9.003 (2010-10-13)
22525	 * @see addPageRegion(), getPageRegions()
22526	 */
22527	public function setPageRegions($regions=array()) {
22528		// empty current regions array
22529		$this->page_regions = array();
22530		// add regions
22531		foreach ($regions as $data) {
22532			$this->addPageRegion($data);
22533		}
22534	}
22535
22536	/**
22537	 * Add a single no-write region on selected page.
22538	 * 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.
22539	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22540	 * You can set multiple regions for the same page.
22541	 * @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).
22542	 * @author Nicola Asuni
22543	 * @public
22544	 * @since 5.9.003 (2010-10-13)
22545	 * @see setPageRegions(), getPageRegions()
22546	 */
22547	public function addPageRegion($region) {
22548		if (!isset($region['page']) OR empty($region['page'])) {
22549			$region['page'] = $this->page;
22550		}
22551		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22552			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22553			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22554			$this->page_regions[] = $region;
22555		}
22556	}
22557
22558	/**
22559	 * Remove a single no-write region.
22560	 * @param $key (int) region key
22561	 * @author Nicola Asuni
22562	 * @public
22563	 * @since 5.9.003 (2010-10-13)
22564	 * @see setPageRegions(), getPageRegions()
22565	 */
22566	public function removePageRegion($key) {
22567		if (isset($this->page_regions[$key])) {
22568			unset($this->page_regions[$key]);
22569		}
22570	}
22571
22572	/**
22573	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22574	 * 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.
22575	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22576	 * @param $h (float) height of the text/image/object to print in user units
22577	 * @param $x (float) current X coordinate in user units
22578	 * @param $y (float) current Y coordinate in user units
22579	 * @return array($x, $y)
22580	 * @author Nicola Asuni
22581	 * @protected
22582	 * @since 5.9.003 (2010-10-13)
22583	 */
22584	protected function checkPageRegions($h, $x, $y) {
22585		// set default values
22586		if ($x === '') {
22587			$x = $this->x;
22588		}
22589		if ($y === '') {
22590			$y = $this->y;
22591		}
22592		if (!$this->check_page_regions OR empty($this->page_regions)) {
22593			// no page regions defined
22594			return array($x, $y);
22595		}
22596		if (empty($h)) {
22597			$h = $this->getCellHeight($this->FontSize);
22598		}
22599		// check for page break
22600		if ($this->checkPageBreak($h, $y)) {
22601			// the content will be printed on a new page
22602			$x = $this->x;
22603			$y = $this->y;
22604		}
22605		if ($this->num_columns > 1) {
22606			if ($this->rtl) {
22607				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22608			} else {
22609				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22610			}
22611		} else {
22612			if ($this->rtl) {
22613				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22614			} else {
22615				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22616			}
22617		}
22618		// adjust coordinates and page margins
22619		foreach ($this->page_regions as $regid => $regdata) {
22620			if ($regdata['page'] == $this->page) {
22621				// check region boundaries
22622				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22623					// Y is inside the region
22624					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22625					$yt = max($y, $regdata['yt']);
22626					$yb = min(($yt + $h), $regdata['yb']);
22627					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22628					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22629					if ($regdata['side'] == 'L') { // left side
22630						$new_margin = max($xt, $xb);
22631						if ($this->lMargin < $new_margin) {
22632							if ($this->rtl) {
22633								// adjust left page margin
22634								$this->lMargin = max(0, $new_margin);
22635							}
22636							if ($x < $new_margin) {
22637								// adjust x position
22638								$x = $new_margin;
22639								if ($new_margin > ($this->w - $this->rMargin)) {
22640									// adjust y position
22641									$y = $regdata['yb'] - $h;
22642								}
22643							}
22644						}
22645					} elseif ($regdata['side'] == 'R') { // right side
22646						$new_margin = min($xt, $xb);
22647						if (($this->w - $this->rMargin) > $new_margin) {
22648							if (!$this->rtl) {
22649								// adjust right page margin
22650								$this->rMargin = max(0, ($this->w - $new_margin));
22651							}
22652							if ($x > $new_margin) {
22653								// adjust x position
22654								$x = $new_margin;
22655								if ($new_margin > $this->lMargin) {
22656									// adjust y position
22657									$y = $regdata['yb'] - $h;
22658								}
22659							}
22660						}
22661					}
22662				}
22663			}
22664		}
22665		return array($x, $y);
22666	}
22667
22668	// --- SVG METHODS ---------------------------------------------------------
22669
22670	/**
22671	 * Embedd a Scalable Vector Graphics (SVG) image.
22672	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22673	 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22674	 * @param $x (float) Abscissa of the upper-left corner.
22675	 * @param $y (float) Ordinate of the upper-left corner.
22676	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22677	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22678	 * @param $link (mixed) URL or identifier returned by AddLink().
22679	 * @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.
22680	 * @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>
22681	 * @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)))
22682	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22683	 * @author Nicola Asuni
22684	 * @since 5.0.000 (2010-05-02)
22685	 * @public
22686	 */
22687	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22688		if ($this->state != 2) {
22689			 return;
22690		}
22691		// reset SVG vars
22692		$this->svggradients = array();
22693		$this->svggradientid = 0;
22694		$this->svgdefsmode = false;
22695		$this->svgdefs = array();
22696		$this->svgclipmode = false;
22697		$this->svgclippaths = array();
22698		$this->svgcliptm = array();
22699		$this->svgclipid = 0;
22700		$this->svgtext = '';
22701		$this->svgtextmode = array();
22702		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22703			// convert SVG to raster image using GD or ImageMagick libraries
22704			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22705		}
22706		if ($file[0] === '@') { // image from string
22707			$this->svgdir = '';
22708			$svgdata = substr($file, 1);
22709		} else { // SVG file
22710			$this->svgdir = dirname($file);
22711			$svgdata = TCPDF_STATIC::fileGetContents($file);
22712		}
22713		if ($svgdata === FALSE) {
22714			$this->Error('SVG file not found: '.$file);
22715		}
22716		if ($x === '') {
22717			$x = $this->x;
22718		}
22719		if ($y === '') {
22720			$y = $this->y;
22721		}
22722		// check page for no-write regions and adapt page margins if necessary
22723		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22724		$k = $this->k;
22725		$ox = 0;
22726		$oy = 0;
22727		$ow = $w;
22728		$oh = $h;
22729		$aspect_ratio_align = 'xMidYMid';
22730		$aspect_ratio_ms = 'meet';
22731		$regs = array();
22732		// get original image width and height
22733		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22734		if (isset($regs[1]) AND !empty($regs[1])) {
22735			$tmp = array();
22736			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22737				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22738			}
22739			$tmp = array();
22740			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22741				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22742			}
22743			$tmp = array();
22744			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22745				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22746			}
22747			$tmp = array();
22748			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22749				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22750			}
22751			$tmp = array();
22752			$view_box = array();
22753			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22754				if (count($tmp) == 5) {
22755					array_shift($tmp);
22756					foreach ($tmp as $key => $val) {
22757						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22758					}
22759					$ox = $view_box[0];
22760					$oy = $view_box[1];
22761				}
22762				// get aspect ratio
22763				$tmp = array();
22764				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22765					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22766					switch (count($aspect_ratio)) {
22767						case 3: {
22768							$aspect_ratio_align = $aspect_ratio[1];
22769							$aspect_ratio_ms = $aspect_ratio[2];
22770							break;
22771						}
22772						case 2: {
22773							$aspect_ratio_align = $aspect_ratio[0];
22774							$aspect_ratio_ms = $aspect_ratio[1];
22775							break;
22776						}
22777						case 1: {
22778							$aspect_ratio_align = $aspect_ratio[0];
22779							$aspect_ratio_ms = 'meet';
22780							break;
22781						}
22782					}
22783				}
22784			}
22785		}
22786		if ($ow <= 0) {
22787			$ow = 1;
22788		}
22789		if ($oh <= 0) {
22790			$oh = 1;
22791		}
22792		// calculate image width and height on document
22793		if (($w <= 0) AND ($h <= 0)) {
22794			// convert image size to document unit
22795			$w = $ow;
22796			$h = $oh;
22797		} elseif ($w <= 0) {
22798			$w = $h * $ow / $oh;
22799		} elseif ($h <= 0) {
22800			$h = $w * $oh / $ow;
22801		}
22802		// fit the image on available space
22803		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22804		if ($this->rasterize_vector_images) {
22805			// convert SVG to raster image using GD or ImageMagick libraries
22806			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22807		}
22808		// set alignment
22809		$this->img_rb_y = $y + $h;
22810		// set alignment
22811		if ($this->rtl) {
22812			if ($palign == 'L') {
22813				$ximg = $this->lMargin;
22814			} elseif ($palign == 'C') {
22815				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22816			} elseif ($palign == 'R') {
22817				$ximg = $this->w - $this->rMargin - $w;
22818			} else {
22819				$ximg = $x - $w;
22820			}
22821			$this->img_rb_x = $ximg;
22822		} else {
22823			if ($palign == 'L') {
22824				$ximg = $this->lMargin;
22825			} elseif ($palign == 'C') {
22826				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22827			} elseif ($palign == 'R') {
22828				$ximg = $this->w - $this->rMargin - $w;
22829			} else {
22830				$ximg = $x;
22831			}
22832			$this->img_rb_x = $ximg + $w;
22833		}
22834		// store current graphic vars
22835		$gvars = $this->getGraphicVars();
22836		// store SVG position and scale factors
22837		$svgoffset_x = ($ximg - $ox) * $this->k;
22838		$svgoffset_y = -($y - $oy) * $this->k;
22839		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22840			$ow = $view_box[2];
22841			$oh = $view_box[3];
22842		} else {
22843			if ($ow <= 0) {
22844				$ow = $w;
22845			}
22846			if ($oh <= 0) {
22847				$oh = $h;
22848			}
22849		}
22850		$svgscale_x = $w / $ow;
22851		$svgscale_y = $h / $oh;
22852		// scaling and alignment
22853		if ($aspect_ratio_align != 'none') {
22854			// store current scaling values
22855			$svgscale_old_x = $svgscale_x;
22856			$svgscale_old_y = $svgscale_y;
22857			// force uniform scaling
22858			if ($aspect_ratio_ms == 'slice') {
22859				// the entire viewport is covered by the viewBox
22860				if ($svgscale_x > $svgscale_y) {
22861					$svgscale_y = $svgscale_x;
22862				} elseif ($svgscale_x < $svgscale_y) {
22863					$svgscale_x = $svgscale_y;
22864				}
22865			} else { // meet
22866				// the entire viewBox is visible within the viewport
22867				if ($svgscale_x < $svgscale_y) {
22868					$svgscale_y = $svgscale_x;
22869				} elseif ($svgscale_x > $svgscale_y) {
22870					$svgscale_x = $svgscale_y;
22871				}
22872			}
22873			// correct X alignment
22874			switch (substr($aspect_ratio_align, 1, 3)) {
22875				case 'Min': {
22876					// do nothing
22877					break;
22878				}
22879				case 'Max': {
22880					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22881					break;
22882				}
22883				default:
22884				case 'Mid': {
22885					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22886					break;
22887				}
22888			}
22889			// correct Y alignment
22890			switch (substr($aspect_ratio_align, 5)) {
22891				case 'Min': {
22892					// do nothing
22893					break;
22894				}
22895				case 'Max': {
22896					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22897					break;
22898				}
22899				default:
22900				case 'Mid': {
22901					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22902					break;
22903				}
22904			}
22905		}
22906		// store current page break mode
22907		$page_break_mode = $this->AutoPageBreak;
22908		$page_break_margin = $this->getBreakMargin();
22909		$cell_padding = $this->cell_padding;
22910		$this->SetCellPadding(0);
22911		$this->SetAutoPageBreak(false);
22912		// save the current graphic state
22913		$this->_out('q'.$this->epsmarker);
22914		// set initial clipping mask
22915		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22916		// scale and translate
22917		$e = $ox * $this->k * (1 - $svgscale_x);
22918		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22919		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22920		// creates a new XML parser to be used by the other XML functions
22921		$this->parser = xml_parser_create('UTF-8');
22922		// the following function allows to use parser inside object
22923		xml_set_object($this->parser, $this);
22924		// disable case-folding for this XML parser
22925		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
22926		// sets the element handler functions for the XML parser
22927		xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
22928		// sets the character data handler function for the XML parser
22929		xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
22930		// start parsing an XML document
22931		if (!xml_parse($this->parser, $svgdata)) {
22932			$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));
22933			$this->Error($error_message);
22934		}
22935		// free this XML parser
22936		xml_parser_free($this->parser);
22937		// restore previous graphic state
22938		$this->_out($this->epsmarker.'Q');
22939		// restore graphic vars
22940		$this->setGraphicVars($gvars);
22941		$this->lasth = $gvars['lasth'];
22942		if (!empty($border)) {
22943			$bx = $this->x;
22944			$by = $this->y;
22945			$this->x = $ximg;
22946			if ($this->rtl) {
22947				$this->x += $w;
22948			}
22949			$this->y = $y;
22950			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
22951			$this->x = $bx;
22952			$this->y = $by;
22953		}
22954		if ($link) {
22955			$this->Link($ximg, $y, $w, $h, $link, 0);
22956		}
22957		// set pointer to align the next text/objects
22958		switch($align) {
22959			case 'T':{
22960				$this->y = $y;
22961				$this->x = $this->img_rb_x;
22962				break;
22963			}
22964			case 'M':{
22965				$this->y = $y + round($h/2);
22966				$this->x = $this->img_rb_x;
22967				break;
22968			}
22969			case 'B':{
22970				$this->y = $this->img_rb_y;
22971				$this->x = $this->img_rb_x;
22972				break;
22973			}
22974			case 'N':{
22975				$this->SetY($this->img_rb_y);
22976				break;
22977			}
22978			default:{
22979				// restore pointer to starting position
22980				$this->x = $gvars['x'];
22981				$this->y = $gvars['y'];
22982				$this->page = $gvars['page'];
22983				$this->current_column = $gvars['current_column'];
22984				$this->tMargin = $gvars['tMargin'];
22985				$this->bMargin = $gvars['bMargin'];
22986				$this->w = $gvars['w'];
22987				$this->h = $gvars['h'];
22988				$this->wPt = $gvars['wPt'];
22989				$this->hPt = $gvars['hPt'];
22990				$this->fwPt = $gvars['fwPt'];
22991				$this->fhPt = $gvars['fhPt'];
22992				break;
22993			}
22994		}
22995		$this->endlinex = $this->img_rb_x;
22996		// restore page break
22997		$this->SetAutoPageBreak($page_break_mode, $page_break_margin);
22998		$this->cell_padding = $cell_padding;
22999	}
23000
23001	/**
23002	 * Convert SVG transformation matrix to PDF.
23003	 * @param $tm (array) original SVG transformation matrix
23004	 * @return array transformation matrix
23005	 * @protected
23006	 * @since 5.0.000 (2010-05-02)
23007	 */
23008	protected function convertSVGtMatrix($tm) {
23009		$a = $tm[0];
23010		$b = -$tm[1];
23011		$c = -$tm[2];
23012		$d = $tm[3];
23013		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23014		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23015		$x = 0;
23016		$y = $this->h * $this->k;
23017		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23018		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23019		return array($a, $b, $c, $d, $e, $f);
23020	}
23021
23022	/**
23023	 * Apply SVG graphic transformation matrix.
23024	 * @param $tm (array) original SVG transformation matrix
23025	 * @protected
23026	 * @since 5.0.000 (2010-05-02)
23027	 */
23028	protected function SVGTransform($tm) {
23029		$this->Transform($this->convertSVGtMatrix($tm));
23030	}
23031
23032	/**
23033	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23034	 * @param $svgstyle (array) array of SVG styles to apply
23035	 * @param $prevsvgstyle (array) array of previous SVG style
23036	 * @param $x (int) X origin of the bounding box
23037	 * @param $y (int) Y origin of the bounding box
23038	 * @param $w (int) width of the bounding box
23039	 * @param $h (int) height of the bounding box
23040	 * @param $clip_function (string) clip function
23041	 * @param $clip_params (array) array of parameters for clipping function
23042	 * @return object style
23043	 * @author Nicola Asuni
23044	 * @since 5.0.000 (2010-05-02)
23045	 * @protected
23046	 */
23047	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23048		if ($this->state != 2) {
23049			 return;
23050		}
23051		$objstyle = '';
23052		$minlen = (0.01 / $this->k); // minimum acceptable length
23053		if (!isset($svgstyle['opacity'])) {
23054			return $objstyle;
23055		}
23056		// clip-path
23057		$regs = array();
23058		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23059			$clip_path = $this->svgclippaths[$regs[1]];
23060			foreach ($clip_path as $cp) {
23061				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23062			}
23063		}
23064		// opacity
23065		if ($svgstyle['opacity'] != 1) {
23066			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23067		}
23068		// color
23069		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23070		$this->SetFillColorArray($fill_color);
23071		// text color
23072		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23073		$this->SetTextColorArray($text_color);
23074		// clip
23075		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23076			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23077			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23078			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23079			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23080			$cx = $x + $left;
23081			$cy = $y + $top;
23082			$cw = $w - $left - $right;
23083			$ch = $h - $top - $bottom;
23084			if ($svgstyle['clip-rule'] == 'evenodd') {
23085				$clip_rule = 'CNZ';
23086			} else {
23087				$clip_rule = 'CEO';
23088			}
23089			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23090		}
23091		// fill
23092		$regs = array();
23093		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23094			// gradient
23095			$gradient = $this->svggradients[$regs[1]];
23096			if (isset($gradient['xref'])) {
23097				// reference to another gradient definition
23098				$newgradient = $this->svggradients[$gradient['xref']];
23099				$newgradient['coords'] = $gradient['coords'];
23100				$newgradient['mode'] = $gradient['mode'];
23101				$newgradient['type'] = $gradient['type'];
23102				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23103				if (isset($gradient['gradientTransform'])) {
23104					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23105				}
23106				$gradient = $newgradient;
23107			}
23108			//save current Graphic State
23109			$this->_outSaveGraphicsState();
23110			//set clipping area
23111			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23112				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23113				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23114					list($x, $y, $w, $h) = $bbox;
23115				}
23116			}
23117			if ($gradient['mode'] == 'measure') {
23118				if (!isset($gradient['coords'][4])) {
23119					$gradient['coords'][4] = 0.5;
23120				}
23121				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23122					$gtm = $gradient['gradientTransform'];
23123					// apply transformation matrix
23124					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23125					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23126					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23127					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23128					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23129					$gradient['coords'][0] = $xa;
23130					$gradient['coords'][1] = $ya;
23131					$gradient['coords'][2] = $xb;
23132					$gradient['coords'][3] = $yb;
23133					$gradient['coords'][4] = $r;
23134				}
23135				// convert SVG coordinates to user units
23136				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23137				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23138				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23139				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23140				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23141				if ($w <= $minlen) {
23142					$w = $minlen;
23143				}
23144				if ($h <= $minlen) {
23145					$h = $minlen;
23146				}
23147				// shift units
23148				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23149					// convert to SVG coordinate system
23150					$gradient['coords'][0] += $x;
23151					$gradient['coords'][1] += $y;
23152					$gradient['coords'][2] += $x;
23153					$gradient['coords'][3] += $y;
23154				}
23155				// calculate percentages
23156				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23157				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23158				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23159				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23160				$gradient['coords'][4] /= $w;
23161			} elseif ($gradient['mode'] == 'percentage') {
23162				foreach($gradient['coords'] as $key => $val) {
23163					$gradient['coords'][$key] = (intval($val) / 100);
23164					if ($val < 0) {
23165						$gradient['coords'][$key] = 0;
23166					} elseif ($val > 1) {
23167						$gradient['coords'][$key] = 1;
23168					}
23169				}
23170			}
23171			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23172				// single color (no shading)
23173				$gradient['coords'][0] = 1;
23174				$gradient['coords'][1] = 0;
23175				$gradient['coords'][2] = 0.999;
23176				$gradient['coords'][3] = 0;
23177			}
23178			// swap Y coordinates
23179			$tmp = $gradient['coords'][1];
23180			$gradient['coords'][1] = $gradient['coords'][3];
23181			$gradient['coords'][3] = $tmp;
23182			// set transformation map for gradient
23183			$cy = ($this->h - $y);
23184			if ($gradient['type'] == 3) {
23185				// circular gradient
23186				$cy -= ($gradient['coords'][1] * ($w + $h));
23187				$h = $w = max($w, $h);
23188			} else {
23189				$cy -= $h;
23190			}
23191			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23192			if (count($gradient['stops']) > 1) {
23193				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23194			}
23195		} elseif ($svgstyle['fill'] != 'none') {
23196			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23197			if ($svgstyle['fill-opacity'] != 1) {
23198				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23199			}
23200			$this->SetFillColorArray($fill_color);
23201			if ($svgstyle['fill-rule'] == 'evenodd') {
23202				$objstyle .= 'F*';
23203			} else {
23204				$objstyle .= 'F';
23205			}
23206		}
23207		// stroke
23208		if ($svgstyle['stroke'] != 'none') {
23209			if ($svgstyle['stroke-opacity'] != 1) {
23210				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23211			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23212				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23213			}
23214			$stroke_style = array(
23215				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23216				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23217				'cap' => $svgstyle['stroke-linecap'],
23218				'join' => $svgstyle['stroke-linejoin']
23219				);
23220			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23221				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23222			}
23223			$this->SetLineStyle($stroke_style);
23224			$objstyle .= 'D';
23225		}
23226		// font
23227		$regs = array();
23228		if (!empty($svgstyle['font'])) {
23229			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23230				$font_family = $this->getFontFamilyName($regs[1]);
23231			} else {
23232				$font_family = $svgstyle['font-family'];
23233			}
23234			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23235				$font_size = trim($regs[1]);
23236			} else {
23237				$font_size = $svgstyle['font-size'];
23238			}
23239			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23240				$font_style = trim($regs[1]);
23241			} else {
23242				$font_style = $svgstyle['font-style'];
23243			}
23244			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23245				$font_weight = trim($regs[1]);
23246			} else {
23247				$font_weight = $svgstyle['font-weight'];
23248			}
23249			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23250				$font_stretch = trim($regs[1]);
23251			} else {
23252				$font_stretch = $svgstyle['font-stretch'];
23253			}
23254			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23255				$font_spacing = trim($regs[1]);
23256			} else {
23257				$font_spacing = $svgstyle['letter-spacing'];
23258			}
23259		} else {
23260			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23261			$font_size = $svgstyle['font-size'];
23262			$font_style = $svgstyle['font-style'];
23263			$font_weight = $svgstyle['font-weight'];
23264			$font_stretch = $svgstyle['font-stretch'];
23265			$font_spacing = $svgstyle['letter-spacing'];
23266		}
23267		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23268		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23269		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23270		switch ($font_style) {
23271			case 'italic': {
23272				$font_style = 'I';
23273				break;
23274			}
23275			case 'oblique': {
23276				$font_style = 'I';
23277				break;
23278			}
23279			default:
23280			case 'normal': {
23281				$font_style = '';
23282				break;
23283			}
23284		}
23285		switch ($font_weight) {
23286			case 'bold':
23287			case 'bolder': {
23288				$font_style .= 'B';
23289				break;
23290			}
23291			case 'normal': {
23292				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23293					$font_family = substr($font_family, 0, -2).'I';
23294				} elseif (substr($font_family, -1) == 'B') {
23295					$font_family = substr($font_family, 0, -1);
23296				}
23297				break;
23298			}
23299		}
23300		switch ($svgstyle['text-decoration']) {
23301			case 'underline': {
23302				$font_style .= 'U';
23303				break;
23304			}
23305			case 'overline': {
23306				$font_style .= 'O';
23307				break;
23308			}
23309			case 'line-through': {
23310				$font_style .= 'D';
23311				break;
23312			}
23313			default:
23314			case 'none': {
23315				break;
23316			}
23317		}
23318		$this->SetFont($font_family, $font_style, $font_size);
23319		$this->setFontStretching($font_stretch);
23320		$this->setFontSpacing($font_spacing);
23321		return $objstyle;
23322	}
23323
23324	/**
23325	 * Draws an SVG path
23326	 * @param $d (string) attribute d of the path SVG element
23327	 * @param $style (string) Style of rendering. Possible values are:
23328	 * <ul>
23329	 *	 <li>D or empty string: Draw (default).</li>
23330	 *	 <li>F: Fill.</li>
23331	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23332	 *	 <li>DF or FD: Draw and fill.</li>
23333	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23334	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23335	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23336	 * </ul>
23337	 * @return array of container box measures (x, y, w, h)
23338	 * @author Nicola Asuni
23339	 * @since 5.0.000 (2010-05-02)
23340	 * @protected
23341	 */
23342	protected function SVGPath($d, $style='') {
23343		if ($this->state != 2) {
23344			 return;
23345		}
23346		// set fill/stroke style
23347		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23348		if (empty($op)) {
23349			return;
23350		}
23351		$paths = array();
23352		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23353		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23354		$x = 0;
23355		$y = 0;
23356		$x1 = 0;
23357		$y1 = 0;
23358		$x2 = 0;
23359		$y2 = 0;
23360		$xmin = 2147483647;
23361		$xmax = 0;
23362		$ymin = 2147483647;
23363		$ymax = 0;
23364		$relcoord = false;
23365		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23366		$firstcmd = true; // used to print first point
23367		// draw curve pieces
23368		foreach ($paths as $key => $val) {
23369			// get curve type
23370			$cmd = trim($val[1]);
23371			if (strtolower($cmd) == $cmd) {
23372				// use relative coordinated instead of absolute
23373				$relcoord = true;
23374				$xoffset = $x;
23375				$yoffset = $y;
23376			} else {
23377				$relcoord = false;
23378				$xoffset = 0;
23379				$yoffset = 0;
23380			}
23381			$params = array();
23382			if (isset($val[2])) {
23383				// get curve parameters
23384				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23385				$params = array();
23386				foreach ($rawparams as $ck => $cp) {
23387					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23388					if (abs($params[$ck]) < $minlen) {
23389						// approximate little values to zero
23390						$params[$ck] = 0;
23391					}
23392				}
23393			}
23394			// store current origin point
23395			$x0 = $x;
23396			$y0 = $y;
23397			switch (strtoupper($cmd)) {
23398				case 'M': { // moveto
23399					foreach ($params as $ck => $cp) {
23400						if (($ck % 2) == 0) {
23401							$x = $cp + $xoffset;
23402						} else {
23403							$y = $cp + $yoffset;
23404							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23405								if ($ck == 1) {
23406									$this->_outPoint($x, $y);
23407									$firstcmd = false;
23408								} else {
23409									$this->_outLine($x, $y);
23410								}
23411								$x0 = $x;
23412								$y0 = $y;
23413							}
23414							$xmin = min($xmin, $x);
23415							$ymin = min($ymin, $y);
23416							$xmax = max($xmax, $x);
23417							$ymax = max($ymax, $y);
23418							if ($relcoord) {
23419								$xoffset = $x;
23420								$yoffset = $y;
23421							}
23422						}
23423					}
23424					break;
23425				}
23426				case 'L': { // lineto
23427					foreach ($params as $ck => $cp) {
23428						if (($ck % 2) == 0) {
23429							$x = $cp + $xoffset;
23430						} else {
23431							$y = $cp + $yoffset;
23432							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23433								$this->_outLine($x, $y);
23434								$x0 = $x;
23435								$y0 = $y;
23436							}
23437							$xmin = min($xmin, $x);
23438							$ymin = min($ymin, $y);
23439							$xmax = max($xmax, $x);
23440							$ymax = max($ymax, $y);
23441							if ($relcoord) {
23442								$xoffset = $x;
23443								$yoffset = $y;
23444							}
23445						}
23446					}
23447					break;
23448				}
23449				case 'H': { // horizontal lineto
23450					foreach ($params as $ck => $cp) {
23451						$x = $cp + $xoffset;
23452						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23453							$this->_outLine($x, $y);
23454							$x0 = $x;
23455							$y0 = $y;
23456						}
23457						$xmin = min($xmin, $x);
23458						$xmax = max($xmax, $x);
23459						if ($relcoord) {
23460							$xoffset = $x;
23461						}
23462					}
23463					break;
23464				}
23465				case 'V': { // vertical lineto
23466					foreach ($params as $ck => $cp) {
23467						$y = $cp + $yoffset;
23468						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23469							$this->_outLine($x, $y);
23470							$x0 = $x;
23471							$y0 = $y;
23472						}
23473						$ymin = min($ymin, $y);
23474						$ymax = max($ymax, $y);
23475						if ($relcoord) {
23476							$yoffset = $y;
23477						}
23478					}
23479					break;
23480				}
23481				case 'C': { // curveto
23482					foreach ($params as $ck => $cp) {
23483						$params[$ck] = $cp;
23484						if ((($ck + 1) % 6) == 0) {
23485							$x1 = $params[($ck - 5)] + $xoffset;
23486							$y1 = $params[($ck - 4)] + $yoffset;
23487							$x2 = $params[($ck - 3)] + $xoffset;
23488							$y2 = $params[($ck - 2)] + $yoffset;
23489							$x = $params[($ck - 1)] + $xoffset;
23490							$y = $params[($ck)] + $yoffset;
23491							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23492							$xmin = min($xmin, $x, $x1, $x2);
23493							$ymin = min($ymin, $y, $y1, $y2);
23494							$xmax = max($xmax, $x, $x1, $x2);
23495							$ymax = max($ymax, $y, $y1, $y2);
23496							if ($relcoord) {
23497								$xoffset = $x;
23498								$yoffset = $y;
23499							}
23500						}
23501					}
23502					break;
23503				}
23504				case 'S': { // shorthand/smooth curveto
23505					foreach ($params as $ck => $cp) {
23506						$params[$ck] = $cp;
23507						if ((($ck + 1) % 4) == 0) {
23508							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23509								$x1 = (2 * $x) - $x2;
23510								$y1 = (2 * $y) - $y2;
23511							} else {
23512								$x1 = $x;
23513								$y1 = $y;
23514							}
23515							$x2 = $params[($ck - 3)] + $xoffset;
23516							$y2 = $params[($ck - 2)] + $yoffset;
23517							$x = $params[($ck - 1)] + $xoffset;
23518							$y = $params[($ck)] + $yoffset;
23519							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23520							$xmin = min($xmin, $x, $x1, $x2);
23521							$ymin = min($ymin, $y, $y1, $y2);
23522							$xmax = max($xmax, $x, $x1, $x2);
23523							$ymax = max($ymax, $y, $y1, $y2);
23524							if ($relcoord) {
23525								$xoffset = $x;
23526								$yoffset = $y;
23527							}
23528						}
23529					}
23530					break;
23531				}
23532				case 'Q': { // quadratic Bezier curveto
23533					foreach ($params as $ck => $cp) {
23534						$params[$ck] = $cp;
23535						if ((($ck + 1) % 4) == 0) {
23536							// convert quadratic points to cubic points
23537							$x1 = $params[($ck - 3)] + $xoffset;
23538							$y1 = $params[($ck - 2)] + $yoffset;
23539							$xa = ($x + (2 * $x1)) / 3;
23540							$ya = ($y + (2 * $y1)) / 3;
23541							$x = $params[($ck - 1)] + $xoffset;
23542							$y = $params[($ck)] + $yoffset;
23543							$xb = ($x + (2 * $x1)) / 3;
23544							$yb = ($y + (2 * $y1)) / 3;
23545							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23546							$xmin = min($xmin, $x, $xa, $xb);
23547							$ymin = min($ymin, $y, $ya, $yb);
23548							$xmax = max($xmax, $x, $xa, $xb);
23549							$ymax = max($ymax, $y, $ya, $yb);
23550							if ($relcoord) {
23551								$xoffset = $x;
23552								$yoffset = $y;
23553							}
23554						}
23555					}
23556					break;
23557				}
23558				case 'T': { // shorthand/smooth quadratic Bezier curveto
23559					foreach ($params as $ck => $cp) {
23560						$params[$ck] = $cp;
23561						if (($ck % 2) != 0) {
23562							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23563								$x1 = (2 * $x) - $x1;
23564								$y1 = (2 * $y) - $y1;
23565							} else {
23566								$x1 = $x;
23567								$y1 = $y;
23568							}
23569							// convert quadratic points to cubic points
23570							$xa = ($x + (2 * $x1)) / 3;
23571							$ya = ($y + (2 * $y1)) / 3;
23572							$x = $params[($ck - 1)] + $xoffset;
23573							$y = $params[($ck)] + $yoffset;
23574							$xb = ($x + (2 * $x1)) / 3;
23575							$yb = ($y + (2 * $y1)) / 3;
23576							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23577							$xmin = min($xmin, $x, $xa, $xb);
23578							$ymin = min($ymin, $y, $ya, $yb);
23579							$xmax = max($xmax, $x, $xa, $xb);
23580							$ymax = max($ymax, $y, $ya, $yb);
23581							if ($relcoord) {
23582								$xoffset = $x;
23583								$yoffset = $y;
23584							}
23585						}
23586					}
23587					break;
23588				}
23589				case 'A': { // elliptical arc
23590					foreach ($params as $ck => $cp) {
23591						$params[$ck] = $cp;
23592						if ((($ck + 1) % 7) == 0) {
23593							$x0 = $x;
23594							$y0 = $y;
23595							$rx = abs($params[($ck - 6)]);
23596							$ry = abs($params[($ck - 5)]);
23597							$ang = -$rawparams[($ck - 4)];
23598							$angle = deg2rad($ang);
23599							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23600							$fs = $rawparams[($ck - 2)]; // sweep-flag
23601							$x = $params[($ck - 1)] + $xoffset;
23602							$y = $params[$ck] + $yoffset;
23603							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23604								// endpoints are almost identical
23605								$xmin = min($xmin, $x);
23606								$ymin = min($ymin, $y);
23607								$xmax = max($xmax, $x);
23608								$ymax = max($ymax, $y);
23609							} else {
23610								$cos_ang = cos($angle);
23611								$sin_ang = sin($angle);
23612								$a = (($x0 - $x) / 2);
23613								$b = (($y0 - $y) / 2);
23614								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23615								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23616								$rx2 = $rx * $rx;
23617								$ry2 = $ry * $ry;
23618								$xa2 = $xa * $xa;
23619								$ya2 = $ya * $ya;
23620								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23621								if ($delta > 1) {
23622									$rx *= sqrt($delta);
23623									$ry *= sqrt($delta);
23624									$rx2 = $rx * $rx;
23625									$ry2 = $ry * $ry;
23626								}
23627								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23628								if ($numerator < 0) {
23629									$root = 0;
23630								} else {
23631									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23632								}
23633								if ($fa == $fs){
23634									$root *= -1;
23635								}
23636								$cax = $root * (($rx * $ya) / $ry);
23637								$cay = -$root * (($ry * $xa) / $rx);
23638								// coordinates of ellipse center
23639								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23640								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23641								// get angles
23642								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23643								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23644								if (($fs == 0) AND ($dang > 0)) {
23645									$dang -= (2 * M_PI);
23646								} elseif (($fs == 1) AND ($dang < 0)) {
23647									$dang += (2 * M_PI);
23648								}
23649								$angf = $angs - $dang;
23650								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23651									// reverse angles
23652									$tmp = $angs;
23653									$angs = $angf;
23654									$angf = $tmp;
23655								}
23656								$angs = round(rad2deg($angs), 6);
23657								$angf = round(rad2deg($angf), 6);
23658								// covent angles to positive values
23659								if (($angs < 0) AND ($angf < 0)) {
23660									$angs += 360;
23661									$angf += 360;
23662								}
23663								$pie = false;
23664								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23665									$pie = true;
23666								}
23667								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23668								$xmin = min($xmin, $x, $axmin);
23669								$ymin = min($ymin, $y, $aymin);
23670								$xmax = max($xmax, $x, $axmax);
23671								$ymax = max($ymax, $y, $aymax);
23672							}
23673							if ($relcoord) {
23674								$xoffset = $x;
23675								$yoffset = $y;
23676							}
23677						}
23678					}
23679					break;
23680				}
23681				case 'Z': {
23682					$this->_out('h');
23683					break;
23684				}
23685			}
23686			$firstcmd = false;
23687		} // end foreach
23688		if (!empty($op)) {
23689			$this->_out($op);
23690		}
23691		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23692	}
23693
23694	/**
23695	 * Return the tag name without the namespace
23696	 * @param $name (string) Tag name
23697	 * @protected
23698	 */
23699	protected function removeTagNamespace($name) {
23700		if(strpos($name, ':') !== false) {
23701			$parts = explode(':', $name);
23702			return $parts[(sizeof($parts) - 1)];
23703		}
23704		return $name;
23705	}
23706
23707	/**
23708	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23709	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23710	 * @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.
23711	 * @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.
23712	 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23713	 * @author Nicola Asuni
23714	 * @since 5.0.000 (2010-05-02)
23715	 * @protected
23716	 */
23717	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23718		$name = $this->removeTagNamespace($name);
23719		// check if we are in clip mode
23720		if ($this->svgclipmode) {
23721			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23722			return;
23723		}
23724		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23725			if (isset($attribs['id'])) {
23726				$attribs['child_elements'] = array();
23727				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23728				return;
23729			}
23730			if (end($this->svgdefs) !== FALSE) {
23731				$last_svgdefs_id = key($this->svgdefs);
23732				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23733					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23734					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23735					return;
23736				}
23737			}
23738			return;
23739		}
23740		$clipping = false;
23741		if ($parser == 'clip-path') {
23742			// set clipping mode
23743			$clipping = true;
23744		}
23745		// get styling properties
23746		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23747		$svgstyle = $this->svgstyles[0]; // set default style
23748		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23749			// default fill attribute for clipping
23750			$attribs['fill'] = 'none';
23751		}
23752		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23753			// fix style for regular expression
23754			$attribs['style'] = ';'.$attribs['style'];
23755		}
23756		foreach ($prev_svgstyle as $key => $val) {
23757			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23758				// inherit previous value
23759				$svgstyle[$key] = $val;
23760			}
23761			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23762				// specific attribute settings
23763				if ($attribs[$key] == 'inherit') {
23764					$svgstyle[$key] = $val;
23765				} else {
23766					$svgstyle[$key] = $attribs[$key];
23767				}
23768			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23769				// CSS style syntax
23770				$attrval = array();
23771				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23772					if ($attrval[1] == 'inherit') {
23773						$svgstyle[$key] = $val;
23774					} else {
23775						$svgstyle[$key] = $attrval[1];
23776					}
23777				}
23778			}
23779		}
23780		// transformation matrix
23781		if (!empty($ctm)) {
23782			$tm = $ctm;
23783		} else {
23784			$tm = array(1,0,0,1,0,0);
23785		}
23786		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23787			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23788		}
23789		$svgstyle['transfmatrix'] = $tm;
23790		$invisible = false;
23791		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23792			// the current graphics element is invisible (nothing is painted)
23793			$invisible = true;
23794		}
23795		// process tag
23796		switch($name) {
23797			case 'defs': {
23798				$this->svgdefsmode = true;
23799				break;
23800			}
23801			// clipPath
23802			case 'clipPath': {
23803				if ($invisible) {
23804					break;
23805				}
23806				$this->svgclipmode = true;
23807				if (!isset($attribs['id'])) {
23808					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23809				}
23810				$this->svgclipid = $attribs['id'];
23811				$this->svgclippaths[$this->svgclipid] = array();
23812				$this->svgcliptm[$this->svgclipid] = $tm;
23813				break;
23814			}
23815			case 'svg': {
23816				// start of SVG object
23817				if(++$this->svg_tag_depth <= 1) {
23818					break;
23819				}
23820				// inner SVG
23821				array_push($this->svgstyles, $svgstyle);
23822				$this->StartTransform();
23823				$svgX = (isset($attribs['x'])?$attribs['x']:0);
23824				$svgY = (isset($attribs['y'])?$attribs['y']:0);
23825				$svgW = (isset($attribs['width'])?$attribs['width']:0);
23826				$svgH = (isset($attribs['height'])?$attribs['height']:0);
23827				// set x, y position using transform matrix
23828				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23829				$this->SVGTransform($tm);
23830				// set clipping for width and height
23831				$x = 0;
23832				$y = 0;
23833				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23834				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23835				// draw clipping rect
23836				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23837				// parse viewbox, calculate extra transformation matrix
23838				if (isset($attribs['viewBox'])) {
23839					$tmp = array();
23840					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23841					$tmp = $tmp[0];
23842					if (sizeof($tmp) == 4) {
23843						$vx = $tmp[0];
23844						$vy = $tmp[1];
23845						$vw = $tmp[2];
23846						$vh = $tmp[3];
23847						// get aspect ratio
23848						$tmp = array();
23849						$aspectX = 'xMid';
23850						$aspectY = 'YMid';
23851						$fit = 'meet';
23852						if (isset($attribs['preserveAspectRatio'])) {
23853							if($attribs['preserveAspectRatio'] == 'none') {
23854								$fit = 'none';
23855							} else {
23856								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
23857								$tmp = $tmp[0];
23858								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
23859									$aspectX = substr($tmp[0], 0, 4);
23860									$aspectY = substr($tmp[0], 4, 4);
23861									$fit = $tmp[1];
23862								}
23863							}
23864						}
23865						$wr = ($svgW / $vw);
23866						$hr = ($svgH / $vh);
23867						$ax = $ay = 0;
23868						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
23869							if ($aspectX == 'xMax') {
23870								$ax = (($vw * ($wr / $hr)) - $vw);
23871							}
23872							if ($aspectX == 'xMid') {
23873								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
23874							}
23875							$wr = $hr;
23876						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
23877							if ($aspectY == 'YMax') {
23878								$ay = (($vh * ($hr / $wr)) - $vh);
23879							}
23880							if ($aspectY == 'YMid') {
23881								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
23882							}
23883							$hr = $wr;
23884						}
23885						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
23886						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
23887						$this->SVGTransform($tm);
23888					}
23889				}
23890				$this->setSVGStyles($svgstyle, $prev_svgstyle);
23891				break;
23892			}
23893			case 'g': {
23894				// group together related graphics elements
23895				array_push($this->svgstyles, $svgstyle);
23896				$this->StartTransform();
23897				$x = (isset($attribs['x'])?$attribs['x']:0);
23898				$y = (isset($attribs['y'])?$attribs['y']:0);
23899				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23900				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23901				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23902				$this->SVGTransform($tm);
23903				$this->setSVGStyles($svgstyle, $prev_svgstyle);
23904				break;
23905			}
23906			case 'linearGradient': {
23907				if ($this->pdfa_mode) {
23908					break;
23909				}
23910				if (!isset($attribs['id'])) {
23911					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23912				}
23913				$this->svggradientid = $attribs['id'];
23914				$this->svggradients[$this->svggradientid] = array();
23915				$this->svggradients[$this->svggradientid]['type'] = 2;
23916				$this->svggradients[$this->svggradientid]['stops'] = array();
23917				if (isset($attribs['gradientUnits'])) {
23918					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23919				} else {
23920					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23921				}
23922				//$attribs['spreadMethod']
23923				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
23924					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
23925						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
23926						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
23927						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
23928					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23929				} else {
23930					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
23931				}
23932				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
23933				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
23934				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
23935				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
23936				if (isset($attribs['gradientTransform'])) {
23937					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23938				}
23939				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
23940				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23941					// gradient is defined on another place
23942					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23943				}
23944				break;
23945			}
23946			case 'radialGradient': {
23947				if ($this->pdfa_mode) {
23948					break;
23949				}
23950				if (!isset($attribs['id'])) {
23951					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23952				}
23953				$this->svggradientid = $attribs['id'];
23954				$this->svggradients[$this->svggradientid] = array();
23955				$this->svggradients[$this->svggradientid]['type'] = 3;
23956				$this->svggradients[$this->svggradientid]['stops'] = array();
23957				if (isset($attribs['gradientUnits'])) {
23958					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23959				} else {
23960					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23961				}
23962				//$attribs['spreadMethod']
23963				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
23964					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
23965					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
23966					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23967				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
23968					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
23969				} else {
23970					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
23971				}
23972				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
23973				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
23974				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
23975				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
23976				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
23977				if (isset($attribs['gradientTransform'])) {
23978					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23979				}
23980				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
23981				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23982					// gradient is defined on another place
23983					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23984				}
23985				break;
23986			}
23987			case 'stop': {
23988				// gradient stops
23989				if (substr($attribs['offset'], -1) == '%') {
23990					$offset = floatval(substr($attribs['offset'], -1)) / 100;
23991				} else {
23992					$offset = floatval($attribs['offset']);
23993					if ($offset > 1) {
23994						$offset /= 100;
23995					}
23996				}
23997				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
23998				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
23999				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24000				break;
24001			}
24002			// paths
24003			case 'path': {
24004				if ($invisible) {
24005					break;
24006				}
24007				if (isset($attribs['d'])) {
24008					$d = trim($attribs['d']);
24009					if (!empty($d)) {
24010						$x = (isset($attribs['x'])?$attribs['x']:0);
24011						$y = (isset($attribs['y'])?$attribs['y']:0);
24012						$w = (isset($attribs['width'])?$attribs['width']:1);
24013						$h = (isset($attribs['height'])?$attribs['height']:1);
24014						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24015						if ($clipping) {
24016							$this->SVGTransform($tm);
24017							$this->SVGPath($d, 'CNZ');
24018						} else {
24019							$this->StartTransform();
24020							$this->SVGTransform($tm);
24021							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24022							if (!empty($obstyle)) {
24023								$this->SVGPath($d, $obstyle);
24024							}
24025							$this->StopTransform();
24026						}
24027					}
24028				}
24029				break;
24030			}
24031			// shapes
24032			case 'rect': {
24033				if ($invisible) {
24034					break;
24035				}
24036				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24037				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24038				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24039				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24040				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24041				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24042				if ($clipping) {
24043					$this->SVGTransform($tm);
24044					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24045				} else {
24046					$this->StartTransform();
24047					$this->SVGTransform($tm);
24048					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24049					if (!empty($obstyle)) {
24050						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24051					}
24052					$this->StopTransform();
24053				}
24054				break;
24055			}
24056			case 'circle': {
24057				if ($invisible) {
24058					break;
24059				}
24060				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24061				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24062				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24063				$x = ($cx - $r);
24064				$y = ($cy - $r);
24065				$w = (2 * $r);
24066				$h = $w;
24067				if ($clipping) {
24068					$this->SVGTransform($tm);
24069					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24070				} else {
24071					$this->StartTransform();
24072					$this->SVGTransform($tm);
24073					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24074					if (!empty($obstyle)) {
24075						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24076					}
24077					$this->StopTransform();
24078				}
24079				break;
24080			}
24081			case 'ellipse': {
24082				if ($invisible) {
24083					break;
24084				}
24085				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24086				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24087				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24088				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24089				$x = ($cx - $rx);
24090				$y = ($cy - $ry);
24091				$w = (2 * $rx);
24092				$h = (2 * $ry);
24093				if ($clipping) {
24094					$this->SVGTransform($tm);
24095					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24096				} else {
24097					$this->StartTransform();
24098					$this->SVGTransform($tm);
24099					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24100					if (!empty($obstyle)) {
24101						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24102					}
24103					$this->StopTransform();
24104				}
24105				break;
24106			}
24107			case 'line': {
24108				if ($invisible) {
24109					break;
24110				}
24111				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24112				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24113				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24114				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24115				$x = $x1;
24116				$y = $y1;
24117				$w = abs($x2 - $x1);
24118				$h = abs($y2 - $y1);
24119				if (!$clipping) {
24120					$this->StartTransform();
24121					$this->SVGTransform($tm);
24122					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24123					$this->Line($x1, $y1, $x2, $y2);
24124					$this->StopTransform();
24125				}
24126				break;
24127			}
24128			case 'polyline':
24129			case 'polygon': {
24130				if ($invisible) {
24131					break;
24132				}
24133				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24134				$points = trim($points);
24135				// note that point may use a complex syntax not covered here
24136				$points = preg_split('/[\,\s]+/si', $points);
24137				if (count($points) < 4) {
24138					break;
24139				}
24140				$p = array();
24141				$xmin = 2147483647;
24142				$xmax = 0;
24143				$ymin = 2147483647;
24144				$ymax = 0;
24145				foreach ($points as $key => $val) {
24146					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24147					if (($key % 2) == 0) {
24148						// X coordinate
24149						$xmin = min($xmin, $p[$key]);
24150						$xmax = max($xmax, $p[$key]);
24151					} else {
24152						// Y coordinate
24153						$ymin = min($ymin, $p[$key]);
24154						$ymax = max($ymax, $p[$key]);
24155					}
24156				}
24157				$x = $xmin;
24158				$y = $ymin;
24159				$w = ($xmax - $xmin);
24160				$h = ($ymax - $ymin);
24161				if ($name == 'polyline') {
24162					$this->StartTransform();
24163					$this->SVGTransform($tm);
24164					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24165					if (!empty($obstyle)) {
24166						$this->PolyLine($p, $obstyle, array(), array());
24167					}
24168					$this->StopTransform();
24169				} else { // polygon
24170					if ($clipping) {
24171						$this->SVGTransform($tm);
24172						$this->Polygon($p, 'CNZ', array(), array(), true);
24173					} else {
24174						$this->StartTransform();
24175						$this->SVGTransform($tm);
24176						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24177						if (!empty($obstyle)) {
24178							$this->Polygon($p, $obstyle, array(), array(), true);
24179						}
24180						$this->StopTransform();
24181					}
24182				}
24183				break;
24184			}
24185			// image
24186			case 'image': {
24187				if ($invisible) {
24188					break;
24189				}
24190				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24191					break;
24192				}
24193				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24194				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24195				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24196				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24197				$img = $attribs['xlink:href'];
24198				if (!$clipping) {
24199					$this->StartTransform();
24200					$this->SVGTransform($tm);
24201					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24202					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24203						// embedded image encoded as base64
24204						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24205					} else {
24206						// fix image path
24207						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24208							// replace relative path with full server path
24209							$img = $this->svgdir.'/'.$img;
24210						}
24211						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24212							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24213							if (($findroot === false) OR ($findroot > 1)) {
24214								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24215									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24216								} else {
24217									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24218								}
24219							}
24220						}
24221						$img = urldecode($img);
24222						$testscrtype = @parse_url($img);
24223						if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24224							// convert URL to server path
24225							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24226						}
24227					}
24228					// get image type
24229					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24230					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24231						$this->ImageEps($img, $x, $y, $w, $h);
24232					} elseif ($imgtype == 'svg') {
24233						// store SVG vars
24234						$svggradients = $this->svggradients;
24235						$svggradientid = $this->svggradientid;
24236						$svgdefsmode = $this->svgdefsmode;
24237						$svgdefs = $this->svgdefs;
24238						$svgclipmode = $this->svgclipmode;
24239						$svgclippaths = $this->svgclippaths;
24240						$svgcliptm = $this->svgcliptm;
24241						$svgclipid = $this->svgclipid;
24242						$svgtext = $this->svgtext;
24243						$svgtextmode = $this->svgtextmode;
24244						$this->ImageSVG($img, $x, $y, $w, $h);
24245						// restore SVG vars
24246						$this->svggradients = $svggradients;
24247						$this->svggradientid = $svggradientid;
24248						$this->svgdefsmode = $svgdefsmode;
24249						$this->svgdefs = $svgdefs;
24250						$this->svgclipmode = $svgclipmode;
24251						$this->svgclippaths = $svgclippaths;
24252						$this->svgcliptm = $svgcliptm;
24253						$this->svgclipid = $svgclipid;
24254						$this->svgtext = $svgtext;
24255						$this->svgtextmode = $svgtextmode;
24256					} else {
24257						$this->Image($img, $x, $y, $w, $h);
24258					}
24259					$this->StopTransform();
24260				}
24261				break;
24262			}
24263			// text
24264			case 'text':
24265			case 'tspan': {
24266				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24267					// @TODO: unsupported feature
24268				}
24269				// only basic support - advanced features must be implemented
24270				$this->svgtextmode['invisible'] = $invisible;
24271				if ($invisible) {
24272					break;
24273				}
24274				array_push($this->svgstyles, $svgstyle);
24275				if (isset($attribs['x'])) {
24276					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24277				} elseif ($name == 'tspan') {
24278					$x = $this->x;
24279				} else {
24280					$x = 0;
24281				}
24282				if (isset($attribs['dx'])) {
24283					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24284				}
24285				if (isset($attribs['y'])) {
24286					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24287				} elseif ($name == 'tspan') {
24288					$y = $this->y;
24289				} else {
24290					$y = 0;
24291				}
24292				if (isset($attribs['dy'])) {
24293					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24294				}
24295				$svgstyle['text-color'] = $svgstyle['fill'];
24296				$this->svgtext = '';
24297				if (isset($svgstyle['text-anchor'])) {
24298					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24299				} else {
24300					$this->svgtextmode['text-anchor'] = 'start';
24301				}
24302				if (isset($svgstyle['direction'])) {
24303					if ($svgstyle['direction'] == 'rtl') {
24304						$this->svgtextmode['rtl'] = true;
24305					} else {
24306						$this->svgtextmode['rtl'] = false;
24307					}
24308				} else {
24309					$this->svgtextmode['rtl'] = false;
24310				}
24311				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24312					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24313				} else {
24314					$this->svgtextmode['stroke'] = false;
24315				}
24316				$this->StartTransform();
24317				$this->SVGTransform($tm);
24318				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24319				$this->x = $x;
24320				$this->y = $y;
24321				break;
24322			}
24323			// use
24324			case 'use': {
24325				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24326					$svgdefid = substr($attribs['xlink:href'], 1);
24327					if (isset($this->svgdefs[$svgdefid])) {
24328						$use = $this->svgdefs[$svgdefid];
24329						if (isset($attribs['xlink:href'])) {
24330							unset($attribs['xlink:href']);
24331						}
24332						if (isset($attribs['id'])) {
24333							unset($attribs['id']);
24334						}
24335						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24336							$attribs['x'] += $use['attribs']['x'];
24337						}
24338						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24339							$attribs['y'] += $use['attribs']['y'];
24340						}
24341						if (empty($attribs['style'])) {
24342							$attribs['style'] = '';
24343						}
24344						if (!empty($use['attribs']['style'])) {
24345							// merge styles
24346							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24347						}
24348						$attribs = array_merge($use['attribs'], $attribs);
24349						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24350						return;
24351					}
24352				}
24353				break;
24354			}
24355			default: {
24356				break;
24357			}
24358		} // end of switch
24359		// process child elements
24360		if (!empty($attribs['child_elements'])) {
24361			$child_elements = $attribs['child_elements'];
24362			unset($attribs['child_elements']);
24363			foreach($child_elements as $child_element) {
24364				if (empty($child_element['attribs']['closing_tag'])) {
24365					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24366				} else {
24367					if (isset($child_element['attribs']['content'])) {
24368						$this->svgtext = $child_element['attribs']['content'];
24369					}
24370					$this->endSVGElementHandler('child-tag', $child_element['name']);
24371				}
24372			}
24373		}
24374	}
24375
24376	/**
24377	 * Sets the closing SVG element handler function for the XML parser.
24378	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24379	 * @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.
24380	 * @author Nicola Asuni
24381	 * @since 5.0.000 (2010-05-02)
24382	 * @protected
24383	 */
24384	protected function endSVGElementHandler($parser, $name) {
24385		$name = $this->removeTagNamespace($name);
24386		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24387			if (end($this->svgdefs) !== FALSE) {
24388				$last_svgdefs_id = key($this->svgdefs);
24389				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24390					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24391						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24392							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24393							return;
24394						}
24395					}
24396					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24397						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24398						return;
24399					}
24400				}
24401			}
24402			return;
24403		}
24404		switch($name) {
24405			case 'defs': {
24406				$this->svgdefsmode = false;
24407				break;
24408			}
24409			// clipPath
24410			case 'clipPath': {
24411				$this->svgclipmode = false;
24412				break;
24413			}
24414			case 'svg': {
24415				if (--$this->svg_tag_depth <= 0) {
24416					break;
24417				}
24418			}
24419			case 'g': {
24420				// ungroup: remove last style from array
24421				array_pop($this->svgstyles);
24422				$this->StopTransform();
24423				break;
24424			}
24425			case 'text':
24426			case 'tspan': {
24427				if ($this->svgtextmode['invisible']) {
24428					// This implementation must be fixed to following the rule:
24429					// 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.
24430					break;
24431				}
24432				// print text
24433				$text = $this->svgtext;
24434				//$text = $this->stringTrim($text);
24435				$textlen = $this->GetStringWidth($text);
24436				if ($this->svgtextmode['text-anchor'] != 'start') {
24437					// check if string is RTL text
24438					if ($this->svgtextmode['text-anchor'] == 'end') {
24439						if ($this->svgtextmode['rtl']) {
24440							$this->x += $textlen;
24441						} else {
24442							$this->x -= $textlen;
24443						}
24444					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24445						if ($this->svgtextmode['rtl']) {
24446							$this->x += ($textlen / 2);
24447						} else {
24448							$this->x -= ($textlen / 2);
24449						}
24450					}
24451				}
24452				$textrendermode = $this->textrendermode;
24453				$textstrokewidth = $this->textstrokewidth;
24454				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24455				if ($name == 'text') {
24456					// store current coordinates
24457					$tmpx = $this->x;
24458					$tmpy = $this->y;
24459				}
24460				// print the text
24461				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24462				if ($name == 'text') {
24463					// restore coordinates
24464					$this->x = $tmpx;
24465					$this->y = $tmpy;
24466				}
24467				// restore previous rendering mode
24468				$this->textrendermode = $textrendermode;
24469				$this->textstrokewidth = $textstrokewidth;
24470				$this->svgtext = '';
24471				$this->StopTransform();
24472				if (!$this->svgdefsmode) {
24473					array_pop($this->svgstyles);
24474				}
24475				break;
24476			}
24477			default: {
24478				break;
24479			}
24480		}
24481	}
24482
24483	/**
24484	 * Sets the character data handler function for the XML parser.
24485	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24486	 * @param $data (string) The second parameter, data, contains the character data as a string.
24487	 * @author Nicola Asuni
24488	 * @since 5.0.000 (2010-05-02)
24489	 * @protected
24490	 */
24491	protected function segSVGContentHandler($parser, $data) {
24492		$this->svgtext .= $data;
24493	}
24494
24495	// --- END SVG METHODS -----------------------------------------------------
24496
24497} // END OF TCPDF CLASS
24498
24499//============================================================+
24500// END OF FILE
24501//============================================================+
24502