1<?php
2//============================================================+
3// File name   : tcpdf.php
4// Version     : 6.4.1
5// Begin       : 2002-08-03
6// Last Update : 2021-03-27
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-2019 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 ImageMagick (http://www.imagemagick.org/www/formats.html)
49//  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50//  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51//  * automatic page header and footer management;
52//  * document encryption up to 256 bit and digital signature certifications;
53//  * transactions to UNDO commands;
54//  * PDF annotations, including links, text and file attachments;
55//  * text rendering modes (fill, stroke and clipping);
56//  * multiple columns mode;
57//  * no-write page regions;
58//  * bookmarks, named destinations and table of content;
59//  * text hyphenation;
60//  * text stretching and spacing (tracking);
61//  * automatic page break, line break and text alignments including justification;
62//  * automatic page numbering and page groups;
63//  * move and delete pages;
64//  * page compression (requires php-zlib extension);
65//  * XOBject Templates;
66//  * Layers and object visibility.
67//	* PDF/A-1b support
68//============================================================+
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 ImageMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.3.2
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.3.2
132 * @author Nicola Asuni - info@tecnick.com
133 * @IgnoreAnnotation("protected")
134 * @IgnoreAnnotation("public")
135 * @IgnoreAnnotation("pre")
136 */
137class TCPDF {
138
139	// Protected properties
140
141	/**
142	 * Current page number.
143	 * @protected
144	 */
145	protected $page;
146
147	/**
148	 * Current object number.
149	 * @protected
150	 */
151	protected $n;
152
153	/**
154	 * Array of object offsets.
155	 * @protected
156	 */
157	protected $offsets = array();
158
159	/**
160	 * Array of object IDs for each page.
161	 * @protected
162	 */
163	protected $pageobjects = array();
164
165	/**
166	 * Buffer holding in-memory PDF.
167	 * @protected
168	 */
169	protected $buffer;
170
171	/**
172	 * Array containing pages.
173	 * @protected
174	 */
175	protected $pages = array();
176
177	/**
178	 * Current document state.
179	 * @protected
180	 */
181	protected $state;
182
183	/**
184	 * Compression flag.
185	 * @protected
186	 */
187	protected $compress;
188
189	/**
190	 * Current page orientation (P = Portrait, L = Landscape).
191	 * @protected
192	 */
193	protected $CurOrientation;
194
195	/**
196	 * Page dimensions.
197	 * @protected
198	 */
199	protected $pagedim = array();
200
201	/**
202	 * Scale factor (number of points in user unit).
203	 * @protected
204	 */
205	protected $k;
206
207	/**
208	 * Width of page format in points.
209	 * @protected
210	 */
211	protected $fwPt;
212
213	/**
214	 * Height of page format in points.
215	 * @protected
216	 */
217	protected $fhPt;
218
219	/**
220	 * Current width of page in points.
221	 * @protected
222	 */
223	protected $wPt;
224
225	/**
226	 * Current height of page in points.
227	 * @protected
228	 */
229	protected $hPt;
230
231	/**
232	 * Current width of page in user unit.
233	 * @protected
234	 */
235	protected $w;
236
237	/**
238	 * Current height of page in user unit.
239	 * @protected
240	 */
241	protected $h;
242
243	/**
244	 * Left margin.
245	 * @protected
246	 */
247	protected $lMargin;
248
249	/**
250	 * Right margin.
251	 * @protected
252	 */
253	protected $rMargin;
254
255	/**
256	 * Cell left margin (used by regions).
257	 * @protected
258	 */
259	protected $clMargin;
260
261	/**
262	 * Cell right margin (used by regions).
263	 * @protected
264	 */
265	protected $crMargin;
266
267	/**
268	 * Top margin.
269	 * @protected
270	 */
271	protected $tMargin;
272
273	/**
274	 * Page break margin.
275	 * @protected
276	 */
277	protected $bMargin;
278
279	/**
280	 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281	 * @since 5.9.000 (2010-10-03)
282	 * @protected
283	 */
284	protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
285
286	/**
287	 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
288	 * @since 5.9.000 (2010-10-04)
289	 * @protected
290	 */
291	protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
292
293	/**
294	 * Current horizontal position in user unit for cell positioning.
295	 * @protected
296	 */
297	protected $x;
298
299	/**
300	 * Current vertical position in user unit for cell positioning.
301	 * @protected
302	 */
303	protected $y;
304
305	/**
306	 * Height of last cell printed.
307	 * @protected
308	 */
309	protected $lasth;
310
311	/**
312	 * Line width in user unit.
313	 * @protected
314	 */
315	protected $LineWidth;
316
317	/**
318	 * Array of standard font names.
319	 * @protected
320	 */
321	protected $CoreFonts;
322
323	/**
324	 * Array of used fonts.
325	 * @protected
326	 */
327	protected $fonts = array();
328
329	/**
330	 * Array of font files.
331	 * @protected
332	 */
333	protected $FontFiles = array();
334
335	/**
336	 * Array of encoding differences.
337	 * @protected
338	 */
339	protected $diffs = array();
340
341	/**
342	 * Array of used images.
343	 * @protected
344	 */
345	protected $images = array();
346
347	/**
348	 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
349	 * @protected
350	 */
351	protected $svg_tag_depth = 0;
352
353	/**
354	 * Array of Annotations in pages.
355	 * @protected
356	 */
357	protected $PageAnnots = array();
358
359	/**
360	 * Array of internal links.
361	 * @protected
362	 */
363	protected $links = array();
364
365	/**
366	 * Current font family.
367	 * @protected
368	 */
369	protected $FontFamily;
370
371	/**
372	 * Current font style.
373	 * @protected
374	 */
375	protected $FontStyle;
376
377	/**
378	 * Current font ascent (distance between font top and baseline).
379	 * @protected
380	 * @since 2.8.000 (2007-03-29)
381	 */
382	protected $FontAscent;
383
384	/**
385	 * Current font descent (distance between font bottom and baseline).
386	 * @protected
387	 * @since 2.8.000 (2007-03-29)
388	 */
389	protected $FontDescent;
390
391	/**
392	 * Underlining flag.
393	 * @protected
394	 */
395	protected $underline;
396
397	/**
398	 * Overlining flag.
399	 * @protected
400	 */
401	protected $overline;
402
403	/**
404	 * Current font info.
405	 * @protected
406	 */
407	protected $CurrentFont;
408
409	/**
410	 * Current font size in points.
411	 * @protected
412	 */
413	protected $FontSizePt;
414
415	/**
416	 * Current font size in user unit.
417	 * @protected
418	 */
419	protected $FontSize;
420
421	/**
422	 * Commands for drawing color.
423	 * @protected
424	 */
425	protected $DrawColor;
426
427	/**
428	 * Commands for filling color.
429	 * @protected
430	 */
431	protected $FillColor;
432
433	/**
434	 * Commands for text color.
435	 * @protected
436	 */
437	protected $TextColor;
438
439	/**
440	 * Indicates whether fill and text colors are different.
441	 * @protected
442	 */
443	protected $ColorFlag;
444
445	/**
446	 * Automatic page breaking.
447	 * @protected
448	 */
449	protected $AutoPageBreak;
450
451	/**
452	 * Threshold used to trigger page breaks.
453	 * @protected
454	 */
455	protected $PageBreakTrigger;
456
457	/**
458	 * Flag set when processing page header.
459	 * @protected
460	 */
461	protected $InHeader = false;
462
463	/**
464	 * Flag set when processing page footer.
465	 * @protected
466	 */
467	protected $InFooter = false;
468
469	/**
470	 * Zoom display mode.
471	 * @protected
472	 */
473	protected $ZoomMode;
474
475	/**
476	 * Layout display mode.
477	 * @protected
478	 */
479	protected $LayoutMode;
480
481	/**
482	 * If true set the document information dictionary in Unicode.
483	 * @protected
484	 */
485	protected $docinfounicode = true;
486
487	/**
488	 * Document title.
489	 * @protected
490	 */
491	protected $title = '';
492
493	/**
494	 * Document subject.
495	 * @protected
496	 */
497	protected $subject = '';
498
499	/**
500	 * Document author.
501	 * @protected
502	 */
503	protected $author = '';
504
505	/**
506	 * Document keywords.
507	 * @protected
508	 */
509	protected $keywords = '';
510
511	/**
512	 * Document creator.
513	 * @protected
514	 */
515	protected $creator = '';
516
517	/**
518	 * Starting page number.
519	 * @protected
520	 */
521	protected $starting_page_number = 1;
522
523	/**
524	 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
525	 * @since 2002-07-31
526	 * @author Nicola Asuni
527	 * @protected
528	 */
529	protected $img_rb_x;
530
531	/**
532	 * The right-bottom corner Y coordinate of last inserted image.
533	 * @since 2002-07-31
534	 * @author Nicola Asuni
535	 * @protected
536	 */
537	protected $img_rb_y;
538
539	/**
540	 * Adjusting factor to convert pixels to user units.
541	 * @since 2004-06-14
542	 * @author Nicola Asuni
543	 * @protected
544	 */
545	protected $imgscale = 1;
546
547	/**
548	 * Boolean flag set to true when the input text is unicode (require unicode fonts).
549	 * @since 2005-01-02
550	 * @author Nicola Asuni
551	 * @protected
552	 */
553	protected $isunicode = false;
554
555	/**
556	 * PDF version.
557	 * @since 1.5.3
558	 * @protected
559	 */
560	protected $PDFVersion = '1.7';
561
562	/**
563	 * ID of the stored default header template (-1 = not set).
564	 * @protected
565	 */
566	protected $header_xobjid = false;
567
568	/**
569	 * If true reset the Header Xobject template at each page
570	 * @protected
571	 */
572	protected $header_xobj_autoreset = false;
573
574	/**
575	 * Minimum distance between header and top page margin.
576	 * @protected
577	 */
578	protected $header_margin;
579
580	/**
581	 * Minimum distance between footer and bottom page margin.
582	 * @protected
583	 */
584	protected $footer_margin;
585
586	/**
587	 * Original left margin value.
588	 * @protected
589	 * @since 1.53.0.TC013
590	 */
591	protected $original_lMargin;
592
593	/**
594	 * Original right margin value.
595	 * @protected
596	 * @since 1.53.0.TC013
597	 */
598	protected $original_rMargin;
599
600	/**
601	 * Default font used on page header.
602	 * @protected
603	 * @var array<int,string>
604	 */
605	protected $header_font;
606
607	/**
608	 * Default font used on page footer.
609	 * @protected
610	 * @var array<int,string>
611	 */
612	protected $footer_font;
613
614	/**
615	 * Language templates.
616	 * @protected
617	 */
618	protected $l;
619
620	/**
621	 * Barcode to print on page footer (only if set).
622	 * @protected
623	 */
624	protected $barcode = false;
625
626	/**
627	 * Boolean flag to print/hide page header.
628	 * @protected
629	 */
630	protected $print_header = true;
631
632	/**
633	 * Boolean flag to print/hide page footer.
634	 * @protected
635	 */
636	protected $print_footer = true;
637
638	/**
639	 * Header image logo.
640	 * @protected
641	 */
642	protected $header_logo = '';
643
644	/**
645	 * Width of header image logo in user units.
646	 * @protected
647	 */
648	protected $header_logo_width = 30;
649
650	/**
651	 * Title to be printed on default page header.
652	 * @protected
653	 */
654	protected $header_title = '';
655
656	/**
657	 * String to pring on page header after title.
658	 * @protected
659	 */
660	protected $header_string = '';
661
662	/**
663	 * Color for header text (RGB array).
664	 * @since 5.9.174 (2012-07-25)
665	 * @protected
666	 */
667	protected $header_text_color = array(0,0,0);
668
669	/**
670	 * Color for header line (RGB array).
671	 * @since 5.9.174 (2012-07-25)
672	 * @protected
673	 */
674	protected $header_line_color = array(0,0,0);
675
676	/**
677	 * Color for footer text (RGB array).
678	 * @since 5.9.174 (2012-07-25)
679	 * @protected
680	 */
681	protected $footer_text_color = array(0,0,0);
682
683	/**
684	 * Color for footer line (RGB array).
685	 * @since 5.9.174 (2012-07-25)
686	 * @protected
687	 */
688	protected $footer_line_color = array(0,0,0);
689
690	/**
691	 * Text shadow data array.
692	 * @since 5.9.174 (2012-07-25)
693	 * @protected
694	 */
695	protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
696
697	/**
698	 * Default number of columns for html table.
699	 * @protected
700	 */
701	protected $default_table_columns = 4;
702
703	// variables for html parser
704
705	/**
706	 * HTML PARSER: array to store current link and rendering styles.
707	 * @protected
708	 */
709	protected $HREF = array();
710
711	/**
712	 * List of available fonts on filesystem.
713	 * @protected
714	 */
715	protected $fontlist = array();
716
717	/**
718	 * Current foreground color.
719	 * @protected
720	 */
721	protected $fgcolor;
722
723	/**
724	 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
725	 * @protected
726	 */
727	protected $listordered = array();
728
729	/**
730	 * HTML PARSER: array count list items on nested lists.
731	 * @protected
732	 */
733	protected $listcount = array();
734
735	/**
736	 * HTML PARSER: current list nesting level.
737	 * @protected
738	 */
739	protected $listnum = 0;
740
741	/**
742	 * HTML PARSER: indent amount for lists.
743	 * @protected
744	 */
745	protected $listindent = 0;
746
747	/**
748	 * HTML PARSER: current list indententation level.
749	 * @protected
750	 */
751	protected $listindentlevel = 0;
752
753	/**
754	 * Current background color.
755	 * @protected
756	 */
757	protected $bgcolor;
758
759	/**
760	 * Temporary font size in points.
761	 * @protected
762	 */
763	protected $tempfontsize = 10;
764
765	/**
766	 * Spacer string for LI tags.
767	 * @protected
768	 */
769	protected $lispacer = '';
770
771	/**
772	 * Default encoding.
773	 * @protected
774	 * @since 1.53.0.TC010
775	 */
776	protected $encoding = 'UTF-8';
777
778	/**
779	 * Boolean flag to indicate if the document language is Right-To-Left.
780	 * @protected
781	 * @since 2.0.000
782	 */
783	protected $rtl = false;
784
785	/**
786	 * Boolean flag used to force RTL or LTR string direction.
787	 * @protected
788	 * @since 2.0.000
789	 */
790	protected $tmprtl = false;
791
792	// --- Variables used for document encryption:
793
794	/**
795	 * IBoolean flag indicating whether document is protected.
796	 * @protected
797	 * @since 2.0.000 (2008-01-02)
798	 */
799	protected $encrypted;
800
801	/**
802	 * Array containing encryption settings.
803	 * @protected
804	 * @since 5.0.005 (2010-05-11)
805	 */
806	protected $encryptdata = array();
807
808	/**
809	 * Last RC4 key encrypted (cached for optimisation).
810	 * @protected
811	 * @since 2.0.000 (2008-01-02)
812	 */
813	protected $last_enc_key;
814
815	/**
816	 * Last RC4 computed key.
817	 * @protected
818	 * @since 2.0.000 (2008-01-02)
819	 */
820	protected $last_enc_key_c;
821
822	/**
823	 * File ID (used on document trailer).
824	 * @protected
825	 * @since 5.0.005 (2010-05-12)
826	 */
827	protected $file_id;
828
829	// --- bookmark ---
830
831	/**
832	 * Outlines for bookmark.
833	 * @protected
834	 * @since 2.1.002 (2008-02-12)
835	 */
836	protected $outlines = array();
837
838	/**
839	 * Outline root for bookmark.
840	 * @protected
841	 * @since 2.1.002 (2008-02-12)
842	 */
843	protected $OutlineRoot;
844
845	// --- javascript and form ---
846
847	/**
848	 * Javascript code.
849	 * @protected
850	 * @since 2.1.002 (2008-02-12)
851	 */
852	protected $javascript = '';
853
854	/**
855	 * Javascript counter.
856	 * @protected
857	 * @since 2.1.002 (2008-02-12)
858	 */
859	protected $n_js;
860
861	/**
862	 * line through state
863	 * @protected
864	 * @since 2.8.000 (2008-03-19)
865	 */
866	protected $linethrough;
867
868	/**
869	 * Array with additional document-wide usage rights for the document.
870	 * @protected
871	 * @since 5.8.014 (2010-08-23)
872	 */
873	protected $ur = array();
874
875	/**
876	 * DPI (Dot Per Inch) Document Resolution (do not change).
877	 * @protected
878	 * @since 3.0.000 (2008-03-27)
879	 */
880	protected $dpi = 72;
881
882	/**
883	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
884	 * @protected
885	 * @since 3.0.000 (2008-03-27)
886	 */
887	protected $newpagegroup = array();
888
889	/**
890	 * Array that contains the number of pages in each page group.
891	 * @protected
892	 * @since 3.0.000 (2008-03-27)
893	 */
894	protected $pagegroups = array();
895
896	/**
897	 * Current page group number.
898	 * @protected
899	 * @since 3.0.000 (2008-03-27)
900	 */
901	protected $currpagegroup = 0;
902
903	/**
904	 * Array of transparency objects and parameters.
905	 * @protected
906	 * @since 3.0.000 (2008-03-27)
907	 */
908	protected $extgstates;
909
910	/**
911	 * Set the default JPEG compression quality (1-100).
912	 * @protected
913	 * @since 3.0.000 (2008-03-27)
914	 */
915	protected $jpeg_quality;
916
917	/**
918	 * Default cell height ratio.
919	 * @protected
920	 * @since 3.0.014 (2008-05-23)
921	 */
922	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
923
924	/**
925	 * PDF viewer preferences.
926	 * @protected
927	 * @since 3.1.000 (2008-06-09)
928	 */
929	protected $viewer_preferences;
930
931	/**
932	 * A name object specifying how the document should be displayed when opened.
933	 * @protected
934	 * @since 3.1.000 (2008-06-09)
935	 */
936	protected $PageMode;
937
938	/**
939	 * Array for storing gradient information.
940	 * @protected
941	 * @since 3.1.000 (2008-06-09)
942	 */
943	protected $gradients = array();
944
945	/**
946	 * Array used to store positions inside the pages buffer (keys are the page numbers).
947	 * @protected
948	 * @since 3.2.000 (2008-06-26)
949	 */
950	protected $intmrk = array();
951
952	/**
953	 * Array used to store positions inside the pages buffer (keys are the page numbers).
954	 * @protected
955	 * @since 5.7.000 (2010-08-03)
956	 */
957	protected $bordermrk = array();
958
959	/**
960	 * Array used to store page positions to track empty pages (keys are the page numbers).
961	 * @protected
962	 * @since 5.8.007 (2010-08-18)
963	 */
964	protected $emptypagemrk = array();
965
966	/**
967	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
968	 * @protected
969	 * @since 4.6.021 (2009-07-20)
970	 */
971	protected $cntmrk = array();
972
973	/**
974	 * Array used to store footer positions of each page.
975	 * @protected
976	 * @since 3.2.000 (2008-07-01)
977	 */
978	protected $footerpos = array();
979
980	/**
981	 * Array used to store footer length of each page.
982	 * @protected
983	 * @since 4.0.014 (2008-07-29)
984	 */
985	protected $footerlen = array();
986
987	/**
988	 * Boolean flag to indicate if a new line is created.
989	 * @protected
990	 * @since 3.2.000 (2008-07-01)
991	 */
992	protected $newline = true;
993
994	/**
995	 * End position of the latest inserted line.
996	 * @protected
997	 * @since 3.2.000 (2008-07-01)
998	 */
999	protected $endlinex = 0;
1000
1001	/**
1002	 * PDF string for width value of the last line.
1003	 * @protected
1004	 * @since 4.0.006 (2008-07-16)
1005	 */
1006	protected $linestyleWidth = '';
1007
1008	/**
1009	 * PDF string for CAP value of the last line.
1010	 * @protected
1011	 * @since 4.0.006 (2008-07-16)
1012	 */
1013	protected $linestyleCap = '0 J';
1014
1015	/**
1016	 * PDF string for join value of the last line.
1017	 * @protected
1018	 * @since 4.0.006 (2008-07-16)
1019	 */
1020	protected $linestyleJoin = '0 j';
1021
1022	/**
1023	 * PDF string for dash value of the last line.
1024	 * @protected
1025	 * @since 4.0.006 (2008-07-16)
1026	 */
1027	protected $linestyleDash = '[] 0 d';
1028
1029	/**
1030	 * Boolean flag to indicate if marked-content sequence is open.
1031	 * @protected
1032	 * @since 4.0.013 (2008-07-28)
1033	 */
1034	protected $openMarkedContent = false;
1035
1036	/**
1037	 * Count the latest inserted vertical spaces on HTML.
1038	 * @protected
1039	 * @since 4.0.021 (2008-08-24)
1040	 */
1041	protected $htmlvspace = 0;
1042
1043	/**
1044	 * Array of Spot colors.
1045	 * @protected
1046	 * @since 4.0.024 (2008-09-12)
1047	 */
1048	protected $spot_colors = array();
1049
1050	/**
1051	 * Symbol used for HTML unordered list items.
1052	 * @protected
1053	 * @since 4.0.028 (2008-09-26)
1054	 */
1055	protected $lisymbol = '';
1056
1057	/**
1058	 * String used to mark the beginning and end of EPS image blocks.
1059	 * @protected
1060	 * @since 4.1.000 (2008-10-18)
1061	 */
1062	protected $epsmarker = 'x#!#EPS#!#x';
1063
1064	/**
1065	 * Array of transformation matrix.
1066	 * @protected
1067	 * @since 4.2.000 (2008-10-29)
1068	 */
1069	protected $transfmatrix = array();
1070
1071	/**
1072	 * Current key for transformation matrix.
1073	 * @protected
1074	 * @since 4.8.005 (2009-09-17)
1075	 */
1076	protected $transfmatrix_key = 0;
1077
1078	/**
1079	 * Booklet mode for double-sided pages.
1080	 * @protected
1081	 * @since 4.2.000 (2008-10-29)
1082	 */
1083	protected $booklet = false;
1084
1085	/**
1086	 * Epsilon value used for float calculations.
1087	 * @protected
1088	 * @since 4.2.000 (2008-10-29)
1089	 */
1090	protected $feps = 0.005;
1091
1092	/**
1093	 * Array used for custom vertical spaces for HTML tags.
1094	 * @protected
1095	 * @since 4.2.001 (2008-10-30)
1096	 */
1097	protected $tagvspaces = array();
1098
1099	/**
1100	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1101	 * @protected
1102	 * @since 4.2.007 (2008-11-12)
1103	 */
1104	protected $customlistindent = -1;
1105
1106	/**
1107	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1108	 * @protected
1109	 * @since 4.2.010 (2008-11-14)
1110	 */
1111	protected $opencell = true;
1112
1113	/**
1114	 * Array of files to embedd.
1115	 * @protected
1116	 * @since 4.4.000 (2008-12-07)
1117	 */
1118	protected $embeddedfiles = array();
1119
1120	/**
1121	 * Boolean flag to indicate if we are inside a PRE tag.
1122	 * @protected
1123	 * @since 4.4.001 (2008-12-08)
1124	 */
1125	protected $premode = false;
1126
1127	/**
1128	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1129	 * keys are the page numbers
1130	 * @protected
1131	 * @since 4.4.002 (2008-12-09)
1132	 */
1133	protected $transfmrk = array();
1134
1135	/**
1136	 * Default color for html links.
1137	 * @protected
1138	 * @since 4.4.003 (2008-12-09)
1139	 */
1140	protected $htmlLinkColorArray = array(0, 0, 255);
1141
1142	/**
1143	 * Default font style to add to html links.
1144	 * @protected
1145	 * @since 4.4.003 (2008-12-09)
1146	 */
1147	protected $htmlLinkFontStyle = 'U';
1148
1149	/**
1150	 * Counts the number of pages.
1151	 * @protected
1152	 * @since 4.5.000 (2008-12-31)
1153	 */
1154	protected $numpages = 0;
1155
1156	/**
1157	 * Array containing page lengths in bytes.
1158	 * @protected
1159	 * @since 4.5.000 (2008-12-31)
1160	 */
1161	protected $pagelen = array();
1162
1163	/**
1164	 * Counts the number of pages.
1165	 * @protected
1166	 * @since 4.5.000 (2008-12-31)
1167	 */
1168	protected $numimages = 0;
1169
1170	/**
1171	 * Store the image keys.
1172	 * @protected
1173	 * @since 4.5.000 (2008-12-31)
1174	 */
1175	protected $imagekeys = array();
1176
1177	/**
1178	 * Length of the buffer in bytes.
1179	 * @protected
1180	 * @since 4.5.000 (2008-12-31)
1181	 */
1182	protected $bufferlen = 0;
1183
1184	/**
1185	 * Counts the number of fonts.
1186	 * @protected
1187	 * @since 4.5.000 (2009-01-02)
1188	 */
1189	protected $numfonts = 0;
1190
1191	/**
1192	 * Store the font keys.
1193	 * @protected
1194	 * @since 4.5.000 (2009-01-02)
1195	 */
1196	protected $fontkeys = array();
1197
1198	/**
1199	 * Store the font object IDs.
1200	 * @protected
1201	 * @since 4.8.001 (2009-09-09)
1202	 */
1203	protected $font_obj_ids = array();
1204
1205	/**
1206	 * Store the fage status (true when opened, false when closed).
1207	 * @protected
1208	 * @since 4.5.000 (2009-01-02)
1209	 */
1210	protected $pageopen = array();
1211
1212	/**
1213	 * Default monospace font.
1214	 * @protected
1215	 * @since 4.5.025 (2009-03-10)
1216	 */
1217	protected $default_monospaced_font = 'courier';
1218
1219	/**
1220	 * Cloned copy of the current class object.
1221	 * @protected
1222	 * @since 4.5.029 (2009-03-19)
1223	 */
1224	protected $objcopy;
1225
1226	/**
1227	 * Array used to store the lengths of cache files.
1228	 * @protected
1229	 * @since 4.5.029 (2009-03-19)
1230	 */
1231	protected $cache_file_length = array();
1232
1233	/**
1234	 * Table header content to be repeated on each new page.
1235	 * @protected
1236	 * @since 4.5.030 (2009-03-20)
1237	 */
1238	protected $thead = '';
1239
1240	/**
1241	 * Margins used for table header.
1242	 * @protected
1243	 * @since 4.5.030 (2009-03-20)
1244	 */
1245	protected $theadMargins = array();
1246
1247	/**
1248	 * Boolean flag to enable document digital signature.
1249	 * @protected
1250	 * @since 4.6.005 (2009-04-24)
1251	 */
1252	protected $sign = false;
1253
1254	/**
1255	 * Digital signature data.
1256	 * @protected
1257	 * @since 4.6.005 (2009-04-24)
1258	 */
1259	protected $signature_data = array();
1260
1261	/**
1262	 * Digital signature max length.
1263	 * @protected
1264	 * @since 4.6.005 (2009-04-24)
1265	 */
1266	protected $signature_max_length = 11742;
1267
1268	/**
1269	 * Data for digital signature appearance.
1270	 * @protected
1271	 * @since 5.3.011 (2010-06-16)
1272	 */
1273	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1274
1275	/**
1276	 * Array of empty digital signature appearances.
1277	 * @protected
1278	 * @since 5.9.101 (2011-07-06)
1279	 */
1280	protected $empty_signature_appearance = array();
1281
1282	/**
1283	 * Boolean flag to enable document timestamping with TSA.
1284	 * @protected
1285	 * @since 6.0.085 (2014-06-19)
1286	 */
1287	protected $tsa_timestamp = false;
1288
1289	/**
1290	 * Timestamping data.
1291	 * @protected
1292	 * @since 6.0.085 (2014-06-19)
1293	 */
1294	protected $tsa_data = array();
1295
1296	/**
1297	 * Regular expression used to find blank characters (required for word-wrapping).
1298	 * @protected
1299	 * @since 4.6.006 (2009-04-28)
1300	 */
1301	protected $re_spaces = '/[^\S\xa0]/';
1302
1303	/**
1304	 * Array of $re_spaces parts.
1305	 * @protected
1306	 * @since 5.5.011 (2010-07-09)
1307	 */
1308	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1309
1310	/**
1311	 * Digital signature object ID.
1312	 * @protected
1313	 * @since 4.6.022 (2009-06-23)
1314	 */
1315	protected $sig_obj_id = 0;
1316
1317	/**
1318	 * ID of page objects.
1319	 * @protected
1320	 * @since 4.7.000 (2009-08-29)
1321	 */
1322	protected $page_obj_id = array();
1323
1324	/**
1325	 * List of form annotations IDs.
1326	 * @protected
1327	 * @since 4.8.000 (2009-09-07)
1328	 */
1329	protected $form_obj_id = array();
1330
1331	/**
1332	 * 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.
1333	 * @protected
1334	 * @since 4.8.000 (2009-09-07)
1335	 */
1336	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1337
1338	/**
1339	 * Javascript objects array.
1340	 * @protected
1341	 * @since 4.8.000 (2009-09-07)
1342	 */
1343	protected $js_objects = array();
1344
1345	/**
1346	 * Current form action (used during XHTML rendering).
1347	 * @protected
1348	 * @since 4.8.000 (2009-09-07)
1349	 */
1350	protected $form_action = '';
1351
1352	/**
1353	 * Current form encryption type (used during XHTML rendering).
1354	 * @protected
1355	 * @since 4.8.000 (2009-09-07)
1356	 */
1357	protected $form_enctype = 'application/x-www-form-urlencoded';
1358
1359	/**
1360	 * Current method to submit forms.
1361	 * @protected
1362	 * @since 4.8.000 (2009-09-07)
1363	 */
1364	protected $form_mode = 'post';
1365
1366	/**
1367	 * List of fonts used on form fields (fontname => fontkey).
1368	 * @protected
1369	 * @since 4.8.001 (2009-09-09)
1370	 */
1371	protected $annotation_fonts = array();
1372
1373	/**
1374	 * List of radio buttons parent objects.
1375	 * @protected
1376	 * @since 4.8.001 (2009-09-09)
1377	 */
1378	protected $radiobutton_groups = array();
1379
1380	/**
1381	 * List of radio group objects IDs.
1382	 * @protected
1383	 * @since 4.8.001 (2009-09-09)
1384	 */
1385	protected $radio_groups = array();
1386
1387	/**
1388	 * Text indentation value (used for text-indent CSS attribute).
1389	 * @protected
1390	 * @since 4.8.006 (2009-09-23)
1391	 */
1392	protected $textindent = 0;
1393
1394	/**
1395	 * Store page number when startTransaction() is called.
1396	 * @protected
1397	 * @since 4.8.006 (2009-09-23)
1398	 */
1399	protected $start_transaction_page = 0;
1400
1401	/**
1402	 * Store Y position when startTransaction() is called.
1403	 * @protected
1404	 * @since 4.9.001 (2010-03-28)
1405	 */
1406	protected $start_transaction_y = 0;
1407
1408	/**
1409	 * True when we are printing the thead section on a new page.
1410	 * @protected
1411	 * @since 4.8.027 (2010-01-25)
1412	 */
1413	protected $inthead = false;
1414
1415	/**
1416	 * Array of column measures (width, space, starting Y position).
1417	 * @protected
1418	 * @since 4.9.001 (2010-03-28)
1419	 */
1420	protected $columns = array();
1421
1422	/**
1423	 * Number of colums.
1424	 * @protected
1425	 * @since 4.9.001 (2010-03-28)
1426	 */
1427	protected $num_columns = 1;
1428
1429	/**
1430	 * Current column number.
1431	 * @protected
1432	 * @since 4.9.001 (2010-03-28)
1433	 */
1434	protected $current_column = 0;
1435
1436	/**
1437	 * Starting page for columns.
1438	 * @protected
1439	 * @since 4.9.001 (2010-03-28)
1440	 */
1441	protected $column_start_page = 0;
1442
1443	/**
1444	 * Maximum page and column selected.
1445	 * @protected
1446	 * @since 5.8.000 (2010-08-11)
1447	 */
1448	protected $maxselcol = array('page' => 0, 'column' => 0);
1449
1450	/**
1451	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1452	 * @protected
1453	 * @since 5.8.000 (2010-08-11)
1454	 */
1455	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1456
1457	/**
1458	 * 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.
1459	 * @protected
1460	 * @since 4.9.008 (2010-04-03)
1461	 */
1462	protected $textrendermode = 0;
1463
1464	/**
1465	 * Text stroke width in doc units.
1466	 * @protected
1467	 * @since 4.9.008 (2010-04-03)
1468	 */
1469	protected $textstrokewidth = 0;
1470
1471	/**
1472	 * Current stroke color.
1473	 * @protected
1474	 * @since 4.9.008 (2010-04-03)
1475	 */
1476	protected $strokecolor;
1477
1478	/**
1479	 * Default unit of measure for document.
1480	 * @protected
1481	 * @since 5.0.000 (2010-04-22)
1482	 */
1483	protected $pdfunit = 'mm';
1484
1485	/**
1486	 * Boolean flag true when we are on TOC (Table Of Content) page.
1487	 * @protected
1488	 */
1489	protected $tocpage = false;
1490
1491	/**
1492	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1493	 * @protected
1494	 * @since 5.0.000 (2010-04-26)
1495	 */
1496	protected $rasterize_vector_images = false;
1497
1498	/**
1499	 * Boolean flag: if true enables font subsetting by default.
1500	 * @protected
1501	 * @since 5.3.002 (2010-06-07)
1502	 */
1503	protected $font_subsetting = true;
1504
1505	/**
1506	 * Array of default graphic settings.
1507	 * @protected
1508	 * @since 5.5.008 (2010-07-02)
1509	 */
1510	protected $default_graphic_vars = array();
1511
1512	/**
1513	 * Array of XObjects.
1514	 * @protected
1515	 * @since 5.8.014 (2010-08-23)
1516	 */
1517	protected $xobjects = array();
1518
1519	/**
1520	 * Boolean value true when we are inside an XObject.
1521	 * @protected
1522	 * @since 5.8.017 (2010-08-24)
1523	 */
1524	protected $inxobj = false;
1525
1526	/**
1527	 * Current XObject ID.
1528	 * @protected
1529	 * @since 5.8.017 (2010-08-24)
1530	 */
1531	protected $xobjid = '';
1532
1533	/**
1534	 * Percentage of character stretching.
1535	 * @protected
1536	 * @since 5.9.000 (2010-09-29)
1537	 */
1538	protected $font_stretching = 100;
1539
1540	/**
1541	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1542	 * @protected
1543	 * @since 5.9.000 (2010-09-29)
1544	 */
1545	protected $font_spacing = 0;
1546
1547	/**
1548	 * Array of no-write regions.
1549	 * ('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)
1550	 * @protected
1551	 * @since 5.9.003 (2010-10-14)
1552	 */
1553	protected $page_regions = array();
1554
1555	/**
1556	 * Boolean value true when page region check is active.
1557	 * @protected
1558	 */
1559	protected $check_page_regions = true;
1560
1561	/**
1562	 * Array of PDF layers data.
1563	 * @protected
1564	 * @since 5.9.102 (2011-07-13)
1565	 */
1566	protected $pdflayers = array();
1567
1568	/**
1569	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1570	 * @protected
1571	 * @since 5.9.097 (2011-06-23)
1572	 */
1573	protected $dests = array();
1574
1575	/**
1576	 * Object ID for Named Destinations
1577	 * @protected
1578	 * @since 5.9.097 (2011-06-23)
1579	 */
1580	protected $n_dests;
1581
1582	/**
1583	 * Embedded Files Names
1584	 * @protected
1585	 * @since 5.9.204 (2013-01-23)
1586	 */
1587	protected $efnames = array();
1588
1589	/**
1590	 * Directory used for the last SVG image.
1591	 * @protected
1592	 * @since 5.0.000 (2010-05-05)
1593	 */
1594	protected $svgdir = '';
1595
1596	/**
1597	 *  Deafult unit of measure for SVG.
1598	 * @protected
1599	 * @since 5.0.000 (2010-05-02)
1600	 */
1601	protected $svgunit = 'px';
1602
1603	/**
1604	 * Array of SVG gradients.
1605	 * @protected
1606	 * @since 5.0.000 (2010-05-02)
1607	 */
1608	protected $svggradients = array();
1609
1610	/**
1611	 * ID of last SVG gradient.
1612	 * @protected
1613	 * @since 5.0.000 (2010-05-02)
1614	 */
1615	protected $svggradientid = 0;
1616
1617	/**
1618	 * Boolean value true when in SVG defs group.
1619	 * @protected
1620	 * @since 5.0.000 (2010-05-02)
1621	 */
1622	protected $svgdefsmode = false;
1623
1624	/**
1625	 * Array of SVG defs.
1626	 * @protected
1627	 * @since 5.0.000 (2010-05-02)
1628	 */
1629	protected $svgdefs = array();
1630
1631	/**
1632	 * Boolean value true when in SVG clipPath tag.
1633	 * @protected
1634	 * @since 5.0.000 (2010-04-26)
1635	 */
1636	protected $svgclipmode = false;
1637
1638	/**
1639	 * Array of SVG clipPath commands.
1640	 * @protected
1641	 * @since 5.0.000 (2010-05-02)
1642	 */
1643	protected $svgclippaths = array();
1644
1645	/**
1646	 * Array of SVG clipPath tranformation matrix.
1647	 * @protected
1648	 * @since 5.8.022 (2010-08-31)
1649	 */
1650	protected $svgcliptm = array();
1651
1652	/**
1653	 * ID of last SVG clipPath.
1654	 * @protected
1655	 * @since 5.0.000 (2010-05-02)
1656	 */
1657	protected $svgclipid = 0;
1658
1659	/**
1660	 * SVG text.
1661	 * @protected
1662	 * @since 5.0.000 (2010-05-02)
1663	 */
1664	protected $svgtext = '';
1665
1666	/**
1667	 * SVG text properties.
1668	 * @protected
1669	 * @since 5.8.013 (2010-08-23)
1670	 */
1671	protected $svgtextmode = array();
1672
1673	/**
1674	 * Array of SVG properties.
1675	 * @protected
1676	 * @since 5.0.000 (2010-05-02)
1677	 */
1678	protected $svgstyles = array(array(
1679		'alignment-baseline' => 'auto',
1680		'baseline-shift' => 'baseline',
1681		'clip' => 'auto',
1682		'clip-path' => 'none',
1683		'clip-rule' => 'nonzero',
1684		'color' => 'black',
1685		'color-interpolation' => 'sRGB',
1686		'color-interpolation-filters' => 'linearRGB',
1687		'color-profile' => 'auto',
1688		'color-rendering' => 'auto',
1689		'cursor' => 'auto',
1690		'direction' => 'ltr',
1691		'display' => 'inline',
1692		'dominant-baseline' => 'auto',
1693		'enable-background' => 'accumulate',
1694		'fill' => 'black',
1695		'fill-opacity' => 1,
1696		'fill-rule' => 'nonzero',
1697		'filter' => 'none',
1698		'flood-color' => 'black',
1699		'flood-opacity' => 1,
1700		'font' => '',
1701		'font-family' => 'helvetica',
1702		'font-size' => 'medium',
1703		'font-size-adjust' => 'none',
1704		'font-stretch' => 'normal',
1705		'font-style' => 'normal',
1706		'font-variant' => 'normal',
1707		'font-weight' => 'normal',
1708		'glyph-orientation-horizontal' => '0deg',
1709		'glyph-orientation-vertical' => 'auto',
1710		'image-rendering' => 'auto',
1711		'kerning' => 'auto',
1712		'letter-spacing' => 'normal',
1713		'lighting-color' => 'white',
1714		'marker' => '',
1715		'marker-end' => 'none',
1716		'marker-mid' => 'none',
1717		'marker-start' => 'none',
1718		'mask' => 'none',
1719		'opacity' => 1,
1720		'overflow' => 'auto',
1721		'pointer-events' => 'visiblePainted',
1722		'shape-rendering' => 'auto',
1723		'stop-color' => 'black',
1724		'stop-opacity' => 1,
1725		'stroke' => 'none',
1726		'stroke-dasharray' => 'none',
1727		'stroke-dashoffset' => 0,
1728		'stroke-linecap' => 'butt',
1729		'stroke-linejoin' => 'miter',
1730		'stroke-miterlimit' => 4,
1731		'stroke-opacity' => 1,
1732		'stroke-width' => 1,
1733		'text-anchor' => 'start',
1734		'text-decoration' => 'none',
1735		'text-rendering' => 'auto',
1736		'unicode-bidi' => 'normal',
1737		'visibility' => 'visible',
1738		'word-spacing' => 'normal',
1739		'writing-mode' => 'lr-tb',
1740		'text-color' => 'black',
1741		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1742		));
1743
1744	/**
1745	 * If true force sRGB color profile for all document.
1746	 * @protected
1747	 * @since 5.9.121 (2011-09-28)
1748	 */
1749	protected $force_srgb = false;
1750
1751	/**
1752	 * If true set the document to PDF/A mode.
1753	 * @protected
1754	 * @since 5.9.121 (2011-09-27)
1755	 */
1756	protected $pdfa_mode = false;
1757
1758	/**
1759	 * version of PDF/A mode (1 - 3).
1760	 * @protected
1761	 * @since 6.2.26 (2019-03-12)
1762	 */
1763	protected $pdfa_version = 1;
1764
1765	/**
1766	 * Document creation date-time
1767	 * @protected
1768	 * @since 5.9.152 (2012-03-22)
1769	 */
1770	protected $doc_creation_timestamp;
1771
1772	/**
1773	 * Document modification date-time
1774	 * @protected
1775	 * @since 5.9.152 (2012-03-22)
1776	 */
1777	protected $doc_modification_timestamp;
1778
1779	/**
1780	 * Custom XMP data.
1781	 * @protected
1782	 * @since 5.9.128 (2011-10-06)
1783	 */
1784	protected $custom_xmp = '';
1785
1786	/**
1787	 * Custom XMP RDF data.
1788	 * @protected
1789	 * @since 6.3.0 (2019-09-19)
1790	 */
1791	protected $custom_xmp_rdf = '';
1792
1793	/**
1794	 * Overprint mode array.
1795	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1796	 * @protected
1797	 * @since 5.9.152 (2012-03-23)
1798	 * @var array<string,bool|int>
1799	 */
1800	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1801
1802	/**
1803	 * Alpha mode array.
1804	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1805	 * @protected
1806	 * @since 5.9.152 (2012-03-23)
1807	 */
1808	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1809
1810	/**
1811	 * Define the page boundaries boxes to be set on document.
1812	 * @protected
1813	 * @since 5.9.152 (2012-03-23)
1814	 */
1815	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1816
1817	/**
1818	 * If true print TCPDF meta link.
1819	 * @protected
1820	 * @since 5.9.152 (2012-03-23)
1821	 */
1822	protected $tcpdflink = true;
1823
1824	/**
1825	 * Cache array for computed GD gamma values.
1826	 * @protected
1827	 * @since 5.9.1632 (2012-06-05)
1828	 */
1829	protected $gdgammacache = array();
1830
1831    /**
1832     * Cache array for file content
1833     * @protected
1834     * @var array
1835     * @since 6.3.5 (2020-09-28)
1836     */
1837	protected $fileContentCache = array();
1838
1839	/**
1840	 * Whether to allow local file path in image html tags, when prefixed with file://
1841	 *
1842	 * @var bool
1843	 * @protected
1844	 * @since 6.4 (2020-07-23)
1845	 */
1846	protected $allowLocalFiles = false;
1847
1848	//------------------------------------------------------------
1849	// METHODS
1850	//------------------------------------------------------------
1851
1852	/**
1853	 * This is the class constructor.
1854	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1855	 *
1856	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1857	 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1858	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1859	 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1860	 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1861	 * @param boolean $diskcache DEPRECATED FEATURE
1862	 * @param integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1863	 * @public
1864	 * @see getPageSizeFromFormat(), setPageFormat()
1865	 */
1866	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1867		// set file ID for trailer
1868		$serformat = (is_array($format) ? json_encode($format) : $format);
1869		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1870		$this->font_obj_ids = array();
1871		$this->page_obj_id = array();
1872		$this->form_obj_id = array();
1873
1874		// set pdf/a mode
1875		if ($pdfa != false) {
1876			$this->pdfa_mode = true;
1877			$this->pdfa_version = $pdfa;  // 1 or 3
1878		} else
1879			$this->pdfa_mode = false;
1880
1881		$this->force_srgb = false;
1882		// set language direction
1883		$this->rtl = false;
1884		$this->tmprtl = false;
1885		// some checks
1886		$this->_dochecks();
1887		// initialization of properties
1888		$this->isunicode = $unicode;
1889		$this->page = 0;
1890		$this->transfmrk[0] = array();
1891		$this->pagedim = array();
1892		$this->n = 2;
1893		$this->buffer = '';
1894		$this->pages = array();
1895		$this->state = 0;
1896		$this->fonts = array();
1897		$this->FontFiles = array();
1898		$this->diffs = array();
1899		$this->images = array();
1900		$this->links = array();
1901		$this->gradients = array();
1902		$this->InFooter = false;
1903		$this->lasth = 0;
1904		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1905		$this->FontStyle = '';
1906		$this->FontSizePt = 12;
1907		$this->underline = false;
1908		$this->overline = false;
1909		$this->linethrough = false;
1910		$this->DrawColor = '0 G';
1911		$this->FillColor = '0 g';
1912		$this->TextColor = '0 g';
1913		$this->ColorFlag = false;
1914		$this->pdflayers = array();
1915		// encryption values
1916		$this->encrypted = false;
1917		$this->last_enc_key = '';
1918		// standard Unicode fonts
1919		$this->CoreFonts = array(
1920			'courier'=>'Courier',
1921			'courierB'=>'Courier-Bold',
1922			'courierI'=>'Courier-Oblique',
1923			'courierBI'=>'Courier-BoldOblique',
1924			'helvetica'=>'Helvetica',
1925			'helveticaB'=>'Helvetica-Bold',
1926			'helveticaI'=>'Helvetica-Oblique',
1927			'helveticaBI'=>'Helvetica-BoldOblique',
1928			'times'=>'Times-Roman',
1929			'timesB'=>'Times-Bold',
1930			'timesI'=>'Times-Italic',
1931			'timesBI'=>'Times-BoldItalic',
1932			'symbol'=>'Symbol',
1933			'zapfdingbats'=>'ZapfDingbats'
1934		);
1935		// set scale factor
1936		$this->setPageUnit($unit);
1937		// set page format and orientation
1938		$this->setPageFormat($format, $orientation);
1939		// page margins (1 cm)
1940		$margin = 28.35 / $this->k;
1941		$this->SetMargins($margin, $margin);
1942		$this->clMargin = $this->lMargin;
1943		$this->crMargin = $this->rMargin;
1944		// internal cell padding
1945		$cpadding = $margin / 10;
1946		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1947		// cell margins
1948		$this->setCellMargins(0, 0, 0, 0);
1949		// line width (0.2 mm)
1950		$this->LineWidth = 0.57 / $this->k;
1951		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1952		$this->linestyleCap = '0 J';
1953		$this->linestyleJoin = '0 j';
1954		$this->linestyleDash = '[] 0 d';
1955		// automatic page break
1956		$this->SetAutoPageBreak(true, (2 * $margin));
1957		// full width display mode
1958		$this->SetDisplayMode('fullwidth');
1959		// compression
1960		$this->SetCompression();
1961		// set default PDF version number
1962		$this->setPDFVersion();
1963		$this->tcpdflink = true;
1964		$this->encoding = $encoding;
1965		$this->HREF = array();
1966		$this->getFontsList();
1967		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1968		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1969		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1970		$this->extgstates = array();
1971		$this->setTextShadow();
1972		// signature
1973		$this->sign = false;
1974		$this->tsa_timestamp = false;
1975		$this->tsa_data = array();
1976		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1977		$this->empty_signature_appearance = array();
1978		// user's rights
1979		$this->ur['enabled'] = false;
1980		$this->ur['document'] = '/FullSave';
1981		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1982		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1983		$this->ur['signature'] = '/Modify';
1984		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1985		$this->ur['formex'] = '';
1986		// set default JPEG quality
1987		$this->jpeg_quality = 75;
1988		// initialize some settings
1989		TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
1990		// set default font
1991		$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1992		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1993		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1994		// check if PCRE Unicode support is enabled
1995		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1996			// PCRE unicode support is turned ON
1997			// \s     : any whitespace character
1998			// \p{Z}  : any separator
1999			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2000			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2001			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2002			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2003		} else {
2004			// PCRE unicode support is turned OFF
2005			$this->setSpacesRE('/[^\S\xa0]/');
2006		}
2007		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2008		// set document creation and modification timestamp
2009		$this->doc_creation_timestamp = time();
2010		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
2011		// get default graphic vars
2012		$this->default_graphic_vars = $this->getGraphicVars();
2013		$this->header_xobj_autoreset = false;
2014		$this->custom_xmp = '';
2015		$this->custom_xmp_rdf = '';
2016		// Call cleanup method after script execution finishes or exit() is called.
2017		// NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
2018		register_shutdown_function(array($this, '_destroy'), true);
2019	}
2020
2021	/**
2022	 * Default destructor.
2023	 * @public
2024	 * @since 1.53.0.TC016
2025	 */
2026	public function __destruct() {
2027		// cleanup
2028		$this->_destroy(true);
2029	}
2030
2031	/**
2032	 * Set the units of measure for the document.
2033	 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2034	 * @public
2035	 * @since 3.0.015 (2008-06-06)
2036	 */
2037	public function setPageUnit($unit) {
2038		$unit = strtolower($unit);
2039		//Set scale factor
2040		switch ($unit) {
2041			// points
2042			case 'px':
2043			case 'pt': {
2044				$this->k = 1;
2045				break;
2046			}
2047			// millimeters
2048			case 'mm': {
2049				$this->k = $this->dpi / 25.4;
2050				break;
2051			}
2052			// centimeters
2053			case 'cm': {
2054				$this->k = $this->dpi / 2.54;
2055				break;
2056			}
2057			// inches
2058			case 'in': {
2059				$this->k = $this->dpi;
2060				break;
2061			}
2062			// unsupported unit
2063			default : {
2064				$this->Error('Incorrect unit: '.$unit);
2065				break;
2066			}
2067		}
2068		$this->pdfunit = $unit;
2069		if (isset($this->CurOrientation)) {
2070			$this->setPageOrientation($this->CurOrientation);
2071		}
2072	}
2073
2074	/**
2075	 * Change the format of the current page
2076	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2077	 * <li>['format'] = page format name (one of the above);</li>
2078	 * <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>
2079	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2080	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2081	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2082	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2083	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2084	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2085	 * <li>['CropBox'] : the visible region of default user space:</li>
2086	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2087	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2088	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2089	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2090	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2091	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2092	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2093	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2094	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2095	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2096	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2097	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2098	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2099	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2100	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2101	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2102	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2103	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2104	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2105	 * <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>
2106	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2107	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2108	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2109	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2110	 * <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>
2111	 * <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>
2112	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2113	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2114	 * <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>
2115	 * <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>
2116	 * <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>
2117	 * <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>
2118	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2119	 * </ul>
2120	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2121	 * <li>P or Portrait (default)</li>
2122	 * <li>L or Landscape</li>
2123	 * <li>'' (empty string) for automatic orientation</li>
2124	 * </ul>
2125	 * @protected
2126	 * @since 3.0.015 (2008-06-06)
2127	 * @see getPageSizeFromFormat()
2128	 */
2129	protected function setPageFormat($format, $orientation='P') {
2130		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2131			// remove inherited values
2132			unset($this->pagedim[$this->page]);
2133		}
2134		if (is_string($format)) {
2135			// get page measures from format name
2136			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2137			$this->fwPt = $pf[0];
2138			$this->fhPt = $pf[1];
2139		} else {
2140			// the boundaries of the physical medium on which the page shall be displayed or printed
2141			if (isset($format['MediaBox'])) {
2142				$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);
2143				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2144				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2145			} else {
2146				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2147					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2148				} else {
2149					if (!isset($format['format'])) {
2150						// default value
2151						$format['format'] = 'A4';
2152					}
2153					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2154				}
2155				$this->fwPt = $pf[0];
2156				$this->fhPt = $pf[1];
2157				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2158			}
2159			// the visible region of default user space
2160			if (isset($format['CropBox'])) {
2161				$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);
2162			}
2163			// the region to which the contents of the page shall be clipped when output in a production environment
2164			if (isset($format['BleedBox'])) {
2165				$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);
2166			}
2167			// the intended dimensions of the finished page after trimming
2168			if (isset($format['TrimBox'])) {
2169				$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);
2170			}
2171			// the page's meaningful content (including potential white space)
2172			if (isset($format['ArtBox'])) {
2173				$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);
2174			}
2175			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2176			if (isset($format['BoxColorInfo'])) {
2177				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2178			}
2179			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2180				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2181				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2182			}
2183			if (isset($format['PZ'])) {
2184				// The page's preferred zoom (magnification) factor
2185				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2186			}
2187			if (isset($format['trans'])) {
2188				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2189				if (isset($format['trans']['Dur'])) {
2190					// The page's display duration
2191					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2192				}
2193				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2194				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2195					// The transition style that shall be used when moving to this page from another during a presentation
2196					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2197					$valid_effect = array('Split', 'Blinds');
2198					$valid_vals = array('H', 'V');
2199					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2200						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2201					}
2202					$valid_effect = array('Split', 'Box', 'Fly');
2203					$valid_vals = array('I', 'O');
2204					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2205						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2206					}
2207					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2208					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2209						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2210							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2211							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2212							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2213						}
2214					}
2215					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2216						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2217					}
2218					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2219						$this->pagedim[$this->page]['trans']['B'] = 'true';
2220					}
2221				} else {
2222					$this->pagedim[$this->page]['trans']['S'] = 'R';
2223				}
2224				if (isset($format['trans']['D'])) {
2225					// The duration of the transition effect, in seconds
2226					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2227				} else {
2228					$this->pagedim[$this->page]['trans']['D'] = 1;
2229				}
2230			}
2231		}
2232		$this->setPageOrientation($orientation);
2233	}
2234
2235	/**
2236	 * Set page orientation.
2237	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2238	 * @param boolean $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2239	 * @param float $bottommargin bottom margin of the page.
2240	 * @public
2241	 * @since 3.0.015 (2008-06-06)
2242	 */
2243	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2244		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2245			// the boundaries of the physical medium on which the page shall be displayed or printed
2246			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2247		}
2248		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2249			// the visible region of default user space
2250			$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);
2251		}
2252		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2253			// the region to which the contents of the page shall be clipped when output in a production environment
2254			$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);
2255		}
2256		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2257			// the intended dimensions of the finished page after trimming
2258			$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);
2259		}
2260		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2261			// the page's meaningful content (including potential white space)
2262			$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);
2263		}
2264		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2265			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2266			$this->pagedim[$this->page]['Rotate'] = 0;
2267		}
2268		if (!isset($this->pagedim[$this->page]['PZ'])) {
2269			// The page's preferred zoom (magnification) factor
2270			$this->pagedim[$this->page]['PZ'] = 1;
2271		}
2272		if ($this->fwPt > $this->fhPt) {
2273			// landscape
2274			$default_orientation = 'L';
2275		} else {
2276			// portrait
2277			$default_orientation = 'P';
2278		}
2279		$valid_orientations = array('P', 'L');
2280		if (empty($orientation)) {
2281			$orientation = $default_orientation;
2282		} else {
2283			$orientation = strtoupper($orientation[0]);
2284		}
2285		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2286			$this->CurOrientation = $orientation;
2287			$this->wPt = $this->fhPt;
2288			$this->hPt = $this->fwPt;
2289		} else {
2290			$this->CurOrientation = $default_orientation;
2291			$this->wPt = $this->fwPt;
2292			$this->hPt = $this->fhPt;
2293		}
2294		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2295			// swap X and Y coordinates (change page orientation)
2296			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2297		}
2298		$this->w = ($this->wPt / $this->k);
2299		$this->h = ($this->hPt / $this->k);
2300		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2301			if (isset($this->AutoPageBreak)) {
2302				$autopagebreak = $this->AutoPageBreak;
2303			} else {
2304				$autopagebreak = true;
2305			}
2306		}
2307		if (TCPDF_STATIC::empty_string($bottommargin)) {
2308			if (isset($this->bMargin)) {
2309				$bottommargin = $this->bMargin;
2310			} else {
2311				// default value = 2 cm
2312				$bottommargin = 2 * 28.35 / $this->k;
2313			}
2314		}
2315		$this->SetAutoPageBreak($autopagebreak, $bottommargin);
2316		// store page dimensions
2317		$this->pagedim[$this->page]['w'] = $this->wPt;
2318		$this->pagedim[$this->page]['h'] = $this->hPt;
2319		$this->pagedim[$this->page]['wk'] = $this->w;
2320		$this->pagedim[$this->page]['hk'] = $this->h;
2321		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2322		$this->pagedim[$this->page]['bm'] = $bottommargin;
2323		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2324		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2325		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2326		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2327		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2328		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2329	}
2330
2331	/**
2332	 * Set regular expression to detect withespaces or word separators.
2333	 * The pattern delimiter must be the forward-slash character "/".
2334	 * Some example patterns are:
2335	 * <pre>
2336	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2337	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2338	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2339	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2340	 *      \s     : any whitespace character
2341	 *      \p{Z}  : any separator
2342	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2343	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2344	 * </pre>
2345	 * @param string $re regular expression (leave empty for default).
2346	 * @public
2347	 * @since 4.6.016 (2009-06-15)
2348	 */
2349	public function setSpacesRE($re='/[^\S\xa0]/') {
2350		$this->re_spaces = $re;
2351		$re_parts = explode('/', $re);
2352		// get pattern parts
2353		$this->re_space = array();
2354		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2355			$this->re_space['p'] = $re_parts[1];
2356		} else {
2357			$this->re_space['p'] = '[\s]';
2358		}
2359		// set pattern modifiers
2360		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2361			$this->re_space['m'] = $re_parts[2];
2362		} else {
2363			$this->re_space['m'] = '';
2364		}
2365	}
2366
2367	/**
2368	 * Enable or disable Right-To-Left language mode
2369	 * @param boolean $enable if true enable Right-To-Left language mode.
2370	 * @param boolean $resetx if true reset the X position on direction change.
2371	 * @public
2372	 * @since 2.0.000 (2008-01-03)
2373	 */
2374	public function setRTL($enable, $resetx=true) {
2375		$enable = $enable ? true : false;
2376		$resetx = ($resetx AND ($enable != $this->rtl));
2377		$this->rtl = $enable;
2378		$this->tmprtl = false;
2379		if ($resetx) {
2380			$this->Ln(0);
2381		}
2382	}
2383
2384	/**
2385	 * Return the RTL status
2386	 * @return bool
2387	 * @public
2388	 * @since 4.0.012 (2008-07-24)
2389	 */
2390	public function getRTL() {
2391		return $this->rtl;
2392	}
2393
2394	/**
2395	 * Force temporary RTL language direction
2396	 * @param mixed $mode can be false, 'L' for LTR or 'R' for RTL
2397	 * @public
2398	 * @since 2.1.000 (2008-01-09)
2399	 */
2400	public function setTempRTL($mode) {
2401		$newmode = false;
2402		switch (strtoupper($mode)) {
2403			case 'LTR':
2404			case 'L': {
2405				if ($this->rtl) {
2406					$newmode = 'L';
2407				}
2408				break;
2409			}
2410			case 'RTL':
2411			case 'R': {
2412				if (!$this->rtl) {
2413					$newmode = 'R';
2414				}
2415				break;
2416			}
2417			case false:
2418			default: {
2419				$newmode = false;
2420				break;
2421			}
2422		}
2423		$this->tmprtl = $newmode;
2424	}
2425
2426	/**
2427	 * Return the current temporary RTL status
2428	 * @return bool
2429	 * @public
2430	 * @since 4.8.014 (2009-11-04)
2431	 */
2432	public function isRTLTextDir() {
2433		return ($this->rtl OR ($this->tmprtl == 'R'));
2434	}
2435
2436	/**
2437	 * Set the last cell height.
2438	 * @param float $h cell height.
2439	 * @author Nicola Asuni
2440	 * @public
2441	 * @since 1.53.0.TC034
2442	 */
2443	public function setLastH($h) {
2444		$this->lasth = $h;
2445	}
2446
2447	/**
2448	 * Return the cell height
2449	 * @param int $fontsize Font size in internal units
2450	 * @param boolean $padding If true add cell padding
2451	 * @public
2452	 */
2453	public function getCellHeight($fontsize, $padding=TRUE) {
2454		$height = ($fontsize * $this->cell_height_ratio);
2455		if ($padding) {
2456			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2457		}
2458		return round($height, 6);
2459	}
2460
2461	/**
2462	 * Reset the last cell height.
2463	 * @public
2464	 * @since 5.9.000 (2010-10-03)
2465	 */
2466	public function resetLastH() {
2467		$this->lasth = $this->getCellHeight($this->FontSize);
2468	}
2469
2470	/**
2471	 * Get the last cell height.
2472	 * @return float last cell height
2473	 * @public
2474	 * @since 4.0.017 (2008-08-05)
2475	 */
2476	public function getLastH() {
2477		return $this->lasth;
2478	}
2479
2480	/**
2481	 * Set the adjusting factor to convert pixels to user units.
2482	 * @param float $scale adjusting factor to convert pixels to user units.
2483	 * @author Nicola Asuni
2484	 * @public
2485	 * @since 1.5.2
2486	 */
2487	public function setImageScale($scale) {
2488		$this->imgscale = $scale;
2489	}
2490
2491	/**
2492	 * Returns the adjusting factor to convert pixels to user units.
2493	 * @return float adjusting factor to convert pixels to user units.
2494	 * @author Nicola Asuni
2495	 * @public
2496	 * @since 1.5.2
2497	 */
2498	public function getImageScale() {
2499		return $this->imgscale;
2500	}
2501
2502	/**
2503	 * Returns an array of page dimensions:
2504	 * <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>
2505	 * @param int $pagenum page number (empty = current page)
2506	 * @return array of page dimensions.
2507	 * @author Nicola Asuni
2508	 * @public
2509	 * @since 4.5.027 (2009-03-16)
2510	 */
2511	public function getPageDimensions($pagenum='') {
2512		if (empty($pagenum)) {
2513			$pagenum = $this->page;
2514		}
2515		return $this->pagedim[$pagenum];
2516	}
2517
2518	/**
2519	 * Returns the page width in units.
2520	 * @param int $pagenum page number (empty = current page)
2521	 * @return int page width.
2522	 * @author Nicola Asuni
2523	 * @public
2524	 * @since 1.5.2
2525	 * @see getPageDimensions()
2526	 */
2527	public function getPageWidth($pagenum='') {
2528		if (empty($pagenum)) {
2529			return $this->w;
2530		}
2531		return $this->pagedim[$pagenum]['w'];
2532	}
2533
2534	/**
2535	 * Returns the page height in units.
2536	 * @param int $pagenum page number (empty = current page)
2537	 * @return int page height.
2538	 * @author Nicola Asuni
2539	 * @public
2540	 * @since 1.5.2
2541	 * @see getPageDimensions()
2542	 */
2543	public function getPageHeight($pagenum='') {
2544		if (empty($pagenum)) {
2545			return $this->h;
2546		}
2547		return $this->pagedim[$pagenum]['h'];
2548	}
2549
2550	/**
2551	 * Returns the page break margin.
2552	 * @param int $pagenum page number (empty = current page)
2553	 * @return int page break margin.
2554	 * @author Nicola Asuni
2555	 * @public
2556	 * @since 1.5.2
2557	 * @see getPageDimensions()
2558	 */
2559	public function getBreakMargin($pagenum='') {
2560		if (empty($pagenum)) {
2561			return $this->bMargin;
2562		}
2563		return $this->pagedim[$pagenum]['bm'];
2564	}
2565
2566	/**
2567	 * Returns the scale factor (number of points in user unit).
2568	 * @return int scale factor.
2569	 * @author Nicola Asuni
2570	 * @public
2571	 * @since 1.5.2
2572	 */
2573	public function getScaleFactor() {
2574		return $this->k;
2575	}
2576
2577	/**
2578	 * Defines the left, top and right margins.
2579	 * @param float $left Left margin.
2580	 * @param float $top Top margin.
2581	 * @param float $right Right margin. Default value is the left one.
2582	 * @param boolean $keepmargins if true overwrites the default page margins
2583	 * @public
2584	 * @since 1.0
2585	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2586	 */
2587	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2588		//Set left, top and right margins
2589		$this->lMargin = $left;
2590		$this->tMargin = $top;
2591		if ($right == -1) {
2592			$right = $left;
2593		}
2594		$this->rMargin = $right;
2595		if ($keepmargins) {
2596			// overwrite original values
2597			$this->original_lMargin = $this->lMargin;
2598			$this->original_rMargin = $this->rMargin;
2599		}
2600	}
2601
2602	/**
2603	 * 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.
2604	 * @param float $margin The margin.
2605	 * @public
2606	 * @since 1.4
2607	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2608	 */
2609	public function SetLeftMargin($margin) {
2610		//Set left margin
2611		$this->lMargin = $margin;
2612		if (($this->page > 0) AND ($this->x < $margin)) {
2613			$this->x = $margin;
2614		}
2615	}
2616
2617	/**
2618	 * Defines the top margin. The method can be called before creating the first page.
2619	 * @param float $margin The margin.
2620	 * @public
2621	 * @since 1.5
2622	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2623	 */
2624	public function SetTopMargin($margin) {
2625		//Set top margin
2626		$this->tMargin = $margin;
2627		if (($this->page > 0) AND ($this->y < $margin)) {
2628			$this->y = $margin;
2629		}
2630	}
2631
2632	/**
2633	 * Defines the right margin. The method can be called before creating the first page.
2634	 * @param float $margin The margin.
2635	 * @public
2636	 * @since 1.5
2637	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2638	 */
2639	public function SetRightMargin($margin) {
2640		$this->rMargin = $margin;
2641		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2642			$this->x = $this->w - $margin;
2643		}
2644	}
2645
2646	/**
2647	 * Set the same internal Cell padding for top, right, bottom, left-
2648	 * @param float $pad internal padding.
2649	 * @public
2650	 * @since 2.1.000 (2008-01-09)
2651	 * @see getCellPaddings(), setCellPaddings()
2652	 */
2653	public function SetCellPadding($pad) {
2654		if ($pad >= 0) {
2655			$this->cell_padding['L'] = $pad;
2656			$this->cell_padding['T'] = $pad;
2657			$this->cell_padding['R'] = $pad;
2658			$this->cell_padding['B'] = $pad;
2659		}
2660	}
2661
2662	/**
2663	 * Set the internal Cell paddings.
2664	 * @param float $left left padding
2665	 * @param float $top top padding
2666	 * @param float $right right padding
2667	 * @param float $bottom bottom padding
2668	 * @public
2669	 * @since 5.9.000 (2010-10-03)
2670	 * @see getCellPaddings(), SetCellPadding()
2671	 */
2672	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2673		if (($left !== '') AND ($left >= 0)) {
2674			$this->cell_padding['L'] = $left;
2675		}
2676		if (($top !== '') AND ($top >= 0)) {
2677			$this->cell_padding['T'] = $top;
2678		}
2679		if (($right !== '') AND ($right >= 0)) {
2680			$this->cell_padding['R'] = $right;
2681		}
2682		if (($bottom !== '') AND ($bottom >= 0)) {
2683			$this->cell_padding['B'] = $bottom;
2684		}
2685	}
2686
2687	/**
2688	 * Get the internal Cell padding array.
2689	 * @return array of padding values
2690	 * @public
2691	 * @since 5.9.000 (2010-10-03)
2692	 * @see setCellPaddings(), SetCellPadding()
2693	 */
2694	public function getCellPaddings() {
2695		return $this->cell_padding;
2696	}
2697
2698	/**
2699	 * Set the internal Cell margins.
2700	 * @param float $left left margin
2701	 * @param float $top top margin
2702	 * @param float $right right margin
2703	 * @param float $bottom bottom margin
2704	 * @public
2705	 * @since 5.9.000 (2010-10-03)
2706	 * @see getCellMargins()
2707	 */
2708	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2709		if (($left !== '') AND ($left >= 0)) {
2710			$this->cell_margin['L'] = $left;
2711		}
2712		if (($top !== '') AND ($top >= 0)) {
2713			$this->cell_margin['T'] = $top;
2714		}
2715		if (($right !== '') AND ($right >= 0)) {
2716			$this->cell_margin['R'] = $right;
2717		}
2718		if (($bottom !== '') AND ($bottom >= 0)) {
2719			$this->cell_margin['B'] = $bottom;
2720		}
2721	}
2722
2723	/**
2724	 * Get the internal Cell margin array.
2725	 * @return array of margin values
2726	 * @public
2727	 * @since 5.9.000 (2010-10-03)
2728	 * @see setCellMargins()
2729	 */
2730	public function getCellMargins() {
2731		return $this->cell_margin;
2732	}
2733
2734	/**
2735	 * Adjust the internal Cell padding array to take account of the line width.
2736	 * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2737	 * @return void|array array of adjustments
2738	 * @public
2739	 * @since 5.9.000 (2010-10-03)
2740	 */
2741	protected function adjustCellPadding($brd=0) {
2742		if (empty($brd)) {
2743			return;
2744		}
2745		if (is_string($brd)) {
2746			// convert string to array
2747			$slen = strlen($brd);
2748			$newbrd = array();
2749			for ($i = 0; $i < $slen; ++$i) {
2750				$newbrd[$brd[$i]] = true;
2751			}
2752			$brd = $newbrd;
2753		} elseif (
2754			($brd === 1)
2755			|| ($brd === true)
2756			|| (is_numeric($brd) && ((int)$brd > 0))
2757		) {
2758			$brd = array('LRTB' => true);
2759		}
2760		if (!is_array($brd)) {
2761			return;
2762		}
2763		// store current cell padding
2764		$cp = $this->cell_padding;
2765		// select border mode
2766		if (isset($brd['mode'])) {
2767			$mode = $brd['mode'];
2768			unset($brd['mode']);
2769		} else {
2770			$mode = 'normal';
2771		}
2772		// process borders
2773		foreach ($brd as $border => $style) {
2774			$line_width = $this->LineWidth;
2775			if (is_array($style) && isset($style['width'])) {
2776				// get border width
2777				$line_width = $style['width'];
2778			}
2779			$adj = 0; // line width inside the cell
2780			switch ($mode) {
2781				case 'ext': {
2782					$adj = 0;
2783					break;
2784				}
2785				case 'int': {
2786					$adj = $line_width;
2787					break;
2788				}
2789				case 'normal':
2790				default: {
2791					$adj = ($line_width / 2);
2792					break;
2793				}
2794			}
2795			// correct internal cell padding if required to avoid overlap between text and lines
2796			if (
2797				is_numeric($this->cell_padding['T'])
2798				&& ($this->cell_padding['T'] < $adj)
2799				&& (strpos($border, 'T') !== false)
2800			) {
2801				$this->cell_padding['T'] = $adj;
2802			}
2803			if (
2804				is_numeric($this->cell_padding['R'])
2805				&& ($this->cell_padding['R'] < $adj)
2806				&& (strpos($border, 'R') !== false)
2807			) {
2808				$this->cell_padding['R'] = $adj;
2809			}
2810			if (
2811				is_numeric($this->cell_padding['B'])
2812				&& ($this->cell_padding['B'] < $adj)
2813				&& (strpos($border, 'B') !== false)
2814			) {
2815				$this->cell_padding['B'] = $adj;
2816			}
2817			if (
2818				is_numeric($this->cell_padding['L'])
2819				&& ($this->cell_padding['L'] < $adj)
2820				&& (strpos($border, 'L') !== false)
2821			) {
2822				$this->cell_padding['L'] = $adj;
2823			}
2824
2825		}
2826
2827		return array(
2828			'T' => ($this->cell_padding['T'] - $cp['T']),
2829			'R' => ($this->cell_padding['R'] - $cp['R']),
2830			'B' => ($this->cell_padding['B'] - $cp['B']),
2831			'L' => ($this->cell_padding['L'] - $cp['L']),
2832		);
2833	}
2834
2835	/**
2836	 * 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.
2837	 * @param boolean $auto Boolean indicating if mode should be on or off.
2838	 * @param float $margin Distance from the bottom of the page.
2839	 * @public
2840	 * @since 1.0
2841	 * @see Cell(), MultiCell(), AcceptPageBreak()
2842	 */
2843	public function SetAutoPageBreak($auto, $margin=0) {
2844		$this->AutoPageBreak = $auto ? true : false;
2845		$this->bMargin = $margin;
2846		$this->PageBreakTrigger = $this->h - $margin;
2847	}
2848
2849	/**
2850	 * Return the auto-page-break mode (true or false).
2851	 * @return bool auto-page-break mode
2852	 * @public
2853	 * @since 5.9.088
2854	 */
2855	public function getAutoPageBreak() {
2856		return $this->AutoPageBreak;
2857	}
2858
2859	/**
2860	 * Defines the way the document is to be displayed by the viewer.
2861	 * @param mixed $zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2862	 * @param string $layout The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2863	 * @param string $mode A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2864	 * @public
2865	 * @since 1.2
2866	 */
2867	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2868		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2869			$this->ZoomMode = $zoom;
2870		} else {
2871			$this->Error('Incorrect zoom display mode: '.$zoom);
2872		}
2873		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2874		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2875	}
2876
2877	/**
2878	 * 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.
2879	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2880	 * @param boolean $compress Boolean indicating if compression must be enabled.
2881	 * @public
2882	 * @since 1.4
2883	 */
2884	public function SetCompression($compress=true) {
2885		$this->compress = false;
2886		if (function_exists('gzcompress')) {
2887			if ($compress) {
2888				if ( !$this->pdfa_mode) {
2889					$this->compress = true;
2890				}
2891			}
2892		}
2893	}
2894
2895	/**
2896	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2897	 * @param boolean $mode If true force sRGB output intent.
2898	 * @public
2899	 * @since 5.9.121 (2011-09-28)
2900	 */
2901	public function setSRGBmode($mode=false) {
2902		$this->force_srgb = $mode ? true : false;
2903	}
2904
2905	/**
2906	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2907	 * This has effect only when unicode mode is set to false.
2908	 * @param boolean $unicode if true set the meta information in Unicode
2909	 * @since 5.9.027 (2010-12-01)
2910	 * @public
2911	 */
2912	public function SetDocInfoUnicode($unicode=true) {
2913		$this->docinfounicode = $unicode ? true : false;
2914	}
2915
2916	/**
2917	 * Defines the title of the document.
2918	 * @param string $title The title.
2919	 * @public
2920	 * @since 1.2
2921	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2922	 */
2923	public function SetTitle($title) {
2924		$this->title = $title;
2925	}
2926
2927	/**
2928	 * Defines the subject of the document.
2929	 * @param string $subject The subject.
2930	 * @public
2931	 * @since 1.2
2932	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2933	 */
2934	public function SetSubject($subject) {
2935		$this->subject = $subject;
2936	}
2937
2938	/**
2939	 * Defines the author of the document.
2940	 * @param string $author The name of the author.
2941	 * @public
2942	 * @since 1.2
2943	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2944	 */
2945	public function SetAuthor($author) {
2946		$this->author = $author;
2947	}
2948
2949	/**
2950	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2951	 * @param string $keywords The list of keywords.
2952	 * @public
2953	 * @since 1.2
2954	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2955	 */
2956	public function SetKeywords($keywords) {
2957		$this->keywords = $keywords;
2958	}
2959
2960	/**
2961	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2962	 * @param string $creator The name of the creator.
2963	 * @public
2964	 * @since 1.2
2965	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2966	 */
2967	public function SetCreator($creator) {
2968		$this->creator = $creator;
2969	}
2970
2971	/**
2972	 * Whether to allow local file path in image html tags, when prefixed with file://
2973	 *
2974	 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
2975	 * @public
2976	 * @since 6.4
2977	 */
2978	public function SetAllowLocalFiles($allowLocalFiles) {
2979		$this->allowLocalFiles = (bool) $allowLocalFiles;
2980	}
2981
2982
2983	/**
2984	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2985	 * @param string $msg The error message
2986	 * @public
2987	 * @since 1.0
2988	 */
2989	public function Error($msg) {
2990		// unset all class variables
2991		$this->_destroy(true);
2992		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2993			die('<strong>TCPDF ERROR: </strong>'.$msg);
2994		} else {
2995			throw new Exception('TCPDF ERROR: '.$msg);
2996		}
2997	}
2998
2999	/**
3000	 * This method begins the generation of the PDF document.
3001	 * It is not necessary to call it explicitly because AddPage() does it automatically.
3002	 * Note: no page is created by this method
3003	 * @public
3004	 * @since 1.0
3005	 * @see AddPage(), Close()
3006	 */
3007	public function Open() {
3008		$this->state = 1;
3009	}
3010
3011	/**
3012	 * Terminates the PDF document.
3013	 * It is not necessary to call this method explicitly because Output() does it automatically.
3014	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3015	 * @public
3016	 * @since 1.0
3017	 * @see Open(), Output()
3018	 */
3019	public function Close() {
3020		if ($this->state == 3) {
3021			return;
3022		}
3023		if ($this->page == 0) {
3024			$this->AddPage();
3025		}
3026		$this->endLayer();
3027		if ($this->tcpdflink) {
3028			// save current graphic settings
3029			$gvars = $this->getGraphicVars();
3030			$this->setEqualColumns();
3031			$this->lastpage(true);
3032			$this->SetAutoPageBreak(false);
3033			$this->x = 0;
3034			$this->y = $this->h - (1 / $this->k);
3035			$this->lMargin = 0;
3036			$this->_outSaveGraphicsState();
3037			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3038			$this->SetFont($font, '', 1);
3039			$this->setTextRenderingMode(0, false, false);
3040			$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";
3041			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3042			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3043			$this->_outRestoreGraphicsState();
3044			// restore graphic settings
3045			$this->setGraphicVars($gvars);
3046		}
3047		// close page
3048		$this->endPage();
3049		// close document
3050		$this->_enddoc();
3051		// unset all class variables (except critical ones)
3052		$this->_destroy(false);
3053	}
3054
3055	/**
3056	 * Move pointer at the specified document page and update page dimensions.
3057	 * @param int $pnum page number (1 ... numpages)
3058	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3059	 * @public
3060	 * @since 2.1.000 (2008-01-07)
3061	 * @see getPage(), lastpage(), getNumPages()
3062	 */
3063	public function setPage($pnum, $resetmargins=false) {
3064		if (($pnum == $this->page) AND ($this->state == 2)) {
3065			return;
3066		}
3067		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3068			$this->state = 2;
3069			// save current graphic settings
3070			//$gvars = $this->getGraphicVars();
3071			$oldpage = $this->page;
3072			$this->page = $pnum;
3073			$this->wPt = $this->pagedim[$this->page]['w'];
3074			$this->hPt = $this->pagedim[$this->page]['h'];
3075			$this->w = $this->pagedim[$this->page]['wk'];
3076			$this->h = $this->pagedim[$this->page]['hk'];
3077			$this->tMargin = $this->pagedim[$this->page]['tm'];
3078			$this->bMargin = $this->pagedim[$this->page]['bm'];
3079			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3080			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3081			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3082			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3083			$this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3084			// restore graphic settings
3085			//$this->setGraphicVars($gvars);
3086			if ($resetmargins) {
3087				$this->lMargin = $this->pagedim[$this->page]['olm'];
3088				$this->rMargin = $this->pagedim[$this->page]['orm'];
3089				$this->SetY($this->tMargin);
3090			} else {
3091				// account for booklet mode
3092				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3093					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3094					$this->lMargin += $deltam;
3095					$this->rMargin -= $deltam;
3096				}
3097			}
3098		} else {
3099			$this->Error('Wrong page number on setPage() function: '.$pnum);
3100		}
3101	}
3102
3103	/**
3104	 * Reset pointer to the last document page.
3105	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3106	 * @public
3107	 * @since 2.0.000 (2008-01-04)
3108	 * @see setPage(), getPage(), getNumPages()
3109	 */
3110	public function lastPage($resetmargins=false) {
3111		$this->setPage($this->getNumPages(), $resetmargins);
3112	}
3113
3114	/**
3115	 * Get current document page number.
3116	 * @return int page number
3117	 * @public
3118	 * @since 2.1.000 (2008-01-07)
3119	 * @see setPage(), lastpage(), getNumPages()
3120	 */
3121	public function getPage() {
3122		return $this->page;
3123	}
3124
3125	/**
3126	 * Get the total number of insered pages.
3127	 * @return int number of pages
3128	 * @public
3129	 * @since 2.1.000 (2008-01-07)
3130	 * @see setPage(), getPage(), lastpage()
3131	 */
3132	public function getNumPages() {
3133		return $this->numpages;
3134	}
3135
3136	/**
3137	 * Adds a new TOC (Table Of Content) page to the document.
3138	 * @param string $orientation page orientation.
3139	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3140	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3141	 * @public
3142	 * @since 5.0.001 (2010-05-06)
3143	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3144	 */
3145	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3146		$this->AddPage($orientation, $format, $keepmargins, true);
3147	}
3148
3149	/**
3150	 * Terminate the current TOC (Table Of Content) page
3151	 * @public
3152	 * @since 5.0.001 (2010-05-06)
3153	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3154	 */
3155	public function endTOCPage() {
3156		$this->endPage(true);
3157	}
3158
3159	/**
3160	 * 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).
3161	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3162	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3163	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3164	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3165	 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3166	 * @public
3167	 * @since 1.0
3168	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3169	 */
3170	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3171		if ($this->inxobj) {
3172			// we are inside an XObject template
3173			return;
3174		}
3175		if (!isset($this->original_lMargin) OR $keepmargins) {
3176			$this->original_lMargin = $this->lMargin;
3177		}
3178		if (!isset($this->original_rMargin) OR $keepmargins) {
3179			$this->original_rMargin = $this->rMargin;
3180		}
3181		// terminate previous page
3182		$this->endPage();
3183		// start new page
3184		$this->startPage($orientation, $format, $tocpage);
3185	}
3186
3187	/**
3188	 * Terminate the current page
3189	 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3190	 * @public
3191	 * @since 4.2.010 (2008-11-14)
3192	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3193	 */
3194	public function endPage($tocpage=false) {
3195		// check if page is already closed
3196		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3197			return;
3198		}
3199		// print page footer
3200		$this->setFooter();
3201		// close page
3202		$this->_endpage();
3203		// mark page as closed
3204		$this->pageopen[$this->page] = false;
3205		if ($tocpage) {
3206			$this->tocpage = false;
3207		}
3208	}
3209
3210	/**
3211	 * Starts a new page to the document. The page must be closed using the endPage() function.
3212	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3213	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3214	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3215	 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3216	 * @since 4.2.010 (2008-11-14)
3217	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3218	 * @public
3219	 */
3220	public function startPage($orientation='', $format='', $tocpage=false) {
3221		if ($tocpage) {
3222			$this->tocpage = true;
3223		}
3224		// move page numbers of documents to be attached
3225		if ($this->tocpage) {
3226			// move reference to unexistent pages (used for page attachments)
3227			// adjust outlines
3228			$tmpoutlines = $this->outlines;
3229			foreach ($tmpoutlines as $key => $outline) {
3230				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3231					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3232				}
3233			}
3234			// adjust dests
3235			$tmpdests = $this->dests;
3236			foreach ($tmpdests as $key => $dest) {
3237				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3238					$this->dests[$key]['p'] = ($dest['p'] + 1);
3239				}
3240			}
3241			// adjust links
3242			$tmplinks = $this->links;
3243			foreach ($tmplinks as $key => $link) {
3244				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3245					$this->links[$key]['p'] = ($link['p'] + 1);
3246				}
3247			}
3248		}
3249		if ($this->numpages > $this->page) {
3250			// this page has been already added
3251			$this->setPage($this->page + 1);
3252			$this->SetY($this->tMargin);
3253			return;
3254		}
3255		// start a new page
3256		if ($this->state == 0) {
3257			$this->Open();
3258		}
3259		++$this->numpages;
3260		$this->swapMargins($this->booklet);
3261		// save current graphic settings
3262		$gvars = $this->getGraphicVars();
3263		// start new page
3264		$this->_beginpage($orientation, $format);
3265		// mark page as open
3266		$this->pageopen[$this->page] = true;
3267		// restore graphic settings
3268		$this->setGraphicVars($gvars);
3269		// mark this point
3270		$this->setPageMark();
3271		// print page header
3272		$this->setHeader();
3273		// restore graphic settings
3274		$this->setGraphicVars($gvars);
3275		// mark this point
3276		$this->setPageMark();
3277		// print table header (if any)
3278		$this->setTableHeader();
3279		// set mark for empty page check
3280		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3281	}
3282
3283	/**
3284	 * Set start-writing mark on current page stream used to put borders and fills.
3285	 * Borders and fills are always created after content and inserted on the position marked by this method.
3286	 * This function must be called after calling Image() function for a background image.
3287	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3288	 * @public
3289	 * @since 4.0.016 (2008-07-30)
3290	 */
3291	public function setPageMark() {
3292		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3293		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3294		$this->setContentMark();
3295	}
3296
3297	/**
3298	 * Set start-writing mark on selected page.
3299	 * Borders and fills are always created after content and inserted on the position marked by this method.
3300	 * @param int $page page number (default is the current page)
3301	 * @protected
3302	 * @since 4.6.021 (2009-07-20)
3303	 */
3304	protected function setContentMark($page=0) {
3305		if ($page <= 0) {
3306			$page = $this->page;
3307		}
3308		if (isset($this->footerlen[$page])) {
3309			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3310		} else {
3311			$this->cntmrk[$page] = $this->pagelen[$page];
3312		}
3313	}
3314
3315	/**
3316	 * Set header data.
3317	 * @param string $ln header image logo
3318	 * @param string $lw header image logo width in mm
3319	 * @param string $ht string to print as title on document header
3320	 * @param string $hs string to print on document header
3321	 * @param array $tc RGB array color for text.
3322	 * @param array $lc RGB array color for line.
3323	 * @public
3324	 */
3325	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3326		$this->header_logo = $ln;
3327		$this->header_logo_width = $lw;
3328		$this->header_title = $ht;
3329		$this->header_string = $hs;
3330		$this->header_text_color = $tc;
3331		$this->header_line_color = $lc;
3332	}
3333
3334	/**
3335	 * Set footer data.
3336	 * @param array $tc RGB array color for text.
3337	 * @param array $lc RGB array color for line.
3338	 * @public
3339	 */
3340	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3341		$this->footer_text_color = $tc;
3342		$this->footer_line_color = $lc;
3343	}
3344
3345	/**
3346	 * Returns header data:
3347	 * <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>
3348	 * @return array<string,mixed>
3349	 * @public
3350	 * @since 4.0.012 (2008-07-24)
3351	 */
3352	public function getHeaderData() {
3353		$ret = array();
3354		$ret['logo'] = $this->header_logo;
3355		$ret['logo_width'] = $this->header_logo_width;
3356		$ret['title'] = $this->header_title;
3357		$ret['string'] = $this->header_string;
3358		$ret['text_color'] = $this->header_text_color;
3359		$ret['line_color'] = $this->header_line_color;
3360		return $ret;
3361	}
3362
3363	/**
3364	 * Set header margin.
3365	 * (minimum distance between header and top page margin)
3366	 * @param int $hm distance in user units
3367	 * @public
3368	 */
3369	public function setHeaderMargin($hm=10) {
3370		$this->header_margin = $hm;
3371	}
3372
3373	/**
3374	 * Returns header margin in user units.
3375	 * @return float
3376	 * @since 4.0.012 (2008-07-24)
3377	 * @public
3378	 */
3379	public function getHeaderMargin() {
3380		return $this->header_margin;
3381	}
3382
3383	/**
3384	 * Set footer margin.
3385	 * (minimum distance between footer and bottom page margin)
3386	 * @param int $fm distance in user units
3387	 * @public
3388	 */
3389	public function setFooterMargin($fm=10) {
3390		$this->footer_margin = $fm;
3391	}
3392
3393	/**
3394	 * Returns footer margin in user units.
3395	 * @return float
3396	 * @since 4.0.012 (2008-07-24)
3397	 * @public
3398	 */
3399	public function getFooterMargin() {
3400		return $this->footer_margin;
3401	}
3402	/**
3403	 * Set a flag to print page header.
3404	 * @param boolean $val set to true to print the page header (default), false otherwise.
3405	 * @public
3406	 */
3407	public function setPrintHeader($val=true) {
3408		$this->print_header = $val ? true : false;
3409	}
3410
3411	/**
3412	 * Set a flag to print page footer.
3413	 * @param boolean $val set to true to print the page footer (default), false otherwise.
3414	 * @public
3415	 */
3416	public function setPrintFooter($val=true) {
3417		$this->print_footer = $val ? true : false;
3418	}
3419
3420	/**
3421	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3422	 * @return float
3423	 * @public
3424	 */
3425	public function getImageRBX() {
3426		return $this->img_rb_x;
3427	}
3428
3429	/**
3430	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3431	 * @return float
3432	 * @public
3433	 */
3434	public function getImageRBY() {
3435		return $this->img_rb_y;
3436	}
3437
3438	/**
3439	 * Reset the xobject template used by Header() method.
3440	 * @public
3441	 */
3442	public function resetHeaderTemplate() {
3443		$this->header_xobjid = false;
3444	}
3445
3446	/**
3447	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3448	 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3449	 * @public
3450	 */
3451	public function setHeaderTemplateAutoreset($val=true) {
3452		$this->header_xobj_autoreset = $val ? true : false;
3453	}
3454
3455	/**
3456	 * This method is used to render the page header.
3457	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3458	 * @public
3459	 */
3460	public function Header() {
3461		if ($this->header_xobjid === false) {
3462			// start a new XObject Template
3463			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3464			$headerfont = $this->getHeaderFont();
3465			$headerdata = $this->getHeaderData();
3466			$this->y = $this->header_margin;
3467			if ($this->rtl) {
3468				$this->x = $this->w - $this->original_rMargin;
3469			} else {
3470				$this->x = $this->original_lMargin;
3471			}
3472			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3473				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3474				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3475					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3476				} elseif ($imgtype == 'svg') {
3477					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3478				} else {
3479					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3480				}
3481				$imgy = $this->getImageRBY();
3482			} else {
3483				$imgy = $this->y;
3484			}
3485			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3486			// set starting margin for text data cell
3487			if ($this->getRTL()) {
3488				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3489			} else {
3490				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3491			}
3492			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3493			$this->SetTextColorArray($this->header_text_color);
3494			// header title
3495			$this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3496			$this->SetX($header_x);
3497			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3498			// header string
3499			$this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3500			$this->SetX($header_x);
3501			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3502			// print an ending header line
3503			$this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3504			$this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3505			if ($this->rtl) {
3506				$this->SetX($this->original_rMargin);
3507			} else {
3508				$this->SetX($this->original_lMargin);
3509			}
3510			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3511			$this->endTemplate();
3512		}
3513		// print header template
3514		$x = 0;
3515		$dx = 0;
3516		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3517			// adjust margins for booklet mode
3518			$dx = ($this->original_lMargin - $this->original_rMargin);
3519		}
3520		if ($this->rtl) {
3521			$x = $this->w + $dx;
3522		} else {
3523			$x = 0 + $dx;
3524		}
3525		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3526		if ($this->header_xobj_autoreset) {
3527			// reset header xobject template at each page
3528			$this->header_xobjid = false;
3529		}
3530	}
3531
3532	/**
3533	 * This method is used to render the page footer.
3534	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3535	 * @public
3536	 */
3537	public function Footer() {
3538		$cur_y = $this->y;
3539		$this->SetTextColorArray($this->footer_text_color);
3540		//set style for cell border
3541		$line_width = (0.85 / $this->k);
3542		$this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3543		//print document barcode
3544		$barcode = $this->getBarcode();
3545		if (!empty($barcode)) {
3546			$this->Ln($line_width);
3547			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3548			$style = array(
3549				'position' => $this->rtl?'R':'L',
3550				'align' => $this->rtl?'R':'L',
3551				'stretch' => false,
3552				'fitwidth' => true,
3553				'cellfitalign' => '',
3554				'border' => false,
3555				'padding' => 0,
3556				'fgcolor' => array(0,0,0),
3557				'bgcolor' => false,
3558				'text' => false
3559			);
3560			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3561		}
3562		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3563		if (empty($this->pagegroups)) {
3564			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3565		} else {
3566			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3567		}
3568		$this->SetY($cur_y);
3569		//Print page number
3570		if ($this->getRTL()) {
3571			$this->SetX($this->original_rMargin);
3572			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3573		} else {
3574			$this->SetX($this->original_lMargin);
3575			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3576		}
3577	}
3578
3579	/**
3580	 * This method is used to render the page header.
3581	 * @protected
3582	 * @since 4.0.012 (2008-07-24)
3583	 */
3584	protected function setHeader() {
3585		if (!$this->print_header OR ($this->state != 2)) {
3586			return;
3587		}
3588		$this->InHeader = true;
3589		$this->setGraphicVars($this->default_graphic_vars);
3590		$temp_thead = $this->thead;
3591		$temp_theadMargins = $this->theadMargins;
3592		$lasth = $this->lasth;
3593		$newline = $this->newline;
3594		$this->_outSaveGraphicsState();
3595		$this->rMargin = $this->original_rMargin;
3596		$this->lMargin = $this->original_lMargin;
3597		$this->SetCellPadding(0);
3598		//set current position
3599		if ($this->rtl) {
3600			$this->SetXY($this->original_rMargin, $this->header_margin);
3601		} else {
3602			$this->SetXY($this->original_lMargin, $this->header_margin);
3603		}
3604		$this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3605		$this->Header();
3606		//restore position
3607		if ($this->rtl) {
3608			$this->SetXY($this->original_rMargin, $this->tMargin);
3609		} else {
3610			$this->SetXY($this->original_lMargin, $this->tMargin);
3611		}
3612		$this->_outRestoreGraphicsState();
3613		$this->lasth = $lasth;
3614		$this->thead = $temp_thead;
3615		$this->theadMargins = $temp_theadMargins;
3616		$this->newline = $newline;
3617		$this->InHeader = false;
3618	}
3619
3620	/**
3621	 * This method is used to render the page footer.
3622	 * @protected
3623	 * @since 4.0.012 (2008-07-24)
3624	 */
3625	protected function setFooter() {
3626		if ($this->state != 2) {
3627			return;
3628		}
3629		$this->InFooter = true;
3630		// save current graphic settings
3631		$gvars = $this->getGraphicVars();
3632		// mark this point
3633		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3634		$this->_out("\n");
3635		if ($this->print_footer) {
3636			$this->setGraphicVars($this->default_graphic_vars);
3637			$this->current_column = 0;
3638			$this->num_columns = 1;
3639			$temp_thead = $this->thead;
3640			$temp_theadMargins = $this->theadMargins;
3641			$lasth = $this->lasth;
3642			$this->_outSaveGraphicsState();
3643			$this->rMargin = $this->original_rMargin;
3644			$this->lMargin = $this->original_lMargin;
3645			$this->SetCellPadding(0);
3646			//set current position
3647			$footer_y = $this->h - $this->footer_margin;
3648			if ($this->rtl) {
3649				$this->SetXY($this->original_rMargin, $footer_y);
3650			} else {
3651				$this->SetXY($this->original_lMargin, $footer_y);
3652			}
3653			$this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3654			$this->Footer();
3655			//restore position
3656			if ($this->rtl) {
3657				$this->SetXY($this->original_rMargin, $this->tMargin);
3658			} else {
3659				$this->SetXY($this->original_lMargin, $this->tMargin);
3660			}
3661			$this->_outRestoreGraphicsState();
3662			$this->lasth = $lasth;
3663			$this->thead = $temp_thead;
3664			$this->theadMargins = $temp_theadMargins;
3665		}
3666		// restore graphic settings
3667		$this->setGraphicVars($gvars);
3668		$this->current_column = $gvars['current_column'];
3669		$this->num_columns = $gvars['num_columns'];
3670		// calculate footer length
3671		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3672		$this->InFooter = false;
3673	}
3674
3675	/**
3676	 * Check if we are on the page body (excluding page header and footer).
3677	 * @return true if we are not in page header nor in page footer, false otherwise.
3678	 * @protected
3679	 * @since 5.9.091 (2011-06-15)
3680	 */
3681	protected function inPageBody() {
3682		return (($this->InHeader === false) AND ($this->InFooter === false));
3683	}
3684
3685	/**
3686	 * This method is used to render the table header on new page (if any).
3687	 * @protected
3688	 * @since 4.5.030 (2009-03-25)
3689	 */
3690	protected function setTableHeader() {
3691		if ($this->num_columns > 1) {
3692			// multi column mode
3693			return;
3694		}
3695		if (isset($this->theadMargins['top'])) {
3696			// restore the original top-margin
3697			$this->tMargin = $this->theadMargins['top'];
3698			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3699			$this->y = $this->tMargin;
3700		}
3701		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3702			// set margins
3703			$prev_lMargin = $this->lMargin;
3704			$prev_rMargin = $this->rMargin;
3705			$prev_cell_padding = $this->cell_padding;
3706			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3707			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3708			$this->cell_padding = $this->theadMargins['cell_padding'];
3709			if ($this->rtl) {
3710				$this->x = $this->w - $this->rMargin;
3711			} else {
3712				$this->x = $this->lMargin;
3713			}
3714			// account for special "cell" mode
3715			if ($this->theadMargins['cell']) {
3716				if ($this->rtl) {
3717					$this->x -= $this->cell_padding['R'];
3718				} else {
3719					$this->x += $this->cell_padding['L'];
3720				}
3721			}
3722			$gvars = $this->getGraphicVars();
3723			if (!empty($this->theadMargins['gvars'])) {
3724				// set the correct graphic style
3725				$this->setGraphicVars($this->theadMargins['gvars']);
3726				$this->rMargin = $gvars['rMargin'];
3727				$this->lMargin = $gvars['lMargin'];
3728			}
3729			// print table header
3730			$this->writeHTML($this->thead, false, false, false, false, '');
3731			$this->setGraphicVars($gvars);
3732			// set new top margin to skip the table headers
3733			if (!isset($this->theadMargins['top'])) {
3734				$this->theadMargins['top'] = $this->tMargin;
3735			}
3736			// store end of header position
3737			if (!isset($this->columns[0]['th'])) {
3738				$this->columns[0]['th'] = array();
3739			}
3740			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3741			$this->tMargin = $this->y;
3742			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3743			$this->lasth = 0;
3744			$this->lMargin = $prev_lMargin;
3745			$this->rMargin = $prev_rMargin;
3746			$this->cell_padding = $prev_cell_padding;
3747		}
3748	}
3749
3750	/**
3751	 * Returns the current page number.
3752	 * @return int page number
3753	 * @public
3754	 * @since 1.0
3755	 * @see getAliasNbPages()
3756	 */
3757	public function PageNo() {
3758		return $this->page;
3759	}
3760
3761	/**
3762	 * Returns the array of spot colors.
3763	 * @return array Spot colors array.
3764	 * @public
3765	 * @since 6.0.038 (2013-09-30)
3766	 */
3767	public function getAllSpotColors() {
3768		return $this->spot_colors;
3769	}
3770
3771	/**
3772	 * Defines a new spot color.
3773	 * It can be expressed in RGB components or gray scale.
3774	 * The method can be called before the first page is created and the value is retained from page to page.
3775	 * @param string $name Full name of the spot color.
3776	 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3777	 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3778	 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3779	 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3780	 * @public
3781	 * @since 4.0.024 (2008-09-12)
3782	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3783	 */
3784	public function AddSpotColor($name, $c, $m, $y, $k) {
3785		if (!isset($this->spot_colors[$name])) {
3786			$i = (1 + count($this->spot_colors));
3787			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3788		}
3789	}
3790
3791	/**
3792	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3793	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3794	 * @param string $name Name of the spot color.
3795	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3796	 * @return string PDF color command.
3797	 * @public
3798	 * @since 5.9.125 (2011-10-03)
3799	 */
3800	public function setSpotColor($type, $name, $tint=100) {
3801		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3802		if ($spotcolor === false) {
3803			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3804		}
3805		$tint = (max(0, min(100, $tint)) / 100);
3806		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3807		switch ($type) {
3808			case 'draw': {
3809				$pdfcolor .= sprintf('CS %F SCN', $tint);
3810				$this->DrawColor = $pdfcolor;
3811				$this->strokecolor = $spotcolor;
3812				break;
3813			}
3814			case 'fill': {
3815				$pdfcolor .= sprintf('cs %F scn', $tint);
3816				$this->FillColor = $pdfcolor;
3817				$this->bgcolor = $spotcolor;
3818				break;
3819			}
3820			case 'text': {
3821				$pdfcolor .= sprintf('cs %F scn', $tint);
3822				$this->TextColor = $pdfcolor;
3823				$this->fgcolor = $spotcolor;
3824				break;
3825			}
3826		}
3827		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3828		if ($this->state == 2) {
3829			$this->_out($pdfcolor);
3830		}
3831		if ($this->inxobj) {
3832			// we are inside an XObject template
3833			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3834		}
3835		return $pdfcolor;
3836	}
3837
3838	/**
3839	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3840	 * @param string $name Name of the spot color.
3841	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3842	 * @public
3843	 * @since 4.0.024 (2008-09-12)
3844	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3845	 */
3846	public function SetDrawSpotColor($name, $tint=100) {
3847		$this->setSpotColor('draw', $name, $tint);
3848	}
3849
3850	/**
3851	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3852	 * @param string $name Name of the spot color.
3853	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3854	 * @public
3855	 * @since 4.0.024 (2008-09-12)
3856	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3857	 */
3858	public function SetFillSpotColor($name, $tint=100) {
3859		$this->setSpotColor('fill', $name, $tint);
3860	}
3861
3862	/**
3863	 * Defines the spot color used for text.
3864	 * @param string $name Name of the spot color.
3865	 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3866	 * @public
3867	 * @since 4.0.024 (2008-09-12)
3868	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3869	 */
3870	public function SetTextSpotColor($name, $tint=100) {
3871		$this->setSpotColor('text', $name, $tint);
3872	}
3873
3874	/**
3875	 * Set the color array for the specified type ('draw', 'fill', 'text').
3876	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3877	 * The method can be called before the first page is created and the value is retained from page to page.
3878	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3879	 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3880	 * @param boolean $ret If true do not send the PDF command.
3881	 * @return string The PDF command or empty string.
3882	 * @public
3883	 * @since 3.1.000 (2008-06-11)
3884	 */
3885	public function setColorArray($type, $color, $ret=false) {
3886		if (is_array($color)) {
3887			$color = array_values($color);
3888			// component: grey, RGB red or CMYK cyan
3889			$c = isset($color[0]) ? $color[0] : -1;
3890			// component: RGB green or CMYK magenta
3891			$m = isset($color[1]) ? $color[1] : -1;
3892			// component: RGB blue or CMYK yellow
3893			$y = isset($color[2]) ? $color[2] : -1;
3894			// component: CMYK black
3895			$k = isset($color[3]) ? $color[3] : -1;
3896			// color name
3897			$name = isset($color[4]) ? $color[4] : '';
3898			if ($c >= 0) {
3899				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3900			}
3901		}
3902		return '';
3903	}
3904
3905	/**
3906	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3907	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3908	 * The method can be called before the first page is created and the value is retained from page to page.
3909	 * @param array $color Array of colors (1, 3 or 4 values).
3910	 * @param boolean $ret If true do not send the PDF command.
3911	 * @return string the PDF command
3912	 * @public
3913	 * @since 3.1.000 (2008-06-11)
3914	 * @see SetDrawColor()
3915	 */
3916	public function SetDrawColorArray($color, $ret=false) {
3917		return $this->setColorArray('draw', $color, $ret);
3918	}
3919
3920	/**
3921	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3922	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3923	 * The method can be called before the first page is created and the value is retained from page to page.
3924	 * @param array $color Array of colors (1, 3 or 4 values).
3925	 * @param boolean $ret If true do not send the PDF command.
3926	 * @public
3927	 * @since 3.1.000 (2008-6-11)
3928	 * @see SetFillColor()
3929	 */
3930	public function SetFillColorArray($color, $ret=false) {
3931		return $this->setColorArray('fill', $color, $ret);
3932	}
3933
3934	/**
3935	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3936	 * The method can be called before the first page is created and the value is retained from page to page.
3937	 * @param array $color Array of colors (1, 3 or 4 values).
3938	 * @param boolean $ret If true do not send the PDF command.
3939	 * @public
3940	 * @since 3.1.000 (2008-6-11)
3941	 * @see SetFillColor()
3942	 */
3943	public function SetTextColorArray($color, $ret=false) {
3944		return $this->setColorArray('text', $color, $ret);
3945	}
3946
3947	/**
3948	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3949	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3950	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3951	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3952	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3953	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3954	 * @param boolean $ret If true do not send the command.
3955	 * @param string $name spot color name (if any)
3956	 * @return string The PDF command or empty string.
3957	 * @public
3958	 * @since 5.9.125 (2011-10-03)
3959	 */
3960	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3961		// set default values
3962		if (!is_numeric($col1)) {
3963			$col1 = 0;
3964		}
3965		if (!is_numeric($col2)) {
3966			$col2 = -1;
3967		}
3968		if (!is_numeric($col3)) {
3969			$col3 = -1;
3970		}
3971		if (!is_numeric($col4)) {
3972			$col4 = -1;
3973		}
3974		// set color by case
3975		$suffix = '';
3976		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3977			// Grey scale
3978			$col1 = max(0, min(255, $col1));
3979			$intcolor = array('G' => $col1);
3980			$pdfcolor = sprintf('%F ', ($col1 / 255));
3981			$suffix = 'g';
3982		} elseif ($col4 == -1) {
3983			// RGB
3984			$col1 = max(0, min(255, $col1));
3985			$col2 = max(0, min(255, $col2));
3986			$col3 = max(0, min(255, $col3));
3987			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3988			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3989			$suffix = 'rg';
3990		} else {
3991			$col1 = max(0, min(100, $col1));
3992			$col2 = max(0, min(100, $col2));
3993			$col3 = max(0, min(100, $col3));
3994			$col4 = max(0, min(100, $col4));
3995			if (empty($name)) {
3996				// CMYK
3997				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3998				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3999				$suffix = 'k';
4000			} else {
4001				// SPOT COLOR
4002				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4003				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4004				$pdfcolor = $this->setSpotColor($type, $name, 100);
4005			}
4006		}
4007		switch ($type) {
4008			case 'draw': {
4009				$pdfcolor .= strtoupper($suffix);
4010				$this->DrawColor = $pdfcolor;
4011				$this->strokecolor = $intcolor;
4012				break;
4013			}
4014			case 'fill': {
4015				$pdfcolor .= $suffix;
4016				$this->FillColor = $pdfcolor;
4017				$this->bgcolor = $intcolor;
4018				break;
4019			}
4020			case 'text': {
4021				$pdfcolor .= $suffix;
4022				$this->TextColor = $pdfcolor;
4023				$this->fgcolor = $intcolor;
4024				break;
4025			}
4026		}
4027		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4028		if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4029			if (!$ret) {
4030				$this->_out($pdfcolor);
4031			}
4032			return $pdfcolor;
4033		}
4034		return '';
4035	}
4036
4037	/**
4038	 * 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.
4039	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4040	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4041	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4042	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4043	 * @param boolean $ret If true do not send the command.
4044	 * @param string $name spot color name (if any)
4045	 * @return string the PDF command
4046	 * @public
4047	 * @since 1.3
4048	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4049	 */
4050	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4051		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4052	}
4053
4054	/**
4055	 * 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.
4056	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4057	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4058	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4059	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4060	 * @param boolean $ret If true do not send the command.
4061	 * @param string $name Spot color name (if any).
4062	 * @return string The PDF command.
4063	 * @public
4064	 * @since 1.3
4065	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4066	 */
4067	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4068		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4069	}
4070
4071	/**
4072	 * 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.
4073	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4074	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4075	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4076	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4077	 * @param boolean $ret If true do not send the command.
4078	 * @param string $name Spot color name (if any).
4079	 * @return string Empty string.
4080	 * @public
4081	 * @since 1.3
4082	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4083	 */
4084	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4085		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4086	}
4087
4088	/**
4089	 * Returns the length of a string in user unit. A font must be selected.<br>
4090	 * @param string $s The string whose length is to be computed
4091	 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4092	 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4093	 * @param float $fontsize Font size in points. The default value is the current size.
4094	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4095	 * @return float[]|float total string length or array of characted widths
4096	 * @author Nicola Asuni
4097	 * @public
4098	 * @since 1.2
4099	 */
4100	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4101		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);
4102	}
4103
4104	/**
4105	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4106	 * @param array $sa The array of chars whose total length is to be computed
4107	 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4108	 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4109	 * @param float $fontsize Font size in points. The default value is the current size.
4110	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4111	 * @return float[]|float total string length or array of characted widths
4112	 * @author Nicola Asuni
4113	 * @public
4114	 * @since 2.4.000 (2008-03-06)
4115	 */
4116	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4117		// store current values
4118		if (!TCPDF_STATIC::empty_string($fontname)) {
4119			$prev_FontFamily = $this->FontFamily;
4120			$prev_FontStyle = $this->FontStyle;
4121			$prev_FontSizePt = $this->FontSizePt;
4122			$this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4123		}
4124		// convert UTF-8 array to Latin1 if required
4125		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4126			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4127		}
4128		$w = 0; // total width
4129		$wa = array(); // array of characters widths
4130		foreach ($sa as $ck => $char) {
4131			// character width
4132			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4133			$wa[] = $cw;
4134			$w += $cw;
4135		}
4136		// restore previous values
4137		if (!TCPDF_STATIC::empty_string($fontname)) {
4138			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4139		}
4140		if ($getarray) {
4141			return $wa;
4142		}
4143		return $w;
4144	}
4145
4146	/**
4147	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4148	 * @param int $char The char code whose length is to be returned
4149	 * @param boolean $notlast If false ignore the font-spacing.
4150	 * @return float char width
4151	 * @author Nicola Asuni
4152	 * @public
4153	 * @since 2.4.000 (2008-03-06)
4154	 */
4155	public function GetCharWidth($char, $notlast=true) {
4156		// get raw width
4157		$chw = $this->getRawCharWidth($char);
4158		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4159			// increase/decrease font spacing
4160			$chw += $this->font_spacing;
4161		}
4162		if ($this->font_stretching != 100) {
4163			// fixed stretching mode
4164			$chw *= ($this->font_stretching / 100);
4165		}
4166		return $chw;
4167	}
4168
4169	/**
4170	 * Returns the length of the char in user unit for the current font.
4171	 * @param int $char The char code whose length is to be returned
4172	 * @return float char width
4173	 * @author Nicola Asuni
4174	 * @public
4175	 * @since 5.9.000 (2010-09-28)
4176	 */
4177	public function getRawCharWidth($char) {
4178		if ($char == 173) {
4179			// SHY character will not be printed
4180			return (0);
4181		}
4182		if (isset($this->CurrentFont['cw'][intval($char)])) {
4183			$w = $this->CurrentFont['cw'][intval($char)];
4184		} elseif (isset($this->CurrentFont['dw'])) {
4185			// default width
4186			$w = $this->CurrentFont['dw'];
4187		} elseif (isset($this->CurrentFont['cw'][32])) {
4188			// default width
4189			$w = $this->CurrentFont['cw'][32];
4190		} else {
4191			$w = 600;
4192		}
4193		return $this->getAbsFontMeasure($w);
4194	}
4195
4196	/**
4197	 * Returns the numbero of characters in a string.
4198	 * @param string $s The input string.
4199	 * @return int number of characters
4200	 * @public
4201	 * @since 2.0.0001 (2008-01-07)
4202	 */
4203	public function GetNumChars($s) {
4204		if ($this->isUnicodeFont()) {
4205			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4206		}
4207		return strlen($s);
4208	}
4209
4210	/**
4211	 * Fill the list of available fonts ($this->fontlist).
4212	 * @protected
4213	 * @since 4.0.013 (2008-07-28)
4214	 */
4215	protected function getFontsList() {
4216		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4217			while (($file = readdir($fontsdir)) !== false) {
4218				if (substr($file, -4) == '.php') {
4219					array_push($this->fontlist, strtolower(basename($file, '.php')));
4220				}
4221			}
4222			closedir($fontsdir);
4223		}
4224	}
4225
4226	/**
4227	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4228	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4229	 * 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.
4230	 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4231	 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4232	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4233	 * @return array|false array containing the font data, or false in case of error.
4234	 * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4235	 * @public
4236	 * @since 1.5
4237	 * @see SetFont(), setFontSubsetting()
4238	 */
4239	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4240		if ($subset === 'default') {
4241			$subset = $this->font_subsetting;
4242		}
4243		if ($this->pdfa_mode) {
4244			$subset = false;
4245		}
4246		if (TCPDF_STATIC::empty_string($family)) {
4247			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4248				$family = $this->FontFamily;
4249			} else {
4250				$this->Error('Empty font family');
4251			}
4252		}
4253		// move embedded styles on $style
4254		if (substr($family, -1) == 'I') {
4255			$style .= 'I';
4256			$family = substr($family, 0, -1);
4257		}
4258		if (substr($family, -1) == 'B') {
4259			$style .= 'B';
4260			$family = substr($family, 0, -1);
4261		}
4262		// normalize family name
4263		$family = strtolower($family);
4264		if ((!$this->isunicode) AND ($family == 'arial')) {
4265			$family = 'helvetica';
4266		}
4267		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4268			$style = '';
4269		}
4270		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4271			// all fonts must be embedded
4272			$family = 'pdfa'.$family;
4273		}
4274		$tempstyle = strtoupper($style);
4275		$style = '';
4276		// underline
4277		if (strpos($tempstyle, 'U') !== false) {
4278			$this->underline = true;
4279		} else {
4280			$this->underline = false;
4281		}
4282		// line-through (deleted)
4283		if (strpos($tempstyle, 'D') !== false) {
4284			$this->linethrough = true;
4285		} else {
4286			$this->linethrough = false;
4287		}
4288		// overline
4289		if (strpos($tempstyle, 'O') !== false) {
4290			$this->overline = true;
4291		} else {
4292			$this->overline = false;
4293		}
4294		// bold
4295		if (strpos($tempstyle, 'B') !== false) {
4296			$style .= 'B';
4297		}
4298		// oblique
4299		if (strpos($tempstyle, 'I') !== false) {
4300			$style .= 'I';
4301		}
4302		$bistyle = $style;
4303		$fontkey = $family.$style;
4304		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4305		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4306		// check if the font has been already added
4307		$fb = $this->getFontBuffer($fontkey);
4308		if ($fb !== false) {
4309			if ($this->inxobj) {
4310				// we are inside an XObject template
4311				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4312			}
4313			return $fontdata;
4314		}
4315		// get specified font directory (if any)
4316		$fontdir = false;
4317		if (!TCPDF_STATIC::empty_string($fontfile)) {
4318			$fontdir = dirname($fontfile);
4319			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4320				$fontdir = '';
4321			} else {
4322				$fontdir .= '/';
4323			}
4324		}
4325		// true when the font style variation is missing
4326		$missing_style = false;
4327		// search and include font file
4328		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4329			// build a standard filenames for specified font
4330			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4331			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4332			if (TCPDF_STATIC::empty_string($fontfile)) {
4333				$missing_style = true;
4334				// try to remove the style part
4335				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4336				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4337			}
4338		}
4339		// include font file
4340		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4341			include($fontfile);
4342		} else {
4343			$this->Error('Could not include font definition file: '.$family.'');
4344		}
4345		// check font parameters
4346		if ((!isset($type)) OR (!isset($cw))) {
4347			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4348		}
4349		// SET default parameters
4350		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4351			$file = '';
4352		}
4353		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4354			$enc = '';
4355		}
4356		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4357			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4358			$cidinfo['uni2cid'] = array();
4359		}
4360		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4361			$ctg = '';
4362		}
4363		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4364			$desc = array();
4365		}
4366		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4367			$up = -100;
4368		}
4369		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4370			$ut = 50;
4371		}
4372		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4373			$cw = array();
4374		}
4375		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4376			// set default width
4377			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4378				$dw = $desc['MissingWidth'];
4379			} elseif (isset($cw[32])) {
4380				$dw = $cw[32];
4381			} else {
4382				$dw = 600;
4383			}
4384		}
4385		++$this->numfonts;
4386		if ($type == 'core') {
4387			$name = $this->CoreFonts[$fontkey];
4388			$subset = false;
4389		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4390			$subset = false;
4391		} elseif ($type == 'TrueTypeUnicode') {
4392			$enc = 'Identity-H';
4393		} elseif ($type == 'cidfont0') {
4394			if ($this->pdfa_mode) {
4395				$this->Error('All fonts must be embedded in PDF/A mode!');
4396			}
4397		} else {
4398			$this->Error('Unknow font type: '.$type.'');
4399		}
4400		// set name if unset
4401		if (!isset($name) OR empty($name)) {
4402			$name = $fontkey;
4403		}
4404		// create artificial font style variations if missing (only works with non-embedded fonts)
4405		if (($type != 'core') AND $missing_style) {
4406			// style variations
4407			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4408			$name .= $styles[$bistyle];
4409			// artificial bold
4410			if (strpos($bistyle, 'B') !== false) {
4411				if (isset($desc['StemV'])) {
4412					// from normal to bold
4413					$desc['StemV'] = round($desc['StemV'] * 1.75);
4414				} else {
4415					// bold
4416					$desc['StemV'] = 123;
4417				}
4418			}
4419			// artificial italic
4420			if (strpos($bistyle, 'I') !== false) {
4421				if (isset($desc['ItalicAngle'])) {
4422					$desc['ItalicAngle'] -= 11;
4423				} else {
4424					$desc['ItalicAngle'] = -11;
4425				}
4426				if (isset($desc['Flags'])) {
4427					$desc['Flags'] |= 64; //bit 7
4428				} else {
4429					$desc['Flags'] = 64;
4430				}
4431			}
4432		}
4433		// check if the array of characters bounding boxes is defined
4434		if (!isset($cbbox)) {
4435			$cbbox = array();
4436		}
4437		// initialize subsetchars
4438		$subsetchars = array_fill(0, 255, true);
4439		$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));
4440		if ($this->inxobj) {
4441			// we are inside an XObject template
4442			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4443		}
4444		if (isset($diff) AND (!empty($diff))) {
4445			//Search existing encodings
4446			$d = 0;
4447			$nb = count($this->diffs);
4448			for ($i=1; $i <= $nb; ++$i) {
4449				if ($this->diffs[$i] == $diff) {
4450					$d = $i;
4451					break;
4452				}
4453			}
4454			if ($d == 0) {
4455				$d = $nb + 1;
4456				$this->diffs[$d] = $diff;
4457			}
4458			$this->setFontSubBuffer($fontkey, 'diff', $d);
4459		}
4460		if (!TCPDF_STATIC::empty_string($file)) {
4461			if (!isset($this->FontFiles[$file])) {
4462				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4463					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4464				} elseif ($type != 'core') {
4465					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4466				}
4467			} else {
4468				// update fontkeys that are sharing this font file
4469				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4470				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4471					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4472				}
4473			}
4474		}
4475		return $fontdata;
4476	}
4477
4478	/**
4479	 * Sets the font used to print character strings.
4480	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4481	 * The method can be called before the first page is created and the font is retained from page to page.
4482	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4483	 * 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 />
4484	 * @param string $family Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4485	 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4486	 * @param float $size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4487	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4488	 * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4489	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4490	 * @author Nicola Asuni
4491	 * @public
4492	 * @since 1.0
4493	 * @see AddFont(), SetFontSize()
4494	 */
4495	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4496		//Select a font; size given in points
4497		if ($size === null) {
4498			$size = $this->FontSizePt;
4499		}
4500		if ($size < 0) {
4501			$size = 0;
4502		}
4503		// try to add font (if not already added)
4504		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4505		$this->FontFamily = $fontdata['family'];
4506		$this->FontStyle = $fontdata['style'];
4507		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4508			// save subset chars of the previous font
4509			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4510		}
4511		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4512		$this->SetFontSize($size, $out);
4513	}
4514
4515	/**
4516	 * Defines the size of the current font.
4517	 * @param float $size The font size in points.
4518	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4519	 * @public
4520	 * @since 1.0
4521	 * @see SetFont()
4522	 */
4523	public function SetFontSize($size, $out=true) {
4524		$size = (float)$size;
4525		// font size in points
4526		$this->FontSizePt = $size;
4527		// font size in user units
4528		$this->FontSize = $size / $this->k;
4529		// calculate some font metrics
4530		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4531			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4532			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4533		} else {
4534			$font_height = $size * 1.219;
4535		}
4536		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4537			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4538		}
4539		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4540			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4541		}
4542		if (!isset($font_ascent) AND !isset($font_descent)) {
4543			// core font
4544			$font_ascent = 0.76 * $font_height;
4545			$font_descent = $font_height - $font_ascent;
4546		} elseif (!isset($font_descent)) {
4547			$font_descent = $font_height - $font_ascent;
4548		} elseif (!isset($font_ascent)) {
4549			$font_ascent = $font_height - $font_descent;
4550		}
4551		$this->FontAscent = ($font_ascent / $this->k);
4552		$this->FontDescent = ($font_descent / $this->k);
4553		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4554			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4555		}
4556	}
4557
4558	/**
4559	 * Returns the bounding box of the current font in user units.
4560	 * @return array
4561	 * @public
4562	 * @since 5.9.152 (2012-03-23)
4563	 */
4564	public function getFontBBox() {
4565		$fbbox = array();
4566		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4567			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4568			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4569		} else {
4570			// Find max width
4571			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4572				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4573			} else {
4574				$maxw = 0;
4575				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4576					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4577				}
4578				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4579					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4580				}
4581				if (isset($this->CurrentFont['dw'])) {
4582					$maxw = max($maxw, $this->CurrentFont['dw']);
4583				}
4584				foreach ($this->CurrentFont['cw'] as $char => $w) {
4585					$maxw = max($maxw, $w);
4586				}
4587				if ($maxw == 0) {
4588					$maxw = 600;
4589				}
4590				$maxw = $this->getAbsFontMeasure($maxw);
4591			}
4592			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4593		}
4594		return $fbbox;
4595	}
4596
4597	/**
4598	 * Convert a relative font measure into absolute value.
4599	 * @param int $s Font measure.
4600	 * @return float Absolute measure.
4601	 * @since 5.9.186 (2012-09-13)
4602	 */
4603	public function getAbsFontMeasure($s) {
4604		return ($s * $this->FontSize / 1000);
4605	}
4606
4607	/**
4608	 * Returns the glyph bounding box of the specified character in the current font in user units.
4609	 * @param int $char Input character code.
4610	 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4611	 * @since 5.9.186 (2012-09-13)
4612	 */
4613	public function getCharBBox($char) {
4614		$c = intval($char);
4615		if (isset($this->CurrentFont['cw'][$c])) {
4616			// glyph is defined ... use zero width & height for glyphs without outlines
4617			$result = array(0,0,0,0);
4618			if (isset($this->CurrentFont['cbbox'][$c])) {
4619				$result = $this->CurrentFont['cbbox'][$c];
4620			}
4621			return array_map(array($this,'getAbsFontMeasure'), $result);
4622		}
4623		return false;
4624	}
4625
4626	/**
4627	 * Return the font descent value
4628	 * @param string $font font name
4629	 * @param string $style font style
4630	 * @param float $size The size (in points)
4631	 * @return int font descent
4632	 * @public
4633	 * @author Nicola Asuni
4634	 * @since 4.9.003 (2010-03-30)
4635	 */
4636	public function getFontDescent($font, $style='', $size=0) {
4637		$fontdata = $this->AddFont($font, $style);
4638		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4639		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4640			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4641		} else {
4642			$descent = (1.219 * 0.24 * $size);
4643		}
4644		return ($descent / $this->k);
4645	}
4646
4647	/**
4648	 * Return the font ascent value.
4649	 * @param string $font font name
4650	 * @param string $style font style
4651	 * @param float $size The size (in points)
4652	 * @return int font ascent
4653	 * @public
4654	 * @author Nicola Asuni
4655	 * @since 4.9.003 (2010-03-30)
4656	 */
4657	public function getFontAscent($font, $style='', $size=0) {
4658		$fontdata = $this->AddFont($font, $style);
4659		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4660		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4661			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4662		} else {
4663			$ascent = 1.219 * 0.76 * $size;
4664		}
4665		return ($ascent / $this->k);
4666	}
4667
4668	/**
4669	 * Return true in the character is present in the specified font.
4670	 * @param mixed $char Character to check (integer value or string)
4671	 * @param string $font Font name (family name).
4672	 * @param string $style Font style.
4673	 * @return bool true if the char is defined, false otherwise.
4674	 * @public
4675	 * @since 5.9.153 (2012-03-28)
4676	 */
4677	public function isCharDefined($char, $font='', $style='') {
4678		if (is_string($char)) {
4679			// get character code
4680			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4681			$char = $char[0];
4682		}
4683		if (TCPDF_STATIC::empty_string($font)) {
4684			if (TCPDF_STATIC::empty_string($style)) {
4685				return (isset($this->CurrentFont['cw'][intval($char)]));
4686			}
4687			$font = $this->FontFamily;
4688		}
4689		$fontdata = $this->AddFont($font, $style);
4690		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4691		return (isset($fontinfo['cw'][intval($char)]));
4692	}
4693
4694	/**
4695	 * Replace missing font characters on selected font with specified substitutions.
4696	 * @param string $text Text to process.
4697	 * @param string $font Font name (family name).
4698	 * @param string $style Font style.
4699	 * @param array $subs Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4700	 * @return string Processed text.
4701	 * @public
4702	 * @since 5.9.153 (2012-03-28)
4703	 */
4704	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4705		if (empty($subs)) {
4706			return $text;
4707		}
4708		if (TCPDF_STATIC::empty_string($font)) {
4709			$font = $this->FontFamily;
4710		}
4711		$fontdata = $this->AddFont($font, $style);
4712		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4713		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4714		foreach ($uniarr as $k => $chr) {
4715			if (!isset($fontinfo['cw'][$chr])) {
4716				// this character is missing on the selected font
4717				if (isset($subs[$chr])) {
4718					// we have available substitutions
4719					if (is_array($subs[$chr])) {
4720						foreach($subs[$chr] as $s) {
4721							if (isset($fontinfo['cw'][$s])) {
4722								$uniarr[$k] = $s;
4723								break;
4724							}
4725						}
4726					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4727						$uniarr[$k] = $subs[$chr];
4728					}
4729				}
4730			}
4731		}
4732		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4733	}
4734
4735	/**
4736	 * Defines the default monospaced font.
4737	 * @param string $font Font name.
4738	 * @public
4739	 * @since 4.5.025
4740	 */
4741	public function SetDefaultMonospacedFont($font) {
4742		$this->default_monospaced_font = $font;
4743	}
4744
4745	/**
4746	 * 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 />
4747	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4748	 * @public
4749	 * @since 1.5
4750	 * @see Cell(), Write(), Image(), Link(), SetLink()
4751	 */
4752	public function AddLink() {
4753		// create a new internal link
4754		$n = count($this->links) + 1;
4755		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4756		return $n;
4757	}
4758
4759	/**
4760	 * Defines the page and position a link points to.
4761	 * @param int $link The link identifier returned by AddLink()
4762	 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4763	 * @param int|string $page Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4764	 * @public
4765	 * @since 1.5
4766	 * @see AddLink()
4767	 */
4768	public function SetLink($link, $y=0, $page=-1) {
4769		$fixed = false;
4770		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4771			$page = intval(substr($page, 1));
4772			// this page number will not be changed when moving/add/deleting pages
4773			$fixed = true;
4774		}
4775		if ($page < 0) {
4776			$page = $this->page;
4777		}
4778		if ($y == -1) {
4779			$y = $this->y;
4780		}
4781		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4782	}
4783
4784	/**
4785	 * Puts a link on a rectangular area of the page.
4786	 * 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.
4787	 * @param float $x Abscissa of the upper-left corner of the rectangle
4788	 * @param float $y Ordinate of the upper-left corner of the rectangle
4789	 * @param float $w Width of the rectangle
4790	 * @param float $h Height of the rectangle
4791	 * @param mixed $link URL or identifier returned by AddLink()
4792	 * @param int $spaces number of spaces on the text to link
4793	 * @public
4794	 * @since 1.5
4795	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4796	 */
4797	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4798		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4799	}
4800
4801	/**
4802	 * Puts a markup annotation on a rectangular area of the page.
4803	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4804	 * @param float $x Abscissa of the upper-left corner of the rectangle
4805	 * @param float $y Ordinate of the upper-left corner of the rectangle
4806	 * @param float $w Width of the rectangle
4807	 * @param float $h Height of the rectangle
4808	 * @param string $text annotation text or alternate content
4809	 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4810	 * @param int $spaces number of spaces on the text to link
4811	 * @public
4812	 * @since 4.0.018 (2008-08-06)
4813	 */
4814	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4815		if ($this->inxobj) {
4816			// store parameters for later use on template
4817			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4818			return;
4819		}
4820		if ($x === '') {
4821			$x = $this->x;
4822		}
4823		if ($y === '') {
4824			$y = $this->y;
4825		}
4826		// check page for no-write regions and adapt page margins if necessary
4827		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4828		// recalculate coordinates to account for graphic transformations
4829		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4830			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4831				$maxid = count($this->transfmatrix[$i]) - 1;
4832				for ($j=$maxid; $j >= 0; --$j) {
4833					$ctm = $this->transfmatrix[$i][$j];
4834					if (isset($ctm['a'])) {
4835						$x = $x * $this->k;
4836						$y = ($this->h - $y) * $this->k;
4837						$w = $w * $this->k;
4838						$h = $h * $this->k;
4839						// top left
4840						$xt = $x;
4841						$yt = $y;
4842						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4843						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4844						// top right
4845						$xt = $x + $w;
4846						$yt = $y;
4847						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4848						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4849						// bottom left
4850						$xt = $x;
4851						$yt = $y - $h;
4852						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4853						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4854						// bottom right
4855						$xt = $x + $w;
4856						$yt = $y - $h;
4857						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4858						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4859						// new coordinates (rectangle area)
4860						$x = min($x1, $x2, $x3, $x4);
4861						$y = max($y1, $y2, $y3, $y4);
4862						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4863						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4864						$x = $x / $this->k;
4865						$y = $this->h - ($y / $this->k);
4866					}
4867				}
4868			}
4869		}
4870		if ($this->page <= 0) {
4871			$page = 1;
4872		} else {
4873			$page = $this->page;
4874		}
4875		if (!isset($this->PageAnnots[$page])) {
4876			$this->PageAnnots[$page] = array();
4877		}
4878		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4879		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4880			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4881				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4882				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4883				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4884			}
4885		}
4886		// Add widgets annotation's icons
4887		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4888			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4889		}
4890		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4891			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4892		}
4893		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4894			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4895		}
4896	}
4897
4898	/**
4899	 * Embedd the attached files.
4900	 * @since 4.4.000 (2008-12-07)
4901	 * @protected
4902	 * @see Annotation()
4903	 */
4904	protected function _putEmbeddedFiles() {
4905		if ($this->pdfa_mode && $this->pdfa_version != 3)  {
4906			// embedded files are not allowed in PDF/A mode version 1 and 2
4907			return;
4908		}
4909		reset($this->embeddedfiles);
4910		foreach ($this->embeddedfiles as $filename => $filedata) {
4911		    $data = $this->getCachedFileContents($filedata['file']);
4912			if ($data !== FALSE) {
4913				$rawsize = strlen($data);
4914				if ($rawsize > 0) {
4915					// update name tree
4916					$this->efnames[$filename] = $filedata['f'].' 0 R';
4917					// embedded file specification object
4918					$out = $this->_getobj($filedata['f'])."\n";
4919					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4920					$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4921					$out .= ' /AFRelationship /Source';
4922					$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4923					$out .= "\n".'endobj';
4924					$this->_out($out);
4925					// embedded file object
4926					$filter = '';
4927					if ($this->compress) {
4928						$data = gzcompress($data);
4929						$filter = ' /Filter /FlateDecode';
4930					}
4931
4932					if ($this->pdfa_version == 3) {
4933						$filter = ' /Subtype /text#2Fxml';
4934					}
4935
4936					$stream = $this->_getrawstream($data, $filedata['n']);
4937					$out = $this->_getobj($filedata['n'])."\n";
4938					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4939					$out .= ' stream'."\n".$stream."\n".'endstream';
4940					$out .= "\n".'endobj';
4941					$this->_out($out);
4942				}
4943			}
4944		}
4945	}
4946
4947	/**
4948	 * Prints a text cell at the specified position.
4949	 * This method allows to place a string precisely on the page.
4950	 * @param float $x Abscissa of the cell origin
4951	 * @param float $y Ordinate of the cell origin
4952	 * @param string $txt String to print
4953	 * @param int $fstroke outline size in user units (false = disable)
4954	 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4955	 * @param boolean $ffill if true fills the text
4956	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4957	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4958	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4959	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
4960	 * @param mixed $link URL or identifier returned by AddLink().
4961	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4962	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
4963	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4964	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4965	 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
4966	 * @public
4967	 * @since 1.0
4968	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4969	 */
4970	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) {
4971		$textrendermode = $this->textrendermode;
4972		$textstrokewidth = $this->textstrokewidth;
4973		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
4974		$this->SetXY($x, $y, $rtloff);
4975		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4976		// restore previous rendering mode
4977		$this->textrendermode = $textrendermode;
4978		$this->textstrokewidth = $textstrokewidth;
4979	}
4980
4981	/**
4982	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4983	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4984	 * This method is called automatically and should not be called directly by the application.
4985	 * @return bool
4986	 * @public
4987	 * @since 1.4
4988	 * @see SetAutoPageBreak()
4989	 */
4990	public function AcceptPageBreak() {
4991		if ($this->num_columns > 1) {
4992			// multi column mode
4993			if ($this->current_column < ($this->num_columns - 1)) {
4994				// go to next column
4995				$this->selectColumn($this->current_column + 1);
4996			} elseif ($this->AutoPageBreak) {
4997				// add a new page
4998				$this->AddPage();
4999				// set first column
5000				$this->selectColumn(0);
5001			}
5002			// avoid page breaking from checkPageBreak()
5003			return false;
5004		}
5005		return $this->AutoPageBreak;
5006	}
5007
5008	/**
5009	 * Add page if needed.
5010	 * @param float $h Cell height. Default value: 0.
5011	 * @param mixed $y starting y position, leave empty for current position.
5012	 * @param bool  $addpage if true add a page, otherwise only return the true/false state
5013	 * @return bool true in case of page break, false otherwise.
5014	 * @since 3.2.000 (2008-07-01)
5015	 * @protected
5016	 */
5017	protected function checkPageBreak($h=0, $y='', $addpage=true) {
5018		if (TCPDF_STATIC::empty_string($y)) {
5019			$y = $this->y;
5020		}
5021		$current_page = $this->page;
5022		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5023			if ($addpage) {
5024				//Automatic page break
5025				$x = $this->x;
5026				$this->AddPage($this->CurOrientation);
5027				$this->y = $this->tMargin;
5028				$oldpage = $this->page - 1;
5029				if ($this->rtl) {
5030					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5031						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5032					} else {
5033						$this->x = $x;
5034					}
5035				} else {
5036					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5037						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5038					} else {
5039						$this->x = $x;
5040					}
5041				}
5042			}
5043			return true;
5044		}
5045		if ($current_page != $this->page) {
5046			// account for columns mode
5047			return true;
5048		}
5049		return false;
5050	}
5051
5052	/**
5053	 * 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 />
5054	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5055	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5056	 * @param float $h Cell height. Default value: 0.
5057	 * @param string $txt String to print. Default value: empty string.
5058	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5059	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5060	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5061	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5062	 * @param mixed $link URL or identifier returned by AddLink().
5063	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5064	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5065	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5066	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5067	 * @public
5068	 * @since 1.0
5069	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5070	 */
5071	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') {
5072		$prev_cell_margin = $this->cell_margin;
5073		$prev_cell_padding = $this->cell_padding;
5074		$this->adjustCellPadding($border);
5075		if (!$ignore_min_height) {
5076			$min_cell_height = $this->getCellHeight($this->FontSize);
5077			if ($h < $min_cell_height) {
5078				$h = $min_cell_height;
5079			}
5080		}
5081		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5082		// apply text shadow if enabled
5083		if ($this->txtshadow['enabled']) {
5084			// save data
5085			$x = $this->x;
5086			$y = $this->y;
5087			$bc = $this->bgcolor;
5088			$fc = $this->fgcolor;
5089			$sc = $this->strokecolor;
5090			$alpha = $this->alpha;
5091			// print shadow
5092			$this->x += $this->txtshadow['depth_w'];
5093			$this->y += $this->txtshadow['depth_h'];
5094			$this->SetFillColorArray($this->txtshadow['color']);
5095			$this->SetTextColorArray($this->txtshadow['color']);
5096			$this->SetDrawColorArray($this->txtshadow['color']);
5097			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5098				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5099			}
5100			if ($this->state == 2) {
5101				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5102			}
5103			//restore data
5104			$this->x = $x;
5105			$this->y = $y;
5106			$this->SetFillColorArray($bc);
5107			$this->SetTextColorArray($fc);
5108			$this->SetDrawColorArray($sc);
5109			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5110				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5111			}
5112		}
5113		if ($this->state == 2) {
5114			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5115		}
5116		$this->cell_padding = $prev_cell_padding;
5117		$this->cell_margin = $prev_cell_margin;
5118	}
5119
5120	/**
5121	 * 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 />
5122	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5123	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5124	 * @param float $h Cell height. Default value: 0.
5125	 * @param string $txt String to print. Default value: empty string.
5126	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5127	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5128	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5129	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5130	 * @param mixed $link URL or identifier returned by AddLink().
5131	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5132	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5133	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5134	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5135	 * @return string containing cell code
5136	 * @protected
5137	 * @since 1.0
5138	 * @see Cell()
5139	 */
5140	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') {
5141		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5142		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5143		$prev_cell_margin = $this->cell_margin;
5144		$prev_cell_padding = $this->cell_padding;
5145		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5146		$rs = ''; //string to be returned
5147		$this->adjustCellPadding($border);
5148		if (!$ignore_min_height) {
5149			$min_cell_height = $this->getCellHeight($this->FontSize);
5150			if ($h < $min_cell_height) {
5151				$h = $min_cell_height;
5152			}
5153		}
5154		$k = $this->k;
5155		// check page for no-write regions and adapt page margins if necessary
5156		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5157		if ($this->rtl) {
5158			$x = $this->x - $this->cell_margin['R'];
5159		} else {
5160			$x = $this->x + $this->cell_margin['L'];
5161		}
5162		$y = $this->y + $this->cell_margin['T'];
5163		$prev_font_stretching = $this->font_stretching;
5164		$prev_font_spacing = $this->font_spacing;
5165		// cell vertical alignment
5166		switch ($calign) {
5167			case 'A': {
5168				// font top
5169				switch ($valign) {
5170					case 'T': {
5171						// top
5172						$y -= $this->cell_padding['T'];
5173						break;
5174					}
5175					case 'B': {
5176						// bottom
5177						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5178						break;
5179					}
5180					default:
5181					case 'C':
5182					case 'M': {
5183						// center
5184						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5185						break;
5186					}
5187				}
5188				break;
5189			}
5190			case 'L': {
5191				// font baseline
5192				switch ($valign) {
5193					case 'T': {
5194						// top
5195						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5196						break;
5197					}
5198					case 'B': {
5199						// bottom
5200						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5201						break;
5202					}
5203					default:
5204					case 'C':
5205					case 'M': {
5206						// center
5207						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5208						break;
5209					}
5210				}
5211				break;
5212			}
5213			case 'D': {
5214				// font bottom
5215				switch ($valign) {
5216					case 'T': {
5217						// top
5218						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5219						break;
5220					}
5221					case 'B': {
5222						// bottom
5223						$y -= ($h - $this->cell_padding['B']);
5224						break;
5225					}
5226					default:
5227					case 'C':
5228					case 'M': {
5229						// center
5230						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5231						break;
5232					}
5233				}
5234				break;
5235			}
5236			case 'B': {
5237				// cell bottom
5238				$y -= $h;
5239				break;
5240			}
5241			case 'C':
5242			case 'M': {
5243				// cell center
5244				$y -= ($h / 2);
5245				break;
5246			}
5247			default:
5248			case 'T': {
5249				// cell top
5250				break;
5251			}
5252		}
5253		// text vertical alignment
5254		switch ($valign) {
5255			case 'T': {
5256				// top
5257				$yt = $y + $this->cell_padding['T'];
5258				break;
5259			}
5260			case 'B': {
5261				// bottom
5262				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5263				break;
5264			}
5265			default:
5266			case 'C':
5267			case 'M': {
5268				// center
5269				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5270				break;
5271			}
5272		}
5273		$basefonty = $yt + $this->FontAscent;
5274		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5275			if ($this->rtl) {
5276				$w = $x - $this->lMargin;
5277			} else {
5278				$w = $this->w - $this->rMargin - $x;
5279			}
5280		}
5281		$s = '';
5282		// fill and borders
5283		if (is_string($border) AND (strlen($border) == 4)) {
5284			// full border
5285			$border = 1;
5286		}
5287		if ($fill OR ($border == 1)) {
5288			if ($fill) {
5289				$op = ($border == 1) ? 'B' : 'f';
5290			} else {
5291				$op = 'S';
5292			}
5293			if ($this->rtl) {
5294				$xk = (($x - $w) * $k);
5295			} else {
5296				$xk = ($x * $k);
5297			}
5298			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5299		}
5300		// draw borders
5301		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5302		if ($txt != '') {
5303			$txt2 = $txt;
5304			if ($this->isunicode) {
5305				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5306					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5307				} else {
5308					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5309					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5310					// replace thai chars (if any)
5311					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5312						// number of chars
5313						$numchars = count($unicode);
5314						// po pla, for far, for fan
5315						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5316						// do chada, to patak
5317						$lowtail = array(0x0e0e, 0x0e0f);
5318						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5319						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5320						// mai ek, mai tho, mai tri, mai chattawa, karan
5321						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5322						// sara u, sara uu, pinthu
5323						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5324						$output = array();
5325						for ($i = 0; $i < $numchars; $i++) {
5326							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5327								$ch0 = $unicode[$i];
5328								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5329								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5330								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5331								if (in_array($ch0, $tonemark)) {
5332									if ($chn == 0x0e33) {
5333										// sara um
5334										if (in_array($ch1, $longtail)) {
5335											// tonemark at upper left
5336											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5337										} else {
5338											// tonemark at upper right (normal position)
5339											$output[] = $ch0;
5340										}
5341									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5342										// tonemark at lower left
5343										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5344									} elseif (in_array($ch1, $upvowel)) {
5345										if (in_array($ch2, $longtail)) {
5346											// tonemark at upper left
5347											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5348										} else {
5349											// tonemark at upper right (normal position)
5350											$output[] = $ch0;
5351										}
5352									} else {
5353										// tonemark at lower right
5354										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5355									}
5356								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5357									// add lower left nikhahit and sara aa
5358									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5359										$output[] = 0xf711;
5360										$this->CurrentFont['subsetchars'][0xf711] = true;
5361										$output[] = 0x0e32;
5362										$this->CurrentFont['subsetchars'][0x0e32] = true;
5363									} else {
5364										$output[] = $ch0;
5365									}
5366								} elseif (in_array($ch1, $longtail)) {
5367									if ($ch0 == 0x0e31) {
5368										// lower left mai hun arkad
5369										$output[] = $this->replaceChar($ch0, 0xf710);
5370									} elseif (in_array($ch0, $upvowel)) {
5371										// lower left
5372										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5373									} elseif ($ch0 == 0x0e47) {
5374										// lower left mai tai koo
5375										$output[] = $this->replaceChar($ch0, 0xf712);
5376									} else {
5377										// normal character
5378										$output[] = $ch0;
5379									}
5380								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5381									// lower vowel
5382									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5383								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5384									// yo ying without lower part
5385									$output[] = $this->replaceChar($ch0, 0xf70f);
5386								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5387									// tho santan without lower part
5388									$output[] = $this->replaceChar($ch0, 0xf700);
5389								} else {
5390									$output[] = $ch0;
5391								}
5392							} else {
5393								// non-thai character
5394								$output[] = $unicode[$i];
5395							}
5396						}
5397						$unicode = $output;
5398						// update font subsetchars
5399						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5400					} // end of K_THAI_TOPCHARS
5401					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5402				}
5403			}
5404			$txt2 = TCPDF_STATIC::_escape($txt2);
5405			// get current text width (considering general font stretching and spacing)
5406			$txwidth = $this->GetStringWidth($txt);
5407			$width = $txwidth;
5408			// check for stretch mode
5409			if ($stretch > 0) {
5410				// calculate ratio between cell width and text width
5411				if ($width <= 0) {
5412					$ratio = 1;
5413				} else {
5414					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5415				}
5416				// check if stretching is required
5417				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5418					// the text will be stretched to fit cell width
5419					if ($stretch > 2) {
5420						// set new character spacing
5421						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5422					} else {
5423						// set new horizontal stretching
5424						$this->font_stretching *= $ratio;
5425					}
5426					// recalculate text width (the text fills the entire cell)
5427					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5428					// reset alignment
5429					$align = '';
5430				}
5431			}
5432			if ($this->font_stretching != 100) {
5433				// apply font stretching
5434				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5435			}
5436			if ($this->font_spacing != 0) {
5437				// increase/decrease font spacing
5438				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5439			}
5440			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5441				$s .= 'q '.$this->TextColor.' ';
5442			}
5443			// rendering mode
5444			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5445			// count number of spaces
5446			$ns = substr_count($txt, chr(32));
5447			// Justification
5448			$spacewidth = 0;
5449			if (($align == 'J') AND ($ns > 0)) {
5450				if ($this->isUnicodeFont()) {
5451					// get string width without spaces
5452					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5453					// calculate average space width
5454					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5455					if ($this->font_stretching != 100) {
5456						// word spacing is affected by stretching
5457						$spacewidth /= ($this->font_stretching / 100);
5458					}
5459					// set word position to be used with TJ operator
5460					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5461					$unicode_justification = true;
5462				} else {
5463					// get string width
5464					$width = $txwidth;
5465					// new space width
5466					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5467					if ($this->font_stretching != 100) {
5468						// word spacing (Tw) is affected by stretching
5469						$spacewidth /= ($this->font_stretching / 100);
5470					}
5471					// set word spacing
5472					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5473				}
5474				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5475			}
5476			// replace carriage return characters
5477			$txt2 = str_replace("\r", ' ', $txt2);
5478			switch ($align) {
5479				case 'C': {
5480					$dx = ($w - $width) / 2;
5481					break;
5482				}
5483				case 'R': {
5484					if ($this->rtl) {
5485						$dx = $this->cell_padding['R'];
5486					} else {
5487						$dx = $w - $width - $this->cell_padding['R'];
5488					}
5489					break;
5490				}
5491				case 'L': {
5492					if ($this->rtl) {
5493						$dx = $w - $width - $this->cell_padding['L'];
5494					} else {
5495						$dx = $this->cell_padding['L'];
5496					}
5497					break;
5498				}
5499				case 'J':
5500				default: {
5501					if ($this->rtl) {
5502						$dx = $this->cell_padding['R'];
5503					} else {
5504						$dx = $this->cell_padding['L'];
5505					}
5506					break;
5507				}
5508			}
5509			if ($this->rtl) {
5510				$xdx = $x - $dx - $width;
5511			} else {
5512				$xdx = $x + $dx;
5513			}
5514			$xdk = $xdx * $k;
5515			// print text
5516			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5517			if (isset($uniblock)) {
5518				// print overlapping characters as separate string
5519				$xshift = 0; // horizontal shift
5520				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5521				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5522				foreach ($uniblock as $uk => $uniarr) {
5523					if (($uk % 2) == 0) {
5524						// x space to skip
5525						if ($spacewidth != 0) {
5526							// justification shift
5527							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5528						}
5529						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5530					} else {
5531						// character to print
5532						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5533						$topchr = TCPDF_STATIC::_escape($topchr);
5534						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5535					}
5536				}
5537			}
5538			if ($this->underline) {
5539				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5540			}
5541			if ($this->linethrough) {
5542				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5543			}
5544			if ($this->overline) {
5545				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5546			}
5547			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5548				$s .= ' Q';
5549			}
5550			if ($link) {
5551				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5552			}
5553		}
5554		// output cell
5555		if ($s) {
5556			// output cell
5557			$rs .= $s;
5558			if ($this->font_spacing != 0) {
5559				// reset font spacing mode
5560				$rs .= ' BT 0 Tc ET';
5561			}
5562			if ($this->font_stretching != 100) {
5563				// reset font stretching mode
5564				$rs .= ' BT 100 Tz ET';
5565			}
5566		}
5567		// reset word spacing
5568		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5569			$rs .= ' BT 0 Tw ET';
5570		}
5571		// reset stretching and spacing
5572		$this->font_stretching = $prev_font_stretching;
5573		$this->font_spacing = $prev_font_spacing;
5574		$this->lasth = $h;
5575		if ($ln > 0) {
5576			//Go to the beginning of the next line
5577			$this->y = $y + $h + $this->cell_margin['B'];
5578			if ($ln == 1) {
5579				if ($this->rtl) {
5580					$this->x = $this->w - $this->rMargin;
5581				} else {
5582					$this->x = $this->lMargin;
5583				}
5584			}
5585		} else {
5586			// go left or right by case
5587			if ($this->rtl) {
5588				$this->x = $x - $w - $this->cell_margin['L'];
5589			} else {
5590				$this->x = $x + $w + $this->cell_margin['R'];
5591			}
5592		}
5593		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5594		$rs = $gstyles.$rs;
5595		$this->cell_padding = $prev_cell_padding;
5596		$this->cell_margin = $prev_cell_margin;
5597		return $rs;
5598	}
5599
5600	/**
5601	 * Replace a char if is defined on the current font.
5602	 * @param int $oldchar Integer code (unicode) of the character to replace.
5603	 * @param int $newchar Integer code (unicode) of the new character.
5604	 * @return int the replaced char or the old char in case the new char i not defined
5605	 * @protected
5606	 * @since 5.9.167 (2012-06-22)
5607	 */
5608	protected function replaceChar($oldchar, $newchar) {
5609		if ($this->isCharDefined($newchar)) {
5610			// add the new char on the subset list
5611			$this->CurrentFont['subsetchars'][$newchar] = true;
5612			// return the new character
5613			return $newchar;
5614		}
5615		// return the old char
5616		return $oldchar;
5617	}
5618
5619	/**
5620	 * Returns the code to draw the cell border
5621	 * @param float $x X coordinate.
5622	 * @param float $y Y coordinate.
5623	 * @param float $w Cell width.
5624	 * @param float $h Cell height.
5625	 * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5626	 * @return string containing cell border code
5627	 * @protected
5628	 * @see SetLineStyle()
5629	 * @since 5.7.000 (2010-08-02)
5630	 */
5631	protected function getCellBorder($x, $y, $w, $h, $brd) {
5632		$s = ''; // string to be returned
5633		if (empty($brd)) {
5634			return $s;
5635		}
5636		if ($brd == 1) {
5637			$brd = array('LRTB' => true);
5638		}
5639		// calculate coordinates for border
5640		$k = $this->k;
5641		if ($this->rtl) {
5642			$xeL = ($x - $w) * $k;
5643			$xeR = $x * $k;
5644		} else {
5645			$xeL = $x * $k;
5646			$xeR = ($x + $w) * $k;
5647		}
5648		$yeL = (($this->h - ($y + $h)) * $k);
5649		$yeT = (($this->h - $y) * $k);
5650		$xeT = $xeL;
5651		$xeB = $xeR;
5652		$yeR = $yeT;
5653		$yeB = $yeL;
5654		if (is_string($brd)) {
5655			// convert string to array
5656			$slen = strlen($brd);
5657			$newbrd = array();
5658			for ($i = 0; $i < $slen; ++$i) {
5659				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5660			}
5661			$brd = $newbrd;
5662		}
5663		if (isset($brd['mode'])) {
5664			$mode = $brd['mode'];
5665			unset($brd['mode']);
5666		} else {
5667			$mode = 'normal';
5668		}
5669		foreach ($brd as $border => $style) {
5670			if (is_array($style) AND !empty($style)) {
5671				// apply border style
5672				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5673				$s .= $this->SetLineStyle($style, true)."\n";
5674			}
5675			switch ($mode) {
5676				case 'ext': {
5677					$off = (($this->LineWidth / 2) * $k);
5678					$xL = $xeL - $off;
5679					$xR = $xeR + $off;
5680					$yT = $yeT + $off;
5681					$yL = $yeL - $off;
5682					$xT = $xL;
5683					$xB = $xR;
5684					$yR = $yT;
5685					$yB = $yL;
5686					$w += $this->LineWidth;
5687					$h += $this->LineWidth;
5688					break;
5689				}
5690				case 'int': {
5691					$off = ($this->LineWidth / 2) * $k;
5692					$xL = $xeL + $off;
5693					$xR = $xeR - $off;
5694					$yT = $yeT - $off;
5695					$yL = $yeL + $off;
5696					$xT = $xL;
5697					$xB = $xR;
5698					$yR = $yT;
5699					$yB = $yL;
5700					$w -= $this->LineWidth;
5701					$h -= $this->LineWidth;
5702					break;
5703				}
5704				case 'normal':
5705				default: {
5706					$xL = $xeL;
5707					$xT = $xeT;
5708					$xB = $xeB;
5709					$xR = $xeR;
5710					$yL = $yeL;
5711					$yT = $yeT;
5712					$yB = $yeB;
5713					$yR = $yeR;
5714					break;
5715				}
5716			}
5717			// draw borders by case
5718			if (strlen($border) == 4) {
5719				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5720			} elseif (strlen($border) == 3) {
5721				if (strpos($border,'B') === false) { // LTR
5722					$s .= sprintf('%F %F m ', $xL, $yL);
5723					$s .= sprintf('%F %F l ', $xT, $yT);
5724					$s .= sprintf('%F %F l ', $xR, $yR);
5725					$s .= sprintf('%F %F l ', $xB, $yB);
5726					$s .= 'S ';
5727				} elseif (strpos($border,'L') === false) { // TRB
5728					$s .= sprintf('%F %F m ', $xT, $yT);
5729					$s .= sprintf('%F %F l ', $xR, $yR);
5730					$s .= sprintf('%F %F l ', $xB, $yB);
5731					$s .= sprintf('%F %F l ', $xL, $yL);
5732					$s .= 'S ';
5733				} elseif (strpos($border,'T') === false) { // RBL
5734					$s .= sprintf('%F %F m ', $xR, $yR);
5735					$s .= sprintf('%F %F l ', $xB, $yB);
5736					$s .= sprintf('%F %F l ', $xL, $yL);
5737					$s .= sprintf('%F %F l ', $xT, $yT);
5738					$s .= 'S ';
5739				} elseif (strpos($border,'R') === false) { // BLT
5740					$s .= sprintf('%F %F m ', $xB, $yB);
5741					$s .= sprintf('%F %F l ', $xL, $yL);
5742					$s .= sprintf('%F %F l ', $xT, $yT);
5743					$s .= sprintf('%F %F l ', $xR, $yR);
5744					$s .= 'S ';
5745				}
5746			} elseif (strlen($border) == 2) {
5747				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5748					$s .= sprintf('%F %F m ', $xL, $yL);
5749					$s .= sprintf('%F %F l ', $xT, $yT);
5750					$s .= sprintf('%F %F l ', $xR, $yR);
5751					$s .= 'S ';
5752				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5753					$s .= sprintf('%F %F m ', $xT, $yT);
5754					$s .= sprintf('%F %F l ', $xR, $yR);
5755					$s .= sprintf('%F %F l ', $xB, $yB);
5756					$s .= 'S ';
5757				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5758					$s .= sprintf('%F %F m ', $xR, $yR);
5759					$s .= sprintf('%F %F l ', $xB, $yB);
5760					$s .= sprintf('%F %F l ', $xL, $yL);
5761					$s .= 'S ';
5762				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5763					$s .= sprintf('%F %F m ', $xB, $yB);
5764					$s .= sprintf('%F %F l ', $xL, $yL);
5765					$s .= sprintf('%F %F l ', $xT, $yT);
5766					$s .= 'S ';
5767				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5768					$s .= sprintf('%F %F m ', $xL, $yL);
5769					$s .= sprintf('%F %F l ', $xT, $yT);
5770					$s .= 'S ';
5771					$s .= sprintf('%F %F m ', $xR, $yR);
5772					$s .= sprintf('%F %F l ', $xB, $yB);
5773					$s .= 'S ';
5774				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5775					$s .= sprintf('%F %F m ', $xT, $yT);
5776					$s .= sprintf('%F %F l ', $xR, $yR);
5777					$s .= 'S ';
5778					$s .= sprintf('%F %F m ', $xB, $yB);
5779					$s .= sprintf('%F %F l ', $xL, $yL);
5780					$s .= 'S ';
5781				}
5782			} else { // strlen($border) == 1
5783				if (strpos($border,'L') !== false) { // L
5784					$s .= sprintf('%F %F m ', $xL, $yL);
5785					$s .= sprintf('%F %F l ', $xT, $yT);
5786					$s .= 'S ';
5787				} elseif (strpos($border,'T') !== false) { // T
5788					$s .= sprintf('%F %F m ', $xT, $yT);
5789					$s .= sprintf('%F %F l ', $xR, $yR);
5790					$s .= 'S ';
5791				} elseif (strpos($border,'R') !== false) { // R
5792					$s .= sprintf('%F %F m ', $xR, $yR);
5793					$s .= sprintf('%F %F l ', $xB, $yB);
5794					$s .= 'S ';
5795				} elseif (strpos($border,'B') !== false) { // B
5796					$s .= sprintf('%F %F m ', $xB, $yB);
5797					$s .= sprintf('%F %F l ', $xL, $yL);
5798					$s .= 'S ';
5799				}
5800			}
5801			if (is_array($style) AND !empty($style)) {
5802				// reset border style to previous value
5803				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5804			}
5805		}
5806		return $s;
5807	}
5808
5809	/**
5810	 * This method allows printing text with line breaks.
5811	 * 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 />
5812	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5813	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5814	 * @param float $h Cell minimum height. The cell extends automatically if needed.
5815	 * @param string $txt String to print
5816	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5817	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5818	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5819	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5820	 * @param float $x x position in user units
5821	 * @param float $y y position in user units
5822	 * @param boolean $reseth if true reset the last cell height (default true).
5823	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5824	 * @param boolean $ishtml INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5825	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5826	 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5827	 * @param string $valign Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5828	 * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5829	 * @return int Return the number of cells or 1 for html mode.
5830	 * @public
5831	 * @since 1.3
5832	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5833	 */
5834	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) {
5835		$prev_cell_margin = $this->cell_margin;
5836		$prev_cell_padding = $this->cell_padding;
5837		// adjust internal padding
5838		$this->adjustCellPadding($border);
5839		$mc_padding = $this->cell_padding;
5840		$mc_margin = $this->cell_margin;
5841		$this->cell_padding['T'] = 0;
5842		$this->cell_padding['B'] = 0;
5843		$this->setCellMargins(0, 0, 0, 0);
5844		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5845			// reset row height
5846			$this->resetLastH();
5847		}
5848		if (!TCPDF_STATIC::empty_string($y)) {
5849			$this->SetY($y); // set y in order to convert negative y values to positive ones
5850		}
5851		$y = $this->GetY();
5852		$resth = 0;
5853		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5854			// spit cell in more pages/columns
5855			$newh = ($this->PageBreakTrigger - $y);
5856			$resth = ($h - $newh); // cell to be printed on the next page/column
5857			$h = $newh;
5858		}
5859		// get current page number
5860		$startpage = $this->page;
5861		// get current column
5862		$startcolumn = $this->current_column;
5863		if (!TCPDF_STATIC::empty_string($x)) {
5864			$this->SetX($x);
5865		} else {
5866			$x = $this->GetX();
5867		}
5868		// check page for no-write regions and adapt page margins if necessary
5869		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5870		// apply margins
5871		$oy = $y + $mc_margin['T'];
5872		if ($this->rtl) {
5873			$ox = ($this->w - $x - $mc_margin['R']);
5874		} else {
5875			$ox = ($x + $mc_margin['L']);
5876		}
5877		$this->x = $ox;
5878		$this->y = $oy;
5879		// set width
5880		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5881			if ($this->rtl) {
5882				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5883			} else {
5884				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5885			}
5886		}
5887		// store original margin values
5888		$lMargin = $this->lMargin;
5889		$rMargin = $this->rMargin;
5890		if ($this->rtl) {
5891			$this->rMargin = ($this->w - $this->x);
5892			$this->lMargin = ($this->x - $w);
5893		} else {
5894			$this->lMargin = ($this->x);
5895			$this->rMargin = ($this->w - $this->x - $w);
5896		}
5897		$this->clMargin = $this->lMargin;
5898		$this->crMargin = $this->rMargin;
5899		if ($autopadding) {
5900			// add top padding
5901			$this->y += $mc_padding['T'];
5902		}
5903		if ($ishtml) { // ******* Write HTML text
5904			$this->writeHTML($txt, true, false, $reseth, true, $align);
5905			$nl = 1;
5906		} else { // ******* Write simple text
5907			$prev_FontSizePt = $this->FontSizePt;
5908			if ($fitcell) {
5909				// ajust height values
5910				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5911				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5912			}
5913			// vertical alignment
5914			if ($maxh > 0) {
5915				// get text height
5916				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5917				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5918					// try to reduce font size to fit text on cell (use a quick search algorithm)
5919					$fmin = 1;
5920					$fmax = $this->FontSizePt;
5921					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5922					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5923					while ($maxit >= 0) {
5924						$fmid = (($fmax + $fmin) / 2);
5925						$this->SetFontSize($fmid, false);
5926						$this->resetLastH();
5927						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5928						$diff = ($maxh - $text_height);
5929						if ($diff >= 0) {
5930							if ($diff <= $diff_epsilon) {
5931								break;
5932							}
5933							$fmin = $fmid;
5934						} else {
5935							$fmax = $fmid;
5936						}
5937						--$maxit;
5938					}
5939					if ($maxit < 0) {
5940						// premature exit, we get the minimum font value to fit the cell
5941						$this->SetFontSize($fmin);
5942						$this->resetLastH();
5943						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5944					} else {
5945						$this->SetFontSize($fmid);
5946						$this->resetLastH();
5947					}
5948				}
5949				if ($text_height < $maxh) {
5950					if ($valign == 'M') {
5951						// text vertically centered
5952						$this->y += (($maxh - $text_height) / 2);
5953					} elseif ($valign == 'B') {
5954						// text vertically aligned on bottom
5955						$this->y += ($maxh - $text_height);
5956					}
5957				}
5958			}
5959			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5960			if ($fitcell) {
5961				// restore font size
5962				$this->SetFontSize($prev_FontSizePt);
5963			}
5964		}
5965		if ($autopadding) {
5966			// add bottom padding
5967			$this->y += $mc_padding['B'];
5968		}
5969		// Get end-of-text Y position
5970		$currentY = $this->y;
5971		// get latest page number
5972		$endpage = $this->page;
5973		if ($resth > 0) {
5974			$skip = ($endpage - $startpage);
5975			$tmpresth = $resth;
5976			while ($tmpresth > 0) {
5977				if ($skip <= 0) {
5978					// add a page (or trig AcceptPageBreak() for multicolumn mode)
5979					$this->checkPageBreak($this->PageBreakTrigger + 1);
5980				}
5981				if ($this->num_columns > 1) {
5982					$tmpresth -= ($this->h - $this->y - $this->bMargin);
5983				} else {
5984					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5985				}
5986				--$skip;
5987			}
5988			$currentY = $this->y;
5989			$endpage = $this->page;
5990		}
5991		// get latest column
5992		$endcolumn = $this->current_column;
5993		if ($this->num_columns == 0) {
5994			$this->num_columns = 1;
5995		}
5996		// disable page regions check
5997		$check_page_regions = $this->check_page_regions;
5998		$this->check_page_regions = false;
5999		// get border modes
6000		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
6001		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
6002		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6003		// design borders around HTML cells.
6004		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6005			$ccode = '';
6006			$this->setPage($page);
6007			if ($this->num_columns < 2) {
6008				// single-column mode
6009				$this->SetX($x);
6010				$this->y = $this->tMargin;
6011			}
6012			// account for margin changes
6013			if ($page > $startpage) {
6014				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6015					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6016				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6017					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6018				}
6019			}
6020			if ($startpage == $endpage) {
6021				// single page
6022				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6023					if ($column != $this->current_column) {
6024						$this->selectColumn($column);
6025					}
6026					if ($this->rtl) {
6027						$this->x -= $mc_margin['R'];
6028					} else {
6029						$this->x += $mc_margin['L'];
6030					}
6031					if ($startcolumn == $endcolumn) { // single column
6032						$cborder = $border;
6033						$h = max($h, ($currentY - $oy));
6034						$this->y = $oy;
6035					} elseif ($column == $startcolumn) { // first column
6036						$cborder = $border_start;
6037						$this->y = $oy;
6038						$h = $this->h - $this->y - $this->bMargin;
6039					} elseif ($column == $endcolumn) { // end column
6040						$cborder = $border_end;
6041						$h = $currentY - $this->y;
6042						if ($resth > $h) {
6043							$h = $resth;
6044						}
6045					} else { // middle column
6046						$cborder = $border_middle;
6047						$h = $this->h - $this->y - $this->bMargin;
6048						$resth -= $h;
6049					}
6050					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6051				} // end for each column
6052			} elseif ($page == $startpage) { // first page
6053				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6054					if ($column != $this->current_column) {
6055						$this->selectColumn($column);
6056					}
6057					if ($this->rtl) {
6058						$this->x -= $mc_margin['R'];
6059					} else {
6060						$this->x += $mc_margin['L'];
6061					}
6062					if ($column == $startcolumn) { // first column
6063						$cborder = $border_start;
6064						$this->y = $oy;
6065						$h = $this->h - $this->y - $this->bMargin;
6066					} else { // middle column
6067						$cborder = $border_middle;
6068						$h = $this->h - $this->y - $this->bMargin;
6069						$resth -= $h;
6070					}
6071					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6072				} // end for each column
6073			} elseif ($page == $endpage) { // last page
6074				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6075					if ($column != $this->current_column) {
6076						$this->selectColumn($column);
6077					}
6078					if ($this->rtl) {
6079						$this->x -= $mc_margin['R'];
6080					} else {
6081						$this->x += $mc_margin['L'];
6082					}
6083					if ($column == $endcolumn) {
6084						// end column
6085						$cborder = $border_end;
6086						$h = $currentY - $this->y;
6087						if ($resth > $h) {
6088							$h = $resth;
6089						}
6090					} else {
6091						// middle column
6092						$cborder = $border_middle;
6093						$h = $this->h - $this->y - $this->bMargin;
6094						$resth -= $h;
6095					}
6096					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6097				} // end for each column
6098			} else { // middle page
6099				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6100					$this->selectColumn($column);
6101					if ($this->rtl) {
6102						$this->x -= $mc_margin['R'];
6103					} else {
6104						$this->x += $mc_margin['L'];
6105					}
6106					$cborder = $border_middle;
6107					$h = $this->h - $this->y - $this->bMargin;
6108					$resth -= $h;
6109					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6110				} // end for each column
6111			}
6112			if ($cborder OR $fill) {
6113				$offsetlen = strlen($ccode);
6114				// draw border and fill
6115				if ($this->inxobj) {
6116					// we are inside an XObject template
6117					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6118						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6119						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6120						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6121					} else {
6122						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6123						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6124					}
6125					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6126					$pstart = substr($pagebuff, 0, $pagemark);
6127					$pend = substr($pagebuff, $pagemark);
6128					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6129				} else {
6130					if (end($this->transfmrk[$this->page]) !== false) {
6131						$pagemarkkey = key($this->transfmrk[$this->page]);
6132						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6133						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6134					} elseif ($this->InFooter) {
6135						$pagemark = $this->footerpos[$this->page];
6136						$this->footerpos[$this->page] += $offsetlen;
6137					} else {
6138						$pagemark = $this->intmrk[$this->page];
6139						$this->intmrk[$this->page] += $offsetlen;
6140					}
6141					$pagebuff = $this->getPageBuffer($this->page);
6142					$pstart = substr($pagebuff, 0, $pagemark);
6143					$pend = substr($pagebuff, $pagemark);
6144					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6145				}
6146			}
6147		} // end for each page
6148		// restore page regions check
6149		$this->check_page_regions = $check_page_regions;
6150		// Get end-of-cell Y position
6151		$currentY = $this->GetY();
6152		// restore previous values
6153		if ($this->num_columns > 1) {
6154			$this->selectColumn();
6155		} else {
6156			// restore original margins
6157			$this->lMargin = $lMargin;
6158			$this->rMargin = $rMargin;
6159			if ($this->page > $startpage) {
6160				// check for margin variations between pages (i.e. booklet mode)
6161				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6162				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6163				if (($dl != 0) OR ($dr != 0)) {
6164					$this->lMargin += $dl;
6165					$this->rMargin += $dr;
6166				}
6167			}
6168		}
6169		if ($ln > 0) {
6170			//Go to the beginning of the next line
6171			$this->SetY($currentY + $mc_margin['B']);
6172			if ($ln == 2) {
6173				$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6174			}
6175		} else {
6176			// go left or right by case
6177			$this->setPage($startpage);
6178			$this->y = $y;
6179			$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6180		}
6181		$this->setContentMark();
6182		$this->cell_padding = $prev_cell_padding;
6183		$this->cell_margin = $prev_cell_margin;
6184		$this->clMargin = $this->lMargin;
6185		$this->crMargin = $this->rMargin;
6186		return $nl;
6187	}
6188
6189	/**
6190	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6191	 * @param string $txt String for calculating his height
6192	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6193	 * @param boolean $reseth if true reset the last cell height (default false).
6194	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6195	 * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
6196	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6197	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6198	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6199	 * @public
6200	 * @since 4.5.011
6201	 */
6202	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6203		if ($txt === NULL) {
6204			return 0;
6205		}
6206		if ($txt === '') {
6207			// empty string
6208			return 1;
6209		}
6210		// adjust internal padding
6211		$prev_cell_padding = $this->cell_padding;
6212		$prev_lasth = $this->lasth;
6213		if (is_array($cellpadding)) {
6214			$this->cell_padding = $cellpadding;
6215		}
6216		$this->adjustCellPadding($border);
6217		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6218			if ($this->rtl) {
6219				$w = $this->x - $this->lMargin;
6220			} else {
6221				$w = $this->w - $this->rMargin - $this->x;
6222			}
6223		}
6224		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6225		if ($reseth) {
6226			// reset row height
6227			$this->resetLastH();
6228		}
6229		$lines = 1;
6230		$sum = 0;
6231		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6232		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6233		$length = count($chars);
6234		$lastSeparator = -1;
6235		for ($i = 0; $i < $length; ++$i) {
6236			$c = $chars[$i];
6237			$charWidth = $charsWidth[$i];
6238			if (($c != 160)
6239					AND (($c == 173)
6240						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6241						OR (($c == 45)
6242							AND ($i > 0) AND ($i < ($length - 1))
6243							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6244							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6245						)
6246					)
6247				) {
6248				$lastSeparator = $i;
6249			}
6250			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6251				++$lines;
6252				if ($c == 10) {
6253					$lastSeparator = -1;
6254					$sum = 0;
6255				} elseif ($lastSeparator != -1) {
6256					$i = $lastSeparator;
6257					$lastSeparator = -1;
6258					$sum = 0;
6259				} else {
6260					$sum = $charWidth;
6261				}
6262			} else {
6263				$sum += $charWidth;
6264			}
6265		}
6266		if ($chars[($length - 1)] == 10) {
6267			--$lines;
6268		}
6269		$this->cell_padding = $prev_cell_padding;
6270		$this->lasth = $prev_lasth;
6271		return $lines;
6272	}
6273
6274	/**
6275	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6276	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6277	 * @pre
6278	 *  // store current object
6279	 *  $pdf->startTransaction();
6280	 *  // store starting values
6281	 *  $start_y = $pdf->GetY();
6282	 *  $start_page = $pdf->getPage();
6283	 *  // call your printing functions with your parameters
6284	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6285	 *  $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);
6286	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6287	 *  // get the new Y
6288	 *  $end_y = $pdf->GetY();
6289	 *  $end_page = $pdf->getPage();
6290	 *  // calculate height
6291	 *  $height = 0;
6292	 *  if ($end_page == $start_page) {
6293	 *  	$height = $end_y - $start_y;
6294	 *  } else {
6295	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6296	 *  		$this->setPage($page);
6297	 *  		if ($page == $start_page) {
6298	 *  			// first page
6299	 *  			$height += $this->h - $start_y - $this->bMargin;
6300	 *  		} elseif ($page == $end_page) {
6301	 *  			// last page
6302	 *  			$height += $end_y - $this->tMargin;
6303	 *  		} else {
6304	 *  			$height += $this->h - $this->tMargin - $this->bMargin;
6305	 *  		}
6306	 *  	}
6307	 *  }
6308	 *  // restore previous object
6309	 *  $pdf = $pdf->rollbackTransaction();
6310	 *
6311	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6312	 * @param string $txt String for calculating his height
6313	 * @param boolean $reseth if true reset the last cell height (default false).
6314	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6315	 * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
6316	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6317	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6318	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6319	 * @public
6320	 */
6321	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6322		// adjust internal padding
6323		$prev_cell_padding = $this->cell_padding;
6324		$prev_lasth = $this->lasth;
6325		if (is_array($cellpadding)) {
6326			$this->cell_padding = $cellpadding;
6327		}
6328		$this->adjustCellPadding($border);
6329		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6330		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6331		$this->cell_padding = $prev_cell_padding;
6332		$this->lasth = $prev_lasth;
6333		return $height;
6334	}
6335
6336	/**
6337	 * This method prints text from the current position.<br />
6338	 * @param float $h Line height
6339	 * @param string $txt String to print
6340	 * @param mixed $link URL or identifier returned by AddLink()
6341	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6342	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6343	 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6344	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6345	 * @param boolean $firstline if true prints only the first line and return the remaining string.
6346	 * @param boolean $firstblock if true the string is the starting of a line.
6347	 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6348	 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6349	 * @param array $margin margin array of the parent container
6350	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6351	 * @public
6352	 * @since 1.5
6353	 */
6354	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6355		// check page for no-write regions and adapt page margins if necessary
6356		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6357		if (strlen($txt) == 0) {
6358			// fix empty text
6359			$txt = ' ';
6360		}
6361		if ($margin === '') {
6362			// set default margins
6363			$margin = $this->cell_margin;
6364		}
6365		// remove carriage returns
6366		$s = str_replace("\r", '', $txt);
6367		// check if string contains arabic text
6368		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6369			$arabic = true;
6370		} else {
6371			$arabic = false;
6372		}
6373		// check if string contains RTL text
6374		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6375			$rtlmode = true;
6376		} else {
6377			$rtlmode = false;
6378		}
6379		// get a char width
6380		$chrwidth = $this->GetCharWidth(46); // dot character
6381		// get array of unicode values
6382		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6383		// calculate maximum width for a single character on string
6384		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6385		array_walk($chrw, array($this, 'getRawCharWidth'));
6386		$maxchwidth = max($chrw);
6387		// get array of chars
6388		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6389		// get the number of characters
6390		$nb = count($chars);
6391		// replacement for SHY character (minus symbol)
6392		$shy_replacement = 45;
6393		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6394		// widht for SHY replacement
6395		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6396		// page width
6397		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6398		// calculate remaining line width ($w)
6399		if ($this->rtl) {
6400			$w = $this->x - $this->lMargin;
6401		} else {
6402			$w = $this->w - $this->rMargin - $this->x;
6403		}
6404		// max column width
6405		$wmax = ($w - $wadj);
6406		if (!$firstline) {
6407			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6408		}
6409		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6410			// the maximum width character do not fit on column
6411			return '';
6412		}
6413		// minimum row height
6414		$row_height = max($h, $this->getCellHeight($this->FontSize));
6415		// max Y
6416		$maxy = $this->y + $maxh - max($row_height, $h);
6417		$start_page = $this->page;
6418		$i = 0; // character position
6419		$j = 0; // current starting position
6420		$sep = -1; // position of the last blank space
6421		$prevsep = $sep; // previous separator
6422		$shy = false; // true if the last blank is a soft hypen (SHY)
6423		$prevshy = $shy; // previous shy mode
6424		$l = 0; // current string length
6425		$nl = 0; //number of lines
6426		$linebreak = false;
6427		$pc = 0; // previous character
6428		// for each character
6429		while ($i < $nb) {
6430			if (($maxh > 0) AND ($this->y > $maxy) ) {
6431				break;
6432			}
6433			//Get the current character
6434			$c = $chars[$i];
6435			if ($c == 10) { // 10 = "\n" = new line
6436				//Explicit line break
6437				if ($align == 'J') {
6438					if ($this->rtl) {
6439						$talign = 'R';
6440					} else {
6441						$talign = 'L';
6442					}
6443				} else {
6444					$talign = $align;
6445				}
6446				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6447				if ($firstline) {
6448					$startx = $this->x;
6449					$tmparr = array_slice($chars, $j, ($i - $j));
6450					if ($rtlmode) {
6451						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6452					}
6453					$linew = $this->GetArrStringWidth($tmparr);
6454					unset($tmparr);
6455					if ($this->rtl) {
6456						$this->endlinex = $startx - $linew;
6457					} else {
6458						$this->endlinex = $startx + $linew;
6459					}
6460					$w = $linew;
6461					$tmpcellpadding = $this->cell_padding;
6462					if ($maxh == 0) {
6463						$this->SetCellPadding(0);
6464					}
6465				}
6466				if ($firstblock AND $this->isRTLTextDir()) {
6467					$tmpstr = $this->stringRightTrim($tmpstr);
6468				}
6469				// Skip newlines at the beginning of a page or column
6470				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6471					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6472				}
6473				unset($tmpstr);
6474				if ($firstline) {
6475					$this->cell_padding = $tmpcellpadding;
6476					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6477				}
6478				++$nl;
6479				$j = $i + 1;
6480				$l = 0;
6481				$sep = -1;
6482				$prevsep = $sep;
6483				$shy = false;
6484				// account for margin changes
6485				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6486					$this->AcceptPageBreak();
6487					if ($this->rtl) {
6488						$this->x -= $margin['R'];
6489					} else {
6490						$this->x += $margin['L'];
6491					}
6492					$this->lMargin += $margin['L'];
6493					$this->rMargin += $margin['R'];
6494				}
6495				$w = $this->getRemainingWidth();
6496				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6497			} else {
6498				// 160 is the non-breaking space.
6499				// 173 is SHY (Soft Hypen).
6500				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6501				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6502				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6503				if (($c != 160)
6504					AND (($c == 173)
6505						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6506						OR (($c == 45)
6507							AND ($i < ($nb - 1))
6508							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6509							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6510						)
6511					)
6512				) {
6513					// update last blank space position
6514					$prevsep = $sep;
6515					$sep = $i;
6516					// check if is a SHY
6517					if (($c == 173) OR ($c == 45)) {
6518						$prevshy = $shy;
6519						$shy = true;
6520						if ($pc == 45) {
6521							$tmp_shy_replacement_width = 0;
6522							$tmp_shy_replacement_char = '';
6523						} else {
6524							$tmp_shy_replacement_width = $shy_replacement_width;
6525							$tmp_shy_replacement_char = $shy_replacement_char;
6526						}
6527					} else {
6528						$shy = false;
6529					}
6530				}
6531				// update string length
6532				if ($this->isUnicodeFont() AND ($arabic)) {
6533					// with bidirectional algorithm some chars may be changed affecting the line length
6534					// *** very slow ***
6535					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6536				} else {
6537					$l += $this->GetCharWidth($c, ($i+1 < $nb));
6538				}
6539				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6540					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6541						$sep = $prevsep;
6542						$shy = $prevshy;
6543					}
6544					// we have reached the end of column
6545					if ($sep == -1) {
6546						// check if the line was already started
6547						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6548							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6549							// print a void cell and go to next line
6550							$this->Cell($w, $h, '', 0, 1);
6551							$linebreak = true;
6552							if ($firstline) {
6553								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6554							}
6555						} else {
6556							// truncate the word because do not fit on column
6557							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6558							if ($firstline) {
6559								$startx = $this->x;
6560								$tmparr = array_slice($chars, $j, ($i - $j));
6561								if ($rtlmode) {
6562									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6563								}
6564								$linew = $this->GetArrStringWidth($tmparr);
6565								unset($tmparr);
6566								if ($this->rtl) {
6567									$this->endlinex = $startx - $linew;
6568								} else {
6569									$this->endlinex = $startx + $linew;
6570								}
6571								$w = $linew;
6572								$tmpcellpadding = $this->cell_padding;
6573								if ($maxh == 0) {
6574									$this->SetCellPadding(0);
6575								}
6576							}
6577							if ($firstblock AND $this->isRTLTextDir()) {
6578								$tmpstr = $this->stringRightTrim($tmpstr);
6579							}
6580							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6581							unset($tmpstr);
6582							if ($firstline) {
6583								$this->cell_padding = $tmpcellpadding;
6584								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6585							}
6586							$j = $i;
6587							--$i;
6588						}
6589					} else {
6590						// word wrapping
6591						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6592							$endspace = 1;
6593						} else {
6594							$endspace = 0;
6595						}
6596						// check the length of the next string
6597						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6598						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6599						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6600							// truncate the word because do not fit on a full page width
6601							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6602							if ($firstline) {
6603								$startx = $this->x;
6604								$tmparr = array_slice($chars, $j, ($i - $j));
6605								if ($rtlmode) {
6606									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6607								}
6608								$linew = $this->GetArrStringWidth($tmparr);
6609								unset($tmparr);
6610								if ($this->rtl) {
6611									$this->endlinex = ($startx - $linew);
6612								} else {
6613									$this->endlinex = ($startx + $linew);
6614								}
6615								$w = $linew;
6616								$tmpcellpadding = $this->cell_padding;
6617								if ($maxh == 0) {
6618									$this->SetCellPadding(0);
6619								}
6620							}
6621							if ($firstblock AND $this->isRTLTextDir()) {
6622								$tmpstr = $this->stringRightTrim($tmpstr);
6623							}
6624							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6625							unset($tmpstr);
6626							if ($firstline) {
6627								$this->cell_padding = $tmpcellpadding;
6628								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6629							}
6630							$j = $i;
6631							--$i;
6632						} else {
6633							// word wrapping
6634							if ($shy) {
6635								// add hypen (minus symbol) at the end of the line
6636								$shy_width = $tmp_shy_replacement_width;
6637								if ($this->rtl) {
6638									$shy_char_left = $tmp_shy_replacement_char;
6639									$shy_char_right = '';
6640								} else {
6641									$shy_char_left = '';
6642									$shy_char_right = $tmp_shy_replacement_char;
6643								}
6644							} else {
6645								$shy_width = 0;
6646								$shy_char_left = '';
6647								$shy_char_right = '';
6648							}
6649							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6650							if ($firstline) {
6651								$startx = $this->x;
6652								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6653								if ($rtlmode) {
6654									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6655								}
6656								$linew = $this->GetArrStringWidth($tmparr);
6657								unset($tmparr);
6658								if ($this->rtl) {
6659									$this->endlinex = $startx - $linew - $shy_width;
6660								} else {
6661									$this->endlinex = $startx + $linew + $shy_width;
6662								}
6663								$w = $linew;
6664								$tmpcellpadding = $this->cell_padding;
6665								if ($maxh == 0) {
6666									$this->SetCellPadding(0);
6667								}
6668							}
6669							// print the line
6670							if ($firstblock AND $this->isRTLTextDir()) {
6671								$tmpstr = $this->stringRightTrim($tmpstr);
6672							}
6673							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6674							unset($tmpstr);
6675							if ($firstline) {
6676								if ($chars[$sep] == 45) {
6677									$endspace += 1;
6678								}
6679								// return the remaining text
6680								$this->cell_padding = $tmpcellpadding;
6681								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6682							}
6683							$i = $sep;
6684							$sep = -1;
6685							$shy = false;
6686							$j = ($i + 1);
6687						}
6688					}
6689					// account for margin changes
6690					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6691						$this->AcceptPageBreak();
6692						if ($this->rtl) {
6693							$this->x -= $margin['R'];
6694						} else {
6695							$this->x += $margin['L'];
6696						}
6697						$this->lMargin += $margin['L'];
6698						$this->rMargin += $margin['R'];
6699					}
6700					$w = $this->getRemainingWidth();
6701					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6702					if ($linebreak) {
6703						$linebreak = false;
6704					} else {
6705						++$nl;
6706						$l = 0;
6707					}
6708				}
6709			}
6710			// save last character
6711			$pc = $c;
6712			++$i;
6713		} // end while i < nb
6714		// print last substring (if any)
6715		if ($l > 0) {
6716			switch ($align) {
6717				case 'J':
6718				case 'C': {
6719					break;
6720				}
6721				case 'L': {
6722					if (!$this->rtl) {
6723						$w = $l;
6724					}
6725					break;
6726				}
6727				case 'R': {
6728					if ($this->rtl) {
6729						$w = $l;
6730					}
6731					break;
6732				}
6733				default: {
6734					$w = $l;
6735					break;
6736				}
6737			}
6738			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6739			if ($firstline) {
6740				$startx = $this->x;
6741				$tmparr = array_slice($chars, $j, ($nb - $j));
6742				if ($rtlmode) {
6743					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6744				}
6745				$linew = $this->GetArrStringWidth($tmparr);
6746				unset($tmparr);
6747				if ($this->rtl) {
6748					$this->endlinex = $startx - $linew;
6749				} else {
6750					$this->endlinex = $startx + $linew;
6751				}
6752				$w = $linew;
6753				$tmpcellpadding = $this->cell_padding;
6754				if ($maxh == 0) {
6755					$this->SetCellPadding(0);
6756				}
6757			}
6758			if ($firstblock AND $this->isRTLTextDir()) {
6759				$tmpstr = $this->stringRightTrim($tmpstr);
6760			}
6761			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6762			unset($tmpstr);
6763			if ($firstline) {
6764				$this->cell_padding = $tmpcellpadding;
6765				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6766			}
6767			++$nl;
6768		}
6769		if ($firstline) {
6770			return '';
6771		}
6772		return $nl;
6773	}
6774
6775	/**
6776	 * Returns the remaining width between the current position and margins.
6777	 * @return float Return the remaining width
6778	 * @protected
6779	 */
6780	protected function getRemainingWidth() {
6781		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6782		if ($this->rtl) {
6783			return ($this->x - $this->lMargin);
6784		} else {
6785			return ($this->w - $this->rMargin - $this->x);
6786		}
6787	}
6788
6789	/**
6790	 * Set the block dimensions accounting for page breaks and page/column fitting
6791	 * @param float $w width
6792	 * @param float $h height
6793	 * @param float $x X coordinate
6794	 * @param float $y Y coodiante
6795	 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6796	 * @return array array($w, $h, $x, $y)
6797	 * @protected
6798	 * @since 5.5.009 (2010-07-05)
6799	 */
6800	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6801		if ($w <= 0) {
6802			// set maximum width
6803			$w = ($this->w - $this->lMargin - $this->rMargin);
6804			if ($w <= 0) {
6805				$w = 1;
6806			}
6807		}
6808		if ($h <= 0) {
6809			// set maximum height
6810			$h = ($this->PageBreakTrigger - $this->tMargin);
6811			if ($h <= 0) {
6812				$h = 1;
6813			}
6814		}
6815		// resize the block to be vertically contained on a single page or single column
6816		if ($fitonpage OR $this->AutoPageBreak) {
6817			$ratio_wh = ($w / $h);
6818			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6819				$h = $this->PageBreakTrigger - $this->tMargin;
6820				$w = ($h * $ratio_wh);
6821			}
6822			// resize the block to be horizontally contained on a single page or single column
6823			if ($fitonpage) {
6824				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6825				if ($w > $maxw) {
6826					$w = $maxw;
6827					$h = ($w / $ratio_wh);
6828				}
6829			}
6830		}
6831		// Check whether we need a new page or new column first as this does not fit
6832		$prev_x = $this->x;
6833		$prev_y = $this->y;
6834		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6835			$y = $this->y;
6836			if ($this->rtl) {
6837				$x += ($prev_x - $this->x);
6838			} else {
6839				$x += ($this->x - $prev_x);
6840			}
6841			$this->newline = true;
6842		}
6843		// resize the block to be contained on the remaining available page or column space
6844		if ($fitonpage) {
6845			$ratio_wh = ($w / $h);
6846			if (($y + $h) > $this->PageBreakTrigger) {
6847				$h = $this->PageBreakTrigger - $y;
6848				$w = ($h * $ratio_wh);
6849			}
6850			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6851				$w = $this->w - $this->rMargin - $x;
6852				$h = ($w / $ratio_wh);
6853			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6854				$w = $x - $this->lMargin;
6855				$h = ($w / $ratio_wh);
6856			}
6857		}
6858		return array($w, $h, $x, $y);
6859	}
6860
6861	/**
6862	 * Puts an image in the page.
6863	 * The upper-left corner must be given.
6864	 * The dimensions can be specified in different ways:<ul>
6865	 * <li>explicit width and height (expressed in user unit)</li>
6866	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6867	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6868	 * 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;
6869	 * The format can be specified explicitly or inferred from the file extension.<br />
6870	 * It is possible to put a link on the image.<br />
6871	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6872	 * @param string $file Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6873	 * @param float $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6874	 * @param float $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6875	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6876	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6877	 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6878	 * @param mixed $link URL or identifier returned by AddLink().
6879	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6880	 * @param mixed $resize If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6881	 * @param int $dpi dot-per-inch resolution used on resize
6882	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6883	 * @param boolean $ismask true if this image is a mask, false otherwise
6884	 * @param mixed $imgmask image object returned by this function or false
6885	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6886	 * @param mixed $fitbox If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6887	 * @param boolean $hidden If true do not display the image.
6888	 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6889	 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6890	 * @param array $altimgs Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6891	 * @return mixed|false image information
6892	 * @public
6893	 * @since 1.1
6894	 */
6895	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()) {
6896		if ($this->state != 2) {
6897			return false;
6898		}
6899		if (strcmp($x, '') === 0) {
6900			$x = $this->x;
6901		}
6902		if (strcmp($y, '') === 0) {
6903			$y = $this->y;
6904		}
6905		// check page for no-write regions and adapt page margins if necessary
6906		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6907		$exurl = ''; // external streams
6908		$imsize = FALSE;
6909
6910        // Make sure the file variable is not empty or null because accessing $file[0] later
6911        // results in error when running PHP 7.4
6912        if (empty($file)) {
6913            return false;
6914        }
6915		// check if we are passing an image as file or string
6916		if ($file[0] === '@') {
6917			// image from string
6918			$imgdata = substr($file, 1);
6919		} else { // image file
6920			if ($file[0] === '*') {
6921				// image as external stream
6922				$file = substr($file, 1);
6923				$exurl = $file;
6924			}
6925			// check if file exist and it is valid
6926			if (!@$this->fileExists($file)) {
6927				return false;
6928			}
6929            if (false !== $info = $this->getImageBuffer($file)) {
6930                $imsize = array($info['w'], $info['h']);
6931            } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
6932                $imgdata = $this->getCachedFileContents($file);
6933            }
6934		}
6935		if (!empty($imgdata)) {
6936			// copy image to cache
6937			$original_file = $file;
6938			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6939			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6940			if (!$fp) {
6941				$this->Error('Unable to write file: '.$file);
6942			}
6943			fwrite($fp, $imgdata);
6944			fclose($fp);
6945			unset($imgdata);
6946			$imsize = @getimagesize($file);
6947			if ($imsize === FALSE) {
6948				unlink($file);
6949				$file = $original_file;
6950			}
6951		}
6952		if ($imsize === FALSE) {
6953			if (($w > 0) AND ($h > 0)) {
6954				// get measures from specified data
6955				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6956				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6957				$imsize = array($pw, $ph);
6958			} else {
6959				$this->Error('[Image] Unable to get the size of the image: '.$file);
6960			}
6961		}
6962		// file hash
6963		$filehash = md5($file);
6964		// get original image width and height in pixels
6965		list($pixw, $pixh) = $imsize;
6966		// calculate image width and height on document
6967		if (($w <= 0) AND ($h <= 0)) {
6968			// convert image size to document unit
6969			$w = $this->pixelsToUnits($pixw);
6970			$h = $this->pixelsToUnits($pixh);
6971		} elseif ($w <= 0) {
6972			$w = $h * $pixw / $pixh;
6973		} elseif ($h <= 0) {
6974			$h = $w * $pixh / $pixw;
6975		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6976			if (strlen($fitbox) !== 2) {
6977				// set default alignment
6978				$fitbox = '--';
6979			}
6980			// scale image dimensions proportionally to fit within the ($w, $h) box
6981			if ((($w * $pixh) / ($h * $pixw)) < 1) {
6982				// store current height
6983				$oldh = $h;
6984				// calculate new height
6985				$h = $w * $pixh / $pixw;
6986				// height difference
6987				$hdiff = ($oldh - $h);
6988				// vertical alignment
6989				switch (strtoupper($fitbox[1])) {
6990					case 'T': {
6991						break;
6992					}
6993					case 'M': {
6994						$y += ($hdiff / 2);
6995						break;
6996					}
6997					case 'B': {
6998						$y += $hdiff;
6999						break;
7000					}
7001				}
7002			} else {
7003				// store current width
7004				$oldw = $w;
7005				// calculate new width
7006				$w = $h * $pixw / $pixh;
7007				// width difference
7008				$wdiff = ($oldw - $w);
7009				// horizontal alignment
7010				switch (strtoupper($fitbox[0])) {
7011					case 'L': {
7012						if ($this->rtl) {
7013							$x -= $wdiff;
7014						}
7015						break;
7016					}
7017					case 'C': {
7018						if ($this->rtl) {
7019							$x -= ($wdiff / 2);
7020						} else {
7021							$x += ($wdiff / 2);
7022						}
7023						break;
7024					}
7025					case 'R': {
7026						if (!$this->rtl) {
7027							$x += $wdiff;
7028						}
7029						break;
7030					}
7031				}
7032			}
7033		}
7034		// fit the image on available space
7035		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7036		// calculate new minimum dimensions in pixels
7037		$neww = round($w * $this->k * $dpi / $this->dpi);
7038		$newh = round($h * $this->k * $dpi / $this->dpi);
7039		// check if resize is necessary (resize is used only to reduce the image)
7040		$newsize = ($neww * $newh);
7041		$pixsize = ($pixw * $pixh);
7042		if (intval($resize) == 2) {
7043			$resize = true;
7044		} elseif ($newsize >= $pixsize) {
7045			$resize = false;
7046		}
7047		// check if image has been already added on document
7048		$newimage = true;
7049		if (in_array($file, $this->imagekeys)) {
7050			$newimage = false;
7051			// get existing image data
7052			$info = $this->getImageBuffer($file);
7053			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7054				// check if the newer image is larger
7055				$oldsize = ($info['w'] * $info['h']);
7056				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7057					$newimage = true;
7058				}
7059			}
7060		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7061			// create temp image file (without alpha channel)
7062			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7063			// create temp alpha file
7064			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7065			// check for cached images
7066			if (in_array($tempfile_plain, $this->imagekeys)) {
7067				// get existing image data
7068				$info = $this->getImageBuffer($tempfile_plain);
7069				// check if the newer image is larger
7070				$oldsize = ($info['w'] * $info['h']);
7071				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7072					$newimage = true;
7073				} else {
7074					$newimage = false;
7075					// embed mask image
7076					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7077					// embed image, masked with previously embedded mask
7078					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7079				}
7080			}
7081		}
7082		if ($newimage) {
7083			//First use of image, get info
7084			$type = strtolower($type);
7085			if ($type == '') {
7086				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7087			} elseif ($type == 'jpg') {
7088				$type = 'jpeg';
7089			}
7090			$mqr = TCPDF_STATIC::get_mqr();
7091			TCPDF_STATIC::set_mqr(false);
7092			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7093			$mtd = '_parse'.$type;
7094			// GD image handler function
7095			$gdfunction = 'imagecreatefrom'.$type;
7096			$info = false;
7097			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7098				// TCPDF image functions
7099				$info = TCPDF_IMAGES::$mtd($file);
7100				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7101					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7102					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7103				}
7104			}
7105			if (($info === false) AND function_exists($gdfunction)) {
7106				try {
7107					// GD library
7108					$img = $gdfunction($file);
7109					if ($img !== false) {
7110						if ($resize) {
7111							$imgr = imagecreatetruecolor($neww, $newh);
7112							if (($type == 'gif') OR ($type == 'png')) {
7113								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7114							}
7115							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7116							$img = $imgr;
7117						}
7118						if (($type == 'gif') OR ($type == 'png')) {
7119							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7120						} else {
7121							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7122						}
7123					}
7124				} catch(Exception $e) {
7125					$info = false;
7126				}
7127			}
7128			if (($info === false) AND extension_loaded('imagick')) {
7129				try {
7130					// ImageMagick library
7131					$img = new Imagick();
7132					if ($type == 'svg') {
7133						if ($file[0] === '@') {
7134							// image from string
7135							$svgimg = substr($file, 1);
7136						} else {
7137							// get SVG file content
7138                            $svgimg = $this->getCachedFileContents($file);
7139						}
7140						if ($svgimg !== FALSE) {
7141							// get width and height
7142							$regs = array();
7143							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7144								$svgtag = $regs[1];
7145								$tmp = array();
7146								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7147									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7148									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7149									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7150								} else {
7151									$ow = $w;
7152								}
7153								$tmp = array();
7154								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7155									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7156									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7157									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7158								} else {
7159									$oh = $h;
7160								}
7161								$tmp = array();
7162								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7163									$vbw = ($ow * $this->imgscale * $this->k);
7164									$vbh = ($oh * $this->imgscale * $this->k);
7165									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7166									$svgtag = $vbox.$svgtag;
7167								}
7168								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7169							}
7170							$img->readImageBlob($svgimg);
7171						}
7172					} else {
7173						$img->readImage($file);
7174					}
7175					if ($resize) {
7176						$img->resizeImage($neww, $newh, 10, 1, false);
7177					}
7178					$img->setCompressionQuality($this->jpeg_quality);
7179					$img->setImageFormat('jpeg');
7180					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7181					$img->writeImage($tempname);
7182					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7183					unlink($tempname);
7184					$img->destroy();
7185				} catch(Exception $e) {
7186					$info = false;
7187				}
7188			}
7189			if ($info === false) {
7190				// unable to process image
7191				return false;
7192			}
7193			TCPDF_STATIC::set_mqr($mqr);
7194			if ($ismask) {
7195				// force grayscale
7196				$info['cs'] = 'DeviceGray';
7197			}
7198			if ($imgmask !== false) {
7199				$info['masked'] = $imgmask;
7200			}
7201			if (!empty($exurl)) {
7202				$info['exurl'] = $exurl;
7203			}
7204			// array of alternative images
7205			$info['altimgs'] = $altimgs;
7206			// add image to document
7207			$info['i'] = $this->setImageBuffer($file, $info);
7208		}
7209		// set alignment
7210		$this->img_rb_x = $x + $w;
7211		$this->img_rb_y = $y + $h;
7212
7213		// set alignment
7214		if ($palign == 'L') {
7215			$ximg = $this->lMargin;
7216		} elseif ($palign == 'C') {
7217			$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7218		} elseif ($palign == 'R') {
7219			$ximg = $this->w - $this->rMargin - $w;
7220		} else {
7221			$ximg = $x;
7222		}
7223
7224		if ($ismask OR $hidden) {
7225			// image is not displayed
7226			return $info['i'];
7227		}
7228		$xkimg = $ximg * $this->k;
7229		if (!$alt) {
7230			// only non-alternative immages will be set
7231			$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']));
7232		}
7233		if (!empty($border)) {
7234			$bx = $this->x;
7235			$by = $this->y;
7236			$this->x = $ximg;
7237			if ($this->rtl) {
7238				$this->x += $w;
7239			}
7240			$this->y = $y;
7241			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7242			$this->x = $bx;
7243			$this->y = $by;
7244		}
7245		if ($link) {
7246			$this->Link($ximg, $y, $w, $h, $link, 0);
7247		}
7248		// set pointer to align the next text/objects
7249		switch($align) {
7250			case 'T': {
7251				$this->y = $y;
7252				$this->x = $this->img_rb_x;
7253				break;
7254			}
7255			case 'M': {
7256				$this->y = $y + round($h/2);
7257				$this->x = $this->img_rb_x;
7258				break;
7259			}
7260			case 'B': {
7261				$this->y = $this->img_rb_y;
7262				$this->x = $this->img_rb_x;
7263				break;
7264			}
7265			case 'N': {
7266				$this->SetY($this->img_rb_y);
7267				break;
7268			}
7269			default:{
7270				break;
7271			}
7272		}
7273		$this->endlinex = $this->img_rb_x;
7274		if ($this->inxobj) {
7275			// we are inside an XObject template
7276			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7277		}
7278		return $info['i'];
7279	}
7280
7281	/**
7282	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7283	 * @param string $file Name of the file containing the image.
7284	 * @param float $x Abscissa of the upper-left corner.
7285	 * @param float $y Ordinate of the upper-left corner.
7286	 * @param float $wpx Original width of the image in pixels.
7287	 * @param float $hpx original height of the image in pixels.
7288	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7289	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7290	 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7291	 * @param mixed $link URL or identifier returned by AddLink().
7292	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7293	 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7294	 * @param int $dpi dot-per-inch resolution used on resize
7295	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7296	 * @param string $filehash File hash used to build unique file names.
7297	 * @author Nicola Asuni
7298	 * @protected
7299	 * @since 4.3.007 (2008-12-04)
7300	 * @see Image()
7301	 */
7302	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7303		// create temp images
7304		if (empty($filehash)) {
7305			$filehash = md5($file);
7306		}
7307		// create temp image file (without alpha channel)
7308		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7309		// create temp alpha file
7310		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7311		$parsed = false;
7312		$parse_error = '';
7313		// ImageMagick extension
7314		if (($parsed === false) AND extension_loaded('imagick')) {
7315			try {
7316				// ImageMagick library
7317				$img = new Imagick();
7318				$img->readImage($file);
7319				// clone image object
7320				$imga = TCPDF_STATIC::objclone($img);
7321				// extract alpha channel
7322				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7323					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7324				} else {
7325					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7326					$img->negateImage(true);
7327				}
7328				$img->setImageFormat('png');
7329				$img->writeImage($tempfile_alpha);
7330				// remove alpha channel
7331				if (method_exists($imga, 'setImageMatte')) {
7332					$imga->setImageMatte(false);
7333				} else {
7334					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7335				}
7336				$imga->setImageFormat('png');
7337				$imga->writeImage($tempfile_plain);
7338				$parsed = true;
7339			} catch (Exception $e) {
7340				// Imagemagick fails, try with GD
7341				$parse_error = 'Imagick library error: '.$e->getMessage();
7342			}
7343		}
7344		// GD extension
7345		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7346			try {
7347				// generate images
7348				$img = imagecreatefrompng($file);
7349				$imgalpha = imagecreate($wpx, $hpx);
7350				// generate gray scale palette (0 -> 255)
7351				for ($c = 0; $c < 256; ++$c) {
7352					ImageColorAllocate($imgalpha, $c, $c, $c);
7353				}
7354				// extract alpha channel
7355				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7356					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7357						$color = imagecolorat($img, $xpx, $ypx);
7358						// get and correct gamma color
7359						$alpha = $this->getGDgamma($img, $color);
7360						imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7361					}
7362				}
7363				imagepng($imgalpha, $tempfile_alpha);
7364				imagedestroy($imgalpha);
7365				// extract image without alpha channel
7366				$imgplain = imagecreatetruecolor($wpx, $hpx);
7367				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7368				imagepng($imgplain, $tempfile_plain);
7369				imagedestroy($imgplain);
7370				$parsed = true;
7371			} catch (Exception $e) {
7372				// GD fails
7373				$parse_error = 'GD library error: '.$e->getMessage();
7374			}
7375		}
7376		if ($parsed === false) {
7377			if (empty($parse_error)) {
7378				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7379			} else {
7380				$this->Error($parse_error);
7381			}
7382		}
7383		// embed mask image
7384		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7385		// embed image, masked with previously embedded mask
7386		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7387	}
7388
7389	/**
7390	 * Get the GD-corrected PNG gamma value from alpha color
7391	 * @param resource $img GD image Resource ID.
7392	 * @param int $c alpha color
7393	 * @protected
7394	 * @since 4.3.007 (2008-12-04)
7395	 */
7396	protected function getGDgamma($img, $c) {
7397		if (!isset($this->gdgammacache['#'.$c])) {
7398			$colors = imagecolorsforindex($img, $c);
7399			// GD alpha is only 7 bit (0 -> 127)
7400			$this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7401			// correct gamma
7402			$this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7403			// store the latest values on cache to improve performances
7404			if (count($this->gdgammacache) > 8) {
7405				// remove one element from the cache array
7406				array_shift($this->gdgammacache);
7407			}
7408		}
7409		return $this->gdgammacache['#'.$c];
7410	}
7411
7412	/**
7413	 * Performs a line break.
7414	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7415	 * @param float $h The height of the break. By default, the value equals the height of the last printed cell.
7416	 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7417	 * @public
7418	 * @since 1.0
7419	 * @see Cell()
7420	 */
7421	public function Ln($h='', $cell=false) {
7422		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'])) {
7423			// revove vertical space from the top of the column
7424			return;
7425		}
7426		if ($cell) {
7427			if ($this->rtl) {
7428				$cellpadding = $this->cell_padding['R'];
7429			} else {
7430				$cellpadding = $this->cell_padding['L'];
7431			}
7432		} else {
7433			$cellpadding = 0;
7434		}
7435		if ($this->rtl) {
7436			$this->x = $this->w - $this->rMargin - $cellpadding;
7437		} else {
7438			$this->x = $this->lMargin + $cellpadding;
7439		}
7440		if (is_string($h)) {
7441			$h = $this->lasth;
7442		}
7443		$this->y += $h;
7444		$this->newline = true;
7445	}
7446
7447	/**
7448	 * Returns the relative X value of current position.
7449	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7450	 * @return float
7451	 * @public
7452	 * @since 1.2
7453	 * @see SetX(), GetY(), SetY()
7454	 */
7455	public function GetX() {
7456		//Get x position
7457		if ($this->rtl) {
7458			return ($this->w - $this->x);
7459		} else {
7460			return $this->x;
7461		}
7462	}
7463
7464	/**
7465	 * Returns the absolute X value of current position.
7466	 * @return float
7467	 * @public
7468	 * @since 1.2
7469	 * @see SetX(), GetY(), SetY()
7470	 */
7471	public function GetAbsX() {
7472		return $this->x;
7473	}
7474
7475	/**
7476	 * Returns the ordinate of the current position.
7477	 * @return float
7478	 * @public
7479	 * @since 1.0
7480	 * @see SetY(), GetX(), SetX()
7481	 */
7482	public function GetY() {
7483		return $this->y;
7484	}
7485
7486	/**
7487	 * Defines the abscissa of the current position.
7488	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7489	 * @param float $x The value of the abscissa in user units.
7490	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7491	 * @public
7492	 * @since 1.2
7493	 * @see GetX(), GetY(), SetY(), SetXY()
7494	 */
7495	public function SetX($x, $rtloff=false) {
7496		$x = floatval($x);
7497		if (!$rtloff AND $this->rtl) {
7498			if ($x >= 0) {
7499				$this->x = $this->w - $x;
7500			} else {
7501				$this->x = abs($x);
7502			}
7503		} else {
7504			if ($x >= 0) {
7505				$this->x = $x;
7506			} else {
7507				$this->x = $this->w + $x;
7508			}
7509		}
7510		if ($this->x < 0) {
7511			$this->x = 0;
7512		}
7513		if ($this->x > $this->w) {
7514			$this->x = $this->w;
7515		}
7516	}
7517
7518	/**
7519	 * Moves the current abscissa back to the left margin and sets the ordinate.
7520	 * If the passed value is negative, it is relative to the bottom of the page.
7521	 * @param float $y The value of the ordinate in user units.
7522	 * @param bool $resetx if true (default) reset the X position.
7523	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7524	 * @public
7525	 * @since 1.0
7526	 * @see GetX(), GetY(), SetY(), SetXY()
7527	 */
7528	public function SetY($y, $resetx=true, $rtloff=false) {
7529		$y = floatval($y);
7530		if ($resetx) {
7531			//reset x
7532			if (!$rtloff AND $this->rtl) {
7533				$this->x = $this->w - $this->rMargin;
7534			} else {
7535				$this->x = $this->lMargin;
7536			}
7537		}
7538		if ($y >= 0) {
7539			$this->y = $y;
7540		} else {
7541			$this->y = $this->h + $y;
7542		}
7543		if ($this->y < 0) {
7544			$this->y = 0;
7545		}
7546		if ($this->y > $this->h) {
7547			$this->y = $this->h;
7548		}
7549	}
7550
7551	/**
7552	 * Defines the abscissa and ordinate of the current position.
7553	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7554	 * @param float $x The value of the abscissa.
7555	 * @param float $y The value of the ordinate.
7556	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7557	 * @public
7558	 * @since 1.2
7559	 * @see SetX(), SetY()
7560	 */
7561	public function SetXY($x, $y, $rtloff=false) {
7562		$this->SetY($y, false, $rtloff);
7563		$this->SetX($x, $rtloff);
7564	}
7565
7566	/**
7567	 * Set the absolute X coordinate of the current pointer.
7568	 * @param float $x The value of the abscissa in user units.
7569	 * @public
7570	 * @since 5.9.186 (2012-09-13)
7571	 * @see setAbsX(), setAbsY(), SetAbsXY()
7572	 */
7573	public function SetAbsX($x) {
7574		$this->x = floatval($x);
7575	}
7576
7577	/**
7578	 * Set the absolute Y coordinate of the current pointer.
7579	 * @param float $y (float) The value of the ordinate in user units.
7580	 * @public
7581	 * @since 5.9.186 (2012-09-13)
7582	 * @see setAbsX(), setAbsY(), SetAbsXY()
7583	 */
7584	public function SetAbsY($y) {
7585		$this->y = floatval($y);
7586	}
7587
7588	/**
7589	 * Set the absolute X and Y coordinates of the current pointer.
7590	 * @param float $x The value of the abscissa in user units.
7591	 * @param float $y (float) The value of the ordinate in user units.
7592	 * @public
7593	 * @since 5.9.186 (2012-09-13)
7594	 * @see setAbsX(), setAbsY(), SetAbsXY()
7595	 */
7596	public function SetAbsXY($x, $y) {
7597		$this->SetAbsX($x);
7598		$this->SetAbsY($y);
7599	}
7600
7601	/**
7602	 * Send the document to a given destination: string, local file or browser.
7603	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7604	 * The method first calls Close() if necessary to terminate the document.
7605	 * @param string $name The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
7606	 * @param string $dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7607	 * @return string
7608	 * @public
7609	 * @since 1.0
7610	 * @see Close()
7611	 */
7612	public function Output($name='doc.pdf', $dest='I') {
7613		//Output PDF to some destination
7614		//Finish document if necessary
7615		if ($this->state < 3) {
7616			$this->Close();
7617		}
7618		//Normalize parameters
7619		if (is_bool($dest)) {
7620			$dest = $dest ? 'D' : 'F';
7621		}
7622		$dest = strtoupper($dest);
7623		if ($dest[0] != 'F') {
7624			$name = preg_replace('/[\s]+/', '_', $name);
7625			$name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7626		}
7627		if ($this->sign) {
7628			// *** apply digital signature to the document ***
7629			// get the document content
7630			$pdfdoc = $this->getBuffer();
7631			// remove last newline
7632			$pdfdoc = substr($pdfdoc, 0, -1);
7633			// remove filler space
7634			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7635			// define the ByteRange
7636			$byte_range = array();
7637			$byte_range[0] = 0;
7638			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7639			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7640			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7641			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7642			// replace the ByteRange
7643			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7644			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7645			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7646			// write the document to a temporary folder
7647			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7648			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7649			if (!$f) {
7650				$this->Error('Unable to create temporary file: '.$tempdoc);
7651			}
7652			$pdfdoc_length = strlen($pdfdoc);
7653			fwrite($f, $pdfdoc, $pdfdoc_length);
7654			fclose($f);
7655			// get digital signature via openssl library
7656			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7657			if (empty($this->signature_data['extracerts'])) {
7658				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7659			} else {
7660				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']);
7661			}
7662			// read signature
7663			$signature = file_get_contents($tempsign);
7664			// extract signature
7665			$signature = substr($signature, $pdfdoc_length);
7666			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7667			$tmparr = explode("\n\n", $signature);
7668			$signature = $tmparr[1];
7669			// decode signature
7670			$signature = base64_decode(trim($signature));
7671			// add TSA timestamp to signature
7672			$signature = $this->applyTSA($signature);
7673			// convert signature to hex
7674			$signature = current(unpack('H*', $signature));
7675			$signature = str_pad($signature, $this->signature_max_length, '0');
7676			// Add signature to the document
7677			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7678			$this->bufferlen = strlen($this->buffer);
7679		}
7680		switch($dest) {
7681			case 'I': {
7682				// Send PDF to the standard output
7683				if (ob_get_contents()) {
7684					$this->Error('Some data has already been output, can\'t send PDF file');
7685				}
7686				if (php_sapi_name() != 'cli') {
7687					// send output to a browser
7688					header('Content-Type: application/pdf');
7689					if (headers_sent()) {
7690						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7691					}
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($this->getBuffer(), $this->bufferlen);
7699				} else {
7700					echo $this->getBuffer();
7701				}
7702				break;
7703			}
7704			case 'D': {
7705				// download PDF as file
7706				if (ob_get_contents()) {
7707					$this->Error('Some data has already been output, can\'t send PDF file');
7708				}
7709				header('Content-Description: File Transfer');
7710				if (headers_sent()) {
7711					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7712				}
7713				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7714				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7715				header('Pragma: public');
7716				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7717				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7718				// force download dialog
7719				if (strpos(php_sapi_name(), 'cgi') === false) {
7720					header('Content-Type: application/force-download');
7721					header('Content-Type: application/octet-stream', false);
7722					header('Content-Type: application/download', false);
7723					header('Content-Type: application/pdf', false);
7724				} else {
7725					header('Content-Type: application/pdf');
7726				}
7727				// use the Content-Disposition header to supply a recommended filename
7728				header('Content-Disposition: attachment; filename="'.basename($name).'"');
7729				header('Content-Transfer-Encoding: binary');
7730				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7731				break;
7732			}
7733			case 'F':
7734			case 'FI':
7735			case 'FD': {
7736				// save PDF to a local file
7737				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7738				if (!$f) {
7739					$this->Error('Unable to create output file: '.$name);
7740				}
7741				fwrite($f, $this->getBuffer(), $this->bufferlen);
7742				fclose($f);
7743				if ($dest == 'FI') {
7744					// send headers to browser
7745					header('Content-Type: application/pdf');
7746					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7747					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7748					header('Pragma: public');
7749					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7750					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7751					header('Content-Disposition: inline; filename="'.basename($name).'"');
7752					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7753				} elseif ($dest == 'FD') {
7754					// send headers to browser
7755					if (ob_get_contents()) {
7756						$this->Error('Some data has already been output, can\'t send PDF file');
7757					}
7758					header('Content-Description: File Transfer');
7759					if (headers_sent()) {
7760						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7761					}
7762					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7763					header('Pragma: public');
7764					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7765					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7766					// force download dialog
7767					if (strpos(php_sapi_name(), 'cgi') === false) {
7768						header('Content-Type: application/force-download');
7769						header('Content-Type: application/octet-stream', false);
7770						header('Content-Type: application/download', false);
7771						header('Content-Type: application/pdf', false);
7772					} else {
7773						header('Content-Type: application/pdf');
7774					}
7775					// use the Content-Disposition header to supply a recommended filename
7776					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7777					header('Content-Transfer-Encoding: binary');
7778					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7779				}
7780				break;
7781			}
7782			case 'E': {
7783				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7784				$retval = 'Content-Type: application/pdf;'."\r\n";
7785				$retval .= ' name="'.$name.'"'."\r\n";
7786				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7787				$retval .= 'Content-Disposition: attachment;'."\r\n";
7788				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7789				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7790				return $retval;
7791			}
7792			case 'S': {
7793				// returns PDF as a string
7794				return $this->getBuffer();
7795			}
7796			default: {
7797				$this->Error('Incorrect output destination: '.$dest);
7798			}
7799		}
7800		return '';
7801	}
7802
7803	protected static $cleaned_ids = array();
7804	/**
7805	 * Unset all class variables except the following critical variables.
7806	 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7807	 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7808	 * @public
7809	 * @since 4.5.016 (2009-02-24)
7810	 */
7811	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7812		if (isset(self::$cleaned_ids[$this->file_id])) {
7813			$destroyall = false;
7814		}
7815		if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7816			self::$cleaned_ids[$this->file_id] = true;
7817			// remove all temporary files
7818			if ($handle = @opendir(K_PATH_CACHE)) {
7819				while ( false !== ( $file_name = readdir( $handle ) ) ) {
7820					if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7821						unlink(K_PATH_CACHE.$file_name);
7822					}
7823				}
7824				closedir($handle);
7825			}
7826			if (isset($this->imagekeys)) {
7827				foreach($this->imagekeys as $file) {
7828					if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
7829						@unlink($file);
7830					}
7831				}
7832			}
7833		}
7834		$preserve = array(
7835			'file_id',
7836			'state',
7837			'bufferlen',
7838			'buffer',
7839			'cached_files',
7840			'imagekeys',
7841			'sign',
7842			'signature_data',
7843			'signature_max_length',
7844			'byterange_string',
7845			'tsa_timestamp',
7846			'tsa_data'
7847		);
7848		foreach (array_keys(get_object_vars($this)) as $val) {
7849			if ($destroyall OR !in_array($val, $preserve)) {
7850				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7851					unset($this->$val);
7852				}
7853			}
7854		}
7855	}
7856
7857	/**
7858	 * Check for locale-related bug
7859	 * @protected
7860	 */
7861	protected function _dochecks() {
7862		//Check for locale-related bug
7863		if (1.1 == 1) {
7864			$this->Error('Don\'t alter the locale before including class file');
7865		}
7866		//Check for decimal separator
7867		if (sprintf('%.1F', 1.0) != '1.0') {
7868			setlocale(LC_NUMERIC, 'C');
7869		}
7870	}
7871
7872	/**
7873	 * Return an array containing variations for the basic page number alias.
7874	 * @param string $a Base alias.
7875	 * @return array of page number aliases
7876	 * @protected
7877	 */
7878	protected function getInternalPageNumberAliases($a= '') {
7879		$alias = array();
7880		// build array of Unicode + ASCII variants (the order is important)
7881		$alias = array('u' => array(), 'a' => array());
7882		$u = '{'.$a.'}';
7883		$alias['u'][] = TCPDF_STATIC::_escape($u);
7884		if ($this->isunicode) {
7885			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7886			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7887			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7888			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7889		}
7890		$alias['a'][] = TCPDF_STATIC::_escape($a);
7891		return $alias;
7892	}
7893
7894	/**
7895	 * Return an array containing all internal page aliases.
7896	 * @return array of page number aliases
7897	 * @protected
7898	 */
7899	protected function getAllInternalPageNumberAliases() {
7900		$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);
7901		$pnalias = array();
7902		foreach($basic_alias as $k => $a) {
7903			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7904		}
7905		return $pnalias;
7906	}
7907
7908	/**
7909	 * Replace right shift page number aliases with spaces to correct right alignment.
7910	 * This works perfectly only when using monospaced fonts.
7911	 * @param string $page Page content.
7912	 * @param array $aliases Array of page aliases.
7913	 * @param int $diff initial difference to add.
7914	 * @return string replaced page content.
7915	 * @protected
7916	 */
7917	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7918		foreach ($aliases as $type => $alias) {
7919			foreach ($alias as $a) {
7920				// find position of compensation factor
7921				$startnum = (strpos($a, ':') + 1);
7922				$a = substr($a, 0, $startnum);
7923				if (($pos = strpos($page, $a)) !== false) {
7924					// end of alias
7925					$endnum = strpos($page, '}', $pos);
7926					// string to be replaced
7927					$aa = substr($page, $pos, ($endnum - $pos + 1));
7928					// get compensation factor
7929					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7930					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7931					$ratio = floatval($ratio);
7932					if ($type == 'u') {
7933						$chrdiff = floor(($diff + 12) * $ratio);
7934						$shift = str_repeat(' ', $chrdiff);
7935						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7936					} else {
7937						$chrdiff = floor(($diff + 11) * $ratio);
7938						$shift = str_repeat(' ', $chrdiff);
7939					}
7940					$page = str_replace($aa, $shift, $page);
7941				}
7942			}
7943		}
7944		return $page;
7945	}
7946
7947	/**
7948	 * Set page boxes to be included on page descriptions.
7949	 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7950	 * @protected
7951	 */
7952	protected function setPageBoxTypes($boxes) {
7953		$this->page_boxes = array();
7954		foreach ($boxes as $box) {
7955			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7956				$this->page_boxes[] = $box;
7957			}
7958		}
7959	}
7960
7961	/**
7962	 * Output pages (and replace page number aliases).
7963	 * @protected
7964	 */
7965	protected function _putpages() {
7966		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7967		// get internal aliases for page numbers
7968		$pnalias = $this->getAllInternalPageNumberAliases();
7969		$num_pages = $this->numpages;
7970		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7971		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7972		$ptp_num_chars = $this->GetNumChars($ptpa);
7973		$pagegroupnum = 0;
7974		$groupnum = 0;
7975		$ptgu = 1;
7976		$ptga = 1;
7977		$ptg_num_chars = 1;
7978		for ($n = 1; $n <= $num_pages; ++$n) {
7979			// get current page
7980			$temppage = $this->getPageBuffer($n);
7981			$pagelen = strlen($temppage);
7982			// set replacements for total pages number
7983			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7984			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7985			$pnp_num_chars = $this->GetNumChars($pnpa);
7986			$pdiff = 0; // difference used for right shift alignment of page numbers
7987			$gdiff = 0; // difference used for right shift alignment of page group numbers
7988			if (!empty($this->pagegroups)) {
7989				if (isset($this->newpagegroup[$n])) {
7990					$pagegroupnum = 0;
7991					++$groupnum;
7992					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7993					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7994					$ptg_num_chars = $this->GetNumChars($ptga);
7995				}
7996				++$pagegroupnum;
7997				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7998				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7999				$png_num_chars = $this->GetNumChars($pnga);
8000				// replace page numbers
8001				$replace = array();
8002				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8003				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8004				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8005				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8006				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8007			}
8008			// replace page numbers
8009			$replace = array();
8010			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8011			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8012			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8013			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8014			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8015			// replace right shift alias
8016			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8017			// replace EPS marker
8018			$temppage = str_replace($this->epsmarker, '', $temppage);
8019			//Page
8020			$this->page_obj_id[$n] = $this->_newobj();
8021			$out = '<<';
8022			$out .= ' /Type /Page';
8023			$out .= ' /Parent 1 0 R';
8024			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8025				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8026			}
8027			$out .= ' /Resources 2 0 R';
8028			foreach ($this->page_boxes as $box) {
8029				$out .= ' /'.$box;
8030				$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']);
8031			}
8032			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8033				$out .= ' /BoxColorInfo <<';
8034				foreach ($this->page_boxes as $box) {
8035					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8036						$out .= ' /'.$box.' <<';
8037						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8038							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8039							$out .= ' /C [';
8040							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8041							$out .= ' ]';
8042						}
8043						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8044							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8045						}
8046						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8047							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8048						}
8049						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8050							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8051							$out .= ' /D [';
8052							foreach ($dashes as $dash) {
8053								$out .= sprintf(' %F', ($dash * $this->k));
8054							}
8055							$out .= ' ]';
8056						}
8057						$out .= ' >>';
8058					}
8059				}
8060				$out .= ' >>';
8061			}
8062			$out .= ' /Contents '.($this->n + 1).' 0 R';
8063			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8064			if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
8065				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8066			}
8067			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8068				// page transitions
8069				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8070					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8071				}
8072				$out .= ' /Trans <<';
8073				$out .= ' /Type /Trans';
8074				if (isset($this->pagedim[$n]['trans']['S'])) {
8075					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8076				}
8077				if (isset($this->pagedim[$n]['trans']['D'])) {
8078					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8079				}
8080				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8081					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8082				}
8083				if (isset($this->pagedim[$n]['trans']['M'])) {
8084					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8085				}
8086				if (isset($this->pagedim[$n]['trans']['Di'])) {
8087					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8088				}
8089				if (isset($this->pagedim[$n]['trans']['SS'])) {
8090					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8091				}
8092				if (isset($this->pagedim[$n]['trans']['B'])) {
8093					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8094				}
8095				$out .= ' >>';
8096			}
8097			$out .= $this->_getannotsrefs($n);
8098			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8099			$out .= ' >>';
8100			$out .= "\n".'endobj';
8101			$this->_out($out);
8102			//Page content
8103			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8104			$this->_newobj();
8105			$p = $this->_getrawstream($p);
8106			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8107		}
8108		//Pages root
8109		$out = $this->_getobj(1)."\n";
8110		$out .= '<< /Type /Pages /Kids [';
8111		foreach($this->page_obj_id as $page_obj) {
8112			$out .= ' '.$page_obj.' 0 R';
8113		}
8114		$out .= ' ] /Count '.$num_pages.' >>';
8115		$out .= "\n".'endobj';
8116		$this->_out($out);
8117	}
8118
8119	/**
8120	 * Get references to page annotations.
8121	 * @param int $n page number
8122	 * @return string
8123	 * @protected
8124	 * @author Nicola Asuni
8125	 * @since 5.0.010 (2010-05-17)
8126	 */
8127	protected function _getannotsrefs($n) {
8128		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8129			return '';
8130		}
8131		$out = ' /Annots [';
8132		if (isset($this->PageAnnots[$n])) {
8133			foreach ($this->PageAnnots[$n] as $key => $val) {
8134				if (!in_array($val['n'], $this->radio_groups)) {
8135					$out .= ' '.$val['n'].' 0 R';
8136				}
8137			}
8138			// add radiobutton groups
8139			if (isset($this->radiobutton_groups[$n])) {
8140				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8141					if (isset($data['n'])) {
8142						$out .= ' '.$data['n'].' 0 R';
8143					}
8144				}
8145			}
8146		}
8147		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8148			// set reference for signature object
8149			$out .= ' '.$this->sig_obj_id.' 0 R';
8150		}
8151		if (!empty($this->empty_signature_appearance)) {
8152			foreach ($this->empty_signature_appearance as $esa) {
8153				if ($esa['page'] == $n) {
8154					// set reference for empty signature objects
8155					$out .= ' '.$esa['objid'].' 0 R';
8156				}
8157			}
8158		}
8159		$out .= ' ]';
8160		return $out;
8161	}
8162
8163	/**
8164	 * Output annotations objects for all pages.
8165	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8166	 * See section 12.5 of PDF 32000_2008 reference.
8167	 * @protected
8168	 * @author Nicola Asuni
8169	 * @since 4.0.018 (2008-08-06)
8170	 */
8171	protected function _putannotsobjs() {
8172		// reset object counter
8173		for ($n=1; $n <= $this->numpages; ++$n) {
8174			if (isset($this->PageAnnots[$n])) {
8175				// set page annotations
8176				foreach ($this->PageAnnots[$n] as $key => $pl) {
8177					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8178					// create annotation object for grouping radiobuttons
8179					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8180						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8181						$annots = '<<';
8182						$annots .= ' /Type /Annot';
8183						$annots .= ' /Subtype /Widget';
8184						$annots .= ' /Rect [0 0 0 0]';
8185						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8186							// read only
8187							$annots .= ' /F 68';
8188							$annots .= ' /Ff 49153';
8189						} else {
8190							$annots .= ' /F 4'; // default print for PDF/A
8191							$annots .= ' /Ff 49152';
8192						}
8193						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8194						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8195							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8196						}
8197						$annots .= ' /FT /Btn';
8198						$annots .= ' /Kids [';
8199						$defval = '';
8200						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8201							if (isset($data['kid'])) {
8202								$annots .= ' '.$data['kid'].' 0 R';
8203								if ($data['def'] !== 'Off') {
8204									$defval = $data['def'];
8205								}
8206							}
8207						}
8208						$annots .= ' ]';
8209						if (!empty($defval)) {
8210							$annots .= ' /V /'.$defval;
8211						}
8212						$annots .= ' >>';
8213						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8214						$this->form_obj_id[] = $radio_button_obj_id;
8215						// store object id to be used on Parent entry of Kids
8216						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8217					}
8218					$formfield = false;
8219					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8220					$a = $pl['x'] * $this->k;
8221					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8222					$c = $pl['w'] * $this->k;
8223					$d = $pl['h'] * $this->k;
8224					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8225					// create new annotation object
8226					$annots = '<</Type /Annot';
8227					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8228					$annots .= ' /Rect ['.$rect.']';
8229					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8230					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8231						$annots .= ' /FT /'.$pl['opt']['ft'];
8232						$formfield = true;
8233					}
8234					if ($pl['opt']['subtype'] !== 'Link') {
8235						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8236					}
8237					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8238					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8239					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8240					if (isset($pl['opt']['f'])) {
8241						$fval = 0;
8242						if (is_array($pl['opt']['f'])) {
8243							foreach ($pl['opt']['f'] as $f) {
8244								switch (strtolower($f)) {
8245									case 'invisible': {
8246										$fval += 1 << 0;
8247										break;
8248									}
8249									case 'hidden': {
8250										$fval += 1 << 1;
8251										break;
8252									}
8253									case 'print': {
8254										$fval += 1 << 2;
8255										break;
8256									}
8257									case 'nozoom': {
8258										$fval += 1 << 3;
8259										break;
8260									}
8261									case 'norotate': {
8262										$fval += 1 << 4;
8263										break;
8264									}
8265									case 'noview': {
8266										$fval += 1 << 5;
8267										break;
8268									}
8269									case 'readonly': {
8270										$fval += 1 << 6;
8271										break;
8272									}
8273									case 'locked': {
8274										$fval += 1 << 8;
8275										break;
8276									}
8277									case 'togglenoview': {
8278										$fval += 1 << 9;
8279										break;
8280									}
8281									case 'lockedcontents': {
8282										$fval += 1 << 10;
8283										break;
8284									}
8285									default: {
8286										break;
8287									}
8288								}
8289							}
8290						} else {
8291							$fval = intval($pl['opt']['f']);
8292						}
8293					} else {
8294						$fval = 4;
8295					}
8296					if ($this->pdfa_mode) {
8297						// force print flag for PDF/A mode
8298						$fval |= 4;
8299					}
8300					$annots .= ' /F '.intval($fval);
8301					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8302						$annots .= ' /AS /'.$pl['opt']['as'];
8303					}
8304					if (isset($pl['opt']['ap'])) {
8305						// appearance stream
8306						$annots .= ' /AP <<';
8307						if (is_array($pl['opt']['ap'])) {
8308							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8309								// $apmode can be: n = normal; r = rollover; d = down;
8310								$annots .= ' /'.strtoupper($apmode);
8311								if (is_array($apdef)) {
8312									$annots .= ' <<';
8313									foreach ($apdef as $apstate => $stream) {
8314										// reference to XObject that define the appearance for this mode-state
8315										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8316										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8317									}
8318									$annots .= ' >>';
8319								} else {
8320									// reference to XObject that define the appearance for this mode
8321									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8322									$annots .= ' '.$apsobjid.' 0 R';
8323								}
8324							}
8325						} else {
8326							$annots .= $pl['opt']['ap'];
8327						}
8328						$annots .= ' >>';
8329					}
8330					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8331						$annots .= ' /BS <<';
8332						$annots .= ' /Type /Border';
8333						if (isset($pl['opt']['bs']['w'])) {
8334							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8335						}
8336						$bstyles = array('S', 'D', 'B', 'I', 'U');
8337						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8338							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8339						}
8340						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8341							$annots .= ' /D [';
8342							foreach ($pl['opt']['bs']['d'] as $cord) {
8343								$annots .= ' '.intval($cord);
8344							}
8345							$annots .= ']';
8346						}
8347						$annots .= ' >>';
8348					} else {
8349						$annots .= ' /Border [';
8350						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8351							$annots .= intval($pl['opt']['border'][0]).' ';
8352							$annots .= intval($pl['opt']['border'][1]).' ';
8353							$annots .= intval($pl['opt']['border'][2]);
8354							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8355								$annots .= ' [';
8356								foreach ($pl['opt']['border'][3] as $dash) {
8357									$annots .= intval($dash).' ';
8358								}
8359								$annots .= ']';
8360							}
8361						} else {
8362							$annots .= '0 0 0';
8363						}
8364						$annots .= ']';
8365					}
8366					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8367						$annots .= ' /BE <<';
8368						$bstyles = array('S', 'C');
8369						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8370							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8371						} else {
8372							$annots .= ' /S /S';
8373						}
8374						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8375							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8376						}
8377						$annots .= '>>';
8378					}
8379					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8380						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8381					}
8382					//$annots .= ' /StructParent ';
8383					//$annots .= ' /OC ';
8384					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8385					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8386						// this is a markup type
8387						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8388							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8389						}
8390						//$annots .= ' /Popup ';
8391						if (isset($pl['opt']['ca'])) {
8392							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8393						}
8394						if (isset($pl['opt']['rc'])) {
8395							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8396						}
8397						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8398						//$annots .= ' /IRT ';
8399						if (isset($pl['opt']['subj'])) {
8400							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8401						}
8402						//$annots .= ' /RT ';
8403						//$annots .= ' /IT ';
8404						//$annots .= ' /ExData ';
8405					}
8406					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8407					// Annotation types
8408					switch (strtolower($pl['opt']['subtype'])) {
8409						case 'text': {
8410							if (isset($pl['opt']['open'])) {
8411								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8412							}
8413							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8414							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8415								$annots .= ' /Name /'.$pl['opt']['name'];
8416							} else {
8417								$annots .= ' /Name /Note';
8418							}
8419							$statemodels = array('Marked', 'Review');
8420							if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8421								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8422							} else {
8423								$pl['opt']['statemodel'] = 'Marked';
8424								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8425							}
8426							if ($pl['opt']['statemodel'] == 'Marked') {
8427								$states = array('Accepted', 'Unmarked');
8428							} else {
8429								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8430							}
8431							if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8432								$annots .= ' /State /'.$pl['opt']['state'];
8433							} else {
8434								if ($pl['opt']['statemodel'] == 'Marked') {
8435									$annots .= ' /State /Unmarked';
8436								} else {
8437									$annots .= ' /State /None';
8438								}
8439							}
8440							break;
8441						}
8442						case 'link': {
8443							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8444								if ($pl['txt'][0] == '#') {
8445									// internal destination
8446									$annots .= ' /A <</S /GoTo /D '.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8447								} elseif ($pl['txt'][0] == '%') {
8448									// embedded PDF file
8449									$filename = basename(substr($pl['txt'], 1));
8450									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8451								} elseif ($pl['txt'][0] == '*') {
8452									// embedded generic file
8453									$filename = basename(substr($pl['txt'], 1));
8454									$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});';
8455									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8456								} else {
8457									$parsedUrl = parse_url($pl['txt']);
8458									if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8459										// relative link to a PDF file
8460										$dest = '[0 /Fit]'; // default page 0
8461										if (!empty($parsedUrl['fragment'])) {
8462											// check for named destination
8463											$tmp = explode('=', $parsedUrl['fragment']);
8464											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8465										}
8466										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8467									} else {
8468										// external URI link
8469										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8470									}
8471								}
8472							} elseif (isset($this->links[$pl['txt']])) {
8473								// internal link ID
8474								$l = $this->links[$pl['txt']];
8475								if (isset($this->page_obj_id[($l['p'])])) {
8476									$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)));
8477								}
8478							}
8479							$hmodes = array('N', 'I', 'O', 'P');
8480							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8481								$annots .= ' /H /'.$pl['opt']['h'];
8482							} else {
8483								$annots .= ' /H /I';
8484							}
8485							//$annots .= ' /PA ';
8486							//$annots .= ' /Quadpoints ';
8487							break;
8488						}
8489						case 'freetext': {
8490							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8491								$annots .= ' /DA ('.$pl['opt']['da'].')';
8492							}
8493							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8494								$annots .= ' /Q '.intval($pl['opt']['q']);
8495							}
8496							if (isset($pl['opt']['rc'])) {
8497								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8498							}
8499							if (isset($pl['opt']['ds'])) {
8500								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8501							}
8502							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8503								$annots .= ' /CL [';
8504								foreach ($pl['opt']['cl'] as $cl) {
8505									$annots .= sprintf('%F ', $cl * $this->k);
8506								}
8507								$annots .= ']';
8508							}
8509							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8510							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8511								$annots .= ' /IT /'.$pl['opt']['it'];
8512							}
8513							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8514								$l = $pl['opt']['rd'][0] * $this->k;
8515								$r = $pl['opt']['rd'][1] * $this->k;
8516								$t = $pl['opt']['rd'][2] * $this->k;
8517								$b = $pl['opt']['rd'][3] * $this->k;
8518								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8519							}
8520							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8521								$annots .= ' /LE /'.$pl['opt']['le'];
8522							}
8523							break;
8524						}
8525						case 'line': {
8526							break;
8527						}
8528						case 'square': {
8529							break;
8530						}
8531						case 'circle': {
8532							break;
8533						}
8534						case 'polygon': {
8535							break;
8536						}
8537						case 'polyline': {
8538							break;
8539						}
8540						case 'highlight': {
8541							break;
8542						}
8543						case 'underline': {
8544							break;
8545						}
8546						case 'squiggly': {
8547							break;
8548						}
8549						case 'strikeout': {
8550							break;
8551						}
8552						case 'stamp': {
8553							break;
8554						}
8555						case 'caret': {
8556							break;
8557						}
8558						case 'ink': {
8559							break;
8560						}
8561						case 'popup': {
8562							break;
8563						}
8564						case 'fileattachment': {
8565							if ($this->pdfa_mode && $this->pdfa_version != 3) {
8566								// embedded files are not allowed in PDF/A mode version 1 and 2
8567								break;
8568							}
8569							if (!isset($pl['opt']['fs'])) {
8570								break;
8571							}
8572							$filename = basename($pl['opt']['fs']);
8573							if (isset($this->embeddedfiles[$filename]['f'])) {
8574								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8575								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8576								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8577									$annots .= ' /Name /'.$pl['opt']['name'];
8578								} else {
8579									$annots .= ' /Name /PushPin';
8580								}
8581								// index (zero-based) of the annotation in the Annots array of this page
8582								$this->embeddedfiles[$filename]['a'] = $key;
8583							}
8584							break;
8585						}
8586						case 'sound': {
8587							if (!isset($pl['opt']['fs'])) {
8588								break;
8589							}
8590							$filename = basename($pl['opt']['fs']);
8591							if (isset($this->embeddedfiles[$filename]['f'])) {
8592								// ... TO BE COMPLETED ...
8593								// /R /C /B /E /CO /CP
8594								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8595								$iconsapp = array('Speaker', 'Mic');
8596								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8597									$annots .= ' /Name /'.$pl['opt']['name'];
8598								} else {
8599									$annots .= ' /Name /Speaker';
8600								}
8601							}
8602							break;
8603						}
8604						case 'movie': {
8605							break;
8606						}
8607						case 'widget': {
8608							$hmode = array('N', 'I', 'O', 'P', 'T');
8609							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8610								$annots .= ' /H /'.$pl['opt']['h'];
8611							}
8612							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8613								$annots .= ' /MK <<';
8614								if (isset($pl['opt']['mk']['r'])) {
8615									$annots .= ' /R '.$pl['opt']['mk']['r'];
8616								}
8617								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8618									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8619								}
8620								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8621									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8622								}
8623								if (isset($pl['opt']['mk']['ca'])) {
8624									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8625								}
8626								if (isset($pl['opt']['mk']['rc'])) {
8627									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8628								}
8629								if (isset($pl['opt']['mk']['ac'])) {
8630									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8631								}
8632								if (isset($pl['opt']['mk']['i'])) {
8633									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8634									if ($info !== false) {
8635										$annots .= ' /I '.$info['n'].' 0 R';
8636									}
8637								}
8638								if (isset($pl['opt']['mk']['ri'])) {
8639									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8640									if ($info !== false) {
8641										$annots .= ' /RI '.$info['n'].' 0 R';
8642									}
8643								}
8644								if (isset($pl['opt']['mk']['ix'])) {
8645									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8646									if ($info !== false) {
8647										$annots .= ' /IX '.$info['n'].' 0 R';
8648									}
8649								}
8650								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8651									$annots .= ' /IF <<';
8652									$if_sw = array('A', 'B', 'S', 'N');
8653									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8654										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8655									}
8656									$if_s = array('A', 'P');
8657									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8658										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8659									}
8660									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8661										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8662									}
8663									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8664										$annots .= ' /FB true';
8665									}
8666									$annots .= '>>';
8667								}
8668								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8669									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8670								}
8671								$annots .= '>>';
8672							} // end MK
8673							// --- Entries for field dictionaries ---
8674							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8675								// set parent
8676								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8677							}
8678							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8679								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8680							}
8681							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8682								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8683							}
8684							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8685								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8686							}
8687							if (isset($pl['opt']['ff'])) {
8688								if (is_array($pl['opt']['ff'])) {
8689									// array of bit settings
8690									$flag = 0;
8691									foreach($pl['opt']['ff'] as $val) {
8692										$flag += 1 << ($val - 1);
8693									}
8694								} else {
8695									$flag = intval($pl['opt']['ff']);
8696								}
8697								$annots .= ' /Ff '.$flag;
8698							}
8699							if (isset($pl['opt']['maxlen'])) {
8700								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8701							}
8702							if (isset($pl['opt']['v'])) {
8703								$annots .= ' /V';
8704								if (is_array($pl['opt']['v'])) {
8705									foreach ($pl['opt']['v'] AS $optval) {
8706										if (is_float($optval)) {
8707											$optval = sprintf('%F', $optval);
8708										}
8709										$annots .= ' '.$optval;
8710									}
8711								} else {
8712									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8713								}
8714							}
8715							if (isset($pl['opt']['dv'])) {
8716								$annots .= ' /DV';
8717								if (is_array($pl['opt']['dv'])) {
8718									foreach ($pl['opt']['dv'] AS $optval) {
8719										if (is_float($optval)) {
8720											$optval = sprintf('%F', $optval);
8721										}
8722										$annots .= ' '.$optval;
8723									}
8724								} else {
8725									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8726								}
8727							}
8728							if (isset($pl['opt']['rv'])) {
8729								$annots .= ' /RV';
8730								if (is_array($pl['opt']['rv'])) {
8731									foreach ($pl['opt']['rv'] AS $optval) {
8732										if (is_float($optval)) {
8733											$optval = sprintf('%F', $optval);
8734										}
8735										$annots .= ' '.$optval;
8736									}
8737								} else {
8738									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8739								}
8740							}
8741							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8742								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8743							}
8744							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8745								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8746							}
8747							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8748								$annots .= ' /DA ('.$pl['opt']['da'].')';
8749							}
8750							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8751								$annots .= ' /Q '.intval($pl['opt']['q']);
8752							}
8753							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8754								$annots .= ' /Opt [';
8755								foreach($pl['opt']['opt'] AS $copt) {
8756									if (is_array($copt)) {
8757										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8758									} else {
8759										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8760									}
8761								}
8762								$annots .= ']';
8763							}
8764							if (isset($pl['opt']['ti'])) {
8765								$annots .= ' /TI '.intval($pl['opt']['ti']);
8766							}
8767							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8768								$annots .= ' /I [';
8769								foreach($pl['opt']['i'] AS $copt) {
8770									$annots .= intval($copt).' ';
8771								}
8772								$annots .= ']';
8773							}
8774							break;
8775						}
8776						case 'screen': {
8777							break;
8778						}
8779						case 'printermark': {
8780							break;
8781						}
8782						case 'trapnet': {
8783							break;
8784						}
8785						case 'watermark': {
8786							break;
8787						}
8788						case '3d': {
8789							break;
8790						}
8791						default: {
8792							break;
8793						}
8794					}
8795					$annots .= '>>';
8796					// create new annotation object
8797					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8798					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8799						// store reference of form object
8800						$this->form_obj_id[] = $annot_obj_id;
8801					}
8802				}
8803			}
8804		} // end for each page
8805	}
8806
8807	/**
8808	 * Put appearance streams XObject used to define annotation's appearance states.
8809	 * @param int $w annotation width
8810	 * @param int $h annotation height
8811	 * @param string $stream appearance stream
8812	 * @return int object ID
8813	 * @protected
8814	 * @since 4.8.001 (2009-09-09)
8815	 */
8816	protected function _putAPXObject($w=0, $h=0, $stream='') {
8817		$stream = trim($stream);
8818		$out = $this->_getobj()."\n";
8819		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8820		$out .= '<<';
8821		$out .= ' /Type /XObject';
8822		$out .= ' /Subtype /Form';
8823		$out .= ' /FormType 1';
8824		if ($this->compress) {
8825			$stream = gzcompress($stream);
8826			$out .= ' /Filter /FlateDecode';
8827		}
8828		$rect = sprintf('%F %F', $w, $h);
8829		$out .= ' /BBox [0 0 '.$rect.']';
8830		$out .= ' /Matrix [1 0 0 1 0 0]';
8831		$out .= ' /Resources 2 0 R';
8832		$stream = $this->_getrawstream($stream);
8833		$out .= ' /Length '.strlen($stream);
8834		$out .= ' >>';
8835		$out .= ' stream'."\n".$stream."\n".'endstream';
8836		$out .= "\n".'endobj';
8837		$this->_out($out);
8838		return $this->n;
8839	}
8840
8841	/**
8842	 * Output fonts.
8843	 * @author Nicola Asuni
8844	 * @protected
8845	 */
8846	protected function _putfonts() {
8847		$nf = $this->n;
8848		foreach ($this->diffs as $diff) {
8849			//Encodings
8850			$this->_newobj();
8851			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8852		}
8853		$mqr = TCPDF_STATIC::get_mqr();
8854		TCPDF_STATIC::set_mqr(false);
8855		foreach ($this->FontFiles as $file => $info) {
8856			// search and get font file to embedd
8857			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8858			if (!TCPDF_STATIC::empty_string($fontfile)) {
8859				$font = file_get_contents($fontfile);
8860				$compressed = (substr($file, -2) == '.z');
8861				if ((!$compressed) AND (isset($info['length2']))) {
8862					$header = (ord($font[0]) == 128);
8863					if ($header) {
8864						// strip first binary header
8865						$font = substr($font, 6);
8866					}
8867					if ($header AND (ord($font[$info['length1']]) == 128)) {
8868						// strip second binary header
8869						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8870					}
8871				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8872					if ($compressed) {
8873						// uncompress font
8874						$font = gzuncompress($font);
8875					}
8876					// merge subset characters
8877					$subsetchars = array(); // used chars
8878					foreach ($info['fontkeys'] as $fontkey) {
8879						$fontinfo = $this->getFontBuffer($fontkey);
8880						$subsetchars += $fontinfo['subsetchars'];
8881					}
8882					// rebuild a font subset
8883					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8884					// calculate new font length
8885					$info['length1'] = strlen($font);
8886					if ($compressed) {
8887						// recompress font
8888						$font = gzcompress($font);
8889					}
8890				}
8891				$this->_newobj();
8892				$this->FontFiles[$file]['n'] = $this->n;
8893				$stream = $this->_getrawstream($font);
8894				$out = '<< /Length '.strlen($stream);
8895				if ($compressed) {
8896					$out .= ' /Filter /FlateDecode';
8897				}
8898				$out .= ' /Length1 '.$info['length1'];
8899				if (isset($info['length2'])) {
8900					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8901				}
8902				$out .= ' >>';
8903				$out .= ' stream'."\n".$stream."\n".'endstream';
8904				$out .= "\n".'endobj';
8905				$this->_out($out);
8906			}
8907		}
8908		TCPDF_STATIC::set_mqr($mqr);
8909		foreach ($this->fontkeys as $k) {
8910			//Font objects
8911			$font = $this->getFontBuffer($k);
8912			$type = $font['type'];
8913			$name = $font['name'];
8914			if ($type == 'core') {
8915				// standard core font
8916				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8917				$out .= '<</Type /Font';
8918				$out .= ' /Subtype /Type1';
8919				$out .= ' /BaseFont /'.$name;
8920				$out .= ' /Name /F'.$font['i'];
8921				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8922					$out .= ' /Encoding /WinAnsiEncoding';
8923				}
8924				if ($k == 'helvetica') {
8925					// add default font for annotations
8926					$this->annotation_fonts[$k] = $font['i'];
8927				}
8928				$out .= ' >>';
8929				$out .= "\n".'endobj';
8930				$this->_out($out);
8931			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8932				// additional Type1 or TrueType font
8933				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8934				$out .= '<</Type /Font';
8935				$out .= ' /Subtype /'.$type;
8936				$out .= ' /BaseFont /'.$name;
8937				$out .= ' /Name /F'.$font['i'];
8938				$out .= ' /FirstChar 32 /LastChar 255';
8939				$out .= ' /Widths '.($this->n + 1).' 0 R';
8940				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8941				if ($font['enc']) {
8942					if (isset($font['diff'])) {
8943						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8944					} else {
8945						$out .= ' /Encoding /WinAnsiEncoding';
8946					}
8947				}
8948				$out .= ' >>';
8949				$out .= "\n".'endobj';
8950				$this->_out($out);
8951				// Widths
8952				$this->_newobj();
8953				$s = '[';
8954				for ($i = 32; $i < 256; ++$i) {
8955					if (isset($font['cw'][$i])) {
8956						$s .= $font['cw'][$i].' ';
8957					} else {
8958						$s .= $font['dw'].' ';
8959					}
8960				}
8961				$s .= ']';
8962				$s .= "\n".'endobj';
8963				$this->_out($s);
8964				//Descriptor
8965				$this->_newobj();
8966				$s = '<</Type /FontDescriptor /FontName /'.$name;
8967				foreach ($font['desc'] as $fdk => $fdv) {
8968					if (is_float($fdv)) {
8969						$fdv = sprintf('%F', $fdv);
8970					}
8971					$s .= ' /'.$fdk.' '.$fdv.'';
8972				}
8973				if (!TCPDF_STATIC::empty_string($font['file'])) {
8974					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8975				}
8976				$s .= '>>';
8977				$s .= "\n".'endobj';
8978				$this->_out($s);
8979			} else {
8980				// additional types
8981				$mtd = '_put'.strtolower($type);
8982				if (!method_exists($this, $mtd)) {
8983					$this->Error('Unsupported font type: '.$type);
8984				}
8985				$this->$mtd($font);
8986			}
8987		}
8988	}
8989
8990	/**
8991	 * Adds unicode fonts.<br>
8992	 * Based on PDF Reference 1.3 (section 5)
8993	 * @param array $font font data
8994	 * @protected
8995	 * @author Nicola Asuni
8996	 * @since 1.52.0.TC005 (2005-01-05)
8997	 */
8998	protected function _puttruetypeunicode($font) {
8999		$fontname = '';
9000		if ($font['subset']) {
9001			// change name for font subsetting
9002			$subtag = sprintf('%06u', $font['i']);
9003			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9004			$fontname .= $subtag.'+';
9005		}
9006		$fontname .= $font['name'];
9007		// Type0 Font
9008		// A composite font composed of other fonts, organized hierarchically
9009		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9010		$out .= '<< /Type /Font';
9011		$out .= ' /Subtype /Type0';
9012		$out .= ' /BaseFont /'.$fontname;
9013		$out .= ' /Name /F'.$font['i'];
9014		$out .= ' /Encoding /'.$font['enc'];
9015		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9016		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9017		$out .= ' >>';
9018		$out .= "\n".'endobj';
9019		$this->_out($out);
9020		// ToUnicode map for Identity-H
9021		$stream = TCPDF_FONT_DATA::$uni_identity_h;
9022		// ToUnicode Object
9023		$this->_newobj();
9024		$stream = ($this->compress) ? gzcompress($stream) : $stream;
9025		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9026		$stream = $this->_getrawstream($stream);
9027		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9028		// CIDFontType2
9029		// A CIDFont whose glyph descriptions are based on TrueType font technology
9030		$oid = $this->_newobj();
9031		$out = '<< /Type /Font';
9032		$out .= ' /Subtype /CIDFontType2';
9033		$out .= ' /BaseFont /'.$fontname;
9034		// A dictionary containing entries that define the character collection of the CIDFont.
9035		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9036		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9037		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9038		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9039		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9040		$out .= ' /DW '.$font['dw']; // default width
9041		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9042		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9043			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9044		}
9045		$out .= ' >>';
9046		$out .= "\n".'endobj';
9047		$this->_out($out);
9048		// Font descriptor
9049		// A font descriptor describing the CIDFont default metrics other than its glyph widths
9050		$this->_newobj();
9051		$out = '<< /Type /FontDescriptor';
9052		$out .= ' /FontName /'.$fontname;
9053		foreach ($font['desc'] as $key => $value) {
9054			if (is_float($value)) {
9055				$value = sprintf('%F', $value);
9056			}
9057			$out .= ' /'.$key.' '.$value;
9058		}
9059		$fontdir = false;
9060		if (!TCPDF_STATIC::empty_string($font['file'])) {
9061			// A stream containing a TrueType font
9062			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9063			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
9064		}
9065		$out .= ' >>';
9066		$out .= "\n".'endobj';
9067		$this->_out($out);
9068		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9069			$this->_newobj();
9070			// Embed CIDToGIDMap
9071			// A specification of the mapping from CIDs to glyph indices
9072			// search and get CTG font file to embedd
9073			$ctgfile = strtolower($font['ctg']);
9074			// search and get ctg font file to embedd
9075			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9076			if (TCPDF_STATIC::empty_string($fontfile)) {
9077				$this->Error('Font file not found: '.$ctgfile);
9078			}
9079			$stream = $this->_getrawstream(file_get_contents($fontfile));
9080			$out = '<< /Length '.strlen($stream).'';
9081			if (substr($fontfile, -2) == '.z') { // check file extension
9082				// Decompresses data encoded using the public-domain
9083				// zlib/deflate compression method, reproducing the
9084				// original text or binary data
9085				$out .= ' /Filter /FlateDecode';
9086			}
9087			$out .= ' >>';
9088			$out .= ' stream'."\n".$stream."\n".'endstream';
9089			$out .= "\n".'endobj';
9090			$this->_out($out);
9091		}
9092	}
9093
9094	/**
9095	 * Output CID-0 fonts.
9096	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9097	 * @param array $font font data
9098	 * @protected
9099	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9100	 * @since 3.2.000 (2008-06-23)
9101	 */
9102	protected function _putcidfont0($font) {
9103		$cidoffset = 0;
9104		if (!isset($font['cw'][1])) {
9105			$cidoffset = 31;
9106		}
9107		if (isset($font['cidinfo']['uni2cid'])) {
9108			// convert unicode to cid.
9109			$uni2cid = $font['cidinfo']['uni2cid'];
9110			$cw = array();
9111			foreach ($font['cw'] as $uni => $width) {
9112				if (isset($uni2cid[$uni])) {
9113					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9114				} elseif ($uni < 256) {
9115					$cw[$uni] = $width;
9116				} // else unknown character
9117			}
9118			$font = array_merge($font, array('cw' => $cw));
9119		}
9120		$name = $font['name'];
9121		$enc = $font['enc'];
9122		if ($enc) {
9123			$longname = $name.'-'.$enc;
9124		} else {
9125			$longname = $name;
9126		}
9127		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9128		$out .= '<</Type /Font';
9129		$out .= ' /Subtype /Type0';
9130		$out .= ' /BaseFont /'.$longname;
9131		$out .= ' /Name /F'.$font['i'];
9132		if ($enc) {
9133			$out .= ' /Encoding /'.$enc;
9134		}
9135		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9136		$out .= ' >>';
9137		$out .= "\n".'endobj';
9138		$this->_out($out);
9139		$oid = $this->_newobj();
9140		$out = '<</Type /Font';
9141		$out .= ' /Subtype /CIDFontType0';
9142		$out .= ' /BaseFont /'.$name;
9143		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9144		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9145		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9146		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9147		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9148		$out .= ' /DW '.$font['dw'];
9149		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9150		$out .= ' >>';
9151		$out .= "\n".'endobj';
9152		$this->_out($out);
9153		$this->_newobj();
9154		$s = '<</Type /FontDescriptor /FontName /'.$name;
9155		foreach ($font['desc'] as $k => $v) {
9156			if ($k != 'Style') {
9157				if (is_float($v)) {
9158					$v = sprintf('%F', $v);
9159				}
9160				$s .= ' /'.$k.' '.$v.'';
9161			}
9162		}
9163		$s .= '>>';
9164		$s .= "\n".'endobj';
9165		$this->_out($s);
9166	}
9167
9168	/**
9169	 * Output images.
9170	 * @protected
9171	 */
9172	protected function _putimages() {
9173		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9174		foreach ($this->imagekeys as $file) {
9175			$info = $this->getImageBuffer($file);
9176			// set object for alternate images array
9177			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9178				$altoid = $this->_newobj();
9179				$out = '[';
9180				foreach ($info['altimgs'] as $altimage) {
9181					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9182						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9183						$out .= ' /DefaultForPrinting';
9184						if ($altimage[1] === true) {
9185							$out .= ' true';
9186						} else {
9187							$out .= ' false';
9188						}
9189						$out .= ' >>';
9190					}
9191				}
9192				$out .= ' ]';
9193				$out .= "\n".'endobj';
9194				$this->_out($out);
9195			}
9196			// set image object
9197			$oid = $this->_newobj();
9198			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9199			$this->setImageSubBuffer($file, 'n', $this->n);
9200			$out = '<</Type /XObject';
9201			$out .= ' /Subtype /Image';
9202			$out .= ' /Width '.$info['w'];
9203			$out .= ' /Height '.$info['h'];
9204			if (array_key_exists('masked', $info)) {
9205				$out .= ' /SMask '.($this->n - 1).' 0 R';
9206			}
9207			// set color space
9208			$icc = false;
9209			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9210				// ICC Colour Space
9211				$icc = true;
9212				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9213			} elseif ($info['cs'] == 'Indexed') {
9214				// Indexed Colour Space
9215				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9216			} else {
9217				// Device Colour Space
9218				$out .= ' /ColorSpace /'.$info['cs'];
9219			}
9220			if ($info['cs'] == 'DeviceCMYK') {
9221				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9222			}
9223			$out .= ' /BitsPerComponent '.$info['bpc'];
9224			if (isset($altoid) AND ($altoid > 0)) {
9225				// reference to alternate images dictionary
9226				$out .= ' /Alternates '.$altoid.' 0 R';
9227			}
9228			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9229				// external stream
9230				$out .= ' /Length 0';
9231				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9232				if (isset($info['f'])) {
9233					$out .= ' /FFilter /'.$info['f'];
9234				}
9235				$out .= ' >>';
9236				$out .= ' stream'."\n".'endstream';
9237			} else {
9238				if (isset($info['f'])) {
9239					$out .= ' /Filter /'.$info['f'];
9240				}
9241				if (isset($info['parms'])) {
9242					$out .= ' '.$info['parms'];
9243				}
9244				if (isset($info['trns']) AND is_array($info['trns'])) {
9245					$trns = '';
9246					$count_info = count($info['trns']);
9247					if ($info['cs'] == 'Indexed') {
9248						$maxval =(pow(2, $info['bpc']) - 1);
9249						for ($i = 0; $i < $count_info; ++$i) {
9250							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9251								// this is not a binary type mask @TODO: create a SMask
9252								$trns = '';
9253								break;
9254							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9255								// store the first fully transparent value
9256								$trns .= $i.' '.$i.' ';
9257							}
9258						}
9259					} else {
9260						// grayscale or RGB
9261						for ($i = 0; $i < $count_info; ++$i) {
9262							if ($info['trns'][$i] == 0) {
9263								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9264							}
9265						}
9266					}
9267					// Colour Key Masking
9268					if (!empty($trns)) {
9269						$out .= ' /Mask ['.$trns.']';
9270					}
9271				}
9272				$stream = $this->_getrawstream($info['data']);
9273				$out .= ' /Length '.strlen($stream).' >>';
9274				$out .= ' stream'."\n".$stream."\n".'endstream';
9275			}
9276			$out .= "\n".'endobj';
9277			$this->_out($out);
9278			if ($icc) {
9279				// ICC colour profile
9280				$this->_newobj();
9281				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9282				$icc = $this->_getrawstream($icc);
9283				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9284			} elseif ($info['cs'] == 'Indexed') {
9285				// colour palette
9286				$this->_newobj();
9287				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9288				$pal = $this->_getrawstream($pal);
9289				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9290			}
9291		}
9292	}
9293
9294	/**
9295	 * Output Form XObjects Templates.
9296	 * @author Nicola Asuni
9297	 * @since 5.8.017 (2010-08-24)
9298	 * @protected
9299	 * @see startTemplate(), endTemplate(), printTemplate()
9300	 */
9301	protected function _putxobjects() {
9302		foreach ($this->xobjects as $key => $data) {
9303			if (isset($data['outdata'])) {
9304				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9305				$out = $this->_getobj($data['n'])."\n";
9306				$out .= '<<';
9307				$out .= ' /Type /XObject';
9308				$out .= ' /Subtype /Form';
9309				$out .= ' /FormType 1';
9310				if ($this->compress) {
9311					$stream = gzcompress($stream);
9312					$out .= ' /Filter /FlateDecode';
9313				}
9314				$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));
9315				$out .= ' /Matrix [1 0 0 1 0 0]';
9316				$out .= ' /Resources <<';
9317				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9318				if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9319					// transparency
9320					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9321						$out .= ' /ExtGState <<';
9322						foreach ($data['extgstates'] as $k => $extgstate) {
9323							if (isset($this->extgstates[$k]['name'])) {
9324								$out .= ' /'.$this->extgstates[$k]['name'];
9325							} else {
9326								$out .= ' /GS'.$k;
9327							}
9328							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9329						}
9330						$out .= ' >>';
9331					}
9332					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9333						$gp = '';
9334						$gs = '';
9335						foreach ($data['gradients'] as $id => $grad) {
9336							// gradient patterns
9337							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9338							// gradient shadings
9339							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9340						}
9341						$out .= ' /Pattern <<'.$gp.' >>';
9342						$out .= ' /Shading <<'.$gs.' >>';
9343					}
9344				}
9345				// spot colors
9346				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9347					$out .= ' /ColorSpace <<';
9348					foreach ($data['spot_colors'] as $name => $color) {
9349						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9350					}
9351					$out .= ' >>';
9352				}
9353				// fonts
9354				if (!empty($data['fonts'])) {
9355					$out .= ' /Font <<';
9356					foreach ($data['fonts'] as $fontkey => $fontid) {
9357						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9358					}
9359					$out .= ' >>';
9360				}
9361				// images or nested xobjects
9362				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9363					$out .= ' /XObject <<';
9364					foreach ($data['images'] as $imgid) {
9365						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9366					}
9367					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9368						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9369					}
9370					$out .= ' >>';
9371				}
9372				$out .= ' >>'; //end resources
9373				if (isset($data['group']) AND ($data['group'] !== false)) {
9374					// set transparency group
9375					$out .= ' /Group << /Type /Group /S /Transparency';
9376					if (is_array($data['group'])) {
9377						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9378							$out .= ' /CS /'.$data['group']['CS'];
9379						}
9380						if (isset($data['group']['I'])) {
9381							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9382						}
9383						if (isset($data['group']['K'])) {
9384							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9385						}
9386					}
9387					$out .= ' >>';
9388				}
9389				$stream = $this->_getrawstream($stream, $data['n']);
9390				$out .= ' /Length '.strlen($stream);
9391				$out .= ' >>';
9392				$out .= ' stream'."\n".$stream."\n".'endstream';
9393				$out .= "\n".'endobj';
9394				$this->_out($out);
9395			}
9396		}
9397	}
9398
9399	/**
9400	 * Output Spot Colors Resources.
9401	 * @protected
9402	 * @since 4.0.024 (2008-09-12)
9403	 */
9404	protected function _putspotcolors() {
9405		foreach ($this->spot_colors as $name => $color) {
9406			$this->_newobj();
9407			$this->spot_colors[$name]['n'] = $this->n;
9408			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9409			$out .= ' /DeviceCMYK <<';
9410			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9411			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9412			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9413			$out .= "\n".'endobj';
9414			$this->_out($out);
9415		}
9416	}
9417
9418	/**
9419	 * Return XObjects Dictionary.
9420	 * @return string XObjects dictionary
9421	 * @protected
9422	 * @since 5.8.014 (2010-08-23)
9423	 */
9424	protected function _getxobjectdict() {
9425		$out = '';
9426		foreach ($this->xobjects as $id => $objid) {
9427			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9428		}
9429		return $out;
9430	}
9431
9432	/**
9433	 * Output Resources Dictionary.
9434	 * @protected
9435	 */
9436	protected function _putresourcedict() {
9437		$out = $this->_getobj(2)."\n";
9438		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9439		$out .= ' /Font <<';
9440		foreach ($this->fontkeys as $fontkey) {
9441			$font = $this->getFontBuffer($fontkey);
9442			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9443		}
9444		$out .= ' >>';
9445		$out .= ' /XObject <<';
9446		$out .= $this->_getxobjectdict();
9447		$out .= ' >>';
9448		// layers
9449		if (!empty($this->pdflayers)) {
9450			$out .= ' /Properties <<';
9451			foreach ($this->pdflayers as $layer) {
9452				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9453			}
9454			$out .= ' >>';
9455		}
9456		if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9457			// transparency
9458			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9459				$out .= ' /ExtGState <<';
9460				foreach ($this->extgstates as $k => $extgstate) {
9461					if (isset($extgstate['name'])) {
9462						$out .= ' /'.$extgstate['name'];
9463					} else {
9464						$out .= ' /GS'.$k;
9465					}
9466					$out .= ' '.$extgstate['n'].' 0 R';
9467				}
9468				$out .= ' >>';
9469			}
9470			if (isset($this->gradients) AND !empty($this->gradients)) {
9471				$gp = '';
9472				$gs = '';
9473				foreach ($this->gradients as $id => $grad) {
9474					// gradient patterns
9475					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9476					// gradient shadings
9477					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9478				}
9479				$out .= ' /Pattern <<'.$gp.' >>';
9480				$out .= ' /Shading <<'.$gs.' >>';
9481			}
9482		}
9483		// spot colors
9484		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9485			$out .= ' /ColorSpace <<';
9486			foreach ($this->spot_colors as $color) {
9487				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9488			}
9489			$out .= ' >>';
9490		}
9491		$out .= ' >>';
9492		$out .= "\n".'endobj';
9493		$this->_out($out);
9494	}
9495
9496	/**
9497	 * Output Resources.
9498	 * @protected
9499	 */
9500	protected function _putresources() {
9501		$this->_putextgstates();
9502		$this->_putocg();
9503		$this->_putfonts();
9504		$this->_putimages();
9505		$this->_putspotcolors();
9506		$this->_putshaders();
9507		$this->_putxobjects();
9508		$this->_putresourcedict();
9509		$this->_putdests();
9510		$this->_putEmbeddedFiles();
9511		$this->_putannotsobjs();
9512		$this->_putjavascript();
9513		$this->_putbookmarks();
9514		$this->_putencryption();
9515	}
9516
9517	/**
9518	 * Adds some Metadata information (Document Information Dictionary)
9519	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9520	 * @return int object id
9521	 * @protected
9522	 */
9523	protected function _putinfo() {
9524		$oid = $this->_newobj();
9525		$out = '<<';
9526		// store current isunicode value
9527		$prev_isunicode = $this->isunicode;
9528		if ($this->docinfounicode) {
9529			$this->isunicode = true;
9530		}
9531		if (!TCPDF_STATIC::empty_string($this->title)) {
9532			// The document's title.
9533			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9534		}
9535		if (!TCPDF_STATIC::empty_string($this->author)) {
9536			// The name of the person who created the document.
9537			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9538		}
9539		if (!TCPDF_STATIC::empty_string($this->subject)) {
9540			// The subject of the document.
9541			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9542		}
9543		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9544			// Keywords associated with the document.
9545			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9546		}
9547		if (!TCPDF_STATIC::empty_string($this->creator)) {
9548			// 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.
9549			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9550		}
9551		// restore previous isunicode value
9552		$this->isunicode = $prev_isunicode;
9553		// default producer
9554		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9555		// The date and time the document was created, in human-readable form
9556		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9557		// The date and time the document was most recently modified, in human-readable form
9558		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9559		// A name object indicating whether the document has been modified to include trapping information
9560		$out .= ' /Trapped /False';
9561		$out .= ' >>';
9562		$out .= "\n".'endobj';
9563		$this->_out($out);
9564		return $oid;
9565	}
9566
9567	/**
9568	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9569	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9570	 * @param string $xmp Custom XMP data.
9571	 * @since 5.9.128 (2011-10-06)
9572	 * @public
9573	 */
9574	public function setExtraXMP($xmp) {
9575		$this->custom_xmp = $xmp;
9576	}
9577
9578	/**
9579	 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9580	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9581	 * @param string $xmp Custom XMP RDF data.
9582	 * @since 6.3.0 (2019-09-19)
9583	 * @public
9584	 */
9585	public function setExtraXMPRDF($xmp) {
9586		$this->custom_xmp_rdf = $xmp;
9587	}
9588
9589	/**
9590	 * Put XMP data object and return ID.
9591	 * @return int The object ID.
9592	 * @since 5.9.121 (2011-09-28)
9593	 * @protected
9594	 */
9595	protected function _putXMP() {
9596		$oid = $this->_newobj();
9597		// store current isunicode value
9598		$prev_isunicode = $this->isunicode;
9599		$this->isunicode = true;
9600		$prev_encrypted = $this->encrypted;
9601		$this->encrypted = false;
9602		// set XMP data
9603		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9604		$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";
9605		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9606		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9607		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9608		$xmp .= "\t\t\t".'<dc:title>'."\n";
9609		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9610		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9611		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9612		$xmp .= "\t\t\t".'</dc:title>'."\n";
9613		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9614		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9615		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9616		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9617		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9618		$xmp .= "\t\t\t".'<dc:description>'."\n";
9619		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9620		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9621		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9622		$xmp .= "\t\t\t".'</dc:description>'."\n";
9623		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9624		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9625		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9626		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9627		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9628		$xmp .= "\t\t".'</rdf:Description>'."\n";
9629		// convert doc creation date format
9630		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9631		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9632		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9633		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9634		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9635		// convert doc modification date format
9636		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9637		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9638		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9639		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9640		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9641		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9642		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9643		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9644		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9645		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9646		$xmp .= "\t\t".'</rdf:Description>'."\n";
9647		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9648		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9649		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9650		$xmp .= "\t\t".'</rdf:Description>'."\n";
9651		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9652		$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);
9653		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9654		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9655		$xmp .= "\t\t".'</rdf:Description>'."\n";
9656		if ($this->pdfa_mode) {
9657			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9658			$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9659			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9660			$xmp .= "\t\t".'</rdf:Description>'."\n";
9661		}
9662		// XMP extension schemas
9663		$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";
9664		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9665		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9666		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9667		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9668		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9669		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9670		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9671		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9672		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9673		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9674		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9675		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9676		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9677		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9678		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9679		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9680		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9681		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9682		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9683		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9684		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9685		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9686		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9687		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9688		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9689		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9690		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9691		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9692		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9693		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9694		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9695		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9696		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9697		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9698		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9699		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9700		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9701		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9702		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9703		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9704		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9705		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9706		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9707		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9708		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9709		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9710		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9711		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9712		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9713		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9714		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9715		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9716		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9717		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9718		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9719		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9720		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9721		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9722		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9723		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9724		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9725		$xmp .= "\t\t".'</rdf:Description>'."\n";
9726		$xmp .= $this->custom_xmp_rdf;
9727		$xmp .= "\t".'</rdf:RDF>'."\n";
9728		$xmp .= $this->custom_xmp;
9729		$xmp .= '</x:xmpmeta>'."\n";
9730		$xmp .= '<?xpacket end="w"?>';
9731		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9732		// restore previous isunicode value
9733		$this->isunicode = $prev_isunicode;
9734		$this->encrypted = $prev_encrypted;
9735		$this->_out($out);
9736		return $oid;
9737	}
9738
9739	/**
9740	 * Output Catalog.
9741	 * @return int object id
9742	 * @protected
9743	 */
9744	protected function _putcatalog() {
9745		// put XMP
9746		$xmpobj = $this->_putXMP();
9747		// if required, add standard sRGB ICC colour profile
9748		if ($this->pdfa_mode OR $this->force_srgb) {
9749			$iccobj = $this->_newobj();
9750			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9751			$filter = '';
9752			if ($this->compress) {
9753				$filter = ' /Filter /FlateDecode';
9754				$icc = gzcompress($icc);
9755			}
9756			$icc = $this->_getrawstream($icc);
9757			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9758		}
9759		// start catalog
9760		$oid = $this->_newobj();
9761		$out = '<< /Type /Catalog';
9762		$out .= ' /Version /'.$this->PDFVersion;
9763		//$out .= ' /Extensions <<>>';
9764		$out .= ' /Pages 1 0 R';
9765		//$out .= ' /PageLabels ' //...;
9766		$out .= ' /Names <<';
9767		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9768			$out .= ' /JavaScript '.$this->n_js;
9769		}
9770		if (!empty($this->efnames)) {
9771			$out .= ' /EmbeddedFiles <</Names [';
9772			foreach ($this->efnames AS $fn => $fref) {
9773				$out .= ' '.$this->_datastring($fn).' '.$fref;
9774			}
9775			$out .= ' ]>>';
9776		}
9777		$out .= ' >>';
9778		if (!empty($this->dests)) {
9779			$out .= ' /Dests '.($this->n_dests).' 0 R';
9780		}
9781		$out .= $this->_putviewerpreferences();
9782		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9783			$out .= ' /PageLayout /'.$this->LayoutMode;
9784		}
9785		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9786			$out .= ' /PageMode /'.$this->PageMode;
9787		}
9788		if (count($this->outlines) > 0) {
9789			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9790			$out .= ' /PageMode /UseOutlines';
9791		}
9792		//$out .= ' /Threads []';
9793		if ($this->ZoomMode == 'fullpage') {
9794			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9795		} elseif ($this->ZoomMode == 'fullwidth') {
9796			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9797		} elseif ($this->ZoomMode == 'real') {
9798			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9799		} elseif (!is_string($this->ZoomMode)) {
9800			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9801		}
9802		//$out .= ' /AA <<>>';
9803		//$out .= ' /URI <<>>';
9804		$out .= ' /Metadata '.$xmpobj.' 0 R';
9805		//$out .= ' /StructTreeRoot <<>>';
9806		//$out .= ' /MarkInfo <<>>';
9807		if (isset($this->l['a_meta_language'])) {
9808			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9809		}
9810		//$out .= ' /SpiderInfo <<>>';
9811		// set OutputIntent to sRGB IEC61966-2.1 if required
9812		if ($this->pdfa_mode OR $this->force_srgb) {
9813			$out .= ' /OutputIntents [<<';
9814			$out .= ' /Type /OutputIntent';
9815			$out .= ' /S /GTS_PDFA1';
9816			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9817			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9818			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9819			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9820			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9821			$out .= ' >>]';
9822		}
9823		//$out .= ' /PieceInfo <<>>';
9824		if (!empty($this->pdflayers)) {
9825			$lyrobjs = '';
9826			$lyrobjs_off = '';
9827			$lyrobjs_lock = '';
9828			foreach ($this->pdflayers as $layer) {
9829				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9830				$lyrobjs .= $layer_obj_ref;
9831				if ($layer['view'] === false) {
9832					$lyrobjs_off .= $layer_obj_ref;
9833				}
9834				if ($layer['lock']) {
9835					$lyrobjs_lock .= $layer_obj_ref;
9836				}
9837			}
9838			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9839			$out .= ' /D <<';
9840			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9841			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9842			$out .= ' /BaseState /ON';
9843			$out .= ' /OFF ['.$lyrobjs_off.']';
9844			$out .= ' /Locked ['.$lyrobjs_lock.']';
9845			$out .= ' /Intent /View';
9846			$out .= ' /AS [';
9847			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9848			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9849			$out .= ' ]';
9850			$out .= ' /Order ['.$lyrobjs.']';
9851			$out .= ' /ListMode /AllPages';
9852			//$out .= ' /RBGroups ['..']';
9853			//$out .= ' /Locked ['..']';
9854			$out .= ' >>';
9855			$out .= ' >>';
9856		}
9857		// AcroForm
9858		if (!empty($this->form_obj_id)
9859			OR ($this->sign AND isset($this->signature_data['cert_type']))
9860			OR !empty($this->empty_signature_appearance)) {
9861			$out .= ' /AcroForm <<';
9862			$objrefs = '';
9863			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9864				// set reference for signature object
9865				$objrefs .= $this->sig_obj_id.' 0 R';
9866			}
9867			if (!empty($this->empty_signature_appearance)) {
9868				foreach ($this->empty_signature_appearance as $esa) {
9869					// set reference for empty signature objects
9870					$objrefs .= ' '.$esa['objid'].' 0 R';
9871				}
9872			}
9873			if (!empty($this->form_obj_id)) {
9874				foreach($this->form_obj_id as $objid) {
9875					$objrefs .= ' '.$objid.' 0 R';
9876				}
9877			}
9878			$out .= ' /Fields ['.$objrefs.']';
9879			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9880			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9881				$out .= ' /NeedAppearances false';
9882			}
9883			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9884				if ($this->signature_data['cert_type'] > 0) {
9885					$out .= ' /SigFlags 3';
9886				} else {
9887					$out .= ' /SigFlags 1';
9888				}
9889			}
9890			//$out .= ' /CO ';
9891			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9892				$out .= ' /DR <<';
9893				$out .= ' /Font <<';
9894				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9895					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9896				}
9897				$out .= ' >> >>';
9898			}
9899			$font = $this->getFontBuffer('helvetica');
9900			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9901			$out .= ' /Q '.(($this->rtl)?'2':'0');
9902			//$out .= ' /XFA ';
9903			$out .= ' >>';
9904			// signatures
9905			if ($this->sign AND isset($this->signature_data['cert_type'])
9906				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9907				if ($this->signature_data['cert_type'] > 0) {
9908					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9909				} else {
9910					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9911				}
9912			}
9913		}
9914		//$out .= ' /Legal <<>>';
9915		//$out .= ' /Requirements []';
9916		//$out .= ' /Collection <<>>';
9917		//$out .= ' /NeedsRendering true';
9918		$out .= ' >>';
9919		$out .= "\n".'endobj';
9920		$this->_out($out);
9921		return $oid;
9922	}
9923
9924	/**
9925	 * Output viewer preferences.
9926	 * @return string for viewer preferences
9927	 * @author Nicola asuni
9928	 * @since 3.1.000 (2008-06-09)
9929	 * @protected
9930	 */
9931	protected function _putviewerpreferences() {
9932		$vp = $this->viewer_preferences;
9933		$out = ' /ViewerPreferences <<';
9934		if ($this->rtl) {
9935			$out .= ' /Direction /R2L';
9936		} else {
9937			$out .= ' /Direction /L2R';
9938		}
9939		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9940			$out .= ' /HideToolbar true';
9941		}
9942		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9943			$out .= ' /HideMenubar true';
9944		}
9945		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9946			$out .= ' /HideWindowUI true';
9947		}
9948		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9949			$out .= ' /FitWindow true';
9950		}
9951		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9952			$out .= ' /CenterWindow true';
9953		}
9954		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9955			$out .= ' /DisplayDocTitle true';
9956		}
9957		if (isset($vp['NonFullScreenPageMode'])) {
9958			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9959		}
9960		if (isset($vp['ViewArea'])) {
9961			$out .= ' /ViewArea /'.$vp['ViewArea'];
9962		}
9963		if (isset($vp['ViewClip'])) {
9964			$out .= ' /ViewClip /'.$vp['ViewClip'];
9965		}
9966		if (isset($vp['PrintArea'])) {
9967			$out .= ' /PrintArea /'.$vp['PrintArea'];
9968		}
9969		if (isset($vp['PrintClip'])) {
9970			$out .= ' /PrintClip /'.$vp['PrintClip'];
9971		}
9972		if (isset($vp['PrintScaling'])) {
9973			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
9974		}
9975		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9976			$out .= ' /Duplex /'.$vp['Duplex'];
9977		}
9978		if (isset($vp['PickTrayByPDFSize'])) {
9979			if ($vp['PickTrayByPDFSize']) {
9980				$out .= ' /PickTrayByPDFSize true';
9981			} else {
9982				$out .= ' /PickTrayByPDFSize false';
9983			}
9984		}
9985		if (isset($vp['PrintPageRange'])) {
9986			$PrintPageRangeNum = '';
9987			foreach ($vp['PrintPageRange'] as $k => $v) {
9988				$PrintPageRangeNum .= ' '.($v - 1).'';
9989			}
9990			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9991		}
9992		if (isset($vp['NumCopies'])) {
9993			$out .= ' /NumCopies '.intval($vp['NumCopies']);
9994		}
9995		$out .= ' >>';
9996		return $out;
9997	}
9998
9999	/**
10000	 * Output PDF File Header (7.5.2).
10001	 * @protected
10002	 */
10003	protected function _putheader() {
10004		$this->_out('%PDF-'.$this->PDFVersion);
10005		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10006	}
10007
10008	/**
10009	 * Output end of document (EOF).
10010	 * @protected
10011	 */
10012	protected function _enddoc() {
10013		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10014			// save subset chars of the previous font
10015			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10016		}
10017		$this->state = 1;
10018		$this->_putheader();
10019		$this->_putpages();
10020		$this->_putresources();
10021		// empty signature fields
10022		if (!empty($this->empty_signature_appearance)) {
10023			foreach ($this->empty_signature_appearance as $key => $esa) {
10024				// widget annotation for empty signature
10025				$out = $this->_getobj($esa['objid'])."\n";
10026				$out .= '<< /Type /Annot';
10027				$out .= ' /Subtype /Widget';
10028				$out .= ' /Rect ['.$esa['rect'].']';
10029				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10030				$out .= ' /F 4';
10031				$out .= ' /FT /Sig';
10032				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10033				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10034				$out .= ' /Ff 0';
10035				$out .= ' >>';
10036				$out .= "\n".'endobj';
10037				$this->_out($out);
10038			}
10039		}
10040		// Signature
10041		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10042			// widget annotation for signature
10043			$out = $this->_getobj($this->sig_obj_id)."\n";
10044			$out .= '<< /Type /Annot';
10045			$out .= ' /Subtype /Widget';
10046			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10047			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10048			$out .= ' /F 4';
10049			$out .= ' /FT /Sig';
10050			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10051			$out .= ' /Ff 0';
10052			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10053			$out .= ' >>';
10054			$out .= "\n".'endobj';
10055			$this->_out($out);
10056			// signature
10057			$this->_putsignature();
10058		}
10059		// Info
10060		$objid_info = $this->_putinfo();
10061		// Catalog
10062		$objid_catalog = $this->_putcatalog();
10063		// Cross-ref
10064		$o = $this->bufferlen;
10065		// XREF section
10066		$this->_out('xref');
10067		$this->_out('0 '.($this->n + 1));
10068		$this->_out('0000000000 65535 f ');
10069		$freegen = ($this->n + 2);
10070		for ($i=1; $i <= $this->n; ++$i) {
10071			if (!isset($this->offsets[$i]) AND ($i > 1)) {
10072				$this->_out(sprintf('0000000000 %05d f ', $freegen));
10073				++$freegen;
10074			} else {
10075				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10076			}
10077		}
10078		// TRAILER
10079		$out = 'trailer'."\n";
10080		$out .= '<<';
10081		$out .= ' /Size '.($this->n + 1);
10082		$out .= ' /Root '.$objid_catalog.' 0 R';
10083		$out .= ' /Info '.$objid_info.' 0 R';
10084		if ($this->encrypted) {
10085			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10086		}
10087		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10088		$out .= ' >>';
10089		$this->_out($out);
10090		$this->_out('startxref');
10091		$this->_out($o);
10092		$this->_out('%%EOF');
10093		$this->state = 3; // end-of-doc
10094	}
10095
10096	/**
10097	 * Initialize a new page.
10098	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10099	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10100	 * @protected
10101	 * @see getPageSizeFromFormat(), setPageFormat()
10102	 */
10103	protected function _beginpage($orientation='', $format='') {
10104		++$this->page;
10105		$this->pageobjects[$this->page] = array();
10106		$this->setPageBuffer($this->page, '');
10107		// initialize array for graphics tranformation positions inside a page buffer
10108		$this->transfmrk[$this->page] = array();
10109		$this->state = 2;
10110		if (TCPDF_STATIC::empty_string($orientation)) {
10111			if (isset($this->CurOrientation)) {
10112				$orientation = $this->CurOrientation;
10113			} elseif ($this->fwPt > $this->fhPt) {
10114				// landscape
10115				$orientation = 'L';
10116			} else {
10117				// portrait
10118				$orientation = 'P';
10119			}
10120		}
10121		if (TCPDF_STATIC::empty_string($format)) {
10122			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10123			$this->setPageOrientation($orientation);
10124		} else {
10125			$this->setPageFormat($format, $orientation);
10126		}
10127		if ($this->rtl) {
10128			$this->x = $this->w - $this->rMargin;
10129		} else {
10130			$this->x = $this->lMargin;
10131		}
10132		$this->y = $this->tMargin;
10133		if (isset($this->newpagegroup[$this->page])) {
10134			// start a new group
10135			$this->currpagegroup = $this->newpagegroup[$this->page];
10136			$this->pagegroups[$this->currpagegroup] = 1;
10137		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10138			++$this->pagegroups[$this->currpagegroup];
10139		}
10140	}
10141
10142	/**
10143	 * Mark end of page.
10144	 * @protected
10145	 */
10146	protected function _endpage() {
10147		$this->setVisibility('all');
10148		$this->state = 1;
10149	}
10150
10151	/**
10152	 * Begin a new object and return the object number.
10153	 * @return int object number
10154	 * @protected
10155	 */
10156	protected function _newobj() {
10157		$this->_out($this->_getobj());
10158		return $this->n;
10159	}
10160
10161	/**
10162	 * Return the starting object string for the selected object ID.
10163	 * @param int $objid Object ID (leave empty to get a new ID).
10164	 * @return string the starting object string
10165	 * @protected
10166	 * @since 5.8.009 (2010-08-20)
10167	 */
10168	protected function _getobj($objid='') {
10169		if ($objid === '') {
10170			++$this->n;
10171			$objid = $this->n;
10172		}
10173		$this->offsets[$objid] = $this->bufferlen;
10174		$this->pageobjects[$this->page][] = $objid;
10175		return $objid.' 0 obj';
10176	}
10177
10178	/**
10179	 * Underline text.
10180	 * @param int $x X coordinate
10181	 * @param int $y Y coordinate
10182	 * @param string $txt text to underline
10183	 * @protected
10184	 */
10185	protected function _dounderline($x, $y, $txt) {
10186		$w = $this->GetStringWidth($txt);
10187		return $this->_dounderlinew($x, $y, $w);
10188	}
10189
10190	/**
10191	 * Underline for rectangular text area.
10192	 * @param int $x X coordinate
10193	 * @param int $y Y coordinate
10194	 * @param int $w width to underline
10195	 * @protected
10196	 * @since 4.8.008 (2009-09-29)
10197	 */
10198	protected function _dounderlinew($x, $y, $w) {
10199		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10200		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10201	}
10202
10203	/**
10204	 * Line through text.
10205	 * @param int $x X coordinate
10206	 * @param int $y Y coordinate
10207	 * @param string $txt text to linethrough
10208	 * @protected
10209	 */
10210	protected function _dolinethrough($x, $y, $txt) {
10211		$w = $this->GetStringWidth($txt);
10212		return $this->_dolinethroughw($x, $y, $w);
10213	}
10214
10215	/**
10216	 * Line through for rectangular text area.
10217	 * @param int $x X coordinate
10218	 * @param int $y Y coordinate
10219	 * @param int $w line length (width)
10220	 * @protected
10221	 * @since 4.9.008 (2009-09-29)
10222	 */
10223	protected function _dolinethroughw($x, $y, $w) {
10224		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10225		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10226	}
10227
10228	/**
10229	 * Overline text.
10230	 * @param int $x X coordinate
10231	 * @param int $y Y coordinate
10232	 * @param string $txt text to overline
10233	 * @protected
10234	 * @since 4.9.015 (2010-04-19)
10235	 */
10236	protected function _dooverline($x, $y, $txt) {
10237		$w = $this->GetStringWidth($txt);
10238		return $this->_dooverlinew($x, $y, $w);
10239	}
10240
10241	/**
10242	 * Overline for rectangular text area.
10243	 * @param int $x X coordinate
10244	 * @param int $y Y coordinate
10245	 * @param int $w width to overline
10246	 * @protected
10247	 * @since 4.9.015 (2010-04-19)
10248	 */
10249	protected function _dooverlinew($x, $y, $w) {
10250		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10251		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10252
10253	}
10254
10255	/**
10256	 * Format a data string for meta information
10257	 * @param string $s data string to escape.
10258	 * @param int $n object ID
10259	 * @return string escaped string.
10260	 * @protected
10261	 */
10262	protected function _datastring($s, $n=0) {
10263		if ($n == 0) {
10264			$n = $this->n;
10265		}
10266		$s = $this->_encrypt_data($n, $s);
10267		return '('. TCPDF_STATIC::_escape($s).')';
10268	}
10269
10270	/**
10271	 * Set the document creation timestamp
10272	 * @param mixed $time Document creation timestamp in seconds or date-time string.
10273	 * @public
10274	 * @since 5.9.152 (2012-03-23)
10275	 */
10276	public function setDocCreationTimestamp($time) {
10277		if (is_string($time)) {
10278			$time = TCPDF_STATIC::getTimestamp($time);
10279		}
10280		$this->doc_creation_timestamp = intval($time);
10281	}
10282
10283	/**
10284	 * Set the document modification timestamp
10285	 * @param mixed $time Document modification timestamp in seconds or date-time string.
10286	 * @public
10287	 * @since 5.9.152 (2012-03-23)
10288	 */
10289	public function setDocModificationTimestamp($time) {
10290		if (is_string($time)) {
10291			$time = TCPDF_STATIC::getTimestamp($time);
10292		}
10293		$this->doc_modification_timestamp = intval($time);
10294	}
10295
10296	/**
10297	 * Returns document creation timestamp in seconds.
10298	 * @return int Creation timestamp in seconds.
10299	 * @public
10300	 * @since 5.9.152 (2012-03-23)
10301	 */
10302	public function getDocCreationTimestamp() {
10303		return $this->doc_creation_timestamp;
10304	}
10305
10306	/**
10307	 * Returns document modification timestamp in seconds.
10308	 * @return int Modfication timestamp in seconds.
10309	 * @public
10310	 * @since 5.9.152 (2012-03-23)
10311	 */
10312	public function getDocModificationTimestamp() {
10313		return $this->doc_modification_timestamp;
10314	}
10315
10316	/**
10317	 * Returns a formatted date for meta information
10318	 * @param int $n Object ID.
10319	 * @param int $timestamp Timestamp to convert.
10320	 * @return string escaped date string.
10321	 * @protected
10322	 * @since 4.6.028 (2009-08-25)
10323	 */
10324	protected function _datestring($n=0, $timestamp=0) {
10325		if ((empty($timestamp)) OR ($timestamp < 0)) {
10326			$timestamp = $this->doc_creation_timestamp;
10327		}
10328		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10329	}
10330
10331	/**
10332	 * Format a text string for meta information
10333	 * @param string $s string to escape.
10334	 * @param int $n object ID
10335	 * @return string escaped string.
10336	 * @protected
10337	 */
10338	protected function _textstring($s, $n=0) {
10339		if ($this->isunicode) {
10340			//Convert string to UTF-16BE
10341			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10342		}
10343		return $this->_datastring($s, $n);
10344	}
10345
10346	/**
10347	 * get raw output stream.
10348	 * @param string $s string to output.
10349	 * @param int $n object reference for encryption mode
10350	 * @protected
10351	 * @author Nicola Asuni
10352	 * @since 5.5.000 (2010-06-22)
10353	 */
10354	protected function _getrawstream($s, $n=0) {
10355		if ($n <= 0) {
10356			// default to current object
10357			$n = $this->n;
10358		}
10359		return $this->_encrypt_data($n, $s);
10360	}
10361
10362	/**
10363	 * Output a string to the document.
10364	 * @param string $s string to output.
10365	 * @protected
10366	 */
10367	protected function _out($s) {
10368		if ($this->state == 2) {
10369			if ($this->inxobj) {
10370				// we are inside an XObject template
10371				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10372			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10373				// puts data before page footer
10374				$pagebuff = $this->getPageBuffer($this->page);
10375				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10376				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10377				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10378				// update footer position
10379				$this->footerpos[$this->page] += strlen($s."\n");
10380			} else {
10381				// set page data
10382				$this->setPageBuffer($this->page, $s."\n", true);
10383			}
10384		} elseif ($this->state > 0) {
10385			// set general data
10386			$this->setBuffer($s."\n");
10387		}
10388	}
10389
10390	/**
10391	 * Set header font.
10392	 * @param array<int,string> $font Array describing the basic font parameters: (family, style, size).
10393	 * @public
10394	 * @since 1.1
10395	 */
10396	public function setHeaderFont($font) {
10397		$this->header_font = $font;
10398	}
10399
10400	/**
10401	 * Get header font.
10402	 * @return array<int,string> Array describing the basic font parameters: (family, style, size).
10403	 * @public
10404	 * @since 4.0.012 (2008-07-24)
10405	 */
10406	public function getHeaderFont() {
10407		return $this->header_font;
10408	}
10409
10410	/**
10411	 * Set footer font.
10412	 * @param array<int,string> $font Array describing the basic font parameters: (family, style, size).
10413	 * @public
10414	 * @since 1.1
10415	 */
10416	public function setFooterFont($font) {
10417		$this->footer_font = $font;
10418	}
10419
10420	/**
10421	 * Get Footer font.
10422	 * @return array<int,string> Array describing the basic font parameters: (family, style, size).
10423	 * @public
10424	 * @since 4.0.012 (2008-07-24)
10425	 */
10426	public function getFooterFont() {
10427		return $this->footer_font;
10428	}
10429
10430	/**
10431	 * Set language array.
10432	 * @param array $language
10433	 * @public
10434	 * @since 1.1
10435	 */
10436	public function setLanguageArray($language) {
10437		$this->l = $language;
10438		if (isset($this->l['a_meta_dir'])) {
10439			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10440		} else {
10441			$this->rtl = false;
10442		}
10443	}
10444
10445	/**
10446	 * Returns the PDF data.
10447	 * @public
10448	 */
10449	public function getPDFData() {
10450		if ($this->state < 3) {
10451			$this->Close();
10452		}
10453		return $this->buffer;
10454	}
10455
10456	/**
10457	 * Output anchor link.
10458	 * @param string $url link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10459	 * @param string $name link name
10460	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10461	 * @param boolean $firstline if true prints only the first line and return the remaining string.
10462	 * @param array $color array of RGB text color
10463	 * @param string $style font style (U, D, B, I)
10464	 * @param boolean $firstblock if true the string is the starting of a line.
10465	 * @return int the number of cells used or the remaining text if $firstline = true;
10466	 * @public
10467	 */
10468	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10469		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10470			// convert url to internal link
10471			$lnkdata = explode(',', $url);
10472			if (isset($lnkdata[0]) ) {
10473				$page = substr($lnkdata[0], 1);
10474				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10475					$lnky = floatval($lnkdata[1]);
10476				} else {
10477					$lnky = 0;
10478				}
10479				$url = $this->AddLink();
10480				$this->SetLink($url, $lnky, $page);
10481			}
10482		}
10483		// store current settings
10484		$prevcolor = $this->fgcolor;
10485		$prevstyle = $this->FontStyle;
10486		if (empty($color)) {
10487			$this->SetTextColorArray($this->htmlLinkColorArray);
10488		} else {
10489			$this->SetTextColorArray($color);
10490		}
10491		if ($style == -1) {
10492			$this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10493		} else {
10494			$this->SetFont('', $this->FontStyle.$style);
10495		}
10496		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10497		// restore settings
10498		$this->SetFont('', $prevstyle);
10499		$this->SetTextColorArray($prevcolor);
10500		return $ret;
10501	}
10502
10503	/**
10504	 * Converts pixels to User's Units.
10505	 * @param int $px pixels
10506	 * @return float value in user's unit
10507	 * @public
10508	 * @see setImageScale(), getImageScale()
10509	 */
10510	public function pixelsToUnits($px) {
10511		return ($px / ($this->imgscale * $this->k));
10512	}
10513
10514	/**
10515	 * Reverse function for htmlentities.
10516	 * Convert entities in UTF-8.
10517	 * @param string $text_to_convert Text to convert.
10518	 * @return string converted text string
10519	 * @public
10520	 */
10521	public function unhtmlentities($text_to_convert) {
10522		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10523	}
10524
10525	// ENCRYPTION METHODS ----------------------------------
10526
10527	/**
10528	 * Compute encryption key depending on object number where the encrypted data is stored.
10529	 * This is used for all strings and streams without crypt filter specifier.
10530	 * @param int $n object number
10531	 * @return int object key
10532	 * @protected
10533	 * @author Nicola Asuni
10534	 * @since 2.0.000 (2008-01-02)
10535	 */
10536	protected function _objectkey($n) {
10537		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10538		if ($this->encryptdata['mode'] == 2) { // AES-128
10539			// AES padding
10540			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10541		}
10542		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10543		$objkey = substr($objkey, 0, 16);
10544		return $objkey;
10545	}
10546
10547	/**
10548	 * Encrypt the input string.
10549	 * @param int $n object number
10550	 * @param string $s data string to encrypt
10551	 * @return string encrypted string
10552	 * @protected
10553	 * @author Nicola Asuni
10554	 * @since 5.0.005 (2010-05-11)
10555	 */
10556	protected function _encrypt_data($n, $s) {
10557		if (!$this->encrypted) {
10558			return $s;
10559		}
10560		switch ($this->encryptdata['mode']) {
10561			case 0:   // RC4-40
10562			case 1: { // RC4-128
10563				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10564				break;
10565			}
10566			case 2: { // AES-128
10567				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10568				break;
10569			}
10570			case 3: { // AES-256
10571				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10572				break;
10573			}
10574		}
10575		return $s;
10576	}
10577
10578	/**
10579	 * Put encryption on PDF document.
10580	 * @protected
10581	 * @author Nicola Asuni
10582	 * @since 2.0.000 (2008-01-02)
10583	 */
10584	protected function _putencryption() {
10585		if (!$this->encrypted) {
10586			return;
10587		}
10588		$this->encryptdata['objid'] = $this->_newobj();
10589		$out = '<<';
10590		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10591			$this->encryptdata['Filter'] = 'Standard';
10592		}
10593		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10594		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10595			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10596		}
10597		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10598			$this->encryptdata['V'] = 1;
10599		}
10600		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10601		$out .= ' /V '.$this->encryptdata['V'];
10602		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10603			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10604			$out .= ' /Length '.$this->encryptdata['Length'];
10605		} else {
10606			$out .= ' /Length 40';
10607		}
10608		if ($this->encryptdata['V'] >= 4) {
10609			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10610				$this->encryptdata['StmF'] = 'Identity';
10611			}
10612			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10613				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10614				$this->encryptdata['StrF'] = 'Identity';
10615			}
10616			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10617			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10618				$out .= ' /CF <<';
10619				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10620				$out .= ' /Type /CryptFilter';
10621				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10622					// The method used
10623					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10624					if ($this->encryptdata['pubkey']) {
10625						$out .= ' /Recipients [';
10626						foreach ($this->encryptdata['Recipients'] as $rec) {
10627							$out .= ' <'.$rec.'>';
10628						}
10629						$out .= ' ]';
10630						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10631							$out .= ' /EncryptMetadata false';
10632						} else {
10633							$out .= ' /EncryptMetadata true';
10634						}
10635					}
10636				} else {
10637					$out .= ' /CFM /None';
10638				}
10639				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10640					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10641					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10642				} else {
10643					$out .= ' /AuthEvent /DocOpen';
10644				}
10645				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10646					// The bit length of the encryption key.
10647					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10648				}
10649				$out .= ' >> >>';
10650			}
10651			// The name of the crypt filter that shall be used by default when decrypting streams.
10652			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10653			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10654			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10655			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10656				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10657				$out .= ' /EFF /'.$this->encryptdata[''];
10658			}
10659		}
10660		// Additional encryption dictionary entries for the standard security handler
10661		if ($this->encryptdata['pubkey']) {
10662			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10663				$out .= ' /Recipients [';
10664				foreach ($this->encryptdata['Recipients'] as $rec) {
10665					$out .= ' <'.$rec.'>';
10666				}
10667				$out .= ' ]';
10668			}
10669		} else {
10670			$out .= ' /R';
10671			if ($this->encryptdata['V'] == 5) { // AES-256
10672				$out .= ' 5';
10673				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10674				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10675				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10676			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10677				$out .= ' 4';
10678			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10679				$out .= ' 2';
10680			} else { // RC-128
10681				$out .= ' 3';
10682			}
10683			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10684			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10685			$out .= ' /P '.$this->encryptdata['P'];
10686			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10687				$out .= ' /EncryptMetadata false';
10688			} else {
10689				$out .= ' /EncryptMetadata true';
10690			}
10691		}
10692		$out .= ' >>';
10693		$out .= "\n".'endobj';
10694		$this->_out($out);
10695	}
10696
10697	/**
10698	 * Compute U value (used for encryption)
10699	 * @return string U value
10700	 * @protected
10701	 * @since 2.0.000 (2008-01-02)
10702	 * @author Nicola Asuni
10703	 */
10704	protected function _Uvalue() {
10705		if ($this->encryptdata['mode'] == 0) { // RC4-40
10706			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10707		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10708			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10709			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10710			$len = strlen($tmp);
10711			for ($i = 1; $i <= 19; ++$i) {
10712				$ek = '';
10713				for ($j = 0; $j < $len; ++$j) {
10714					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10715				}
10716				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10717			}
10718			$enc .= str_repeat("\x00", 16);
10719			return substr($enc, 0, 32);
10720		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10721			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10722			// User Validation Salt
10723			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10724			// User Key Salt
10725			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10726			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10727		}
10728	}
10729
10730	/**
10731	 * Compute UE value (used for encryption)
10732	 * @return string UE value
10733	 * @protected
10734	 * @since 5.9.006 (2010-10-19)
10735	 * @author Nicola Asuni
10736	 */
10737	protected function _UEvalue() {
10738		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10739		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10740	}
10741
10742	/**
10743	 * Compute O value (used for encryption)
10744	 * @return string O value
10745	 * @protected
10746	 * @since 2.0.000 (2008-01-02)
10747	 * @author Nicola Asuni
10748	 */
10749	protected function _Ovalue() {
10750		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10751			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10752			if ($this->encryptdata['mode'] > 0) {
10753				for ($i = 0; $i < 50; ++$i) {
10754					$tmp = TCPDF_STATIC::_md5_16($tmp);
10755				}
10756			}
10757			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10758			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10759			if ($this->encryptdata['mode'] > 0) {
10760				$len = strlen($owner_key);
10761				for ($i = 1; $i <= 19; ++$i) {
10762					$ek = '';
10763					for ($j = 0; $j < $len; ++$j) {
10764						$ek .= chr(ord($owner_key[$j]) ^ $i);
10765					}
10766					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10767				}
10768			}
10769			return $enc;
10770		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10771			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10772			// Owner Validation Salt
10773			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10774			// Owner Key Salt
10775			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10776			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10777		}
10778	}
10779
10780	/**
10781	 * Compute OE value (used for encryption)
10782	 * @return string OE value
10783	 * @protected
10784	 * @since 5.9.006 (2010-10-19)
10785	 * @author Nicola Asuni
10786	 */
10787	protected function _OEvalue() {
10788		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10789		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10790	}
10791
10792	/**
10793	 * Convert password for AES-256 encryption mode
10794	 * @param string $password password
10795	 * @return string password
10796	 * @protected
10797	 * @since 5.9.006 (2010-10-19)
10798	 * @author Nicola Asuni
10799	 */
10800	protected function _fixAES256Password($password) {
10801		$psw = ''; // password to be returned
10802		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10803		foreach ($psw_array as $c) {
10804			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10805		}
10806		return substr($psw, 0, 127);
10807	}
10808
10809	/**
10810	 * Compute encryption key
10811	 * @protected
10812	 * @since 2.0.000 (2008-01-02)
10813	 * @author Nicola Asuni
10814	 */
10815	protected function _generateencryptionkey() {
10816		$keybytelen = ($this->encryptdata['Length'] / 8);
10817		if (!$this->encryptdata['pubkey']) { // standard mode
10818			if ($this->encryptdata['mode'] == 3) { // AES-256
10819				// generate 256 bit random key
10820				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10821				// truncate passwords
10822				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10823				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10824				// Compute U value
10825				$this->encryptdata['U'] = $this->_Uvalue();
10826				// Compute UE value
10827				$this->encryptdata['UE'] = $this->_UEvalue();
10828				// Compute O value
10829				$this->encryptdata['O'] = $this->_Ovalue();
10830				// Compute OE value
10831				$this->encryptdata['OE'] = $this->_OEvalue();
10832				// Compute P value
10833				$this->encryptdata['P'] = $this->encryptdata['protection'];
10834				// Computing the encryption dictionary's Perms (permissions) value
10835				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10836				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10837				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10838					$perms .= 'F';
10839				} else {
10840					$perms .= 'T';
10841				}
10842				$perms .= 'adb'; // bytes 9-11
10843				$perms .= 'nick'; // bytes 12-15
10844				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10845			} else { // RC4-40, RC4-128, AES-128
10846				// Pad passwords
10847				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10848				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10849				// Compute O value
10850				$this->encryptdata['O'] = $this->_Ovalue();
10851				// get default permissions (reverse byte order)
10852				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10853				// Compute encryption key
10854				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10855				if ($this->encryptdata['mode'] > 0) {
10856					for ($i = 0; $i < 50; ++$i) {
10857						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10858					}
10859				}
10860				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10861				// Compute U value
10862				$this->encryptdata['U'] = $this->_Uvalue();
10863				// Compute P value
10864				$this->encryptdata['P'] = $this->encryptdata['protection'];
10865			}
10866		} else { // Public-Key mode
10867			// random 20-byte seed
10868			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10869			$recipient_bytes = '';
10870			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10871				// for each public certificate
10872				if (isset($pubkey['p'])) {
10873					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10874				} else {
10875					$pkprotection = $this->encryptdata['protection'];
10876				}
10877				// get default permissions (reverse byte order)
10878				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10879				// envelope data
10880				$envelope = $seed.$pkpermissions;
10881				// write the envelope data to a temporary file
10882				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10883				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10884				if (!$f) {
10885					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10886				}
10887				$envelope_length = strlen($envelope);
10888				fwrite($f, $envelope, $envelope_length);
10889				fclose($f);
10890				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10891				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10892					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10893				}
10894				// read encryption signature
10895				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10896				// extract signature
10897				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10898				$tmparr = explode("\n\n", $signature);
10899				$signature = trim($tmparr[1]);
10900				unset($tmparr);
10901				// decode signature
10902				$signature = base64_decode($signature);
10903				// convert signature to hex
10904				$hexsignature = current(unpack('H*', $signature));
10905				// store signature on recipients array
10906				$this->encryptdata['Recipients'][] = $hexsignature;
10907				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10908				$recipient_bytes .= $signature;
10909			}
10910			// calculate encryption key
10911			if ($this->encryptdata['mode'] == 3) { // AES-256
10912				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10913			} else { // RC4-40, RC4-128, AES-128
10914				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10915			}
10916		}
10917	}
10918
10919	/**
10920	 * Set document protection
10921	 * Remark: the protection against modification is for people who have the full Acrobat product.
10922	 * 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.
10923	 * 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.
10924	 * @param array $permissions the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
10925	 * @param string $user_pass user password. Empty by default.
10926	 * @param string $owner_pass owner password. If not specified, a random value is used.
10927	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10928	 * @param string $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10929	 * @public
10930	 * @since 2.0.000 (2008-01-02)
10931	 * @author Nicola Asuni
10932	 */
10933	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) {
10934		if ($this->pdfa_mode) {
10935			// encryption is not allowed in PDF/A mode
10936			return;
10937		}
10938		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10939		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10940			// public-key mode
10941			$this->encryptdata['pubkeys'] = $pubkeys;
10942			if ($mode == 0) {
10943				// public-Key Security requires at least 128 bit
10944				$mode = 1;
10945			}
10946			if (!function_exists('openssl_pkcs7_encrypt')) {
10947				$this->Error('Public-Key Security requires openssl library.');
10948			}
10949			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10950			$this->encryptdata['pubkey'] = true;
10951			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10952			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
10953			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
10954		} else {
10955			// standard mode (password mode)
10956			$this->encryptdata['pubkey'] = false;
10957			$this->encryptdata['Filter'] = 'Standard';
10958			$this->encryptdata['StmF'] = 'StdCF';
10959			$this->encryptdata['StrF'] = 'StdCF';
10960		}
10961		if ($mode > 1) { // AES
10962			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10963				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10964			}
10965			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10966				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10967			}
10968			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10969				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10970			}
10971			if (($mode == 3) AND !function_exists('hash')) {
10972				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10973				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10974			}
10975		}
10976		if ($owner_pass === null) {
10977			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10978		}
10979		$this->encryptdata['user_password'] = $user_pass;
10980		$this->encryptdata['owner_password'] = $owner_pass;
10981		$this->encryptdata['mode'] = $mode;
10982		switch ($mode) {
10983			case 0: { // RC4 40 bit
10984				$this->encryptdata['V'] = 1;
10985				$this->encryptdata['Length'] = 40;
10986				$this->encryptdata['CF']['CFM'] = 'V2';
10987				break;
10988			}
10989			case 1: { // RC4 128 bit
10990				$this->encryptdata['V'] = 2;
10991				$this->encryptdata['Length'] = 128;
10992				$this->encryptdata['CF']['CFM'] = 'V2';
10993				if ($this->encryptdata['pubkey']) {
10994					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10995					$this->encryptdata['Recipients'] = array();
10996				}
10997				break;
10998			}
10999			case 2: { // AES 128 bit
11000				$this->encryptdata['V'] = 4;
11001				$this->encryptdata['Length'] = 128;
11002				$this->encryptdata['CF']['CFM'] = 'AESV2';
11003				$this->encryptdata['CF']['Length'] = 128;
11004				if ($this->encryptdata['pubkey']) {
11005					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11006					$this->encryptdata['Recipients'] = array();
11007				}
11008				break;
11009			}
11010			case 3: { // AES 256 bit
11011				$this->encryptdata['V'] = 5;
11012				$this->encryptdata['Length'] = 256;
11013				$this->encryptdata['CF']['CFM'] = 'AESV3';
11014				$this->encryptdata['CF']['Length'] = 256;
11015				if ($this->encryptdata['pubkey']) {
11016					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11017					$this->encryptdata['Recipients'] = array();
11018				}
11019				break;
11020			}
11021		}
11022		$this->encrypted = true;
11023		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11024		$this->_generateencryptionkey();
11025	}
11026
11027	// END OF ENCRYPTION FUNCTIONS -------------------------
11028
11029	// START TRANSFORMATIONS SECTION -----------------------
11030
11031	/**
11032	 * Starts a 2D tranformation saving current graphic state.
11033	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11034	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11035	 * @public
11036	 * @since 2.1.000 (2008-01-07)
11037	 * @see StartTransform(), StopTransform()
11038	 */
11039	public function StartTransform() {
11040		if ($this->state != 2) {
11041			return;
11042		}
11043		$this->_outSaveGraphicsState();
11044		if ($this->inxobj) {
11045			// we are inside an XObject template
11046			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11047		} else {
11048			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11049		}
11050		++$this->transfmatrix_key;
11051		$this->transfmatrix[$this->transfmatrix_key] = array();
11052	}
11053
11054	/**
11055	 * Stops a 2D tranformation restoring previous graphic state.
11056	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11057	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11058	 * @public
11059	 * @since 2.1.000 (2008-01-07)
11060	 * @see StartTransform(), StopTransform()
11061	 */
11062	public function StopTransform() {
11063		if ($this->state != 2) {
11064			return;
11065		}
11066		$this->_outRestoreGraphicsState();
11067		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11068			array_pop($this->transfmatrix[$this->transfmatrix_key]);
11069			--$this->transfmatrix_key;
11070		}
11071		if ($this->inxobj) {
11072			// we are inside an XObject template
11073			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11074		} else {
11075			array_pop($this->transfmrk[$this->page]);
11076		}
11077	}
11078	/**
11079	 * Horizontal Scaling.
11080	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11081	 * @param int $x abscissa of the scaling center. Default is current x position
11082	 * @param int $y ordinate of the scaling center. Default is current y position
11083	 * @public
11084	 * @since 2.1.000 (2008-01-07)
11085	 * @see StartTransform(), StopTransform()
11086	 */
11087	public function ScaleX($s_x, $x='', $y='') {
11088		$this->Scale($s_x, 100, $x, $y);
11089	}
11090
11091	/**
11092	 * Vertical Scaling.
11093	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11094	 * @param int $x abscissa of the scaling center. Default is current x position
11095	 * @param int $y ordinate of the scaling center. Default is current y position
11096	 * @public
11097	 * @since 2.1.000 (2008-01-07)
11098	 * @see StartTransform(), StopTransform()
11099	 */
11100	public function ScaleY($s_y, $x='', $y='') {
11101		$this->Scale(100, $s_y, $x, $y);
11102	}
11103
11104	/**
11105	 * Vertical and horizontal proportional Scaling.
11106	 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11107	 * @param int $x abscissa of the scaling center. Default is current x position
11108	 * @param int $y ordinate of the scaling center. Default is current y position
11109	 * @public
11110	 * @since 2.1.000 (2008-01-07)
11111	 * @see StartTransform(), StopTransform()
11112	 */
11113	public function ScaleXY($s, $x='', $y='') {
11114		$this->Scale($s, $s, $x, $y);
11115	}
11116
11117	/**
11118	 * Vertical and horizontal non-proportional Scaling.
11119	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11120	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11121	 * @param int $x abscissa of the scaling center. Default is current x position
11122	 * @param int $y ordinate of the scaling center. Default is current y position
11123	 * @public
11124	 * @since 2.1.000 (2008-01-07)
11125	 * @see StartTransform(), StopTransform()
11126	 */
11127	public function Scale($s_x, $s_y, $x='', $y='') {
11128		if ($x === '') {
11129			$x = $this->x;
11130		}
11131		if ($y === '') {
11132			$y = $this->y;
11133		}
11134		if (($s_x == 0) OR ($s_y == 0)) {
11135			$this->Error('Please do not use values equal to zero for scaling');
11136		}
11137		$y = ($this->h - $y) * $this->k;
11138		$x *= $this->k;
11139		//calculate elements of transformation matrix
11140		$s_x /= 100;
11141		$s_y /= 100;
11142		$tm = array();
11143		$tm[0] = $s_x;
11144		$tm[1] = 0;
11145		$tm[2] = 0;
11146		$tm[3] = $s_y;
11147		$tm[4] = $x * (1 - $s_x);
11148		$tm[5] = $y * (1 - $s_y);
11149		//scale the coordinate system
11150		$this->Transform($tm);
11151	}
11152
11153	/**
11154	 * Horizontal Mirroring.
11155	 * @param int $x abscissa of the point. Default is current x position
11156	 * @public
11157	 * @since 2.1.000 (2008-01-07)
11158	 * @see StartTransform(), StopTransform()
11159	 */
11160	public function MirrorH($x='') {
11161		$this->Scale(-100, 100, $x);
11162	}
11163
11164	/**
11165	 * Verical Mirroring.
11166	 * @param int $y ordinate of the point. Default is current y position
11167	 * @public
11168	 * @since 2.1.000 (2008-01-07)
11169	 * @see StartTransform(), StopTransform()
11170	 */
11171	public function MirrorV($y='') {
11172		$this->Scale(100, -100, '', $y);
11173	}
11174
11175	/**
11176	 * Point reflection mirroring.
11177	 * @param int $x abscissa of the point. Default is current x position
11178	 * @param int $y ordinate of the point. Default is current y position
11179	 * @public
11180	 * @since 2.1.000 (2008-01-07)
11181	 * @see StartTransform(), StopTransform()
11182	 */
11183	public function MirrorP($x='',$y='') {
11184		$this->Scale(-100, -100, $x, $y);
11185	}
11186
11187	/**
11188	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11189	 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11190	 * @param int $x abscissa of the point. Default is current x position
11191	 * @param int $y ordinate of the point. Default is current y position
11192	 * @public
11193	 * @since 2.1.000 (2008-01-07)
11194	 * @see StartTransform(), StopTransform()
11195	 */
11196	public function MirrorL($angle=0, $x='',$y='') {
11197		$this->Scale(-100, 100, $x, $y);
11198		$this->Rotate(-2*($angle-90), $x, $y);
11199	}
11200
11201	/**
11202	 * Translate graphic object horizontally.
11203	 * @param int $t_x movement to the right (or left for RTL)
11204	 * @public
11205	 * @since 2.1.000 (2008-01-07)
11206	 * @see StartTransform(), StopTransform()
11207	 */
11208	public function TranslateX($t_x) {
11209		$this->Translate($t_x, 0);
11210	}
11211
11212	/**
11213	 * Translate graphic object vertically.
11214	 * @param int $t_y movement to the bottom
11215	 * @public
11216	 * @since 2.1.000 (2008-01-07)
11217	 * @see StartTransform(), StopTransform()
11218	 */
11219	public function TranslateY($t_y) {
11220		$this->Translate(0, $t_y);
11221	}
11222
11223	/**
11224	 * Translate graphic object horizontally and vertically.
11225	 * @param int $t_x movement to the right
11226	 * @param int $t_y movement to the bottom
11227	 * @public
11228	 * @since 2.1.000 (2008-01-07)
11229	 * @see StartTransform(), StopTransform()
11230	 */
11231	public function Translate($t_x, $t_y) {
11232		//calculate elements of transformation matrix
11233		$tm = array();
11234		$tm[0] = 1;
11235		$tm[1] = 0;
11236		$tm[2] = 0;
11237		$tm[3] = 1;
11238		$tm[4] = $t_x * $this->k;
11239		$tm[5] = -$t_y * $this->k;
11240		//translate the coordinate system
11241		$this->Transform($tm);
11242	}
11243
11244	/**
11245	 * Rotate object.
11246	 * @param float $angle angle in degrees for counter-clockwise rotation
11247	 * @param int $x abscissa of the rotation center. Default is current x position
11248	 * @param int $y ordinate of the rotation center. Default is current y position
11249	 * @public
11250	 * @since 2.1.000 (2008-01-07)
11251	 * @see StartTransform(), StopTransform()
11252	 */
11253	public function Rotate($angle, $x='', $y='') {
11254		if ($x === '') {
11255			$x = $this->x;
11256		}
11257		if ($y === '') {
11258			$y = $this->y;
11259		}
11260		$y = ($this->h - $y) * $this->k;
11261		$x *= $this->k;
11262		//calculate elements of transformation matrix
11263		$tm = array();
11264		$tm[0] = cos(deg2rad($angle));
11265		$tm[1] = sin(deg2rad($angle));
11266		$tm[2] = -$tm[1];
11267		$tm[3] = $tm[0];
11268		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11269		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11270		//rotate the coordinate system around ($x,$y)
11271		$this->Transform($tm);
11272	}
11273
11274	/**
11275	 * Skew horizontally.
11276	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11277	 * @param int $x abscissa of the skewing center. default is current x position
11278	 * @param int $y ordinate of the skewing center. default is current y position
11279	 * @public
11280	 * @since 2.1.000 (2008-01-07)
11281	 * @see StartTransform(), StopTransform()
11282	 */
11283	public function SkewX($angle_x, $x='', $y='') {
11284		$this->Skew($angle_x, 0, $x, $y);
11285	}
11286
11287	/**
11288	 * Skew vertically.
11289	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11290	 * @param int $x abscissa of the skewing center. default is current x position
11291	 * @param int $y ordinate of the skewing center. default is current y position
11292	 * @public
11293	 * @since 2.1.000 (2008-01-07)
11294	 * @see StartTransform(), StopTransform()
11295	 */
11296	public function SkewY($angle_y, $x='', $y='') {
11297		$this->Skew(0, $angle_y, $x, $y);
11298	}
11299
11300	/**
11301	 * Skew.
11302	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11303	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11304	 * @param int $x abscissa of the skewing center. default is current x position
11305	 * @param int $y ordinate of the skewing center. default is current y position
11306	 * @public
11307	 * @since 2.1.000 (2008-01-07)
11308	 * @see StartTransform(), StopTransform()
11309	 */
11310	public function Skew($angle_x, $angle_y, $x='', $y='') {
11311		if ($x === '') {
11312			$x = $this->x;
11313		}
11314		if ($y === '') {
11315			$y = $this->y;
11316		}
11317		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11318			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11319		}
11320		$x *= $this->k;
11321		$y = ($this->h - $y) * $this->k;
11322		//calculate elements of transformation matrix
11323		$tm = array();
11324		$tm[0] = 1;
11325		$tm[1] = tan(deg2rad($angle_y));
11326		$tm[2] = tan(deg2rad($angle_x));
11327		$tm[3] = 1;
11328		$tm[4] = -$tm[2] * $y;
11329		$tm[5] = -$tm[1] * $x;
11330		//skew the coordinate system
11331		$this->Transform($tm);
11332	}
11333
11334	/**
11335	 * Apply graphic transformations.
11336	 * @param array $tm transformation matrix
11337	 * @protected
11338	 * @since 2.1.000 (2008-01-07)
11339	 * @see StartTransform(), StopTransform()
11340	 */
11341	protected function Transform($tm) {
11342		if ($this->state != 2) {
11343			return;
11344		}
11345		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11346		// add tranformation matrix
11347		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11348		// update transformation mark
11349		if ($this->inxobj) {
11350			// we are inside an XObject template
11351			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11352				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11353				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11354			}
11355		} elseif (end($this->transfmrk[$this->page]) !== false) {
11356			$key = key($this->transfmrk[$this->page]);
11357			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11358		}
11359	}
11360
11361	// END TRANSFORMATIONS SECTION -------------------------
11362
11363	// START GRAPHIC FUNCTIONS SECTION ---------------------
11364	// The following section is based on the code provided by David Hernandez Sanz
11365
11366	/**
11367	 * 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.
11368	 * @param float $width The width.
11369	 * @public
11370	 * @since 1.0
11371	 * @see Line(), Rect(), Cell(), MultiCell()
11372	 */
11373	public function SetLineWidth($width) {
11374		//Set line width
11375		$this->LineWidth = $width;
11376		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11377		if ($this->state == 2) {
11378			$this->_out($this->linestyleWidth);
11379		}
11380	}
11381
11382	/**
11383	 * Returns the current the line width.
11384	 * @return int Line width
11385	 * @public
11386	 * @since 2.1.000 (2008-01-07)
11387	 * @see Line(), SetLineWidth()
11388	 */
11389	public function GetLineWidth() {
11390		return $this->LineWidth;
11391	}
11392
11393	/**
11394	 * Set line style.
11395	 * @param array $style Line style. Array with keys among the following:
11396	 * <ul>
11397	 *	 <li>width (float): Width of the line in user units.</li>
11398	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11399	 * butt, round, square. The difference between "square" and "butt" is that
11400	 * "square" projects a flat end past the end of the line.</li>
11401	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11402	 * bevel.</li>
11403	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11404	 * series of length values, which are the lengths of the on and off dashes.
11405	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11406	 * 1 off, 2 on, 1 off, ...</li>
11407	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11408	 * the point at which the pattern starts.</li>
11409	 *	 <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>
11410	 * </ul>
11411	 * @param boolean $ret if true do not send the command.
11412	 * @return string the PDF command
11413	 * @public
11414	 * @since 2.1.000 (2008-01-08)
11415	 */
11416	public function SetLineStyle($style, $ret=false) {
11417		$s = ''; // string to be returned
11418		if (!is_array($style)) {
11419			return $s;
11420		}
11421		if (isset($style['width'])) {
11422			$this->LineWidth = $style['width'];
11423			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11424			$s .= $this->linestyleWidth.' ';
11425		}
11426		if (isset($style['cap'])) {
11427			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11428			if (isset($ca[$style['cap']])) {
11429				$this->linestyleCap = $ca[$style['cap']].' J';
11430				$s .= $this->linestyleCap.' ';
11431			}
11432		}
11433		if (isset($style['join'])) {
11434			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11435			if (isset($ja[$style['join']])) {
11436				$this->linestyleJoin = $ja[$style['join']].' j';
11437				$s .= $this->linestyleJoin.' ';
11438			}
11439		}
11440		if (isset($style['dash'])) {
11441			$dash_string = '';
11442			if ($style['dash']) {
11443				if (preg_match('/^.+,/', $style['dash']) > 0) {
11444					$tab = explode(',', $style['dash']);
11445				} else {
11446					$tab = array($style['dash']);
11447				}
11448				$dash_string = '';
11449				foreach ($tab as $i => $v) {
11450					if ($i) {
11451						$dash_string .= ' ';
11452					}
11453					$dash_string .= sprintf('%F', $v);
11454				}
11455			}
11456			if (!isset($style['phase']) OR !$style['dash']) {
11457				$style['phase'] = 0;
11458			}
11459			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11460			$s .= $this->linestyleDash.' ';
11461		}
11462		if (isset($style['color'])) {
11463			$s .= $this->SetDrawColorArray($style['color'], true).' ';
11464		}
11465		if (!$ret AND ($this->state == 2)) {
11466			$this->_out($s);
11467		}
11468		return $s;
11469	}
11470
11471	/**
11472	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11473	 * @param float $x Abscissa of point.
11474	 * @param float $y Ordinate of point.
11475	 * @protected
11476	 * @since 2.1.000 (2008-01-08)
11477	 */
11478	protected function _outPoint($x, $y) {
11479		if ($this->state == 2) {
11480			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11481		}
11482	}
11483
11484	/**
11485	 * Append a straight line segment from the current point to the point (x, y).
11486	 * The new current point shall be (x, y).
11487	 * @param float $x Abscissa of end point.
11488	 * @param float $y Ordinate of end point.
11489	 * @protected
11490	 * @since 2.1.000 (2008-01-08)
11491	 */
11492	protected function _outLine($x, $y) {
11493		if ($this->state == 2) {
11494			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11495		}
11496	}
11497
11498	/**
11499	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11500	 * @param float $x Abscissa of upper-left corner.
11501	 * @param float $y Ordinate of upper-left corner.
11502	 * @param float $w Width.
11503	 * @param float $h Height.
11504	 * @param string $op options
11505	 * @protected
11506	 * @since 2.1.000 (2008-01-08)
11507	 */
11508	protected function _outRect($x, $y, $w, $h, $op) {
11509		if ($this->state == 2) {
11510			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11511		}
11512	}
11513
11514	/**
11515	 * 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.
11516	 * The new current point shall be (x3, y3).
11517	 * @param float $x1 Abscissa of control point 1.
11518	 * @param float $y1 Ordinate of control point 1.
11519	 * @param float $x2 Abscissa of control point 2.
11520	 * @param float $y2 Ordinate of control point 2.
11521	 * @param float $x3 Abscissa of end point.
11522	 * @param float $y3 Ordinate of end point.
11523	 * @protected
11524	 * @since 2.1.000 (2008-01-08)
11525	 */
11526	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11527		if ($this->state == 2) {
11528			$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)));
11529		}
11530	}
11531
11532	/**
11533	 * 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.
11534	 * The new current point shall be (x3, y3).
11535	 * @param float $x2 Abscissa of control point 2.
11536	 * @param float $y2 Ordinate of control point 2.
11537	 * @param float $x3 Abscissa of end point.
11538	 * @param float $y3 Ordinate of end point.
11539	 * @protected
11540	 * @since 4.9.019 (2010-04-26)
11541	 */
11542	protected function _outCurveV($x2, $y2, $x3, $y3) {
11543		if ($this->state == 2) {
11544			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11545		}
11546	}
11547
11548	/**
11549	 * 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.
11550	 * The new current point shall be (x3, y3).
11551	 * @param float $x1 Abscissa of control point 1.
11552	 * @param float $y1 Ordinate of control point 1.
11553	 * @param float $x3 Abscissa of end point.
11554	 * @param float $y3 Ordinate of end point.
11555	 * @protected
11556	 * @since 2.1.000 (2008-01-08)
11557	 */
11558	protected function _outCurveY($x1, $y1, $x3, $y3) {
11559		if ($this->state == 2) {
11560			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11561		}
11562	}
11563
11564	/**
11565	 * Draws a line between two points.
11566	 * @param float $x1 Abscissa of first point.
11567	 * @param float $y1 Ordinate of first point.
11568	 * @param float $x2 Abscissa of second point.
11569	 * @param float $y2 Ordinate of second point.
11570	 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11571	 * @public
11572	 * @since 1.0
11573	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11574	 */
11575	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11576		if ($this->state != 2) {
11577			return;
11578		}
11579		if (is_array($style)) {
11580			$this->SetLineStyle($style);
11581		}
11582		$this->_outPoint($x1, $y1);
11583		$this->_outLine($x2, $y2);
11584		$this->_out('S');
11585	}
11586
11587	/**
11588	 * Draws a rectangle.
11589	 * @param float $x Abscissa of upper-left corner.
11590	 * @param float $y Ordinate of upper-left corner.
11591	 * @param float $w Width.
11592	 * @param float $h Height.
11593	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11594	 * @param array $border_style Border style of rectangle. Array with keys among the following:
11595	 * <ul>
11596	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11597	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11598	 * </ul>
11599	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11600	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11601	 * @public
11602	 * @since 1.0
11603	 * @see SetLineStyle()
11604	 */
11605	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11606		if ($this->state != 2) {
11607			return;
11608		}
11609		if (empty($style)) {
11610			$style = 'S';
11611		}
11612		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11613			// set background color
11614			$this->SetFillColorArray($fill_color);
11615		}
11616		if (!empty($border_style)) {
11617			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11618				//set global style for border
11619				$this->SetLineStyle($border_style['all']);
11620				$border_style = array();
11621			} else {
11622				// remove stroke operator from style
11623				$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*' );
11624				if (isset($opnostroke[$style])) {
11625					$style = $opnostroke[$style];
11626				}
11627			}
11628		}
11629		if (!empty($style)) {
11630			$op = TCPDF_STATIC::getPathPaintOperator($style);
11631			$this->_outRect($x, $y, $w, $h, $op);
11632		}
11633		if (!empty($border_style)) {
11634			$border_style2 = array();
11635			foreach ($border_style as $line => $value) {
11636				$length = strlen($line);
11637				for ($i = 0; $i < $length; ++$i) {
11638					$border_style2[$line[$i]] = $value;
11639				}
11640			}
11641			$border_style = $border_style2;
11642			if (isset($border_style['L']) AND $border_style['L']) {
11643				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11644			}
11645			if (isset($border_style['T']) AND $border_style['T']) {
11646				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11647			}
11648			if (isset($border_style['R']) AND $border_style['R']) {
11649				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11650			}
11651			if (isset($border_style['B']) AND $border_style['B']) {
11652				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11653			}
11654		}
11655	}
11656
11657	/**
11658	 * Draws a Bezier curve.
11659	 * The Bezier curve is a tangent to the line between the control points at
11660	 * either end of the curve.
11661	 * @param float $x0 Abscissa of start point.
11662	 * @param float $y0 Ordinate of start point.
11663	 * @param float $x1 Abscissa of control point 1.
11664	 * @param float $y1 Ordinate of control point 1.
11665	 * @param float $x2 Abscissa of control point 2.
11666	 * @param float $y2 Ordinate of control point 2.
11667	 * @param float $x3 Abscissa of end point.
11668	 * @param float $y3 Ordinate of end point.
11669	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11670	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11671	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11672	 * @public
11673	 * @see SetLineStyle()
11674	 * @since 2.1.000 (2008-01-08)
11675	 */
11676	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11677		if ($this->state != 2) {
11678			return;
11679		}
11680		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11681			$this->SetFillColorArray($fill_color);
11682		}
11683		$op = TCPDF_STATIC::getPathPaintOperator($style);
11684		if ($line_style) {
11685			$this->SetLineStyle($line_style);
11686		}
11687		$this->_outPoint($x0, $y0);
11688		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11689		$this->_out($op);
11690	}
11691
11692	/**
11693	 * Draws a poly-Bezier curve.
11694	 * Each Bezier curve segment is a tangent to the line between the control points at
11695	 * either end of the curve.
11696	 * @param float $x0 Abscissa of start point.
11697	 * @param float $y0 Ordinate of start point.
11698	 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11699	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11700	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11701	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11702	 * @public
11703	 * @see SetLineStyle()
11704	 * @since 3.0008 (2008-05-12)
11705	 */
11706	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11707		if ($this->state != 2) {
11708			return;
11709		}
11710		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11711			$this->SetFillColorArray($fill_color);
11712		}
11713		$op = TCPDF_STATIC::getPathPaintOperator($style);
11714		if ($op == 'f') {
11715			$line_style = array();
11716		}
11717		if ($line_style) {
11718			$this->SetLineStyle($line_style);
11719		}
11720		$this->_outPoint($x0, $y0);
11721		foreach ($segments as $segment) {
11722			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11723			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11724		}
11725		$this->_out($op);
11726	}
11727
11728	/**
11729	 * Draws an ellipse.
11730	 * An ellipse is formed from n Bezier curves.
11731	 * @param float $x0 Abscissa of center point.
11732	 * @param float $y0 Ordinate of center point.
11733	 * @param float $rx Horizontal radius.
11734	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11735	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11736	 * @param float $astart Angle start of draw line. Default value: 0.
11737	 * @param float $afinish Angle finish of draw line. Default value: 360.
11738	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11739	 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11740	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11741	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11742	 * @author Nicola Asuni
11743	 * @public
11744	 * @since 2.1.000 (2008-01-08)
11745	 */
11746	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11747		if ($this->state != 2) {
11748			return;
11749		}
11750		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11751			$ry = $rx;
11752		}
11753		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11754			$this->SetFillColorArray($fill_color);
11755		}
11756		$op = TCPDF_STATIC::getPathPaintOperator($style);
11757		if ($op == 'f') {
11758			$line_style = array();
11759		}
11760		if ($line_style) {
11761			$this->SetLineStyle($line_style);
11762		}
11763		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11764		$this->_out($op);
11765	}
11766
11767	/**
11768	 * Append an elliptical arc to the current path.
11769	 * An ellipse is formed from n Bezier curves.
11770	 * @param float $xc Abscissa of center point.
11771	 * @param float $yc Ordinate of center point.
11772	 * @param float $rx Horizontal radius.
11773	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11774	 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11775	 * @param float $angs Angle start of draw line. Default value: 0.
11776	 * @param float $angf Angle finish of draw line. Default value: 360.
11777	 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11778	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11779	 * @param boolean $startpoint if true output a starting point.
11780	 * @param boolean $ccw if true draws in counter-clockwise.
11781	 * @param boolean $svg if true the angles are in svg mode (already calculated).
11782	 * @return array bounding box coordinates (x min, y min, x max, y max)
11783	 * @author Nicola Asuni
11784	 * @protected
11785	 * @since 4.9.019 (2010-04-26)
11786	 */
11787	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11788		if (($rx <= 0) OR ($ry < 0)) {
11789			return;
11790		}
11791		$k = $this->k;
11792		if ($nc < 2) {
11793			$nc = 2;
11794		}
11795		$xmin = 2147483647;
11796		$ymin = 2147483647;
11797		$xmax = 0;
11798		$ymax = 0;
11799		if ($pie) {
11800			// center of the arc
11801			$this->_outPoint($xc, $yc);
11802		}
11803		$xang = deg2rad((float) $xang);
11804		$angs = deg2rad((float) $angs);
11805		$angf = deg2rad((float) $angf);
11806		if ($svg) {
11807			$as = $angs;
11808			$af = $angf;
11809		} else {
11810			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11811			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11812		}
11813		if ($as < 0) {
11814			$as += (2 * M_PI);
11815		}
11816		if ($af < 0) {
11817			$af += (2 * M_PI);
11818		}
11819		if ($ccw AND ($as > $af)) {
11820			// reverse rotation
11821			$as -= (2 * M_PI);
11822		} elseif (!$ccw AND ($as < $af)) {
11823			// reverse rotation
11824			$af -= (2 * M_PI);
11825		}
11826		$total_angle = ($af - $as);
11827		if ($nc < 2) {
11828			$nc = 2;
11829		}
11830		// total arcs to draw
11831		$nc *= (2 * abs($total_angle) / M_PI);
11832		$nc = round($nc) + 1;
11833		// angle of each arc
11834		$arcang = ($total_angle / $nc);
11835		// center point in PDF coordinates
11836		$x0 = $xc;
11837		$y0 = ($this->h - $yc);
11838		// starting angle
11839		$ang = $as;
11840		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11841		$cos_xang = cos($xang);
11842		$sin_xang = sin($xang);
11843		$cos_ang = cos($ang);
11844		$sin_ang = sin($ang);
11845		// first arc point
11846		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11847		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11848		// first Bezier control point
11849		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11850		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11851		if ($pie) {
11852			// line from center to arc starting point
11853			$this->_outLine($px1, $this->h - $py1);
11854		} elseif ($startpoint) {
11855			// arc starting point
11856			$this->_outPoint($px1, $this->h - $py1);
11857		}
11858		// draw arcs
11859		for ($i = 1; $i <= $nc; ++$i) {
11860			// starting angle
11861			$ang = $as + ($i * $arcang);
11862			if ($i == $nc) {
11863				$ang = $af;
11864			}
11865			$cos_ang = cos($ang);
11866			$sin_ang = sin($ang);
11867			// second arc point
11868			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11869			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11870			// second Bezier control point
11871			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11872			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11873			// draw arc
11874			$cx1 = ($px1 + $qx1);
11875			$cy1 = ($this->h - ($py1 + $qy1));
11876			$cx2 = ($px2 - $qx2);
11877			$cy2 = ($this->h - ($py2 - $qy2));
11878			$cx3 = $px2;
11879			$cy3 = ($this->h - $py2);
11880			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11881			// get bounding box coordinates
11882			$xmin = min($xmin, $cx1, $cx2, $cx3);
11883			$ymin = min($ymin, $cy1, $cy2, $cy3);
11884			$xmax = max($xmax, $cx1, $cx2, $cx3);
11885			$ymax = max($ymax, $cy1, $cy2, $cy3);
11886			// move to next point
11887			$px1 = $px2;
11888			$py1 = $py2;
11889			$qx1 = $qx2;
11890			$qy1 = $qy2;
11891		}
11892		if ($pie) {
11893			$this->_outLine($xc, $yc);
11894			// get bounding box coordinates
11895			$xmin = min($xmin, $xc);
11896			$ymin = min($ymin, $yc);
11897			$xmax = max($xmax, $xc);
11898			$ymax = max($ymax, $yc);
11899		}
11900		return array($xmin, $ymin, $xmax, $ymax);
11901	}
11902
11903	/**
11904	 * Draws a circle.
11905	 * A circle is formed from n Bezier curves.
11906	 * @param float $x0 Abscissa of center point.
11907	 * @param float $y0 Ordinate of center point.
11908	 * @param float $r Radius.
11909	 * @param float $angstr Angle start of draw line. Default value: 0.
11910	 * @param float $angend Angle finish of draw line. Default value: 360.
11911	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11912	 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11913	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11914	 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
11915	 * @public
11916	 * @since 2.1.000 (2008-01-08)
11917	 */
11918	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11919		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11920	}
11921
11922	/**
11923	 * Draws a polygonal line
11924	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11925	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11926	 * @param array $line_style Line style of polygon. Array with keys among the following:
11927	 * <ul>
11928	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11929	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11930	 * </ul>
11931	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11932	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11933	 * @since 4.8.003 (2009-09-15)
11934	 * @public
11935	 */
11936	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11937		$this->Polygon($p, $style, $line_style, $fill_color, false);
11938	}
11939
11940	/**
11941	 * Draws a polygon.
11942	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11943	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11944	 * @param array $line_style Line style of polygon. Array with keys among the following:
11945	 * <ul>
11946	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11947	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11948	 * </ul>
11949	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11950	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11951	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
11952	 * @public
11953	 * @since 2.1.000 (2008-01-08)
11954	 */
11955	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11956		if ($this->state != 2) {
11957			return;
11958		}
11959		$nc = count($p); // number of coordinates
11960		$np = $nc / 2; // number of points
11961		if ($closed) {
11962			// close polygon by adding the first 2 points at the end (one line)
11963			for ($i = 0; $i < 4; ++$i) {
11964				$p[$nc + $i] = $p[$i];
11965			}
11966			// copy style for the last added line
11967			if (isset($line_style[0])) {
11968				$line_style[$np] = $line_style[0];
11969			}
11970			$nc += 4;
11971		}
11972		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11973			$this->SetFillColorArray($fill_color);
11974		}
11975		$op = TCPDF_STATIC::getPathPaintOperator($style);
11976		if ($op == 'f') {
11977			$line_style = array();
11978		}
11979		$draw = true;
11980		if ($line_style) {
11981			if (isset($line_style['all'])) {
11982				$this->SetLineStyle($line_style['all']);
11983			} else {
11984				$draw = false;
11985				if ($op == 'B') {
11986					// draw fill
11987					$op = 'f';
11988					$this->_outPoint($p[0], $p[1]);
11989					for ($i = 2; $i < $nc; $i = $i + 2) {
11990						$this->_outLine($p[$i], $p[$i + 1]);
11991					}
11992					$this->_out($op);
11993				}
11994				// draw outline
11995				$this->_outPoint($p[0], $p[1]);
11996				for ($i = 2; $i < $nc; $i = $i + 2) {
11997					$line_num = ($i / 2) - 1;
11998					if (isset($line_style[$line_num])) {
11999						if ($line_style[$line_num] != 0) {
12000							if (is_array($line_style[$line_num])) {
12001								$this->_out('S');
12002								$this->SetLineStyle($line_style[$line_num]);
12003								$this->_outPoint($p[$i - 2], $p[$i - 1]);
12004								$this->_outLine($p[$i], $p[$i + 1]);
12005								$this->_out('S');
12006								$this->_outPoint($p[$i], $p[$i + 1]);
12007							} else {
12008								$this->_outLine($p[$i], $p[$i + 1]);
12009							}
12010						}
12011					} else {
12012						$this->_outLine($p[$i], $p[$i + 1]);
12013					}
12014				}
12015				$this->_out($op);
12016			}
12017		}
12018		if ($draw) {
12019			$this->_outPoint($p[0], $p[1]);
12020			for ($i = 2; $i < $nc; $i = $i + 2) {
12021				$this->_outLine($p[$i], $p[$i + 1]);
12022			}
12023			$this->_out($op);
12024		}
12025	}
12026
12027	/**
12028	 * Draws a regular polygon.
12029	 * @param float $x0 Abscissa of center point.
12030	 * @param float $y0 Ordinate of center point.
12031	 * @param float $r Radius of inscribed circle.
12032	 * @param integer $ns Number of sides.
12033	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12034	 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12035	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12036	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12037	 * <ul>
12038	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12039	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12040	 * </ul>
12041	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12042	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12043	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12044	 * <ul>
12045	 *	 <li>D or empty string: Draw (default).</li>
12046	 *	 <li>F: Fill.</li>
12047	 *	 <li>DF or FD: Draw and fill.</li>
12048	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12049	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12050	 * </ul>
12051	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12052	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12053	 * @public
12054	 * @since 2.1.000 (2008-01-08)
12055	 */
12056	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()) {
12057		if (3 > $ns) {
12058			$ns = 3;
12059		}
12060		if ($draw_circle) {
12061			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12062		}
12063		$p = array();
12064		for ($i = 0; $i < $ns; ++$i) {
12065			$a = $angle + ($i * 360 / $ns);
12066			$a_rad = deg2rad((float) $a);
12067			$p[] = $x0 + ($r * sin($a_rad));
12068			$p[] = $y0 + ($r * cos($a_rad));
12069		}
12070		$this->Polygon($p, $style, $line_style, $fill_color);
12071	}
12072
12073	/**
12074	 * Draws a star polygon
12075	 * @param float $x0 Abscissa of center point.
12076	 * @param float $y0 Ordinate of center point.
12077	 * @param float $r Radius of inscribed circle.
12078	 * @param integer $nv Number of vertices.
12079	 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12080	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12081	 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12082	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12083	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12084	 * <ul>
12085	 *	 <li>all: Line style of all sides. Array like for
12086	 * SetLineStyle().</li>
12087	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12088	 * </ul>
12089	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12090	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12091	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12092	 * <ul>
12093	 *	 <li>D or empty string: Draw (default).</li>
12094	 *	 <li>F: Fill.</li>
12095	 *	 <li>DF or FD: Draw and fill.</li>
12096	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12097	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12098	 * </ul>
12099	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12100	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12101	 * @public
12102	 * @since 2.1.000 (2008-01-08)
12103	 */
12104	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()) {
12105		if ($nv < 2) {
12106			$nv = 2;
12107		}
12108		if ($draw_circle) {
12109			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12110		}
12111		$p2 = array();
12112		$visited = array();
12113		for ($i = 0; $i < $nv; ++$i) {
12114			$a = $angle + ($i * 360 / $nv);
12115			$a_rad = deg2rad((float) $a);
12116			$p2[] = $x0 + ($r * sin($a_rad));
12117			$p2[] = $y0 + ($r * cos($a_rad));
12118			$visited[] = false;
12119		}
12120		$p = array();
12121		$i = 0;
12122		do {
12123			$p[] = $p2[$i * 2];
12124			$p[] = $p2[($i * 2) + 1];
12125			$visited[$i] = true;
12126			$i += $ng;
12127			$i %= $nv;
12128		} while (!$visited[$i]);
12129		$this->Polygon($p, $style, $line_style, $fill_color);
12130	}
12131
12132	/**
12133	 * Draws a rounded rectangle.
12134	 * @param float $x Abscissa of upper-left corner.
12135	 * @param float $y Ordinate of upper-left corner.
12136	 * @param float $w Width.
12137	 * @param float $h Height.
12138	 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12139	 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12140	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12141	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12142	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12143	 * @public
12144	 * @since 2.1.000 (2008-01-08)
12145	 */
12146	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12147		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12148	}
12149
12150	/**
12151	 * Draws a rounded rectangle.
12152	 * @param float $x Abscissa of upper-left corner.
12153	 * @param float $y Ordinate of upper-left corner.
12154	 * @param float $w Width.
12155	 * @param float $h Height.
12156	 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12157	 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12158	 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12159	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12160	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12161	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12162	 * @public
12163	 * @since 4.9.019 (2010-04-22)
12164	 */
12165	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12166		if ($this->state != 2) {
12167			return;
12168		}
12169		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12170			// Not rounded
12171			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12172			return;
12173		}
12174		// Rounded
12175		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12176			$this->SetFillColorArray($fill_color);
12177		}
12178		$op = TCPDF_STATIC::getPathPaintOperator($style);
12179		if ($op == 'f') {
12180			$border_style = array();
12181		}
12182		if ($border_style) {
12183			$this->SetLineStyle($border_style);
12184		}
12185		$MyArc = 4 / 3 * (sqrt(2) - 1);
12186		$this->_outPoint($x + $rx, $y);
12187		$xc = $x + $w - $rx;
12188		$yc = $y + $ry;
12189		$this->_outLine($xc, $y);
12190		if ($round_corner[0]) {
12191			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12192		} else {
12193			$this->_outLine($x + $w, $y);
12194		}
12195		$xc = $x + $w - $rx;
12196		$yc = $y + $h - $ry;
12197		$this->_outLine($x + $w, $yc);
12198		if ($round_corner[1]) {
12199			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12200		} else {
12201			$this->_outLine($x + $w, $y + $h);
12202		}
12203		$xc = $x + $rx;
12204		$yc = $y + $h - $ry;
12205		$this->_outLine($xc, $y + $h);
12206		if ($round_corner[2]) {
12207			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12208		} else {
12209			$this->_outLine($x, $y + $h);
12210		}
12211		$xc = $x + $rx;
12212		$yc = $y + $ry;
12213		$this->_outLine($x, $yc);
12214		if ($round_corner[3]) {
12215			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12216		} else {
12217			$this->_outLine($x, $y);
12218			$this->_outLine($x + $rx, $y);
12219		}
12220		$this->_out($op);
12221	}
12222
12223	/**
12224	 * Draws a grahic arrow.
12225	 * @param float $x0 Abscissa of first point.
12226	 * @param float $y0 Ordinate of first point.
12227	 * @param float $x1 Abscissa of second point.
12228	 * @param float $y1 Ordinate of second point.
12229	 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12230	 * @param float $arm_size length of arrowhead arms
12231	 * @param int $arm_angle angle between an arm and the shaft
12232	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12233	 * @since 4.6.018 (2009-07-10)
12234	 */
12235	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12236		// getting arrow direction angle
12237		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12238		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12239		if ($dir_angle < 0) {
12240			$dir_angle += (2 * M_PI);
12241		}
12242		$arm_angle = deg2rad($arm_angle);
12243		$sx1 = $x1;
12244		$sy1 = $y1;
12245		if ($head_style > 0) {
12246			// calculate the stopping point for the arrow shaft
12247			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12248			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12249		}
12250		// main arrow line / shaft
12251		$this->Line($x0, $y0, $sx1, $sy1);
12252		// left arrowhead arm tip
12253		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12254		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12255		// right arrowhead arm tip
12256		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12257		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12258		$mode = 'D';
12259		$style = array();
12260		switch ($head_style) {
12261			case 0: {
12262				// draw only arrowhead arms
12263				$mode = 'D';
12264				$style = array(1, 1, 0);
12265				break;
12266			}
12267			case 1: {
12268				// draw closed arrowhead, but no fill
12269				$mode = 'D';
12270				break;
12271			}
12272			case 2: {
12273				// closed and filled arrowhead
12274				$mode = 'DF';
12275				break;
12276			}
12277			case 3: {
12278				// filled arrowhead
12279				$mode = 'F';
12280				break;
12281			}
12282		}
12283		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12284	}
12285
12286	// END GRAPHIC FUNCTIONS SECTION -----------------------
12287
12288	/**
12289	 * Add a Named Destination.
12290	 * NOTE: destination names are unique, so only last entry will be saved.
12291	 * @param string $name Destination name.
12292	 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12293	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12294	 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12295	 * @return string|false Stripped named destination identifier or false in case of error.
12296	 * @public
12297	 * @author Christian Deligant, Nicola Asuni
12298	 * @since 5.9.097 (2011-06-23)
12299	 */
12300	public function setDestination($name, $y=-1, $page='', $x=-1) {
12301		// remove unsupported characters
12302		$name = TCPDF_STATIC::encodeNameObject($name);
12303		if (TCPDF_STATIC::empty_string($name)) {
12304			return false;
12305		}
12306		if ($y == -1) {
12307			$y = $this->GetY();
12308		} elseif ($y < 0) {
12309			$y = 0;
12310		} elseif ($y > $this->h) {
12311			$y = $this->h;
12312		}
12313		if ($x == -1) {
12314			$x = $this->GetX();
12315		} elseif ($x < 0) {
12316			$x = 0;
12317		} elseif ($x > $this->w) {
12318			$x = $this->w;
12319		}
12320		$fixed = false;
12321		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12322			$page = intval(substr($page, 1));
12323			// this page number will not be changed when moving/add/deleting pages
12324			$fixed = true;
12325		}
12326		if (empty($page)) {
12327			$page = $this->PageNo();
12328			if (empty($page)) {
12329				return;
12330			}
12331		}
12332		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12333		return $name;
12334	}
12335
12336	/**
12337	 * Return the Named Destination array.
12338	 * @return array Named Destination array.
12339	 * @public
12340	 * @author Nicola Asuni
12341	 * @since 5.9.097 (2011-06-23)
12342	 */
12343	public function getDestination() {
12344		return $this->dests;
12345	}
12346
12347	/**
12348	 * Insert Named Destinations.
12349	 * @protected
12350	 * @author Johannes G\FCntert, Nicola Asuni
12351	 * @since 5.9.098 (2011-06-23)
12352	 */
12353	protected function _putdests() {
12354		if (empty($this->dests)) {
12355			return;
12356		}
12357		$this->n_dests = $this->_newobj();
12358		$out = ' <<';
12359		foreach($this->dests as $name => $o) {
12360			$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)));
12361		}
12362		$out .= ' >>';
12363		$out .= "\n".'endobj';
12364		$this->_out($out);
12365	}
12366
12367	/**
12368	 * Adds a bookmark - alias for Bookmark().
12369	 * @param string $txt Bookmark description.
12370	 * @param int $level Bookmark level (minimum value is 0).
12371	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12372	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12373	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12374	 * @param array $color RGB color array (values from 0 to 255).
12375	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12376	 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12377	 * @public
12378	 */
12379	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12380		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12381	}
12382
12383	/**
12384	 * Adds a bookmark.
12385	 * @param string $txt Bookmark description.
12386	 * @param int $level Bookmark level (minimum value is 0).
12387	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12388	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12389	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12390	 * @param array $color RGB color array (values from 0 to 255).
12391	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12392	 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12393	 * @public
12394	 * @since 2.1.002 (2008-02-12)
12395	 */
12396	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12397		if ($level < 0) {
12398			$level = 0;
12399		}
12400		if (isset($this->outlines[0])) {
12401			$lastoutline = end($this->outlines);
12402			$maxlevel = $lastoutline['l'] + 1;
12403		} else {
12404			$maxlevel = 0;
12405		}
12406		if ($level > $maxlevel) {
12407			$level = $maxlevel;
12408		}
12409		if ($y == -1) {
12410			$y = $this->GetY();
12411		} elseif ($y < 0) {
12412			$y = 0;
12413		} elseif ($y > $this->h) {
12414			$y = $this->h;
12415		}
12416		if ($x == -1) {
12417			$x = $this->GetX();
12418		} elseif ($x < 0) {
12419			$x = 0;
12420		} elseif ($x > $this->w) {
12421			$x = $this->w;
12422		}
12423		$fixed = false;
12424		$pageAsString = (string) $page;
12425		if ($pageAsString && $pageAsString[0] == '*') {
12426			$page = intval(substr($page, 1));
12427			// this page number will not be changed when moving/add/deleting pages
12428			$fixed = true;
12429		}
12430		if (empty($page)) {
12431			$page = $this->PageNo();
12432			if (empty($page)) {
12433				return;
12434			}
12435		}
12436		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12437	}
12438
12439	/**
12440	 * Sort bookmarks for page and key.
12441	 * @protected
12442	 * @since 5.9.119 (2011-09-19)
12443	 */
12444	protected function sortBookmarks() {
12445		// get sorting columns
12446		$outline_p = array();
12447		$outline_y = array();
12448		foreach ($this->outlines as $key => $row) {
12449			$outline_p[$key] = $row['p'];
12450			$outline_k[$key] = $key;
12451		}
12452		// sort outlines by page and original position
12453		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12454	}
12455
12456	/**
12457	 * Create a bookmark PDF string.
12458	 * @protected
12459	 * @author Olivier Plathey, Nicola Asuni
12460	 * @since 2.1.002 (2008-02-12)
12461	 */
12462	protected function _putbookmarks() {
12463		$nb = count($this->outlines);
12464		if ($nb == 0) {
12465			return;
12466		}
12467		// sort bookmarks
12468		$this->sortBookmarks();
12469		$lru = array();
12470		$level = 0;
12471		foreach ($this->outlines as $i => $o) {
12472			if ($o['l'] > 0) {
12473				$parent = $lru[($o['l'] - 1)];
12474				//Set parent and last pointers
12475				$this->outlines[$i]['parent'] = $parent;
12476				$this->outlines[$parent]['last'] = $i;
12477				if ($o['l'] > $level) {
12478					//Level increasing: set first pointer
12479					$this->outlines[$parent]['first'] = $i;
12480				}
12481			} else {
12482				$this->outlines[$i]['parent'] = $nb;
12483			}
12484			if (($o['l'] <= $level) AND ($i > 0)) {
12485				//Set prev and next pointers
12486				$prev = $lru[$o['l']];
12487				$this->outlines[$prev]['next'] = $i;
12488				$this->outlines[$i]['prev'] = $prev;
12489			}
12490			$lru[$o['l']] = $i;
12491			$level = $o['l'];
12492		}
12493		//Outline items
12494		$n = $this->n + 1;
12495		$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';
12496		foreach ($this->outlines as $i => $o) {
12497			$oid = $this->_newobj();
12498			// covert HTML title to string
12499			$title = preg_replace($nltags, "\n", $o['t']);
12500			$title = preg_replace("/[\r]+/si", '', $title);
12501			$title = preg_replace("/[\n]+/si", "\n", $title);
12502			$title = strip_tags($title);
12503			$title = $this->stringTrim($title);
12504			$out = '<</Title '.$this->_textstring($title, $oid);
12505			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12506			if (isset($o['prev'])) {
12507				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12508			}
12509			if (isset($o['next'])) {
12510				$out .= ' /Next '.($n + $o['next']).' 0 R';
12511			}
12512			if (isset($o['first'])) {
12513				$out .= ' /First '.($n + $o['first']).' 0 R';
12514			}
12515			if (isset($o['last'])) {
12516				$out .= ' /Last '.($n + $o['last']).' 0 R';
12517			}
12518			if (isset($o['u']) AND !empty($o['u'])) {
12519				// link
12520				if (is_string($o['u'])) {
12521					if ($o['u'][0] == '#') {
12522						// internal destination
12523						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12524					} elseif ($o['u'][0] == '%') {
12525						// embedded PDF file
12526						$filename = basename(substr($o['u'], 1));
12527						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12528					} elseif ($o['u'][0] == '*') {
12529						// embedded generic file
12530						$filename = basename(substr($o['u'], 1));
12531						$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});';
12532						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12533					} else {
12534						// external URI link
12535						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12536					}
12537				} elseif (isset($this->links[$o['u']])) {
12538					// internal link ID
12539					$l = $this->links[$o['u']];
12540					if (isset($this->page_obj_id[($l['p'])])) {
12541						$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)));
12542					}
12543				}
12544			} elseif (isset($this->page_obj_id[($o['p'])])) {
12545				// link to a page
12546				$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)));
12547			}
12548			// set font style
12549			$style = 0;
12550			if (!empty($o['s'])) {
12551				// bold
12552				if (strpos($o['s'], 'B') !== false) {
12553					$style |= 2;
12554				}
12555				// oblique
12556				if (strpos($o['s'], 'I') !== false) {
12557					$style |= 1;
12558				}
12559			}
12560			$out .= sprintf(' /F %d', $style);
12561			// set bookmark color
12562			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12563				$color = array_values($o['c']);
12564				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12565			} else {
12566				// black
12567				$out .= ' /C [0.0 0.0 0.0]';
12568			}
12569			$out .= ' /Count 0'; // normally closed item
12570			$out .= ' >>';
12571			$out .= "\n".'endobj';
12572			$this->_out($out);
12573		}
12574		//Outline root
12575		$this->OutlineRoot = $this->_newobj();
12576		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12577	}
12578
12579	// --- JAVASCRIPT ------------------------------------------------------
12580
12581	/**
12582	 * Adds a javascript
12583	 * @param string $script Javascript code
12584	 * @public
12585	 * @author Johannes G\FCntert, Nicola Asuni
12586	 * @since 2.1.002 (2008-02-12)
12587	 */
12588	public function IncludeJS($script) {
12589		$this->javascript .= $script;
12590	}
12591
12592	/**
12593	 * Adds a javascript object and return object ID
12594	 * @param string $script Javascript code
12595	 * @param boolean $onload if true executes this object when opening the document
12596	 * @return int internal object ID
12597	 * @public
12598	 * @author Nicola Asuni
12599	 * @since 4.8.000 (2009-09-07)
12600	 */
12601	public function addJavascriptObject($script, $onload=false) {
12602		if ($this->pdfa_mode) {
12603			// javascript is not allowed in PDF/A mode
12604			return false;
12605		}
12606		++$this->n;
12607		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12608		return $this->n;
12609	}
12610
12611	/**
12612	 * Create a javascript PDF string.
12613	 * @protected
12614	 * @author Johannes G\FCntert, Nicola Asuni
12615	 * @since 2.1.002 (2008-02-12)
12616	 */
12617	protected function _putjavascript() {
12618		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12619			return;
12620		}
12621		if (strpos($this->javascript, 'this.addField') > 0) {
12622			if (!$this->ur['enabled']) {
12623				//$this->setUserRights();
12624			}
12625			// the following two lines are used to avoid form fields duplication after saving
12626			// The addField method only works when releasing user rights (UR3)
12627			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12628			$jsb = "getField('tcpdfdocsaved').value='saved';";
12629			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12630		}
12631		// name tree for javascript
12632		$this->n_js = '<< /Names [';
12633		if (!empty($this->javascript)) {
12634			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12635		}
12636		if (!empty($this->js_objects)) {
12637			foreach ($this->js_objects as $key => $val) {
12638				if ($val['onload']) {
12639					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12640				}
12641			}
12642		}
12643		$this->n_js .= ' ] >>';
12644		// default Javascript object
12645		if (!empty($this->javascript)) {
12646			$obj_id = $this->_newobj();
12647			$out = '<< /S /JavaScript';
12648			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12649			$out .= ' >>';
12650			$out .= "\n".'endobj';
12651			$this->_out($out);
12652		}
12653		// additional Javascript objects
12654		if (!empty($this->js_objects)) {
12655			foreach ($this->js_objects as $key => $val) {
12656				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12657				$this->_out($out);
12658			}
12659		}
12660	}
12661
12662	/**
12663	 * Adds a javascript form field.
12664	 * @param string $type field type
12665	 * @param string $name field name
12666	 * @param int $x horizontal position
12667	 * @param int $y vertical position
12668	 * @param int $w width
12669	 * @param int $h height
12670	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12671	 * @protected
12672	 * @author Denis Van Nuffelen, Nicola Asuni
12673	 * @since 2.1.002 (2008-02-12)
12674	 */
12675	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12676		if ($this->rtl) {
12677			$x = $x - $w;
12678		}
12679		// the followind avoid fields duplication after saving the document
12680		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12681		$k = $this->k;
12682		$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";
12683		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12684		foreach($prop as $key => $val) {
12685			if (strcmp(substr($key, -5), 'Color') == 0) {
12686				$val = TCPDF_COLORS::_JScolor($val);
12687			} else {
12688				$val = "'".$val."'";
12689			}
12690			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12691		}
12692		if ($this->rtl) {
12693			$this->x -= $w;
12694		} else {
12695			$this->x += $w;
12696		}
12697		$this->javascript .= '}';
12698	}
12699
12700	// --- FORM FIELDS -----------------------------------------------------
12701
12702
12703
12704	/**
12705	 * Set default properties for form fields.
12706	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12707	 * @public
12708	 * @author Nicola Asuni
12709	 * @since 4.8.000 (2009-09-06)
12710	 */
12711	public function setFormDefaultProp($prop=array()) {
12712		$this->default_form_prop = $prop;
12713	}
12714
12715	/**
12716	 * Return the default properties for form fields.
12717	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12718	 * @public
12719	 * @author Nicola Asuni
12720	 * @since 4.8.000 (2009-09-06)
12721	 */
12722	public function getFormDefaultProp() {
12723		return $this->default_form_prop;
12724	}
12725
12726	/**
12727	 * Creates a text field
12728	 * @param string $name field name
12729	 * @param float $w Width of the rectangle
12730	 * @param float $h Height of the rectangle
12731	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12732	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12733	 * @param float $x Abscissa of the upper-left corner of the rectangle
12734	 * @param float $y Ordinate of the upper-left corner of the rectangle
12735	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12736	 * @public
12737	 * @author Nicola Asuni
12738	 * @since 4.8.000 (2009-09-07)
12739	 */
12740	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12741		if ($x === '') {
12742			$x = $this->x;
12743		}
12744		if ($y === '') {
12745			$y = $this->y;
12746		}
12747		// check page for no-write regions and adapt page margins if necessary
12748		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12749		if ($js) {
12750			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12751			return;
12752		}
12753		// get default style
12754		$prop = array_merge($this->getFormDefaultProp(), $prop);
12755		// get annotation data
12756		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12757		// set default appearance stream
12758		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12759		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12760		$popt['da'] = $fontstyle;
12761		// build appearance stream
12762		$popt['ap'] = array();
12763		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12764		$text = '';
12765		if (isset($prop['value']) AND !empty($prop['value'])) {
12766			$text = $prop['value'];
12767		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12768			$text = $opt['v'];
12769		}
12770		$tmpid = $this->startTemplate($w, $h, false);
12771		$align = '';
12772		if (isset($popt['q'])) {
12773			switch ($popt['q']) {
12774				case 0: {
12775					$align = 'L';
12776					break;
12777				}
12778				case 1: {
12779					$align = 'C';
12780					break;
12781				}
12782				case 2: {
12783					$align = 'R';
12784					break;
12785				}
12786				default: {
12787					$align = '';
12788					break;
12789				}
12790			}
12791		}
12792		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12793		$this->endTemplate();
12794		--$this->n;
12795		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12796		unset($this->xobjects[$tmpid]);
12797		$popt['ap']['n'] .= 'Q EMC';
12798		// merge options
12799		$opt = array_merge($popt, $opt);
12800		// remove some conflicting options
12801		unset($opt['bs']);
12802		// set remaining annotation data
12803		$opt['Subtype'] = 'Widget';
12804		$opt['ft'] = 'Tx';
12805		$opt['t'] = $name;
12806		// Additional annotation's parameters (check _putannotsobj() method):
12807		//$opt['f']
12808		//$opt['as']
12809		//$opt['bs']
12810		//$opt['be']
12811		//$opt['c']
12812		//$opt['border']
12813		//$opt['h']
12814		//$opt['mk'];
12815		//$opt['mk']['r']
12816		//$opt['mk']['bc'];
12817		//$opt['mk']['bg'];
12818		unset($opt['mk']['ca']);
12819		unset($opt['mk']['rc']);
12820		unset($opt['mk']['ac']);
12821		unset($opt['mk']['i']);
12822		unset($opt['mk']['ri']);
12823		unset($opt['mk']['ix']);
12824		unset($opt['mk']['if']);
12825		//$opt['mk']['if']['sw'];
12826		//$opt['mk']['if']['s'];
12827		//$opt['mk']['if']['a'];
12828		//$opt['mk']['if']['fb'];
12829		unset($opt['mk']['tp']);
12830		//$opt['tu']
12831		//$opt['tm']
12832		//$opt['ff']
12833		//$opt['v']
12834		//$opt['dv']
12835		//$opt['a']
12836		//$opt['aa']
12837		//$opt['q']
12838		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12839		if ($this->rtl) {
12840			$this->x -= $w;
12841		} else {
12842			$this->x += $w;
12843		}
12844	}
12845
12846	/**
12847	 * Creates a RadioButton field.
12848	 * @param string $name Field name.
12849	 * @param int $w Width of the radio button.
12850	 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12851	 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12852	 * @param string $onvalue Value to be returned if selected.
12853	 * @param boolean $checked Define the initial state.
12854	 * @param float $x Abscissa of the upper-left corner of the rectangle
12855	 * @param float $y Ordinate of the upper-left corner of the rectangle
12856	 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12857	 * @public
12858	 * @author Nicola Asuni
12859	 * @since 4.8.000 (2009-09-07)
12860	 */
12861	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12862		if ($x === '') {
12863			$x = $this->x;
12864		}
12865		if ($y === '') {
12866			$y = $this->y;
12867		}
12868		// check page for no-write regions and adapt page margins if necessary
12869		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12870		if ($js) {
12871			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12872			return;
12873		}
12874		if (TCPDF_STATIC::empty_string($onvalue)) {
12875			$onvalue = 'On';
12876		}
12877		if ($checked) {
12878			$defval = $onvalue;
12879		} else {
12880			$defval = 'Off';
12881		}
12882		// set font
12883		$font = 'zapfdingbats';
12884		if ($this->pdfa_mode) {
12885			// all fonts must be embedded
12886			$font = 'pdfa'.$font;
12887		}
12888		$this->AddFont($font);
12889		$tmpfont = $this->getFontBuffer($font);
12890		// set data for parent group
12891		if (!isset($this->radiobutton_groups[$this->page])) {
12892			$this->radiobutton_groups[$this->page] = array();
12893		}
12894		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12895			$this->radiobutton_groups[$this->page][$name] = array();
12896			++$this->n;
12897			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12898			$this->radio_groups[] = $this->n;
12899		}
12900		$kid = ($this->n + 1);
12901		// save object ID to be added on Kids entry on parent object
12902		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12903		// get default style
12904		$prop = array_merge($this->getFormDefaultProp(), $prop);
12905		$prop['NoToggleToOff'] = 'true';
12906		$prop['Radio'] = 'true';
12907		$prop['borderStyle'] = 'inset';
12908		// get annotation data
12909		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12910		// set additional default options
12911		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12912		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12913		$popt['da'] = $fontstyle;
12914		// build appearance stream
12915		$popt['ap'] = array();
12916		$popt['ap']['n'] = array();
12917		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12918		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12919		$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);
12920		$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);
12921		if (!isset($popt['mk'])) {
12922			$popt['mk'] = array();
12923		}
12924		$popt['mk']['ca'] = '(l)';
12925		// merge options
12926		$opt = array_merge($popt, $opt);
12927		// set remaining annotation data
12928		$opt['Subtype'] = 'Widget';
12929		$opt['ft'] = 'Btn';
12930		if ($checked) {
12931			$opt['v'] = array('/'.$onvalue);
12932			$opt['as'] = $onvalue;
12933		} else {
12934			$opt['as'] = 'Off';
12935		}
12936		// store readonly flag
12937		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12938			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12939		}
12940		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12941		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12942		if ($this->rtl) {
12943			$this->x -= $w;
12944		} else {
12945			$this->x += $w;
12946		}
12947	}
12948
12949	/**
12950	 * Creates a List-box field
12951	 * @param string $name field name
12952	 * @param int $w width
12953	 * @param int $h height
12954	 * @param array $values array containing the list of values.
12955	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12956	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12957	 * @param float $x Abscissa of the upper-left corner of the rectangle
12958	 * @param float $y Ordinate of the upper-left corner of the rectangle
12959	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12960	 * @public
12961	 * @author Nicola Asuni
12962	 * @since 4.8.000 (2009-09-07)
12963	 */
12964	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12965		if ($x === '') {
12966			$x = $this->x;
12967		}
12968		if ($y === '') {
12969			$y = $this->y;
12970		}
12971		// check page for no-write regions and adapt page margins if necessary
12972		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12973		if ($js) {
12974			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12975			$s = '';
12976			foreach ($values as $value) {
12977				if (is_array($value)) {
12978					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12979				} else {
12980					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12981				}
12982			}
12983			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12984			return;
12985		}
12986		// get default style
12987		$prop = array_merge($this->getFormDefaultProp(), $prop);
12988		// get annotation data
12989		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12990		// set additional default values
12991		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12992		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12993		$popt['da'] = $fontstyle;
12994		// build appearance stream
12995		$popt['ap'] = array();
12996		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12997		$text = '';
12998		foreach($values as $item) {
12999			if (is_array($item)) {
13000				$text .= $item[1]."\n";
13001			} else {
13002				$text .= $item."\n";
13003			}
13004		}
13005		$tmpid = $this->startTemplate($w, $h, false);
13006		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13007		$this->endTemplate();
13008		--$this->n;
13009		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13010		unset($this->xobjects[$tmpid]);
13011		$popt['ap']['n'] .= 'Q EMC';
13012		// merge options
13013		$opt = array_merge($popt, $opt);
13014		// set remaining annotation data
13015		$opt['Subtype'] = 'Widget';
13016		$opt['ft'] = 'Ch';
13017		$opt['t'] = $name;
13018		$opt['opt'] = $values;
13019		unset($opt['mk']['ca']);
13020		unset($opt['mk']['rc']);
13021		unset($opt['mk']['ac']);
13022		unset($opt['mk']['i']);
13023		unset($opt['mk']['ri']);
13024		unset($opt['mk']['ix']);
13025		unset($opt['mk']['if']);
13026		unset($opt['mk']['tp']);
13027		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13028		if ($this->rtl) {
13029			$this->x -= $w;
13030		} else {
13031			$this->x += $w;
13032		}
13033	}
13034
13035	/**
13036	 * Creates a Combo-box field
13037	 * @param string $name field name
13038	 * @param int $w width
13039	 * @param int $h height
13040	 * @param array $values array containing the list of values.
13041	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13042	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13043	 * @param float $x Abscissa of the upper-left corner of the rectangle
13044	 * @param float $y Ordinate of the upper-left corner of the rectangle
13045	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13046	 * @public
13047	 * @author Nicola Asuni
13048	 * @since 4.8.000 (2009-09-07)
13049	 */
13050	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13051		if ($x === '') {
13052			$x = $this->x;
13053		}
13054		if ($y === '') {
13055			$y = $this->y;
13056		}
13057		// check page for no-write regions and adapt page margins if necessary
13058		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13059		if ($js) {
13060			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13061			$s = '';
13062			foreach ($values as $value) {
13063				if (is_array($value)) {
13064					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13065				} else {
13066					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13067				}
13068			}
13069			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13070			return;
13071		}
13072		// get default style
13073		$prop = array_merge($this->getFormDefaultProp(), $prop);
13074		$prop['Combo'] = true;
13075		// get annotation data
13076		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13077		// set additional default options
13078		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13079		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13080		$popt['da'] = $fontstyle;
13081		// build appearance stream
13082		$popt['ap'] = array();
13083		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13084		$text = '';
13085		foreach($values as $item) {
13086			if (is_array($item)) {
13087				$text .= $item[1]."\n";
13088			} else {
13089				$text .= $item."\n";
13090			}
13091		}
13092		$tmpid = $this->startTemplate($w, $h, false);
13093		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13094		$this->endTemplate();
13095		--$this->n;
13096		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13097		unset($this->xobjects[$tmpid]);
13098		$popt['ap']['n'] .= 'Q EMC';
13099		// merge options
13100		$opt = array_merge($popt, $opt);
13101		// set remaining annotation data
13102		$opt['Subtype'] = 'Widget';
13103		$opt['ft'] = 'Ch';
13104		$opt['t'] = $name;
13105		$opt['opt'] = $values;
13106		unset($opt['mk']['ca']);
13107		unset($opt['mk']['rc']);
13108		unset($opt['mk']['ac']);
13109		unset($opt['mk']['i']);
13110		unset($opt['mk']['ri']);
13111		unset($opt['mk']['ix']);
13112		unset($opt['mk']['if']);
13113		unset($opt['mk']['tp']);
13114		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13115		if ($this->rtl) {
13116			$this->x -= $w;
13117		} else {
13118			$this->x += $w;
13119		}
13120	}
13121
13122	/**
13123	 * Creates a CheckBox field
13124	 * @param string $name field name
13125	 * @param int $w width
13126	 * @param boolean $checked define the initial state.
13127	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13128	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13129	 * @param string $onvalue value to be returned if selected.
13130	 * @param float $x Abscissa of the upper-left corner of the rectangle
13131	 * @param float $y Ordinate of the upper-left corner of the rectangle
13132	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13133	 * @public
13134	 * @author Nicola Asuni
13135	 * @since 4.8.000 (2009-09-07)
13136	 */
13137	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13138		if ($x === '') {
13139			$x = $this->x;
13140		}
13141		if ($y === '') {
13142			$y = $this->y;
13143		}
13144		// check page for no-write regions and adapt page margins if necessary
13145		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13146		if ($js) {
13147			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13148			return;
13149		}
13150		if (!isset($prop['value'])) {
13151			$prop['value'] = array('Yes');
13152		}
13153		// get default style
13154		$prop = array_merge($this->getFormDefaultProp(), $prop);
13155		$prop['borderStyle'] = 'inset';
13156		// get annotation data
13157		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13158		// set additional default options
13159		$font = 'zapfdingbats';
13160		if ($this->pdfa_mode) {
13161			// all fonts must be embedded
13162			$font = 'pdfa'.$font;
13163		}
13164		$this->AddFont($font);
13165		$tmpfont = $this->getFontBuffer($font);
13166		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13167		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13168		$popt['da'] = $fontstyle;
13169		// build appearance stream
13170		$popt['ap'] = array();
13171		$popt['ap']['n'] = array();
13172		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13173		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13174		$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);
13175		$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);
13176		// merge options
13177		$opt = array_merge($popt, $opt);
13178		// set remaining annotation data
13179		$opt['Subtype'] = 'Widget';
13180		$opt['ft'] = 'Btn';
13181		$opt['t'] = $name;
13182		if (TCPDF_STATIC::empty_string($onvalue)) {
13183			$onvalue = 'Yes';
13184		}
13185		$opt['opt'] = array($onvalue);
13186		if ($checked) {
13187			$opt['v'] = array('/Yes');
13188			$opt['as'] = 'Yes';
13189		} else {
13190			$opt['v'] = array('/Off');
13191			$opt['as'] = 'Off';
13192		}
13193		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13194		if ($this->rtl) {
13195			$this->x -= $w;
13196		} else {
13197			$this->x += $w;
13198		}
13199	}
13200
13201	/**
13202	 * Creates a button field
13203	 * @param string $name field name
13204	 * @param int $w width
13205	 * @param int $h height
13206	 * @param string $caption caption.
13207	 * @param mixed $action action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13208	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13209	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13210	 * @param float $x Abscissa of the upper-left corner of the rectangle
13211	 * @param float $y Ordinate of the upper-left corner of the rectangle
13212	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13213	 * @public
13214	 * @author Nicola Asuni
13215	 * @since 4.8.000 (2009-09-07)
13216	 */
13217	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13218		if ($x === '') {
13219			$x = $this->x;
13220		}
13221		if ($y === '') {
13222			$y = $this->y;
13223		}
13224		// check page for no-write regions and adapt page margins if necessary
13225		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13226		if ($js) {
13227			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13228			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13229			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13230			$this->javascript .= 'f'.$name.".highlight='push';\n";
13231			$this->javascript .= 'f'.$name.".print=false;\n";
13232			return;
13233		}
13234		// get default style
13235		$prop = array_merge($this->getFormDefaultProp(), $prop);
13236		$prop['Pushbutton'] = 'true';
13237		$prop['highlight'] = 'push';
13238		$prop['display'] = 'display.noPrint';
13239		// get annotation data
13240		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13241		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13242		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13243		$popt['da'] = $fontstyle;
13244		// build appearance stream
13245		$popt['ap'] = array();
13246		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13247		$tmpid = $this->startTemplate($w, $h, false);
13248		$bw = (2 / $this->k); // border width
13249		$border = array(
13250			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13251			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13252			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13253			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13254		$this->SetFillColor(204);
13255		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13256		$this->endTemplate();
13257		--$this->n;
13258		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13259		unset($this->xobjects[$tmpid]);
13260		$popt['ap']['n'] .= 'Q EMC';
13261		// set additional default options
13262		if (!isset($popt['mk'])) {
13263			$popt['mk'] = array();
13264		}
13265		$ann_obj_id = ($this->n + 1);
13266		if (!empty($action) AND !is_array($action)) {
13267			$ann_obj_id = ($this->n + 2);
13268		}
13269		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13270		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13271		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13272		// merge options
13273		$opt = array_merge($popt, $opt);
13274		// set remaining annotation data
13275		$opt['Subtype'] = 'Widget';
13276		$opt['ft'] = 'Btn';
13277		$opt['t'] = $caption;
13278		$opt['v'] = $name;
13279		if (!empty($action)) {
13280			if (is_array($action)) {
13281				// form action options as on section 12.7.5 of PDF32000_2008.
13282				$opt['aa'] = '/D <<';
13283				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13284				foreach ($action AS $key => $val) {
13285					if (($key == 'S') AND in_array($val, $bmode)) {
13286						$opt['aa'] .= ' /S /'.$val;
13287					} elseif (($key == 'F') AND (!empty($val))) {
13288						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13289					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13290						$opt['aa'] .= ' /Fields [';
13291						foreach ($val AS $field) {
13292							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13293						}
13294						$opt['aa'] .= ']';
13295					} elseif (($key == 'Flags')) {
13296						$ff = 0;
13297						if (is_array($val)) {
13298							foreach ($val AS $flag) {
13299								switch ($flag) {
13300									case 'Include/Exclude': {
13301										$ff += 1 << 0;
13302										break;
13303									}
13304									case 'IncludeNoValueFields': {
13305										$ff += 1 << 1;
13306										break;
13307									}
13308									case 'ExportFormat': {
13309										$ff += 1 << 2;
13310										break;
13311									}
13312									case 'GetMethod': {
13313										$ff += 1 << 3;
13314										break;
13315									}
13316									case 'SubmitCoordinates': {
13317										$ff += 1 << 4;
13318										break;
13319									}
13320									case 'XFDF': {
13321										$ff += 1 << 5;
13322										break;
13323									}
13324									case 'IncludeAppendSaves': {
13325										$ff += 1 << 6;
13326										break;
13327									}
13328									case 'IncludeAnnotations': {
13329										$ff += 1 << 7;
13330										break;
13331									}
13332									case 'SubmitPDF': {
13333										$ff += 1 << 8;
13334										break;
13335									}
13336									case 'CanonicalFormat': {
13337										$ff += 1 << 9;
13338										break;
13339									}
13340									case 'ExclNonUserAnnots': {
13341										$ff += 1 << 10;
13342										break;
13343									}
13344									case 'ExclFKey': {
13345										$ff += 1 << 11;
13346										break;
13347									}
13348									case 'EmbedForm': {
13349										$ff += 1 << 13;
13350										break;
13351									}
13352								}
13353							}
13354						} else {
13355							$ff = intval($val);
13356						}
13357						$opt['aa'] .= ' /Flags '.$ff;
13358					}
13359				}
13360				$opt['aa'] .= ' >>';
13361			} else {
13362				// Javascript action or raw action command
13363				$js_obj_id = $this->addJavascriptObject($action);
13364				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13365			}
13366		}
13367		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13368		if ($this->rtl) {
13369			$this->x -= $w;
13370		} else {
13371			$this->x += $w;
13372		}
13373	}
13374
13375	// --- END FORMS FIELDS ------------------------------------------------
13376
13377	/**
13378	 * Add certification signature (DocMDP or UR3)
13379	 * You can set only one signature type
13380	 * @protected
13381	 * @author Nicola Asuni
13382	 * @since 4.6.008 (2009-05-07)
13383	 */
13384	protected function _putsignature() {
13385		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13386			return;
13387		}
13388		$sigobjid = ($this->sig_obj_id + 1);
13389		$out = $this->_getobj($sigobjid)."\n";
13390		$out .= '<< /Type /Sig';
13391		$out .= ' /Filter /Adobe.PPKLite';
13392		$out .= ' /SubFilter /adbe.pkcs7.detached';
13393		$out .= ' '.TCPDF_STATIC::$byterange_string;
13394		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13395		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13396			$out .= ' /Reference ['; // array of signature reference dictionaries
13397			$out .= ' << /Type /SigRef';
13398			if ($this->signature_data['cert_type'] > 0) {
13399				$out .= ' /TransformMethod /DocMDP';
13400				$out .= ' /TransformParams <<';
13401				$out .= ' /Type /TransformParams';
13402				$out .= ' /P '.$this->signature_data['cert_type'];
13403				$out .= ' /V /1.2';
13404			} else {
13405				$out .= ' /TransformMethod /UR3';
13406				$out .= ' /TransformParams <<';
13407				$out .= ' /Type /TransformParams';
13408				$out .= ' /V /2.2';
13409				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13410					$out .= ' /Document['.$this->ur['document'].']';
13411				}
13412				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13413					$out .= ' /Form['.$this->ur['form'].']';
13414				}
13415				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13416					$out .= ' /Signature['.$this->ur['signature'].']';
13417				}
13418				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13419					$out .= ' /Annots['.$this->ur['annots'].']';
13420				}
13421				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13422					$out .= ' /EF['.$this->ur['ef'].']';
13423				}
13424				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13425					$out .= ' /FormEX['.$this->ur['formex'].']';
13426				}
13427			}
13428			$out .= ' >>'; // close TransformParams
13429			// optional digest data (values must be calculated and replaced later)
13430			//$out .= ' /Data ********** 0 R';
13431			//$out .= ' /DigestMethod/MD5';
13432			//$out .= ' /DigestLocation[********** 34]';
13433			//$out .= ' /DigestValue<********************************>';
13434			$out .= ' >>';
13435			$out .= ' ]'; // end of reference
13436		}
13437		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13438			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13439		}
13440		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13441			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13442		}
13443		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13444			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13445		}
13446		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13447			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13448		}
13449		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13450		$out .= ' >>';
13451		$out .= "\n".'endobj';
13452		$this->_out($out);
13453	}
13454
13455	/**
13456	 * Set User's Rights for PDF Reader
13457	 * WARNING: This is experimental and currently do not work.
13458	 * Check the PDF Reference 8.7.1 Transform Methods,
13459	 * Table 8.105 Entries in the UR transform parameters dictionary
13460	 * @param boolean $enable if true enable user's rights on PDF reader
13461	 * @param string $document Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13462	 * @param string $annots Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13463	 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13464	 * @param string $signature Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13465	 * @param string $ef Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13466	 Names specifying additional embedded-files-related usage rights for the document.
13467	 * @param string $formex Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13468	 * @public
13469	 * @author Nicola Asuni
13470	 * @since 2.9.000 (2008-03-26)
13471	 */
13472	public function setUserRights(
13473			$enable=true,
13474			$document='/FullSave',
13475			$annots='/Create/Delete/Modify/Copy/Import/Export',
13476			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13477			$signature='/Modify',
13478			$ef='/Create/Delete/Modify/Import',
13479			$formex='') {
13480		$this->ur['enabled'] = $enable;
13481		$this->ur['document'] = $document;
13482		$this->ur['annots'] = $annots;
13483		$this->ur['form'] = $form;
13484		$this->ur['signature'] = $signature;
13485		$this->ur['ef'] = $ef;
13486		$this->ur['formex'] = $formex;
13487		if (!$this->sign) {
13488			$this->setSignature('', '', '', '', 0, array());
13489		}
13490	}
13491
13492	/**
13493	 * Enable document signature (requires the OpenSSL Library).
13494	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13495	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13496	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13497	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13498	 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13499	 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13500	 * @param string $private_key_password password
13501	 * @param string $extracerts specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13502	 * @param int $cert_type The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13503	 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13504	 * @param string $approval Enable approval signature eg. for PDF incremental update
13505	 * @public
13506	 * @author Nicola Asuni
13507	 * @since 4.6.005 (2009-04-24)
13508	 */
13509	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13510		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13511		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13512		// to convert pfx certificate to pem: openssl
13513		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13514		$this->sign = true;
13515		++$this->n;
13516		$this->sig_obj_id = $this->n; // signature widget
13517		++$this->n; // signature object ($this->sig_obj_id + 1)
13518		$this->signature_data = array();
13519		if (strlen($signing_cert) == 0) {
13520			$this->Error('Please provide a certificate file and password!');
13521		}
13522		if (strlen($private_key) == 0) {
13523			$private_key = $signing_cert;
13524		}
13525		$this->signature_data['signcert'] = $signing_cert;
13526		$this->signature_data['privkey'] = $private_key;
13527		$this->signature_data['password'] = $private_key_password;
13528		$this->signature_data['extracerts'] = $extracerts;
13529		$this->signature_data['cert_type'] = $cert_type;
13530		$this->signature_data['info'] = $info;
13531		$this->signature_data['approval'] = $approval;
13532	}
13533
13534	/**
13535	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13536	 * @param float $x Abscissa of the upper-left corner.
13537	 * @param float $y Ordinate of the upper-left corner.
13538	 * @param float $w Width of the signature area.
13539	 * @param float $h Height of the signature area.
13540	 * @param int $page option page number (if < 0 the current page is used).
13541	 * @param string $name Name of the signature.
13542	 * @public
13543	 * @author Nicola Asuni
13544	 * @since 5.3.011 (2010-06-17)
13545	 */
13546	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13547		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13548	}
13549
13550	/**
13551	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13552	 * @param float $x Abscissa of the upper-left corner.
13553	 * @param float $y Ordinate of the upper-left corner.
13554	 * @param float $w Width of the signature area.
13555	 * @param float $h Height of the signature area.
13556	 * @param int $page option page number (if < 0 the current page is used).
13557	 * @param string $name Name of the signature.
13558	 * @public
13559	 * @author Nicola Asuni
13560	 * @since 5.9.101 (2011-07-06)
13561	 */
13562	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13563		++$this->n;
13564		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13565	}
13566
13567	/**
13568	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13569	 * @param float $x Abscissa of the upper-left corner.
13570	 * @param float $y Ordinate of the upper-left corner.
13571	 * @param float $w Width of the signature area.
13572	 * @param float $h Height of the signature area.
13573	 * @param int $page option page number (if < 0 the current page is used).
13574	 * @param string $name Name of the signature.
13575	 * @return array Array defining page and rectangle coordinates of signature appearance.
13576	 * @protected
13577	 * @author Nicola Asuni
13578	 * @since 5.9.101 (2011-07-06)
13579	 */
13580	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13581		$sigapp = array();
13582		if (($page < 1) OR ($page > $this->numpages)) {
13583			$sigapp['page'] = $this->page;
13584		} else {
13585			$sigapp['page'] = intval($page);
13586		}
13587		if (empty($name)) {
13588			$sigapp['name'] = 'Signature';
13589		} else {
13590			$sigapp['name'] = $name;
13591		}
13592		$a = $x * $this->k;
13593		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13594		$c = $w * $this->k;
13595		$d = $h * $this->k;
13596		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13597		return $sigapp;
13598	}
13599
13600	/**
13601	 * Enable document timestamping (requires the OpenSSL Library).
13602	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13603	 * Use with digital signature only!
13604	 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13605	 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13606	 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13607	 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13608	 * @public
13609	 * @author Richard Stockinger
13610	 * @since 6.0.090 (2014-06-16)
13611	 */
13612	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13613		$this->tsa_data = array();
13614		if (!function_exists('curl_init')) {
13615			$this->Error('Please enable cURL PHP extension!');
13616		}
13617		if (strlen($tsa_host) == 0) {
13618			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13619		}
13620		$this->tsa_data['tsa_host'] = $tsa_host;
13621		if (is_file($tsa_username)) {
13622			$this->tsa_data['tsa_auth'] = $tsa_username;
13623		} else {
13624			$this->tsa_data['tsa_username'] = $tsa_username;
13625		}
13626		$this->tsa_data['tsa_password'] = $tsa_password;
13627		$this->tsa_data['tsa_cert'] = $tsa_cert;
13628		$this->tsa_timestamp = true;
13629	}
13630
13631	/**
13632	 * NOT YET IMPLEMENTED
13633	 * Request TSA for a timestamp
13634	 * @param string $signature Digital signature as binary string
13635	 * @return string Timestamped digital signature
13636	 * @protected
13637	 * @author Richard Stockinger
13638	 * @since 6.0.090 (2014-06-16)
13639	 */
13640	protected function applyTSA($signature) {
13641		if (!$this->tsa_timestamp) {
13642			return $signature;
13643		}
13644		//@TODO: implement this feature
13645		return $signature;
13646	}
13647
13648	/**
13649	 * Create a new page group.
13650	 * NOTE: call this function before calling AddPage()
13651	 * @param int $page starting group page (leave empty for next page).
13652	 * @public
13653	 * @since 3.0.000 (2008-03-27)
13654	 */
13655	public function startPageGroup($page='') {
13656		if (empty($page)) {
13657			$page = $this->page + 1;
13658		}
13659		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13660	}
13661
13662	/**
13663	 * Set the starting page number.
13664	 * @param int $num Starting page number.
13665	 * @since 5.9.093 (2011-06-16)
13666	 * @public
13667	 */
13668	public function setStartingPageNumber($num=1) {
13669		$this->starting_page_number = max(0, intval($num));
13670	}
13671
13672	/**
13673	 * Returns the string alias used right align page numbers.
13674	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13675	 * @return string
13676	 * @since 5.9.099 (2011-06-27)
13677	 * @public
13678	 */
13679	public function getAliasRightShift() {
13680		// calculate aproximatively the ratio between widths of aliases and replacements.
13681		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13682		$rep = str_repeat(' ', $this->GetNumChars($ref));
13683		$wrep = $this->GetStringWidth($rep);
13684		if ($wrep > 0) {
13685			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13686		} else {
13687			$wdiff = 1;
13688		}
13689		$sdiff = sprintf('%F', $wdiff);
13690		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13691		if ($this->isUnicodeFont()) {
13692			$alias = '{'.$alias;
13693		}
13694		return $alias;
13695	}
13696
13697	/**
13698	 * Returns the string alias used for the total number of pages.
13699	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13700	 * This alias will be replaced by the total number of pages in the document.
13701	 * @return string
13702	 * @since 4.0.018 (2008-08-08)
13703	 * @public
13704	 */
13705	public function getAliasNbPages() {
13706		if ($this->isUnicodeFont()) {
13707			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13708		}
13709		return TCPDF_STATIC::$alias_tot_pages;
13710	}
13711
13712	/**
13713	 * Returns the string alias used for the page number.
13714	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13715	 * This alias will be replaced by the page number.
13716	 * @return string
13717	 * @since 4.5.000 (2009-01-02)
13718	 * @public
13719	 */
13720	public function getAliasNumPage() {
13721		if ($this->isUnicodeFont()) {
13722			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13723		}
13724		return TCPDF_STATIC::$alias_num_page;
13725	}
13726
13727	/**
13728	 * Return the alias for the total number of pages in the current page group.
13729	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13730	 * This alias will be replaced by the total number of pages in this group.
13731	 * @return string alias of the current page group
13732	 * @public
13733	 * @since 3.0.000 (2008-03-27)
13734	 */
13735	public function getPageGroupAlias() {
13736		if ($this->isUnicodeFont()) {
13737			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13738		}
13739		return TCPDF_STATIC::$alias_group_tot_pages;
13740	}
13741
13742	/**
13743	 * Return the alias for the page number on the current page group.
13744	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13745	 * This alias will be replaced by the page number (relative to the belonging group).
13746	 * @return string alias of the current page group
13747	 * @public
13748	 * @since 4.5.000 (2009-01-02)
13749	 */
13750	public function getPageNumGroupAlias() {
13751		if ($this->isUnicodeFont()) {
13752			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13753		}
13754		return TCPDF_STATIC::$alias_group_num_page;
13755	}
13756
13757	/**
13758	 * Return the current page in the group.
13759	 * @return int current page in the group
13760	 * @public
13761	 * @since 3.0.000 (2008-03-27)
13762	 */
13763	public function getGroupPageNo() {
13764		return $this->pagegroups[$this->currpagegroup];
13765	}
13766
13767	/**
13768	 * Returns the current group page number formatted as a string.
13769	 * @public
13770	 * @since 4.3.003 (2008-11-18)
13771	 * @see PaneNo(), formatPageNumber()
13772	 */
13773	public function getGroupPageNoFormatted() {
13774		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13775	}
13776
13777	/**
13778	 * Returns the current page number formatted as a string.
13779	 * @public
13780	 * @since 4.2.005 (2008-11-06)
13781	 * @see PaneNo(), formatPageNumber()
13782	 */
13783	public function PageNoFormatted() {
13784		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13785	}
13786
13787	/**
13788	 * Put pdf layers.
13789	 * @protected
13790	 * @since 3.0.000 (2008-03-27)
13791	 */
13792	protected function _putocg() {
13793		if (empty($this->pdflayers)) {
13794			return;
13795		}
13796		foreach ($this->pdflayers as $key => $layer) {
13797			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13798			 $out = '<< /Type /OCG';
13799			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13800			 $out .= ' /Usage <<';
13801			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13802				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13803			 }
13804			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13805			 $out .= ' >> >>';
13806			 $out .= "\n".'endobj';
13807			 $this->_out($out);
13808		}
13809	}
13810
13811	/**
13812	 * Start a new pdf layer.
13813	 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13814	 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13815	 * @param boolean $view Set to true to view this layer.
13816	 * @param boolean $lock If true lock the layer
13817	 * @public
13818	 * @since 5.9.102 (2011-07-13)
13819	 */
13820	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13821		if ($this->state != 2) {
13822			return;
13823		}
13824		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13825		if (empty($name)) {
13826			$name = $layer;
13827		} else {
13828			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13829		}
13830		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13831		$this->openMarkedContent = true;
13832		$this->_out('/OC /'.$layer.' BDC');
13833	}
13834
13835	/**
13836	 * End the current PDF layer.
13837	 * @public
13838	 * @since 5.9.102 (2011-07-13)
13839	 */
13840	public function endLayer() {
13841		if ($this->state != 2) {
13842			return;
13843		}
13844		if ($this->openMarkedContent) {
13845			// close existing open marked-content layer
13846			$this->_out('EMC');
13847			$this->openMarkedContent = false;
13848		}
13849	}
13850
13851	/**
13852	 * Set the visibility of the successive elements.
13853	 * This can be useful, for instance, to put a background
13854	 * image or color that will show on screen but won't print.
13855	 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13856	 * @public
13857	 * @since 3.0.000 (2008-03-27)
13858	 */
13859	public function setVisibility($v) {
13860		if ($this->state != 2) {
13861			return;
13862		}
13863		$this->endLayer();
13864		switch($v) {
13865			case 'print': {
13866				$this->startLayer('Print', true, false);
13867				break;
13868			}
13869			case 'view':
13870			case 'screen': {
13871				$this->startLayer('View', false, true);
13872				break;
13873			}
13874			case 'all': {
13875				$this->_out('');
13876				break;
13877			}
13878			default: {
13879				$this->Error('Incorrect visibility: '.$v);
13880				break;
13881			}
13882		}
13883	}
13884
13885	/**
13886	 * Add transparency parameters to the current extgstate
13887	 * @param array $parms parameters
13888	 * @return int|void the number of extgstates
13889	 * @protected
13890	 * @since 3.0.000 (2008-03-27)
13891	 */
13892	protected function addExtGState($parms) {
13893		if ($this->pdfa_mode || $this->pdfa_version >= 2) {
13894			// transparencies are not allowed in PDF/A mode
13895			return;
13896		}
13897		// check if this ExtGState already exist
13898		foreach ($this->extgstates as $i => $ext) {
13899			if ($ext['parms'] == $parms) {
13900				if ($this->inxobj) {
13901					// we are inside an XObject template
13902					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13903				}
13904				// return reference to existing ExtGState
13905				return $i;
13906			}
13907		}
13908		$n = (count($this->extgstates) + 1);
13909		$this->extgstates[$n] = array('parms' => $parms);
13910		if ($this->inxobj) {
13911			// we are inside an XObject template
13912			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13913		}
13914		return $n;
13915	}
13916
13917	/**
13918	 * Add an extgstate
13919	 * @param int $gs extgstate
13920	 * @protected
13921	 * @since 3.0.000 (2008-03-27)
13922	 */
13923	protected function setExtGState($gs) {
13924		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
13925			// transparency is not allowed in PDF/A-1 mode
13926			return;
13927		}
13928		$this->_out(sprintf('/GS%d gs', $gs));
13929	}
13930
13931	/**
13932	 * Put extgstates for object transparency
13933	 * @protected
13934	 * @since 3.0.000 (2008-03-27)
13935	 */
13936	protected function _putextgstates() {
13937		foreach ($this->extgstates as $i => $ext) {
13938			$this->extgstates[$i]['n'] = $this->_newobj();
13939			$out = '<< /Type /ExtGState';
13940			foreach ($ext['parms'] as $k => $v) {
13941				if (is_float($v)) {
13942					$v = sprintf('%F', $v);
13943				} elseif ($v === true) {
13944					$v = 'true';
13945				} elseif ($v === false) {
13946					$v = 'false';
13947				}
13948				$out .= ' /'.$k.' '.$v;
13949			}
13950			$out .= ' >>';
13951			$out .= "\n".'endobj';
13952			$this->_out($out);
13953		}
13954	}
13955
13956	/**
13957	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13958	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13959	 * @param boolean $stroking If true apply overprint for stroking operations.
13960	 * @param boolean $nonstroking If true apply overprint for painting operations other than stroking.
13961	 * @param integer $mode Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
13962	 * @public
13963	 * @since 5.9.152 (2012-03-23)
13964	 */
13965	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13966		if ($this->state != 2) {
13967			return;
13968		}
13969		$stroking = $stroking ? true : false;
13970		if (TCPDF_STATIC::empty_string($nonstroking)) {
13971			// default value if not set
13972			$nonstroking = $stroking;
13973		} else {
13974			$nonstroking = $nonstroking ? true : false;
13975		}
13976		if (($mode != 0) AND ($mode != 1)) {
13977			$mode = 0;
13978		}
13979		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13980		$gs = $this->addExtGState($this->overprint);
13981		$this->setExtGState($gs);
13982	}
13983
13984	/**
13985	 * Get the overprint mode array (OP, op, OPM).
13986	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13987	 * @return array<string,bool|int>
13988	 * @public
13989	 * @since 5.9.152 (2012-03-23)
13990	 */
13991	public function getOverprint() {
13992		return $this->overprint;
13993	}
13994
13995	/**
13996	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13997	 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13998	 * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
13999	 * @param float $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14000	 * @param boolean $ais
14001	 * @public
14002	 * @since 3.0.000 (2008-03-27)
14003	 */
14004	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
14005		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14006			// transparency is not allowed in PDF/A-1 mode
14007			return;
14008		}
14009		$stroking = floatval($stroking);
14010		if (TCPDF_STATIC::empty_string($nonstroking)) {
14011			// default value if not set
14012			$nonstroking = $stroking;
14013		} else {
14014			$nonstroking = floatval($nonstroking);
14015		}
14016		if ($bm[0] == '/') {
14017			// remove trailing slash
14018			$bm = substr($bm, 1);
14019		}
14020		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14021			$bm = 'Normal';
14022		}
14023		$ais = $ais ? true : false;
14024		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14025		$gs = $this->addExtGState($this->alpha);
14026		$this->setExtGState($gs);
14027	}
14028
14029	/**
14030	 * Get the alpha mode array (CA, ca, BM, AIS).
14031	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14032	 * @return array<string,bool|string>
14033	 * @public
14034	 * @since 5.9.152 (2012-03-23)
14035	 */
14036	public function getAlpha() {
14037		return $this->alpha;
14038	}
14039
14040	/**
14041	 * Set the default JPEG compression quality (1-100)
14042	 * @param int $quality JPEG quality, integer between 1 and 100
14043	 * @public
14044	 * @since 3.0.000 (2008-03-27)
14045	 */
14046	public function setJPEGQuality($quality) {
14047		if (($quality < 1) OR ($quality > 100)) {
14048			$quality = 75;
14049		}
14050		$this->jpeg_quality = intval($quality);
14051	}
14052
14053	/**
14054	 * Set the default number of columns in a row for HTML tables.
14055	 * @param int $cols number of columns
14056	 * @public
14057	 * @since 3.0.014 (2008-06-04)
14058	 */
14059	public function setDefaultTableColumns($cols=4) {
14060		$this->default_table_columns = intval($cols);
14061	}
14062
14063	/**
14064	 * Set the height of the cell (line height) respect the font height.
14065	 * @param int $h cell proportion respect font height (typical value = 1.25).
14066	 * @public
14067	 * @since 3.0.014 (2008-06-04)
14068	 */
14069	public function setCellHeightRatio($h) {
14070		$this->cell_height_ratio = $h;
14071	}
14072
14073	/**
14074	 * return the height of cell repect font height.
14075	 * @public
14076	 * @since 4.0.012 (2008-07-24)
14077	 */
14078	public function getCellHeightRatio() {
14079		return $this->cell_height_ratio;
14080	}
14081
14082	/**
14083	 * Set the PDF version (check PDF reference for valid values).
14084	 * @param string $version PDF document version.
14085	 * @public
14086	 * @since 3.1.000 (2008-06-09)
14087	 */
14088	public function setPDFVersion($version='1.7') {
14089		if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14090			// PDF/A-1 mode
14091			$this->PDFVersion = '1.4';
14092		} elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14093            // PDF/A-2 mode
14094            $this->PDFVersion = '1.7';
14095        } else {
14096			$this->PDFVersion = $version;
14097		}
14098	}
14099
14100	/**
14101	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14102	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14103	 * <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>
14104	 * @param array $preferences array of options.
14105	 * @author Nicola Asuni
14106	 * @public
14107	 * @since 3.1.000 (2008-06-09)
14108	 */
14109	public function setViewerPreferences($preferences) {
14110		$this->viewer_preferences = $preferences;
14111	}
14112
14113	/**
14114	 * Paints color transition registration bars
14115	 * @param float $x abscissa of the top left corner of the rectangle.
14116	 * @param float $y ordinate of the top left corner of the rectangle.
14117	 * @param float $w width of the rectangle.
14118	 * @param float $h height of the rectangle.
14119	 * @param boolean $transition if true prints tcolor transitions to white.
14120	 * @param boolean $vertical if true prints bar vertically.
14121	 * @param string $colors colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14122	 * @author Nicola Asuni
14123	 * @since 4.9.000 (2010-03-26)
14124	 * @public
14125	 */
14126	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14127		if (strpos($colors, 'ALLSPOT') !== false) {
14128			// expand spot colors
14129			$spot_colors = '';
14130			foreach ($this->spot_colors as $spot_color_name => $v) {
14131				$spot_colors .= ','.$spot_color_name;
14132			}
14133			if (!empty($spot_colors)) {
14134				$spot_colors = substr($spot_colors, 1);
14135				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14136			} else {
14137				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14138			}
14139		}
14140		$bars = explode(',', $colors);
14141		$numbars = count($bars); // number of bars to print
14142		if ($numbars <= 0) {
14143			return;
14144		}
14145		// set bar measures
14146		if ($vertical) {
14147			$coords = array(0, 0, 0, 1);
14148			$wb = $w / $numbars; // bar width
14149			$hb = $h; // bar height
14150			$xd = $wb; // delta x
14151			$yd = 0; // delta y
14152		} else {
14153			$coords = array(1, 0, 0, 0);
14154			$wb = $w; // bar width
14155			$hb = $h / $numbars; // bar height
14156			$xd = 0; // delta x
14157			$yd = $hb; // delta y
14158		}
14159		$xb = $x;
14160		$yb = $y;
14161		foreach ($bars as $col) {
14162			switch ($col) {
14163				// set transition colors
14164				case 'A': { // BLACK (GRAYSCALE)
14165					$col_a = array(255);
14166					$col_b = array(0);
14167					break;
14168				}
14169				case 'W': { // WHITE (GRAYSCALE)
14170					$col_a = array(0);
14171					$col_b = array(255);
14172					break;
14173				}
14174				case 'R': { // RED (RGB)
14175					$col_a = array(255,255,255);
14176					$col_b = array(255,0,0);
14177					break;
14178				}
14179				case 'G': { // GREEN (RGB)
14180					$col_a = array(255,255,255);
14181					$col_b = array(0,255,0);
14182					break;
14183				}
14184				case 'B': { // BLUE (RGB)
14185					$col_a = array(255,255,255);
14186					$col_b = array(0,0,255);
14187					break;
14188				}
14189				case 'C': { // CYAN (CMYK)
14190					$col_a = array(0,0,0,0);
14191					$col_b = array(100,0,0,0);
14192					break;
14193				}
14194				case 'M': { // MAGENTA (CMYK)
14195					$col_a = array(0,0,0,0);
14196					$col_b = array(0,100,0,0);
14197					break;
14198				}
14199				case 'Y': { // YELLOW (CMYK)
14200					$col_a = array(0,0,0,0);
14201					$col_b = array(0,0,100,0);
14202					break;
14203				}
14204				case 'K': { // KEY - BLACK (CMYK)
14205					$col_a = array(0,0,0,0);
14206					$col_b = array(0,0,0,100);
14207					break;
14208				}
14209				case 'RGB': { // BLACK REGISTRATION (RGB)
14210					$col_a = array(255,255,255);
14211					$col_b = array(0,0,0);
14212					break;
14213				}
14214				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14215					$col_a = array(0,0,0,0);
14216					$col_b = array(100,100,100,100);
14217					break;
14218				}
14219				case 'ALL': { // SPOT COLOR REGISTRATION
14220					$col_a = array(0,0,0,0,'None');
14221					$col_b = array(100,100,100,100,'All');
14222					break;
14223				}
14224				case 'NONE': { // SKIP THIS COLOR
14225					$col_a = array(0,0,0,0,'None');
14226					$col_b = array(0,0,0,0,'None');
14227					break;
14228				}
14229				default: { // SPECIFIC SPOT COLOR NAME
14230					$col_a = array(0,0,0,0,'None');
14231					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14232					if ($col_b === false) {
14233						// in case of error defaults to the registration color
14234						$col_b = array(100,100,100,100,'All');
14235					}
14236					break;
14237				}
14238			}
14239			if ($col != 'NONE') {
14240				if ($transition) {
14241					// color gradient
14242					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14243				} else {
14244					$this->SetFillColorArray($col_b);
14245					// colored rectangle
14246					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14247				}
14248				$xb += $xd;
14249				$yb += $yd;
14250			}
14251		}
14252	}
14253
14254	/**
14255	 * Paints crop marks.
14256	 * @param float $x abscissa of the crop mark center.
14257	 * @param float $y ordinate of the crop mark center.
14258	 * @param float $w width of the crop mark.
14259	 * @param float $h height of the crop mark.
14260	 * @param string $type type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14261	 * @param array $color crop mark color (default spot registration color).
14262	 * @author Nicola Asuni
14263	 * @since 4.9.000 (2010-03-26)
14264	 * @public
14265	 */
14266	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14267		$this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14268		$type = strtoupper($type);
14269		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14270		// split type in single components
14271		$type = str_replace('-', ',', $type);
14272		$type = str_replace('TL', 'T,L', $type);
14273		$type = str_replace('TR', 'T,R', $type);
14274		$type = str_replace('BL', 'F,L', $type);
14275		$type = str_replace('BR', 'F,R', $type);
14276		$type = str_replace('A', 'T,L', $type);
14277		$type = str_replace('B', 'T,R', $type);
14278		$type = str_replace('T,RO', 'BO', $type);
14279		$type = str_replace('C', 'F,L', $type);
14280		$type = str_replace('D', 'F,R', $type);
14281		$crops = explode(',', strtoupper($type));
14282		// remove duplicates
14283		$crops = array_unique($crops);
14284		$dw = ($w / 4); // horizontal space to leave before the intersection point
14285		$dh = ($h / 4); // vertical space to leave before the intersection point
14286		foreach ($crops as $crop) {
14287			switch ($crop) {
14288				case 'T':
14289				case 'TOP': {
14290					$x1 = $x;
14291					$y1 = ($y - $h);
14292					$x2 = $x;
14293					$y2 = ($y - $dh);
14294					break;
14295				}
14296				case 'F':
14297				case 'BOTTOM': {
14298					$x1 = $x;
14299					$y1 = ($y + $dh);
14300					$x2 = $x;
14301					$y2 = ($y + $h);
14302					break;
14303				}
14304				case 'L':
14305				case 'LEFT': {
14306					$x1 = ($x - $w);
14307					$y1 = $y;
14308					$x2 = ($x - $dw);
14309					$y2 = $y;
14310					break;
14311				}
14312				case 'R':
14313				case 'RIGHT': {
14314					$x1 = ($x + $dw);
14315					$y1 = $y;
14316					$x2 = ($x + $w);
14317					$y2 = $y;
14318					break;
14319				}
14320			}
14321			$this->Line($x1, $y1, $x2, $y2);
14322		}
14323	}
14324
14325	/**
14326	 * Paints a registration mark
14327	 * @param float $x abscissa of the registration mark center.
14328	 * @param float $y ordinate of the registration mark center.
14329	 * @param float $r radius of the crop mark.
14330	 * @param boolean $double if true print two concentric crop marks.
14331	 * @param array $cola crop mark color (default spot registration color 'All').
14332	 * @param array $colb second crop mark color (default spot registration color 'None').
14333	 * @author Nicola Asuni
14334	 * @since 4.9.000 (2010-03-26)
14335	 * @public
14336	 */
14337	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14338		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14339		$this->SetFillColorArray($cola);
14340		$this->PieSector($x, $y, $r, 90, 180, 'F');
14341		$this->PieSector($x, $y, $r, 270, 360, 'F');
14342		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14343		if ($double) {
14344			$ri = $r * 0.5;
14345			$this->SetFillColorArray($colb);
14346			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14347			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14348			$this->SetFillColorArray($cola);
14349			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14350			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14351			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14352		}
14353	}
14354
14355	/**
14356	 * Paints a CMYK registration mark
14357	 * @param float $x abscissa of the registration mark center.
14358	 * @param float $y ordinate of the registration mark center.
14359	 * @param float $r radius of the crop mark.
14360	 * @author Nicola Asuni
14361	 * @since 6.0.038 (2013-09-30)
14362	 * @public
14363	 */
14364	public function registrationMarkCMYK($x, $y, $r) {
14365		// line width
14366		$lw = max((0.5 / $this->k),($r / 8));
14367		// internal radius
14368		$ri = ($r * 0.6);
14369		// external radius
14370		$re = ($r * 1.3);
14371		// Cyan
14372		$this->SetFillColorArray(array(100,0,0,0));
14373		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14374		// Magenta
14375		$this->SetFillColorArray(array(0,100,0,0));
14376		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14377		// Yellow
14378		$this->SetFillColorArray(array(0,0,100,0));
14379		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14380		// Key - black
14381		$this->SetFillColorArray(array(0,0,0,100));
14382		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14383		// registration color
14384		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14385		$this->SetFillColorArray(array(100,100,100,100,'All'));
14386		// external circle
14387		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14388		// cross lines
14389		$this->Line($x, ($y - $re), $x, ($y - $ri));
14390		$this->Line($x, ($y + $ri), $x, ($y + $re));
14391		$this->Line(($x - $re), $y, ($x - $ri), $y);
14392		$this->Line(($x + $ri), $y, ($x + $re), $y);
14393	}
14394
14395	/**
14396	 * Paints a linear colour gradient.
14397	 * @param float $x abscissa of the top left corner of the rectangle.
14398	 * @param float $y ordinate of the top left corner of the rectangle.
14399	 * @param float $w width of the rectangle.
14400	 * @param float $h height of the rectangle.
14401	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14402	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14403	 * @param array $coords array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14404	 * @author Andreas W\FCrmser, Nicola Asuni
14405	 * @since 3.1.000 (2008-06-09)
14406	 * @public
14407	 */
14408	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14409		$this->Clip($x, $y, $w, $h);
14410		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14411	}
14412
14413	/**
14414	 * Paints a radial colour gradient.
14415	 * @param float $x abscissa of the top left corner of the rectangle.
14416	 * @param float $y ordinate of the top left corner of the rectangle.
14417	 * @param float $w width of the rectangle.
14418	 * @param float $h height of the rectangle.
14419	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14420	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14421	 * @param array $coords array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14422	 * @author Andreas W\FCrmser, Nicola Asuni
14423	 * @since 3.1.000 (2008-06-09)
14424	 * @public
14425	 */
14426	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14427		$this->Clip($x, $y, $w, $h);
14428		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14429	}
14430
14431	/**
14432	 * Paints a coons patch mesh.
14433	 * @param float $x abscissa of the top left corner of the rectangle.
14434	 * @param float $y ordinate of the top left corner of the rectangle.
14435	 * @param float $w width of the rectangle.
14436	 * @param float $h height of the rectangle.
14437	 * @param array $col1 first color (lower left corner) (RGB components).
14438	 * @param array $col2 second color (lower right corner) (RGB components).
14439	 * @param array $col3 third color (upper right corner) (RGB components).
14440	 * @param array $col4 fourth color (upper left corner) (RGB components).
14441	 * @param array $coords <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14442	 * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14443	 * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14444	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14445	 * @author Andreas W\FCrmser, Nicola Asuni
14446	 * @since 3.1.000 (2008-06-09)
14447	 * @public
14448	 */
14449	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) {
14450		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14451			return;
14452		}
14453		$this->Clip($x, $y, $w, $h);
14454		$n = count($this->gradients) + 1;
14455		$this->gradients[$n] = array();
14456		$this->gradients[$n]['type'] = 6; //coons patch mesh
14457		$this->gradients[$n]['coords'] = array();
14458		$this->gradients[$n]['antialias'] = $antialias;
14459		$this->gradients[$n]['colors'] = array();
14460		$this->gradients[$n]['transparency'] = false;
14461		//check the coords array if it is the simple array or the multi patch array
14462		if (!isset($coords[0]['f'])) {
14463			//simple array -> convert to multi patch array
14464			if (!isset($col1[1])) {
14465				$col1[1] = $col1[2] = $col1[0];
14466			}
14467			if (!isset($col2[1])) {
14468				$col2[1] = $col2[2] = $col2[0];
14469			}
14470			if (!isset($col3[1])) {
14471				$col3[1] = $col3[2] = $col3[0];
14472			}
14473			if (!isset($col4[1])) {
14474				$col4[1] = $col4[2] = $col4[0];
14475			}
14476			$patch_array[0]['f'] = 0;
14477			$patch_array[0]['points'] = $coords;
14478			$patch_array[0]['colors'][0]['r'] = $col1[0];
14479			$patch_array[0]['colors'][0]['g'] = $col1[1];
14480			$patch_array[0]['colors'][0]['b'] = $col1[2];
14481			$patch_array[0]['colors'][1]['r'] = $col2[0];
14482			$patch_array[0]['colors'][1]['g'] = $col2[1];
14483			$patch_array[0]['colors'][1]['b'] = $col2[2];
14484			$patch_array[0]['colors'][2]['r'] = $col3[0];
14485			$patch_array[0]['colors'][2]['g'] = $col3[1];
14486			$patch_array[0]['colors'][2]['b'] = $col3[2];
14487			$patch_array[0]['colors'][3]['r'] = $col4[0];
14488			$patch_array[0]['colors'][3]['g'] = $col4[1];
14489			$patch_array[0]['colors'][3]['b'] = $col4[2];
14490		} else {
14491			//multi patch array
14492			$patch_array = $coords;
14493		}
14494		$bpcd = 65535; //16 bits per coordinate
14495		//build the data stream
14496		$this->gradients[$n]['stream'] = '';
14497		$count_patch = count($patch_array);
14498		for ($i=0; $i < $count_patch; ++$i) {
14499			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14500			$count_points = count($patch_array[$i]['points']);
14501			for ($j=0; $j < $count_points; ++$j) {
14502				//each point as 16 bit
14503				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14504				if ($patch_array[$i]['points'][$j] < 0) {
14505					$patch_array[$i]['points'][$j] = 0;
14506				}
14507				if ($patch_array[$i]['points'][$j] > $bpcd) {
14508					$patch_array[$i]['points'][$j] = $bpcd;
14509				}
14510				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14511				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14512			}
14513			$count_cols = count($patch_array[$i]['colors']);
14514			for ($j=0; $j < $count_cols; ++$j) {
14515				//each color component as 8 bit
14516				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14517				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14518				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14519			}
14520		}
14521		//paint the gradient
14522		$this->_out('/Sh'.$n.' sh');
14523		//restore previous Graphic State
14524		$this->_outRestoreGraphicsState();
14525		if ($this->inxobj) {
14526			// we are inside an XObject template
14527			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14528		}
14529	}
14530
14531	/**
14532	 * Set a rectangular clipping area.
14533	 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14534	 * @param float $y ordinate of the top left corner of the rectangle.
14535	 * @param float $w width of the rectangle.
14536	 * @param float $h height of the rectangle.
14537	 * @author Andreas W\FCrmser, Nicola Asuni
14538	 * @since 3.1.000 (2008-06-09)
14539	 * @protected
14540	 */
14541	protected function Clip($x, $y, $w, $h) {
14542		if ($this->state != 2) {
14543			 return;
14544		}
14545		if ($this->rtl) {
14546			$x = $this->w - $x - $w;
14547		}
14548		//save current Graphic State
14549		$s = 'q';
14550		//set clipping area
14551		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14552		//set up transformation matrix for gradient
14553		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14554		$this->_out($s);
14555	}
14556
14557	/**
14558	 * Output gradient.
14559	 * @param int $type type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14560	 * @param array $coords array of coordinates.
14561	 * @param array $stops array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14562	 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14563	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14564	 * @author Nicola Asuni
14565	 * @since 3.1.000 (2008-06-09)
14566	 * @public
14567	 */
14568	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14569		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14570			return;
14571		}
14572		$n = count($this->gradients) + 1;
14573		$this->gradients[$n] = array();
14574		$this->gradients[$n]['type'] = $type;
14575		$this->gradients[$n]['coords'] = $coords;
14576		$this->gradients[$n]['antialias'] = $antialias;
14577		$this->gradients[$n]['colors'] = array();
14578		$this->gradients[$n]['transparency'] = false;
14579		// color space
14580		$numcolspace = count($stops[0]['color']);
14581		$bcolor = array_values($background);
14582		switch($numcolspace) {
14583			case 5:   // SPOT
14584			case 4: { // CMYK
14585				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14586				if (!empty($background)) {
14587					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14588				}
14589				break;
14590			}
14591			case 3: { // RGB
14592				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14593				if (!empty($background)) {
14594					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14595				}
14596				break;
14597			}
14598			case 1: { // GRAY SCALE
14599				$this->gradients[$n]['colspace'] = 'DeviceGray';
14600				if (!empty($background)) {
14601					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14602				}
14603				break;
14604			}
14605		}
14606		$num_stops = count($stops);
14607		$last_stop_id = $num_stops - 1;
14608		foreach ($stops as $key => $stop) {
14609			$this->gradients[$n]['colors'][$key] = array();
14610			// offset represents a location along the gradient vector
14611			if (isset($stop['offset'])) {
14612				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14613			} else {
14614				if ($key == 0) {
14615					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14616				} elseif ($key == $last_stop_id) {
14617					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14618				} else {
14619					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14620					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14621				}
14622			}
14623			if (isset($stop['opacity'])) {
14624				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14625				if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) {
14626					$this->gradients[$n]['transparency'] = true;
14627				}
14628			} else {
14629				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14630			}
14631			// exponent for the exponential interpolation function
14632			if (isset($stop['exponent'])) {
14633				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14634			} else {
14635				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14636			}
14637			// set colors
14638			$color = array_values($stop['color']);
14639			switch($numcolspace) {
14640				case 5:   // SPOT
14641				case 4: { // CMYK
14642					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14643					break;
14644				}
14645				case 3: { // RGB
14646					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14647					break;
14648				}
14649				case 1: { // GRAY SCALE
14650					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14651					break;
14652				}
14653			}
14654		}
14655		if ($this->gradients[$n]['transparency']) {
14656			// paint luminosity gradient
14657			$this->_out('/TGS'.$n.' gs');
14658		}
14659		//paint the gradient
14660		$this->_out('/Sh'.$n.' sh');
14661		//restore previous Graphic State
14662		$this->_outRestoreGraphicsState();
14663		if ($this->inxobj) {
14664			// we are inside an XObject template
14665			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14666		}
14667	}
14668
14669	/**
14670	 * Output gradient shaders.
14671	 * @author Nicola Asuni
14672	 * @since 3.1.000 (2008-06-09)
14673	 * @protected
14674	 */
14675	function _putshaders() {
14676		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14677			return;
14678		}
14679		$idt = count($this->gradients); //index for transparency gradients
14680		foreach ($this->gradients as $id => $grad) {
14681			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14682				$fc = $this->_newobj();
14683				$out = '<<';
14684				$out .= ' /FunctionType 3';
14685				$out .= ' /Domain [0 1]';
14686				$functions = '';
14687				$bounds = '';
14688				$encode = '';
14689				$i = 1;
14690				$num_cols = count($grad['colors']);
14691				$lastcols = $num_cols - 1;
14692				for ($i = 1; $i < $num_cols; ++$i) {
14693					$functions .= ($fc + $i).' 0 R ';
14694					if ($i < $lastcols) {
14695						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14696					}
14697					$encode .= '0 1 ';
14698				}
14699				$out .= ' /Functions ['.trim($functions).']';
14700				$out .= ' /Bounds ['.trim($bounds).']';
14701				$out .= ' /Encode ['.trim($encode).']';
14702				$out .= ' >>';
14703				$out .= "\n".'endobj';
14704				$this->_out($out);
14705				for ($i = 1; $i < $num_cols; ++$i) {
14706					$this->_newobj();
14707					$out = '<<';
14708					$out .= ' /FunctionType 2';
14709					$out .= ' /Domain [0 1]';
14710					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14711					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14712					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14713					$out .= ' >>';
14714					$out .= "\n".'endobj';
14715					$this->_out($out);
14716				}
14717				// set transparency functions
14718				if ($grad['transparency']) {
14719					$ft = $this->_newobj();
14720					$out = '<<';
14721					$out .= ' /FunctionType 3';
14722					$out .= ' /Domain [0 1]';
14723					$functions = '';
14724					$i = 1;
14725					$num_cols = count($grad['colors']);
14726					for ($i = 1; $i < $num_cols; ++$i) {
14727						$functions .= ($ft + $i).' 0 R ';
14728					}
14729					$out .= ' /Functions ['.trim($functions).']';
14730					$out .= ' /Bounds ['.trim($bounds).']';
14731					$out .= ' /Encode ['.trim($encode).']';
14732					$out .= ' >>';
14733					$out .= "\n".'endobj';
14734					$this->_out($out);
14735					for ($i = 1; $i < $num_cols; ++$i) {
14736						$this->_newobj();
14737						$out = '<<';
14738						$out .= ' /FunctionType 2';
14739						$out .= ' /Domain [0 1]';
14740						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14741						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14742						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14743						$out .= ' >>';
14744						$out .= "\n".'endobj';
14745						$this->_out($out);
14746					}
14747				}
14748			}
14749			// set shading object
14750			$this->_newobj();
14751			$out = '<< /ShadingType '.$grad['type'];
14752			if (isset($grad['colspace'])) {
14753				$out .= ' /ColorSpace /'.$grad['colspace'];
14754			} else {
14755				$out .= ' /ColorSpace /DeviceRGB';
14756			}
14757			if (isset($grad['background']) AND !empty($grad['background'])) {
14758				$out .= ' /Background ['.$grad['background'].']';
14759			}
14760			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14761				$out .= ' /AntiAlias true';
14762			}
14763			if ($grad['type'] == 2) {
14764				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14765				$out .= ' /Domain [0 1]';
14766				$out .= ' /Function '.$fc.' 0 R';
14767				$out .= ' /Extend [true true]';
14768				$out .= ' >>';
14769			} elseif ($grad['type'] == 3) {
14770				//x0, y0, r0, x1, y1, r1
14771				//at this this time radius of inner circle is 0
14772				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14773				$out .= ' /Domain [0 1]';
14774				$out .= ' /Function '.$fc.' 0 R';
14775				$out .= ' /Extend [true true]';
14776				$out .= ' >>';
14777			} elseif ($grad['type'] == 6) {
14778				$out .= ' /BitsPerCoordinate 16';
14779				$out .= ' /BitsPerComponent 8';
14780				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14781				$out .= ' /BitsPerFlag 8';
14782				$stream = $this->_getrawstream($grad['stream']);
14783				$out .= ' /Length '.strlen($stream);
14784				$out .= ' >>';
14785				$out .= ' stream'."\n".$stream."\n".'endstream';
14786			}
14787			$out .= "\n".'endobj';
14788			$this->_out($out);
14789			if ($grad['transparency']) {
14790				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14791				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14792			}
14793			$this->gradients[$id]['id'] = $this->n;
14794			// set pattern object
14795			$this->_newobj();
14796			$out = '<< /Type /Pattern /PatternType 2';
14797			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14798			$out .= ' >>';
14799			$out .= "\n".'endobj';
14800			$this->_out($out);
14801			$this->gradients[$id]['pattern'] = $this->n;
14802			// set shading and pattern for transparency mask
14803			if ($grad['transparency']) {
14804				// luminosity pattern
14805				$idgs = $id + $idt;
14806				$this->_newobj();
14807				$this->_out($shading_transparency);
14808				$this->gradients[$idgs]['id'] = $this->n;
14809				$this->_newobj();
14810				$out = '<< /Type /Pattern /PatternType 2';
14811				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14812				$out .= ' >>';
14813				$out .= "\n".'endobj';
14814				$this->_out($out);
14815				$this->gradients[$idgs]['pattern'] = $this->n;
14816				// luminosity XObject
14817				$oid = $this->_newobj();
14818				$this->xobjects['LX'.$oid] = array('n' => $oid);
14819				$filter = '';
14820				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14821				if ($this->compress) {
14822					$filter = ' /Filter /FlateDecode';
14823					$stream = gzcompress($stream);
14824				}
14825				$stream = $this->_getrawstream($stream);
14826				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14827				$out .= ' /Length '.strlen($stream);
14828				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14829				$out .= ' /BBox [0 0 '.$rect.']';
14830				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14831				$out .= ' /Resources <<';
14832				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14833				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14834				$out .= ' >>';
14835				$out .= ' >> ';
14836				$out .= ' stream'."\n".$stream."\n".'endstream';
14837				$out .= "\n".'endobj';
14838				$this->_out($out);
14839				// SMask
14840				$this->_newobj();
14841				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14842				$this->_out($out);
14843				// ExtGState
14844				$this->_newobj();
14845				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14846				$this->_out($out);
14847				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14848			}
14849		}
14850	}
14851
14852	/**
14853	 * Draw the sector of a circle.
14854	 * It can be used for instance to render pie charts.
14855	 * @param float $xc abscissa of the center.
14856	 * @param float $yc ordinate of the center.
14857	 * @param float $r radius.
14858	 * @param float $a start angle (in degrees).
14859	 * @param float $b end angle (in degrees).
14860	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14861	 * @param float $cw indicates whether to go clockwise (default: true).
14862	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14863	 * @author Maxime Delorme, Nicola Asuni
14864	 * @since 3.1.000 (2008-06-09)
14865	 * @public
14866	 */
14867	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14868		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14869	}
14870
14871	/**
14872	 * Draw the sector of an ellipse.
14873	 * It can be used for instance to render pie charts.
14874	 * @param float $xc abscissa of the center.
14875	 * @param float $yc ordinate of the center.
14876	 * @param float $rx the x-axis radius.
14877	 * @param float $ry the y-axis radius.
14878	 * @param float $a start angle (in degrees).
14879	 * @param float $b end angle (in degrees).
14880	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14881	 * @param float $cw indicates whether to go clockwise.
14882	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14883	 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14884	 * @author Maxime Delorme, Nicola Asuni
14885	 * @since 3.1.000 (2008-06-09)
14886	 * @public
14887	 */
14888	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14889		if ($this->state != 2) {
14890			 return;
14891		}
14892		if ($this->rtl) {
14893			$xc = ($this->w - $xc);
14894		}
14895		$op = TCPDF_STATIC::getPathPaintOperator($style);
14896		if ($op == 'f') {
14897			$line_style = array();
14898		}
14899		if ($cw) {
14900			$d = $b;
14901			$b = (360 - $a + $o);
14902			$a = (360 - $d + $o);
14903		} else {
14904			$b += $o;
14905			$a += $o;
14906		}
14907		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14908		$this->_out($op);
14909	}
14910
14911	/**
14912	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14913	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14914	 * Only vector drawing is supported, not text or bitmap.
14915	 * 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).
14916	 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14917	 * @param float $x Abscissa of the upper-left corner.
14918	 * @param float $y Ordinate of the upper-left corner.
14919	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14920	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14921	 * @param mixed $link URL or identifier returned by AddLink().
14922	 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14923	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
14924	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
14925	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
14926	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
14927	 * @param boolean $fixoutvals if true remove values outside the bounding box.
14928	 * @author Valentin Schmidt, Nicola Asuni
14929	 * @since 3.1.000 (2008-06-09)
14930	 * @public
14931	 */
14932	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14933		if ($this->state != 2) {
14934			 return;
14935		}
14936		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14937			// convert EPS to raster image using GD or ImageMagick libraries
14938			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14939		}
14940		if ($x === '') {
14941			$x = $this->x;
14942		}
14943		if ($y === '') {
14944			$y = $this->y;
14945		}
14946		// check page for no-write regions and adapt page margins if necessary
14947		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14948		$k = $this->k;
14949		if ($file[0] === '@') { // image from string
14950			$data = substr($file, 1);
14951		} else { // EPS/AI file
14952            $data = $this->getCachedFileContents($file);
14953		}
14954		if ($data === FALSE) {
14955			$this->Error('EPS file not found: '.$file);
14956		}
14957		$regs = array();
14958		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14959		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14960		if (count($regs) > 1) {
14961			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14962			if (strpos($version_str, 'Adobe Illustrator') !== false) {
14963				$versexp = explode(' ', $version_str);
14964				$version = (float)array_pop($versexp);
14965				if ($version >= 9) {
14966					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14967				}
14968			}
14969		}
14970		// strip binary bytes in front of PS-header
14971		$start = strpos($data, '%!PS-Adobe');
14972		if ($start > 0) {
14973			$data = substr($data, $start);
14974		}
14975		// find BoundingBox params
14976		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14977		if (count($regs) > 1) {
14978			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14979		} else {
14980			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
14981		}
14982		$start = strpos($data, '%%EndSetup');
14983		if ($start === false) {
14984			$start = strpos($data, '%%EndProlog');
14985		}
14986		if ($start === false) {
14987			$start = strpos($data, '%%BoundingBox');
14988		}
14989		$data = substr($data, $start);
14990		$end = strpos($data, '%%PageTrailer');
14991		if ($end===false) {
14992			$end = strpos($data, 'showpage');
14993		}
14994		if ($end) {
14995			$data = substr($data, 0, $end);
14996		}
14997		// calculate image width and height on document
14998		if (($w <= 0) AND ($h <= 0)) {
14999			$w = ($x2 - $x1) / $k;
15000			$h = ($y2 - $y1) / $k;
15001		} elseif ($w <= 0) {
15002			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15003		} elseif ($h <= 0) {
15004			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15005		}
15006		// fit the image on available space
15007		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15008		if ($this->rasterize_vector_images) {
15009			// convert EPS to raster image using GD or ImageMagick libraries
15010			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15011		}
15012		// set scaling factors
15013		$scale_x = $w / (($x2 - $x1) / $k);
15014		$scale_y = $h / (($y2 - $y1) / $k);
15015		// set alignment
15016		$this->img_rb_y = $y + $h;
15017		// set alignment
15018		if ($this->rtl) {
15019			if ($palign == 'L') {
15020				$ximg = $this->lMargin;
15021			} elseif ($palign == 'C') {
15022				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15023			} elseif ($palign == 'R') {
15024				$ximg = $this->w - $this->rMargin - $w;
15025			} else {
15026				$ximg = $x - $w;
15027			}
15028			$this->img_rb_x = $ximg;
15029		} else {
15030			if ($palign == 'L') {
15031				$ximg = $this->lMargin;
15032			} elseif ($palign == 'C') {
15033				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15034			} elseif ($palign == 'R') {
15035				$ximg = $this->w - $this->rMargin - $w;
15036			} else {
15037				$ximg = $x;
15038			}
15039			$this->img_rb_x = $ximg + $w;
15040		}
15041		if ($useBoundingBox) {
15042			$dx = $ximg * $k - $x1;
15043			$dy = $y * $k - $y1;
15044		} else {
15045			$dx = $ximg * $k;
15046			$dy = $y * $k;
15047		}
15048		// save the current graphic state
15049		$this->_out('q'.$this->epsmarker);
15050		// translate
15051		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15052		// scale
15053		if (isset($scale_x)) {
15054			$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15055		}
15056		// handle pc/unix/mac line endings
15057		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15058		$u=0;
15059		$cnt = count($lines);
15060		for ($i=0; $i < $cnt; ++$i) {
15061			$line = $lines[$i];
15062			if (($line == '') OR ($line[0] == '%')) {
15063				continue;
15064			}
15065			$len = strlen($line);
15066			// check for spot color names
15067			$color_name = '';
15068			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15069				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15070					// extract spot color name
15071					$color_name = $matches[0];
15072					// remove color name from string
15073					$line = str_replace(' '.$color_name, '', $line);
15074					// remove pharentesis from color name
15075					$color_name = substr($color_name, 1, -1);
15076				}
15077			}
15078			$chunks = explode(' ', $line);
15079			$cmd = trim(array_pop($chunks));
15080			// RGB
15081			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15082				$b = array_pop($chunks);
15083				$g = array_pop($chunks);
15084				$r = array_pop($chunks);
15085				$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!
15086				continue;
15087			}
15088			$skip = false;
15089			if ($fixoutvals) {
15090				// check for values outside the bounding box
15091				switch ($cmd) {
15092					case 'm':
15093					case 'l':
15094					case 'L': {
15095						// skip values outside bounding box
15096						foreach ($chunks as $key => $val) {
15097							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15098								$skip = true;
15099							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15100								$skip = true;
15101							}
15102						}
15103					}
15104				}
15105			}
15106			switch ($cmd) {
15107				case 'm':
15108				case 'l':
15109				case 'v':
15110				case 'y':
15111				case 'c':
15112				case 'k':
15113				case 'K':
15114				case 'g':
15115				case 'G':
15116				case 's':
15117				case 'S':
15118				case 'J':
15119				case 'j':
15120				case 'w':
15121				case 'M':
15122				case 'd':
15123				case 'n': {
15124					if ($skip) {
15125						break;
15126					}
15127					$this->_out($line);
15128					break;
15129				}
15130				case 'x': {// custom fill color
15131					if (empty($color_name)) {
15132						// CMYK color
15133						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15134						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15135					} else {
15136						// Spot Color (CMYK + tint)
15137						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15138						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15139						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15140						$this->_out($color_cmd);
15141					}
15142					break;
15143				}
15144				case 'X': { // custom stroke color
15145					if (empty($color_name)) {
15146						// CMYK color
15147						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15148						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15149					} else {
15150						// Spot Color (CMYK + tint)
15151						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15152						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15153						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15154						$this->_out($color_cmd);
15155					}
15156					break;
15157				}
15158				case 'Y':
15159				case 'N':
15160				case 'V':
15161				case 'L':
15162				case 'C': {
15163					if ($skip) {
15164						break;
15165					}
15166					$line[($len - 1)] = strtolower($cmd);
15167					$this->_out($line);
15168					break;
15169				}
15170				case 'b':
15171				case 'B': {
15172					$this->_out($cmd . '*');
15173					break;
15174				}
15175				case 'f':
15176				case 'F': {
15177					if ($u > 0) {
15178						$isU = false;
15179						$max = min(($i + 5), $cnt);
15180						for ($j = ($i + 1); $j < $max; ++$j) {
15181							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15182						}
15183						if ($isU) {
15184							$this->_out('f*');
15185						}
15186					} else {
15187						$this->_out('f*');
15188					}
15189					break;
15190				}
15191				case '*u': {
15192					++$u;
15193					break;
15194				}
15195				case '*U': {
15196					--$u;
15197					break;
15198				}
15199			}
15200		}
15201		// restore previous graphic state
15202		$this->_out($this->epsmarker.'Q');
15203		if (!empty($border)) {
15204			$bx = $this->x;
15205			$by = $this->y;
15206			$this->x = $ximg;
15207			if ($this->rtl) {
15208				$this->x += $w;
15209			}
15210			$this->y = $y;
15211			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15212			$this->x = $bx;
15213			$this->y = $by;
15214		}
15215		if ($link) {
15216			$this->Link($ximg, $y, $w, $h, $link, 0);
15217		}
15218		// set pointer to align the next text/objects
15219		switch($align) {
15220			case 'T':{
15221				$this->y = $y;
15222				$this->x = $this->img_rb_x;
15223				break;
15224			}
15225			case 'M':{
15226				$this->y = $y + round($h/2);
15227				$this->x = $this->img_rb_x;
15228				break;
15229			}
15230			case 'B':{
15231				$this->y = $this->img_rb_y;
15232				$this->x = $this->img_rb_x;
15233				break;
15234			}
15235			case 'N':{
15236				$this->SetY($this->img_rb_y);
15237				break;
15238			}
15239			default:{
15240				break;
15241			}
15242		}
15243		$this->endlinex = $this->img_rb_x;
15244	}
15245
15246	/**
15247	 * Set document barcode.
15248	 * @param string $bc barcode
15249	 * @public
15250	 */
15251	public function setBarcode($bc='') {
15252		$this->barcode = $bc;
15253	}
15254
15255	/**
15256	 * Get current barcode.
15257	 * @return string
15258	 * @public
15259	 * @since 4.0.012 (2008-07-24)
15260	 */
15261	public function getBarcode() {
15262		return $this->barcode;
15263	}
15264
15265	/**
15266	 * Print a Linear Barcode.
15267	 * @param string $code code to print
15268	 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15269	 * @param int $x x position in user units (empty string = current x position)
15270	 * @param int $y y position in user units (empty string = current y position)
15271	 * @param int $w width in user units (empty string = remaining page width)
15272	 * @param int $h height in user units (empty string = remaining page height)
15273	 * @param float $xres width of the smallest bar in user units (empty string = default value = 0.4mm)
15274	 * @param array $style array of options:<ul>
15275	 * <li>boolean $style['border'] if true prints a border</li>
15276	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15277	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15278	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15279	 * <li>array $style['fgcolor'] color array for bars and text</li>
15280	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15281	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15282	 * <li>string $style['label'] override default label</li>
15283	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15284	 * <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>
15285	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15286	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15287	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15288	 * <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>
15289	 * <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>
15290	 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15291	 * @author Nicola Asuni
15292	 * @since 3.1.000 (2008-06-09)
15293	 * @public
15294	 */
15295	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') {
15296		if (TCPDF_STATIC::empty_string(trim($code))) {
15297			return;
15298		}
15299		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15300		// save current graphic settings
15301		$gvars = $this->getGraphicVars();
15302		// create new barcode object
15303		$barcodeobj = new TCPDFBarcode($code, $type);
15304		$arrcode = $barcodeobj->getBarcodeArray();
15305		if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15306			$this->Error('Error in 1D barcode string');
15307		}
15308		if ($arrcode['maxh'] <= 0) {
15309			$arrcode['maxh'] = 1;
15310		}
15311		// set default values
15312		if (!isset($style['position'])) {
15313			$style['position'] = '';
15314		} elseif ($style['position'] == 'S') {
15315			// keep this for backward compatibility
15316			$style['position'] = '';
15317			$style['stretch'] = true;
15318		}
15319		if (!isset($style['fitwidth'])) {
15320			if (!isset($style['stretch'])) {
15321				$style['fitwidth'] = true;
15322			} else {
15323				$style['fitwidth'] = false;
15324			}
15325		}
15326		if ($style['fitwidth']) {
15327			// disable stretch
15328			$style['stretch'] = false;
15329		}
15330		if (!isset($style['stretch'])) {
15331			if (($w === '') OR ($w <= 0)) {
15332				$style['stretch'] = false;
15333			} else {
15334				$style['stretch'] = true;
15335			}
15336		}
15337		if (!isset($style['fgcolor'])) {
15338			$style['fgcolor'] = array(0,0,0); // default black
15339		}
15340		if (!isset($style['bgcolor'])) {
15341			$style['bgcolor'] = false; // default transparent
15342		}
15343		if (!isset($style['border'])) {
15344			$style['border'] = false;
15345		}
15346		$fontsize = 0;
15347		if (!isset($style['text'])) {
15348			$style['text'] = false;
15349		}
15350		if ($style['text'] AND isset($style['font'])) {
15351			if (isset($style['fontsize'])) {
15352				$fontsize = $style['fontsize'];
15353			}
15354			$this->SetFont($style['font'], '', $fontsize);
15355		}
15356		if (!isset($style['stretchtext'])) {
15357			$style['stretchtext'] = 4;
15358		}
15359		if ($x === '') {
15360			$x = $this->x;
15361		}
15362		if ($y === '') {
15363			$y = $this->y;
15364		}
15365		// check page for no-write regions and adapt page margins if necessary
15366		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15367		if (($w === '') OR ($w <= 0)) {
15368			if ($this->rtl) {
15369				$w = $x - $this->lMargin;
15370			} else {
15371				$w = $this->w - $this->rMargin - $x;
15372			}
15373		}
15374		// padding
15375		if (!isset($style['padding'])) {
15376			$padding = 0;
15377		} elseif ($style['padding'] === 'auto') {
15378			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15379		} else {
15380			$padding = floatval($style['padding']);
15381		}
15382		// horizontal padding
15383		if (!isset($style['hpadding'])) {
15384			$hpadding = $padding;
15385		} elseif ($style['hpadding'] === 'auto') {
15386			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15387		} else {
15388			$hpadding = floatval($style['hpadding']);
15389		}
15390		// vertical padding
15391		if (!isset($style['vpadding'])) {
15392			$vpadding = $padding;
15393		} elseif ($style['vpadding'] === 'auto') {
15394			$vpadding = ($hpadding / 2);
15395		} else {
15396			$vpadding = floatval($style['vpadding']);
15397		}
15398		// calculate xres (single bar width)
15399		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15400		if ($style['stretch']) {
15401			$xres = $max_xres;
15402		} else {
15403			if (TCPDF_STATIC::empty_string($xres)) {
15404				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15405			}
15406			if ($xres > $max_xres) {
15407				// correct xres to fit on $w
15408				$xres = $max_xres;
15409			}
15410			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15411				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15412				$hpadding = 10 * $xres;
15413				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15414					$vpadding = ($hpadding / 2);
15415				}
15416			}
15417		}
15418		if ($style['fitwidth']) {
15419			$wold = $w;
15420			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15421			if (isset($style['cellfitalign'])) {
15422				switch ($style['cellfitalign']) {
15423					case 'L': {
15424						if ($this->rtl) {
15425							$x -= ($wold - $w);
15426						}
15427						break;
15428					}
15429					case 'R': {
15430						if (!$this->rtl) {
15431							$x += ($wold - $w);
15432						}
15433						break;
15434					}
15435					case 'C': {
15436						if ($this->rtl) {
15437							$x -= (($wold - $w) / 2);
15438						} else {
15439							$x += (($wold - $w) / 2);
15440						}
15441						break;
15442					}
15443					default : {
15444						break;
15445					}
15446				}
15447			}
15448		}
15449		$text_height = $this->getCellHeight($fontsize / $this->k);
15450		// height
15451		if (($h === '') OR ($h <= 0)) {
15452			// set default height
15453			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15454		}
15455		$barh = $h - $text_height - (2 * $vpadding);
15456		if ($barh <=0) {
15457			// try to reduce font or padding to fit barcode on available height
15458			if ($text_height > $h) {
15459				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15460				$text_height = $this->getCellHeight($fontsize / $this->k);
15461				$this->SetFont($style['font'], '', $fontsize);
15462			}
15463			if ($vpadding > 0) {
15464				$vpadding = (($h - $text_height) / 4);
15465			}
15466			$barh = $h - $text_height - (2 * $vpadding);
15467		}
15468		// fit the barcode on available space
15469		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15470		// set alignment
15471		$this->img_rb_y = $y + $h;
15472		// set alignment
15473		if ($this->rtl) {
15474			if ($style['position'] == 'L') {
15475				$xpos = $this->lMargin;
15476			} elseif ($style['position'] == 'C') {
15477				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15478			} elseif ($style['position'] == 'R') {
15479				$xpos = $this->w - $this->rMargin - $w;
15480			} else {
15481				$xpos = $x - $w;
15482			}
15483			$this->img_rb_x = $xpos;
15484		} else {
15485			if ($style['position'] == 'L') {
15486				$xpos = $this->lMargin;
15487			} elseif ($style['position'] == 'C') {
15488				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15489			} elseif ($style['position'] == 'R') {
15490				$xpos = $this->w - $this->rMargin - $w;
15491			} else {
15492				$xpos = $x;
15493			}
15494			$this->img_rb_x = $xpos + $w;
15495		}
15496		$xpos_rect = $xpos;
15497		if (!isset($style['align'])) {
15498			$style['align'] = 'C';
15499		}
15500		switch ($style['align']) {
15501			case 'L': {
15502				$xpos = $xpos_rect + $hpadding;
15503				break;
15504			}
15505			case 'R': {
15506				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15507				break;
15508			}
15509			case 'C':
15510			default : {
15511				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15512				break;
15513			}
15514		}
15515		$xpos_text = $xpos;
15516		// barcode is always printed in LTR direction
15517		$tempRTL = $this->rtl;
15518		$this->rtl = false;
15519		// print background color
15520		if ($style['bgcolor']) {
15521			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15522		} elseif ($style['border']) {
15523			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15524		}
15525		// set foreground color
15526		$this->SetDrawColorArray($style['fgcolor']);
15527		$this->SetTextColorArray($style['fgcolor']);
15528		// print bars
15529		foreach ($arrcode['bcode'] as $k => $v) {
15530			$bw = ($v['w'] * $xres);
15531			if ($v['t']) {
15532				// draw a vertical bar
15533				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15534				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15535			}
15536			$xpos += $bw;
15537		}
15538		// print text
15539		if ($style['text']) {
15540			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15541				$label = $style['label'];
15542			} else {
15543				$label = $code;
15544			}
15545			$txtwidth = ($arrcode['maxw'] * $xres);
15546			if ($this->GetStringWidth($label) > $txtwidth) {
15547				$style['stretchtext'] = 2;
15548			}
15549			// print text
15550			$this->x = $xpos_text;
15551			$this->y = $y + $vpadding + $barh;
15552			$cellpadding = $this->cell_padding;
15553			$this->SetCellPadding(0);
15554			$this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15555			$this->cell_padding = $cellpadding;
15556		}
15557		// restore original direction
15558		$this->rtl = $tempRTL;
15559		// restore previous settings
15560		$this->setGraphicVars($gvars);
15561		// set pointer to align the next text/objects
15562		switch($align) {
15563			case 'T':{
15564				$this->y = $y;
15565				$this->x = $this->img_rb_x;
15566				break;
15567			}
15568			case 'M':{
15569				$this->y = $y + round($h / 2);
15570				$this->x = $this->img_rb_x;
15571				break;
15572			}
15573			case 'B':{
15574				$this->y = $this->img_rb_y;
15575				$this->x = $this->img_rb_x;
15576				break;
15577			}
15578			case 'N':{
15579				$this->SetY($this->img_rb_y);
15580				break;
15581			}
15582			default:{
15583				break;
15584			}
15585		}
15586		$this->endlinex = $this->img_rb_x;
15587	}
15588
15589	/**
15590	 * Print 2D Barcode.
15591	 * @param string $code code to print
15592	 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15593	 * @param int $x x position in user units
15594	 * @param int $y y position in user units
15595	 * @param int $w width in user units
15596	 * @param int $h height in user units
15597	 * @param array $style array of options:<ul>
15598	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15599	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15600	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15601	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15602	 * <li>int $style['module_width'] width of a single module in points</li>
15603	 * <li>int $style['module_height'] height of a single module in points</li>
15604	 * <li>array $style['fgcolor'] color array for bars and text</li>
15605	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15606	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15607	 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15608	 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15609	 * @author Nicola Asuni
15610	 * @since 4.5.037 (2009-04-07)
15611	 * @public
15612	 */
15613	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) {
15614		if (TCPDF_STATIC::empty_string(trim($code))) {
15615			return;
15616		}
15617		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15618		// save current graphic settings
15619		$gvars = $this->getGraphicVars();
15620		// create new barcode object
15621		$barcodeobj = new TCPDF2DBarcode($code, $type);
15622		$arrcode = $barcodeobj->getBarcodeArray();
15623		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)) {
15624			$this->Error('Error in 2D barcode string');
15625		}
15626		// set default values
15627		if (!isset($style['position'])) {
15628			$style['position'] = '';
15629		}
15630		if (!isset($style['fgcolor'])) {
15631			$style['fgcolor'] = array(0,0,0); // default black
15632		}
15633		if (!isset($style['bgcolor'])) {
15634			$style['bgcolor'] = false; // default transparent
15635		}
15636		if (!isset($style['border'])) {
15637			$style['border'] = false;
15638		}
15639		// padding
15640		if (!isset($style['padding'])) {
15641			$style['padding'] = 0;
15642		} elseif ($style['padding'] === 'auto') {
15643			$style['padding'] = 4;
15644		}
15645		if (!isset($style['hpadding'])) {
15646			$style['hpadding'] = $style['padding'];
15647		} elseif ($style['hpadding'] === 'auto') {
15648			$style['hpadding'] = 4;
15649		}
15650		if (!isset($style['vpadding'])) {
15651			$style['vpadding'] = $style['padding'];
15652		} elseif ($style['vpadding'] === 'auto') {
15653			$style['vpadding'] = 4;
15654		}
15655		$hpad = (2 * $style['hpadding']);
15656		$vpad = (2 * $style['vpadding']);
15657		// cell (module) dimension
15658		if (!isset($style['module_width'])) {
15659			$style['module_width'] = 1; // width of a single module in points
15660		}
15661		if (!isset($style['module_height'])) {
15662			$style['module_height'] = 1; // height of a single module in points
15663		}
15664		if ($x === '') {
15665			$x = $this->x;
15666		}
15667		if ($y === '') {
15668			$y = $this->y;
15669		}
15670		// check page for no-write regions and adapt page margins if necessary
15671		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15672		// number of barcode columns and rows
15673		$rows = $arrcode['num_rows'];
15674		$cols = $arrcode['num_cols'];
15675		if (($rows <= 0) || ($cols <= 0)){
15676			$this->Error('Error in 2D barcode string');
15677		}
15678		// module width and height
15679		$mw = $style['module_width'];
15680		$mh = $style['module_height'];
15681		if (($mw <= 0) OR ($mh <= 0)) {
15682			$this->Error('Error in 2D barcode string');
15683		}
15684		// get max dimensions
15685		if ($this->rtl) {
15686			$maxw = $x - $this->lMargin;
15687		} else {
15688			$maxw = $this->w - $this->rMargin - $x;
15689		}
15690		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15691		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15692		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15693		if (!$distort) {
15694			if (($maxw * $ratioHW) > $maxh) {
15695				$maxw = $maxh * $ratioWH;
15696			}
15697			if (($maxh * $ratioWH) > $maxw) {
15698				$maxh = $maxw * $ratioHW;
15699			}
15700		}
15701		// set maximum dimensions
15702		if ($w > $maxw) {
15703			$w = $maxw;
15704		}
15705		if ($h > $maxh) {
15706			$h = $maxh;
15707		}
15708		// set dimensions
15709		if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15710			$w = ($cols + $hpad) * ($mw / $this->k);
15711			$h = ($rows + $vpad) * ($mh / $this->k);
15712		} elseif (($w === '') OR ($w <= 0)) {
15713			$w = $h * $ratioWH;
15714		} elseif (($h === '') OR ($h <= 0)) {
15715			$h = $w * $ratioHW;
15716		}
15717		// barcode size (excluding padding)
15718		$bw = ($w * $cols) / ($cols + $hpad);
15719		$bh = ($h * $rows) / ($rows + $vpad);
15720		// dimension of single barcode cell unit
15721		$cw = $bw / $cols;
15722		$ch = $bh / $rows;
15723		if (!$distort) {
15724			if (($cw / $ch) > ($mw / $mh)) {
15725				// correct horizontal distortion
15726				$cw = $ch * $mw / $mh;
15727				$bw = $cw * $cols;
15728				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15729			} else {
15730				// correct vertical distortion
15731				$ch = $cw * $mh / $mw;
15732				$bh = $ch * $rows;
15733				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15734			}
15735		}
15736		// fit the barcode on available space
15737		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15738		// set alignment
15739		$this->img_rb_y = $y + $h;
15740		// set alignment
15741		if ($this->rtl) {
15742			if ($style['position'] == 'L') {
15743				$xpos = $this->lMargin;
15744			} elseif ($style['position'] == 'C') {
15745				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15746			} elseif ($style['position'] == 'R') {
15747				$xpos = $this->w - $this->rMargin - $w;
15748			} else {
15749				$xpos = $x - $w;
15750			}
15751			$this->img_rb_x = $xpos;
15752		} else {
15753			if ($style['position'] == 'L') {
15754				$xpos = $this->lMargin;
15755			} elseif ($style['position'] == 'C') {
15756				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15757			} elseif ($style['position'] == 'R') {
15758				$xpos = $this->w - $this->rMargin - $w;
15759			} else {
15760				$xpos = $x;
15761			}
15762			$this->img_rb_x = $xpos + $w;
15763		}
15764		$xstart = $xpos + ($style['hpadding'] * $cw);
15765		$ystart = $y + ($style['vpadding'] * $ch);
15766		// barcode is always printed in LTR direction
15767		$tempRTL = $this->rtl;
15768		$this->rtl = false;
15769		// print background color
15770		if ($style['bgcolor']) {
15771			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15772		} elseif ($style['border']) {
15773			$this->Rect($xpos, $y, $w, $h, 'D');
15774		}
15775		// set foreground color
15776		$this->SetDrawColorArray($style['fgcolor']);
15777		// print barcode cells
15778		// for each row
15779		for ($r = 0; $r < $rows; ++$r) {
15780			$xr = $xstart;
15781			// for each column
15782			for ($c = 0; $c < $cols; ++$c) {
15783				if ($arrcode['bcode'][$r][$c] == 1) {
15784					// draw a single barcode cell
15785					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15786				}
15787				$xr += $cw;
15788			}
15789			$ystart += $ch;
15790		}
15791		// restore original direction
15792		$this->rtl = $tempRTL;
15793		// restore previous settings
15794		$this->setGraphicVars($gvars);
15795		// set pointer to align the next text/objects
15796		switch($align) {
15797			case 'T':{
15798				$this->y = $y;
15799				$this->x = $this->img_rb_x;
15800				break;
15801			}
15802			case 'M':{
15803				$this->y = $y + round($h/2);
15804				$this->x = $this->img_rb_x;
15805				break;
15806			}
15807			case 'B':{
15808				$this->y = $this->img_rb_y;
15809				$this->x = $this->img_rb_x;
15810				break;
15811			}
15812			case 'N':{
15813				$this->SetY($this->img_rb_y);
15814				break;
15815			}
15816			default:{
15817				break;
15818			}
15819		}
15820		$this->endlinex = $this->img_rb_x;
15821	}
15822
15823	/**
15824	 * Returns an array containing current margins:
15825	 * <ul>
15826			<li>$ret['left'] = left margin</li>
15827			<li>$ret['right'] = right margin</li>
15828			<li>$ret['top'] = top margin</li>
15829			<li>$ret['bottom'] = bottom margin</li>
15830			<li>$ret['header'] = header margin</li>
15831			<li>$ret['footer'] = footer margin</li>
15832			<li>$ret['cell'] = cell padding array</li>
15833			<li>$ret['padding_left'] = cell left padding</li>
15834			<li>$ret['padding_top'] = cell top padding</li>
15835			<li>$ret['padding_right'] = cell right padding</li>
15836			<li>$ret['padding_bottom'] = cell bottom padding</li>
15837	 * </ul>
15838	 * @return array containing all margins measures
15839	 * @public
15840	 * @since 3.2.000 (2008-06-23)
15841	 */
15842	public function getMargins() {
15843		$ret = array(
15844			'left' => $this->lMargin,
15845			'right' => $this->rMargin,
15846			'top' => $this->tMargin,
15847			'bottom' => $this->bMargin,
15848			'header' => $this->header_margin,
15849			'footer' => $this->footer_margin,
15850			'cell' => $this->cell_padding,
15851			'padding_left' => $this->cell_padding['L'],
15852			'padding_top' => $this->cell_padding['T'],
15853			'padding_right' => $this->cell_padding['R'],
15854			'padding_bottom' => $this->cell_padding['B']
15855		);
15856		return $ret;
15857	}
15858
15859	/**
15860	 * Returns an array containing original margins:
15861	 * <ul>
15862			<li>$ret['left'] = left margin</li>
15863			<li>$ret['right'] = right margin</li>
15864	 * </ul>
15865	 * @return array containing all margins measures
15866	 * @public
15867	 * @since 4.0.012 (2008-07-24)
15868	 */
15869	public function getOriginalMargins() {
15870		$ret = array(
15871			'left' => $this->original_lMargin,
15872			'right' => $this->original_rMargin
15873		);
15874		return $ret;
15875	}
15876
15877	/**
15878	 * Returns the current font size.
15879	 * @return float current font size
15880	 * @public
15881	 * @since 3.2.000 (2008-06-23)
15882	 */
15883	public function getFontSize() {
15884		return $this->FontSize;
15885	}
15886
15887	/**
15888	 * Returns the current font size in points unit.
15889	 * @return int current font size in points unit
15890	 * @public
15891	 * @since 3.2.000 (2008-06-23)
15892	 */
15893	public function getFontSizePt() {
15894		return $this->FontSizePt;
15895	}
15896
15897	/**
15898	 * Returns the current font family name.
15899	 * @return string current font family name
15900	 * @public
15901	 * @since 4.3.008 (2008-12-05)
15902	 */
15903	public function getFontFamily() {
15904		return $this->FontFamily;
15905	}
15906
15907	/**
15908	 * Returns the current font style.
15909	 * @return string current font style
15910	 * @public
15911	 * @since 4.3.008 (2008-12-05)
15912	 */
15913	public function getFontStyle() {
15914		return $this->FontStyle;
15915	}
15916
15917	/**
15918	 * Cleanup HTML code (requires HTML Tidy library).
15919	 * @param string $html htmlcode to fix
15920	 * @param string $default_css CSS commands to add
15921	 * @param array $tagvs parameters for setHtmlVSpace method
15922	 * @param array $tidy_options options for tidy_parse_string function
15923	 * @return string XHTML code cleaned up
15924	 * @author Nicola Asuni
15925	 * @public
15926	 * @since 5.9.017 (2010-11-16)
15927	 * @see setHtmlVSpace()
15928	 */
15929	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15930		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15931	}
15932
15933	/**
15934	 * Returns the border width from CSS property
15935	 * @param string $width border width
15936	 * @return int with in user units
15937	 * @protected
15938	 * @since 5.7.000 (2010-08-02)
15939	 */
15940	protected function getCSSBorderWidth($width) {
15941		if ($width == 'thin') {
15942			$width = (2 / $this->k);
15943		} elseif ($width == 'medium') {
15944			$width = (4 / $this->k);
15945		} elseif ($width == 'thick') {
15946			$width = (6 / $this->k);
15947		} else {
15948			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15949		}
15950		return $width;
15951	}
15952
15953	/**
15954	 * Returns the border dash style from CSS property
15955	 * @param string $style border style to convert
15956	 * @return int sash style (return -1 in case of none or hidden border)
15957	 * @protected
15958	 * @since 5.7.000 (2010-08-02)
15959	 */
15960	protected function getCSSBorderDashStyle($style) {
15961		switch (strtolower($style)) {
15962			case 'none':
15963			case 'hidden': {
15964				$dash = -1;
15965				break;
15966			}
15967			case 'dotted': {
15968				$dash = 1;
15969				break;
15970			}
15971			case 'dashed': {
15972				$dash = 3;
15973				break;
15974			}
15975			case 'double':
15976			case 'groove':
15977			case 'ridge':
15978			case 'inset':
15979			case 'outset':
15980			case 'solid':
15981			default: {
15982				$dash = 0;
15983				break;
15984			}
15985		}
15986		return $dash;
15987	}
15988
15989	/**
15990	 * Returns the border style array from CSS border properties
15991	 * @param string $cssborder border properties
15992	 * @return array containing border properties
15993	 * @protected
15994	 * @since 5.7.000 (2010-08-02)
15995	 */
15996	protected function getCSSBorderStyle($cssborder) {
15997		$bprop = preg_split('/[\s]+/', trim($cssborder));
15998		$border = array(); // value to be returned
15999		switch (count($bprop)) {
16000			case 3: {
16001				$width = $bprop[0];
16002				$style = $bprop[1];
16003				$color = $bprop[2];
16004				break;
16005			}
16006			case 2: {
16007				$width = 'medium';
16008				$style = $bprop[0];
16009				$color = $bprop[1];
16010				break;
16011			}
16012			case 1: {
16013				$width = 'medium';
16014				$style = $bprop[0];
16015				$color = 'black';
16016				break;
16017			}
16018			default: {
16019				$width = 'medium';
16020				$style = 'solid';
16021				$color = 'black';
16022				break;
16023			}
16024		}
16025		if ($style == 'none') {
16026			return array();
16027		}
16028		$border['cap'] = 'square';
16029		$border['join'] = 'miter';
16030		$border['dash'] = $this->getCSSBorderDashStyle($style);
16031		if ($border['dash'] < 0) {
16032			return array();
16033		}
16034		$border['width'] = $this->getCSSBorderWidth($width);
16035		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16036		return $border;
16037	}
16038
16039	/**
16040	 * Get the internal Cell padding from CSS attribute.
16041	 * @param string $csspadding padding properties
16042	 * @param float $width width of the containing element
16043	 * @return array of cell paddings
16044	 * @public
16045	 * @since 5.9.000 (2010-10-04)
16046	 */
16047	public function getCSSPadding($csspadding, $width=0) {
16048		$padding = preg_split('/[\s]+/', trim($csspadding));
16049		$cell_padding = array(); // value to be returned
16050		switch (count($padding)) {
16051			case 4: {
16052				$cell_padding['T'] = $padding[0];
16053				$cell_padding['R'] = $padding[1];
16054				$cell_padding['B'] = $padding[2];
16055				$cell_padding['L'] = $padding[3];
16056				break;
16057			}
16058			case 3: {
16059				$cell_padding['T'] = $padding[0];
16060				$cell_padding['R'] = $padding[1];
16061				$cell_padding['B'] = $padding[2];
16062				$cell_padding['L'] = $padding[1];
16063				break;
16064			}
16065			case 2: {
16066				$cell_padding['T'] = $padding[0];
16067				$cell_padding['R'] = $padding[1];
16068				$cell_padding['B'] = $padding[0];
16069				$cell_padding['L'] = $padding[1];
16070				break;
16071			}
16072			case 1: {
16073				$cell_padding['T'] = $padding[0];
16074				$cell_padding['R'] = $padding[0];
16075				$cell_padding['B'] = $padding[0];
16076				$cell_padding['L'] = $padding[0];
16077				break;
16078			}
16079			default: {
16080				return $this->cell_padding;
16081			}
16082		}
16083		if ($width == 0) {
16084			$width = $this->w - $this->lMargin - $this->rMargin;
16085		}
16086		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16087		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16088		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16089		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16090		return $cell_padding;
16091	}
16092
16093	/**
16094	 * Get the internal Cell margin from CSS attribute.
16095	 * @param string $cssmargin margin properties
16096	 * @param float $width width of the containing element
16097	 * @return array of cell margins
16098	 * @public
16099	 * @since 5.9.000 (2010-10-04)
16100	 */
16101	public function getCSSMargin($cssmargin, $width=0) {
16102		$margin = preg_split('/[\s]+/', trim($cssmargin));
16103		$cell_margin = array(); // value to be returned
16104		switch (count($margin)) {
16105			case 4: {
16106				$cell_margin['T'] = $margin[0];
16107				$cell_margin['R'] = $margin[1];
16108				$cell_margin['B'] = $margin[2];
16109				$cell_margin['L'] = $margin[3];
16110				break;
16111			}
16112			case 3: {
16113				$cell_margin['T'] = $margin[0];
16114				$cell_margin['R'] = $margin[1];
16115				$cell_margin['B'] = $margin[2];
16116				$cell_margin['L'] = $margin[1];
16117				break;
16118			}
16119			case 2: {
16120				$cell_margin['T'] = $margin[0];
16121				$cell_margin['R'] = $margin[1];
16122				$cell_margin['B'] = $margin[0];
16123				$cell_margin['L'] = $margin[1];
16124				break;
16125			}
16126			case 1: {
16127				$cell_margin['T'] = $margin[0];
16128				$cell_margin['R'] = $margin[0];
16129				$cell_margin['B'] = $margin[0];
16130				$cell_margin['L'] = $margin[0];
16131				break;
16132			}
16133			default: {
16134				return $this->cell_margin;
16135			}
16136		}
16137		if ($width == 0) {
16138			$width = $this->w - $this->lMargin - $this->rMargin;
16139		}
16140		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16141		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16142		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16143		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16144		return $cell_margin;
16145	}
16146
16147	/**
16148	 * Get the border-spacing from CSS attribute.
16149	 * @param string $cssbspace border-spacing CSS properties
16150	 * @param float $width width of the containing element
16151	 * @return array of border spacings
16152	 * @public
16153	 * @since 5.9.010 (2010-10-27)
16154	 */
16155	public function getCSSBorderMargin($cssbspace, $width=0) {
16156		$space = preg_split('/[\s]+/', trim($cssbspace));
16157		$border_spacing = array(); // value to be returned
16158		switch (count($space)) {
16159			case 2: {
16160				$border_spacing['H'] = $space[0];
16161				$border_spacing['V'] = $space[1];
16162				break;
16163			}
16164			case 1: {
16165				$border_spacing['H'] = $space[0];
16166				$border_spacing['V'] = $space[0];
16167				break;
16168			}
16169			default: {
16170				return array('H' => 0, 'V' => 0);
16171			}
16172		}
16173		if ($width == 0) {
16174			$width = $this->w - $this->lMargin - $this->rMargin;
16175		}
16176		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16177		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16178		return $border_spacing;
16179	}
16180
16181	/**
16182	 * Returns the letter-spacing value from CSS value
16183	 * @param string $spacing letter-spacing value
16184	 * @param float $parent font spacing (tracking) value of the parent element
16185	 * @return float quantity to increases or decreases the space between characters in a text.
16186	 * @protected
16187	 * @since 5.9.000 (2010-10-02)
16188	 */
16189	protected function getCSSFontSpacing($spacing, $parent=0) {
16190		$val = 0; // value to be returned
16191		$spacing = trim($spacing);
16192		switch ($spacing) {
16193			case 'normal': {
16194				$val = 0;
16195				break;
16196			}
16197			case 'inherit': {
16198				if ($parent == 'normal') {
16199					$val = 0;
16200				} else {
16201					$val = $parent;
16202				}
16203				break;
16204			}
16205			default: {
16206				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16207			}
16208		}
16209		return $val;
16210	}
16211
16212	/**
16213	 * Returns the percentage of font stretching from CSS value
16214	 * @param string $stretch stretch mode
16215	 * @param float $parent stretch value of the parent element
16216	 * @return float font stretching percentage
16217	 * @protected
16218	 * @since 5.9.000 (2010-10-02)
16219	 */
16220	protected function getCSSFontStretching($stretch, $parent=100) {
16221		$val = 100; // value to be returned
16222		$stretch = trim($stretch);
16223		switch ($stretch) {
16224			case 'ultra-condensed': {
16225				$val = 40;
16226				break;
16227			}
16228			case 'extra-condensed': {
16229				$val = 55;
16230				break;
16231			}
16232			case 'condensed': {
16233				$val = 70;
16234				break;
16235			}
16236			case 'semi-condensed': {
16237				$val = 85;
16238				break;
16239			}
16240			case 'normal': {
16241				$val = 100;
16242				break;
16243			}
16244			case 'semi-expanded': {
16245				$val = 115;
16246				break;
16247			}
16248			case 'expanded': {
16249				$val = 130;
16250				break;
16251			}
16252			case 'extra-expanded': {
16253				$val = 145;
16254				break;
16255			}
16256			case 'ultra-expanded': {
16257				$val = 160;
16258				break;
16259			}
16260			case 'wider': {
16261				$val = ($parent + 10);
16262				break;
16263			}
16264			case 'narrower': {
16265				$val = ($parent - 10);
16266				break;
16267			}
16268			case 'inherit': {
16269				if ($parent == 'normal') {
16270					$val = 100;
16271				} else {
16272					$val = $parent;
16273				}
16274				break;
16275			}
16276			default: {
16277				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16278			}
16279		}
16280		return $val;
16281	}
16282
16283	/**
16284	 * Convert HTML string containing font size value to points
16285	 * @param string $val String containing font size value and unit.
16286	 * @param float $refsize Reference font size in points.
16287	 * @param float $parent_size Parent font size in points.
16288	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16289	 * @return float value in points
16290	 * @public
16291	 */
16292	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16293		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16294		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16295		switch ($val) {
16296			case 'xx-small': {
16297				$size = ($refsize - 4);
16298				break;
16299			}
16300			case 'x-small': {
16301				$size = ($refsize - 3);
16302				break;
16303			}
16304			case 'small': {
16305				$size = ($refsize - 2);
16306				break;
16307			}
16308			case 'medium': {
16309				$size = $refsize;
16310				break;
16311			}
16312			case 'large': {
16313				$size = ($refsize + 2);
16314				break;
16315			}
16316			case 'x-large': {
16317				$size = ($refsize + 4);
16318				break;
16319			}
16320			case 'xx-large': {
16321				$size = ($refsize + 6);
16322				break;
16323			}
16324			case 'smaller': {
16325				$size = ($parent_size - 3);
16326				break;
16327			}
16328			case 'larger': {
16329				$size = ($parent_size + 3);
16330				break;
16331			}
16332			default: {
16333				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16334			}
16335		}
16336		return $size;
16337	}
16338
16339	/**
16340	 * Returns the HTML DOM array.
16341	 * @param string $html html code
16342	 * @return array
16343	 * @protected
16344	 * @since 3.2.000 (2008-06-20)
16345	 */
16346	protected function getHtmlDomArray($html) {
16347		// array of CSS styles ( selector => properties).
16348		$css = array();
16349		// get CSS array defined at previous call
16350		$matches = array();
16351		if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16352			if (isset($matches[1][0])) {
16353				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16354			}
16355			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16356		}
16357		// extract external CSS files
16358		$matches = array();
16359		if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16360			foreach ($matches[1] as $key => $link) {
16361				$type = array();
16362				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16363					$type = array();
16364					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16365					// get 'all' and 'print' media, other media types are discarded
16366					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16367					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16368						$type = array();
16369						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16370							// read CSS data file
16371                            $cssdata = $this->getCachedFileContents(trim($type[1]));
16372							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16373								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16374							}
16375						}
16376					}
16377				}
16378			}
16379		}
16380		// extract style tags
16381		$matches = array();
16382		if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16383			foreach ($matches[1] as $key => $media) {
16384				$type = array();
16385				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16386				// get 'all' and 'print' media, other media types are discarded
16387				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16388				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16389					$cssdata = $matches[2][$key];
16390					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16391				}
16392			}
16393		}
16394		// create a special tag to contain the CSS array (used for table content)
16395		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16396		// remove head and style blocks
16397		$html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16398		$html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16399		// define block tags
16400		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16401		// define self-closing tags
16402		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16403		// remove all unsupported tags (the line below lists all supported tags)
16404		$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>');
16405		//replace some blank characters
16406		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16407		$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);
16408		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16409		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16410		$html = strtr($html, $repTable);
16411		$offset = 0;
16412		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16413			$html_a = substr($html, 0, $offset);
16414			$html_b = substr($html, $offset, ($pos - $offset + 6));
16415			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16416				// preserve newlines on <pre> tag
16417				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16418			}
16419			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16420				// preserve spaces on <pre> tag
16421				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16422			}
16423			$html = $html_a.$html_b.substr($html, $pos + 6);
16424			$offset = strlen($html_a.$html_b);
16425		}
16426		$offset = 0;
16427		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16428			$html_a = substr($html, 0, $offset);
16429			$html_b = substr($html, $offset, ($pos - $offset + 11));
16430			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16431				// preserve newlines on <textarea> tag
16432				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16433				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16434			}
16435			$html = $html_a.$html_b.substr($html, $pos + 11);
16436			$offset = strlen($html_a.$html_b);
16437		}
16438		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16439		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16440		$offset = 0;
16441		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16442			$html_a = substr($html, 0, $offset);
16443			$html_b = substr($html, $offset, ($pos - $offset + 9));
16444			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16445				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16446				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16447			}
16448			$html = $html_a.$html_b.substr($html, $pos + 9);
16449			$offset = strlen($html_a.$html_b);
16450		}
16451		if (preg_match("'</select'si", $html)) {
16452			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16453			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16454		}
16455		$html = str_replace("\n", ' ', $html);
16456		// restore textarea newlines
16457		$html = str_replace('<TBR>', "\n", $html);
16458		// remove extra spaces from code
16459		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16460		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16461		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16462		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16463		$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);
16464		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16465		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16466		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16467		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16468		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16469		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16470		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16471		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16472		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16473		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16474		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16475		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16476		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16477		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16478		// trim string
16479		$html = $this->stringTrim($html);
16480		// fix br tag after li
16481		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16482		// fix first image tag alignment
16483		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16484		// pattern for generic tag
16485		$tagpattern = '/(<[^>]+>)/';
16486		// explodes the string
16487		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16488		// count elements
16489		$maxel = count($a);
16490		$elkey = 0;
16491		$key = 0;
16492		// create an array of elements
16493		$dom = array();
16494		$dom[$key] = array();
16495		// set inheritable properties fot the first void element
16496		// 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
16497		$dom[$key]['tag'] = false;
16498		$dom[$key]['block'] = false;
16499		$dom[$key]['value'] = '';
16500		$dom[$key]['parent'] = 0;
16501		$dom[$key]['hide'] = false;
16502		$dom[$key]['fontname'] = $this->FontFamily;
16503		$dom[$key]['fontstyle'] = $this->FontStyle;
16504		$dom[$key]['fontsize'] = $this->FontSizePt;
16505		$dom[$key]['font-stretch'] = $this->font_stretching;
16506		$dom[$key]['letter-spacing'] = $this->font_spacing;
16507		$dom[$key]['stroke'] = $this->textstrokewidth;
16508		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16509		$dom[$key]['clip'] = ($this->textrendermode > 3);
16510		$dom[$key]['line-height'] = $this->cell_height_ratio;
16511		$dom[$key]['bgcolor'] = false;
16512		$dom[$key]['fgcolor'] = $this->fgcolor; // color
16513		$dom[$key]['strokecolor'] = $this->strokecolor;
16514		$dom[$key]['align'] = '';
16515		$dom[$key]['listtype'] = '';
16516		$dom[$key]['text-indent'] = 0;
16517		$dom[$key]['text-transform'] = '';
16518		$dom[$key]['border'] = array();
16519		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16520		$thead = false; // true when we are inside the THEAD tag
16521		++$key;
16522		$level = array();
16523		array_push($level, 0); // root
16524		while ($elkey < $maxel) {
16525			$dom[$key] = array();
16526			$element = $a[$elkey];
16527			$dom[$key]['elkey'] = $elkey;
16528			if (preg_match($tagpattern, $element)) {
16529				// html tag
16530				$element = substr($element, 1, -1);
16531				// get tag name
16532				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16533				$tagname = strtolower($tag[1]);
16534				// check if we are inside a table header
16535				if ($tagname == 'thead') {
16536					if ($element[0] == '/') {
16537						$thead = false;
16538					} else {
16539						$thead = true;
16540					}
16541					++$elkey;
16542					continue;
16543				}
16544				$dom[$key]['tag'] = true;
16545				$dom[$key]['value'] = $tagname;
16546				if (in_array($dom[$key]['value'], $blocktags)) {
16547					$dom[$key]['block'] = true;
16548				} else {
16549					$dom[$key]['block'] = false;
16550				}
16551				if ($element[0] == '/') {
16552					// *** closing html tag
16553					$dom[$key]['opening'] = false;
16554					$dom[$key]['parent'] = end($level);
16555					array_pop($level);
16556					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16557					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16558					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16559					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16560					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16561					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16562					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16563					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16564					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16565					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16566					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16567					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16568					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16569					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16570					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16571					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16572					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16573						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16574					}
16575					// set the number of columns in table tag
16576					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16577						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16578					}
16579					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16580						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16581						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16582							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16583						}
16584						$key = $i;
16585						// mark nested tables
16586						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16587						// remove thead sections from nested tables
16588						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16589						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16590					}
16591					// store header rows on a new table
16592					if (
16593						($dom[$key]['value'] === 'tr')
16594						&& !empty($dom[($dom[$key]['parent'])]['thead'])
16595						&& ($dom[($dom[$key]['parent'])]['thead'] === true)
16596					) {
16597						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16598							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16599						}
16600						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16601							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16602						}
16603						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16604							$dom[($dom[$key]['parent'])]['attribute'] = array();
16605						}
16606						// header elements must be always contained in a single page
16607						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16608					}
16609					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16610						// remove the nobr attributes from the table header
16611						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16612						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16613					}
16614				} else {
16615					// *** opening or self-closing html tag
16616					$dom[$key]['opening'] = true;
16617					$dom[$key]['parent'] = end($level);
16618					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16619						// self-closing tag
16620						$dom[$key]['self'] = true;
16621					} else {
16622						// opening tag
16623						array_push($level, $key);
16624						$dom[$key]['self'] = false;
16625					}
16626					// copy some values from parent
16627					$parentkey = 0;
16628					if ($key > 0) {
16629						$parentkey = $dom[$key]['parent'];
16630						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16631						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16632						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16633						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16634						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16635						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16636						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16637						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16638						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16639						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16640						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16641						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16642						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16643						$dom[$key]['align'] = $dom[$parentkey]['align'];
16644						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16645						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16646						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16647						$dom[$key]['border'] = array();
16648						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16649					}
16650					// get attributes
16651					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16652					$dom[$key]['attribute'] = array(); // reset attribute array
16653                    foreach($attr_array[1] as $id => $name) {
16654                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16655                    }
16656					if (!empty($css)) {
16657						// merge CSS style to current style
16658						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16659						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16660					}
16661					// split style attributes
16662					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16663						// get style attributes
16664						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16665						$dom[$key]['style'] = array(); // reset style attribute array
16666                        foreach($style_array[1] as $id => $name) {
16667                            // in case of duplicate attribute the last replace the previous
16668                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16669                        }
16670						// --- get some style attributes ---
16671						// text direction
16672						if (isset($dom[$key]['style']['direction'])) {
16673							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16674						}
16675						// display
16676						if (isset($dom[$key]['style']['display'])) {
16677							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16678						}
16679						// font family
16680						if (isset($dom[$key]['style']['font-family'])) {
16681							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16682						}
16683						// list-style-type
16684						if (isset($dom[$key]['style']['list-style-type'])) {
16685							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16686							if ($dom[$key]['listtype'] == 'inherit') {
16687								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16688							}
16689						}
16690						// text-indent
16691						if (isset($dom[$key]['style']['text-indent'])) {
16692							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16693							if ($dom[$key]['text-indent'] == 'inherit') {
16694								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16695							}
16696						}
16697						// text-transform
16698						if (isset($dom[$key]['style']['text-transform'])) {
16699							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16700						}
16701						// font size
16702						if (isset($dom[$key]['style']['font-size'])) {
16703							$fsize = trim($dom[$key]['style']['font-size']);
16704							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16705						}
16706						// font-stretch
16707						if (isset($dom[$key]['style']['font-stretch'])) {
16708							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16709						}
16710						// letter-spacing
16711						if (isset($dom[$key]['style']['letter-spacing'])) {
16712							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16713						}
16714						// line-height (internally is the cell height ratio)
16715						if (isset($dom[$key]['style']['line-height'])) {
16716							$lineheight = trim($dom[$key]['style']['line-height']);
16717							switch ($lineheight) {
16718								// A normal line height. This is default
16719								case 'normal': {
16720									$dom[$key]['line-height'] = $dom[0]['line-height'];
16721									break;
16722								}
16723								case 'inherit': {
16724									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16725								}
16726								default: {
16727									if (is_numeric($lineheight)) {
16728										// convert to percentage of font height
16729										$lineheight = ($lineheight * 100).'%';
16730									}
16731									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16732									if (substr($lineheight, -1) !== '%') {
16733										if ($dom[$key]['fontsize'] <= 0) {
16734											$dom[$key]['line-height'] = 1;
16735										} else {
16736											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16737										}
16738									}
16739								}
16740							}
16741						}
16742						// font style
16743						if (isset($dom[$key]['style']['font-weight'])) {
16744							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16745								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16746									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16747								}
16748							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16749								$dom[$key]['fontstyle'] .= 'B';
16750							}
16751						}
16752						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16753							$dom[$key]['fontstyle'] .= 'I';
16754						}
16755						// font color
16756						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16757							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16758						} elseif ($dom[$key]['value'] == 'a') {
16759							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16760						}
16761						// background color
16762						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16763							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16764						}
16765						// text-decoration
16766						if (isset($dom[$key]['style']['text-decoration'])) {
16767							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16768							foreach ($decors as $dec) {
16769								$dec = trim($dec);
16770								if (!TCPDF_STATIC::empty_string($dec)) {
16771									if ($dec[0] == 'u') {
16772										// underline
16773										$dom[$key]['fontstyle'] .= 'U';
16774									} elseif ($dec[0] == 'l') {
16775										// line-through
16776										$dom[$key]['fontstyle'] .= 'D';
16777									} elseif ($dec[0] == 'o') {
16778										// overline
16779										$dom[$key]['fontstyle'] .= 'O';
16780									}
16781								}
16782							}
16783						} elseif ($dom[$key]['value'] == 'a') {
16784							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16785						}
16786						// check for width attribute
16787						if (isset($dom[$key]['style']['width'])) {
16788							$dom[$key]['width'] = $dom[$key]['style']['width'];
16789						}
16790						// check for height attribute
16791						if (isset($dom[$key]['style']['height'])) {
16792							$dom[$key]['height'] = $dom[$key]['style']['height'];
16793						}
16794						// check for text alignment
16795						if (isset($dom[$key]['style']['text-align'])) {
16796							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16797						}
16798						// check for CSS border properties
16799						if (isset($dom[$key]['style']['border'])) {
16800							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16801							if (!empty($borderstyle)) {
16802								$dom[$key]['border']['LTRB'] = $borderstyle;
16803							}
16804						}
16805						if (isset($dom[$key]['style']['border-color'])) {
16806							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16807							if (isset($brd_colors[3])) {
16808								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16809							}
16810							if (isset($brd_colors[1])) {
16811								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16812							}
16813							if (isset($brd_colors[0])) {
16814								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16815							}
16816							if (isset($brd_colors[2])) {
16817								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16818							}
16819						}
16820						if (isset($dom[$key]['style']['border-width'])) {
16821							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16822							if (isset($brd_widths[3])) {
16823								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16824							}
16825							if (isset($brd_widths[1])) {
16826								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16827							}
16828							if (isset($brd_widths[0])) {
16829								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16830							}
16831							if (isset($brd_widths[2])) {
16832								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16833							}
16834						}
16835						if (isset($dom[$key]['style']['border-style'])) {
16836							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16837							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16838								$dom[$key]['border']['L']['cap'] = 'square';
16839								$dom[$key]['border']['L']['join'] = 'miter';
16840								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16841								if ($dom[$key]['border']['L']['dash'] < 0) {
16842									$dom[$key]['border']['L'] = array();
16843								}
16844							}
16845							if (isset($brd_styles[1])) {
16846								$dom[$key]['border']['R']['cap'] = 'square';
16847								$dom[$key]['border']['R']['join'] = 'miter';
16848								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16849								if ($dom[$key]['border']['R']['dash'] < 0) {
16850									$dom[$key]['border']['R'] = array();
16851								}
16852							}
16853							if (isset($brd_styles[0])) {
16854								$dom[$key]['border']['T']['cap'] = 'square';
16855								$dom[$key]['border']['T']['join'] = 'miter';
16856								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16857								if ($dom[$key]['border']['T']['dash'] < 0) {
16858									$dom[$key]['border']['T'] = array();
16859								}
16860							}
16861							if (isset($brd_styles[2])) {
16862								$dom[$key]['border']['B']['cap'] = 'square';
16863								$dom[$key]['border']['B']['join'] = 'miter';
16864								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16865								if ($dom[$key]['border']['B']['dash'] < 0) {
16866									$dom[$key]['border']['B'] = array();
16867								}
16868							}
16869						}
16870						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16871						foreach ($cellside as $bsk => $bsv) {
16872							if (isset($dom[$key]['style']['border-'.$bsv])) {
16873								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16874								if (!empty($borderstyle)) {
16875									$dom[$key]['border'][$bsk] = $borderstyle;
16876								}
16877							}
16878							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16879								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16880							}
16881							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16882								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16883							}
16884							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16885								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16886								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16887									$dom[$key]['border'][$bsk] = array();
16888								}
16889							}
16890						}
16891						// check for CSS padding properties
16892						if (isset($dom[$key]['style']['padding'])) {
16893							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16894						} else {
16895							$dom[$key]['padding'] = $this->cell_padding;
16896						}
16897						foreach ($cellside as $psk => $psv) {
16898							if (isset($dom[$key]['style']['padding-'.$psv])) {
16899								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16900							}
16901						}
16902						// check for CSS margin properties
16903						if (isset($dom[$key]['style']['margin'])) {
16904							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16905						} else {
16906							$dom[$key]['margin'] = $this->cell_margin;
16907						}
16908						foreach ($cellside as $psk => $psv) {
16909							if (isset($dom[$key]['style']['margin-'.$psv])) {
16910								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16911							}
16912						}
16913						// check for CSS border-spacing properties
16914						if (isset($dom[$key]['style']['border-spacing'])) {
16915							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16916						}
16917						// page-break-inside
16918						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16919							$dom[$key]['attribute']['nobr'] = 'true';
16920						}
16921						// page-break-before
16922						if (isset($dom[$key]['style']['page-break-before'])) {
16923							if ($dom[$key]['style']['page-break-before'] == 'always') {
16924								$dom[$key]['attribute']['pagebreak'] = 'true';
16925							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16926								$dom[$key]['attribute']['pagebreak'] = 'left';
16927							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16928								$dom[$key]['attribute']['pagebreak'] = 'right';
16929							}
16930						}
16931						// page-break-after
16932						if (isset($dom[$key]['style']['page-break-after'])) {
16933							if ($dom[$key]['style']['page-break-after'] == 'always') {
16934								$dom[$key]['attribute']['pagebreakafter'] = 'true';
16935							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16936								$dom[$key]['attribute']['pagebreakafter'] = 'left';
16937							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16938								$dom[$key]['attribute']['pagebreakafter'] = 'right';
16939							}
16940						}
16941					}
16942					if (isset($dom[$key]['attribute']['display'])) {
16943						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16944					}
16945					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16946						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16947						if (!empty($borderstyle)) {
16948							$dom[$key]['border']['LTRB'] = $borderstyle;
16949						}
16950					}
16951					// check for font tag
16952					if ($dom[$key]['value'] == 'font') {
16953						// font family
16954						if (isset($dom[$key]['attribute']['face'])) {
16955							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16956						}
16957						// font size
16958						if (isset($dom[$key]['attribute']['size'])) {
16959							if ($key > 0) {
16960								if ($dom[$key]['attribute']['size'][0] == '+') {
16961									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16962								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
16963									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16964								} else {
16965									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16966								}
16967							} else {
16968								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16969							}
16970						}
16971					}
16972					// force natural alignment for lists
16973					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16974						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16975						if ($this->rtl) {
16976							$dom[$key]['align'] = 'R';
16977						} else {
16978							$dom[$key]['align'] = 'L';
16979						}
16980					}
16981					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16982						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16983							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16984						}
16985					}
16986					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16987						$dom[$key]['fontstyle'] .= 'B';
16988					}
16989					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16990						$dom[$key]['fontstyle'] .= 'I';
16991					}
16992					if ($dom[$key]['value'] == 'u') {
16993						$dom[$key]['fontstyle'] .= 'U';
16994					}
16995					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16996						$dom[$key]['fontstyle'] .= 'D';
16997					}
16998					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16999						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17000					}
17001					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17002						$dom[$key]['fontname'] = $this->default_monospaced_font;
17003					}
17004					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)) {
17005						// headings h1, h2, h3, h4, h5, h6
17006						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17007							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17008							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17009						}
17010						if (!isset($dom[$key]['style']['font-weight'])) {
17011							$dom[$key]['fontstyle'] .= 'B';
17012						}
17013					}
17014					if (($dom[$key]['value'] == 'table')) {
17015						$dom[$key]['rows'] = 0; // number of rows
17016						$dom[$key]['trids'] = array(); // IDs of TR elements
17017						$dom[$key]['thead'] = ''; // table header rows
17018					}
17019					if (($dom[$key]['value'] == 'tr')) {
17020						$dom[$key]['cols'] = 0;
17021						if ($thead) {
17022							$dom[$key]['thead'] = true;
17023							// rows on thead block are printed as a separate table
17024						} else {
17025							$dom[$key]['thead'] = false;
17026							$parent = $dom[$key]['parent'];
17027
17028							if (!isset($dom[$parent]['rows'])) {
17029								$dom[$parent]['rows'] = 0;
17030							}
17031							// store the number of rows on table element
17032							++$dom[$parent]['rows'];
17033
17034							if (!isset($dom[$parent]['trids'])) {
17035								$dom[$parent]['trids'] = array();
17036							}
17037
17038							// store the TR elements IDs on table element
17039							array_push($dom[$parent]['trids'], $key);
17040						}
17041					}
17042					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17043						if (isset($dom[$key]['attribute']['colspan'])) {
17044							$colspan = intval($dom[$key]['attribute']['colspan']);
17045						} else {
17046							$colspan = 1;
17047						}
17048						$dom[$key]['attribute']['colspan'] = $colspan;
17049						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
17050					}
17051					// text direction
17052					if (isset($dom[$key]['attribute']['dir'])) {
17053						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17054					}
17055					// set foreground color attribute
17056					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17057						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17058					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17059						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17060					}
17061					// set background color attribute
17062					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17063						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17064					}
17065					// set stroke color attribute
17066					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17067						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17068					}
17069					// check for width attribute
17070					if (isset($dom[$key]['attribute']['width'])) {
17071						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
17072					}
17073					// check for height attribute
17074					if (isset($dom[$key]['attribute']['height'])) {
17075						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
17076					}
17077					// check for text alignment
17078					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17079						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17080					}
17081					// check for text rendering mode (the following attributes do not exist in HTML)
17082					if (isset($dom[$key]['attribute']['stroke'])) {
17083						// font stroke width
17084						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17085					}
17086					if (isset($dom[$key]['attribute']['fill'])) {
17087						// font fill
17088						if ($dom[$key]['attribute']['fill'] == 'true') {
17089							$dom[$key]['fill'] = true;
17090						} else {
17091							$dom[$key]['fill'] = false;
17092						}
17093					}
17094					if (isset($dom[$key]['attribute']['clip'])) {
17095						// clipping mode
17096						if ($dom[$key]['attribute']['clip'] == 'true') {
17097							$dom[$key]['clip'] = true;
17098						} else {
17099							$dom[$key]['clip'] = false;
17100						}
17101					}
17102				} // end opening tag
17103			} else {
17104				// text
17105				$dom[$key]['tag'] = false;
17106				$dom[$key]['block'] = false;
17107				$dom[$key]['parent'] = end($level);
17108				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17109				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17110					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17111					if (function_exists('mb_convert_case')) {
17112						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17113						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17114							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17115						}
17116					} elseif (!$this->isunicode) {
17117						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17118							case 'capitalize': {
17119								$element = ucwords(strtolower($element));
17120								break;
17121							}
17122							case 'uppercase': {
17123								$element = strtoupper($element);
17124								break;
17125							}
17126							case 'lowercase': {
17127								$element = strtolower($element);
17128								break;
17129							}
17130						}
17131					}
17132				}
17133				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17134			}
17135			++$elkey;
17136			++$key;
17137		}
17138		return $dom;
17139	}
17140
17141	/**
17142	 * Returns the string used to find spaces
17143	 * @return string
17144	 * @protected
17145	 * @author Nicola Asuni
17146	 * @since 4.8.024 (2010-01-15)
17147	 */
17148	protected function getSpaceString() {
17149		$spacestr = chr(32);
17150		if ($this->isUnicodeFont()) {
17151			$spacestr = chr(0).chr(32);
17152		}
17153		return $spacestr;
17154	}
17155
17156	/**
17157	 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17158	 * @param string $data serialized data
17159	 * @return string
17160	 * @public static
17161	 */
17162	protected function getHashForTCPDFtagParams($data) {
17163		return md5(strlen($data).$this->file_id.$data);
17164	}
17165
17166	/**
17167	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17168	 * @param array $data parameters array
17169	 * @return string containing serialized data
17170	 * @public static
17171	 */
17172	public function serializeTCPDFtagParameters($data) {
17173		$encoded = urlencode(json_encode($data));
17174		return $this->getHashForTCPDFtagParams($encoded).$encoded;
17175	}
17176
17177	/**
17178	 * Unserialize parameters to be used with TCPDF tag in HTML code.
17179	 * @param string $data serialized data
17180	 * @return array containing unserialized data
17181	 * @protected static
17182	 */
17183	protected function unserializeTCPDFtagParameters($data) {
17184		$hash = substr($data, 0, 32);
17185		$encoded = substr($data, 32);
17186		if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17187			$this->Error('Invalid parameters');
17188		}
17189		return json_decode(urldecode($encoded), true);
17190	}
17191
17192	/**
17193	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17194	 * 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 />
17195	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17196	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17197	 * 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
17198	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17199	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17200	 * @param float $h Cell minimum height. The cell extends automatically if needed.
17201	 * @param float $x upper-left corner X coordinate
17202	 * @param float $y upper-left corner Y coordinate
17203	 * @param string $html html text to print. Default value: empty string.
17204	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17205	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
17206Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17207	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17208	 * @param boolean $reseth if true reset the last cell height (default true).
17209	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17210	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17211	 * @see Multicell(), writeHTML()
17212	 * @public
17213	 */
17214	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17215		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17216	}
17217
17218	/**
17219	 * Allows to preserve some HTML formatting (limited support).<br />
17220	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17221	 * 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
17222	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17223	 * @param string $html text to display
17224	 * @param boolean $ln if true add a new line after text (default = true)
17225	 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17226	 * @param boolean $reseth if true reset the last cell height (default false).
17227	 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17228	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17229	 * @public
17230	 */
17231	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17232		$gvars = $this->getGraphicVars();
17233		// store current values
17234		$prev_cell_margin = $this->cell_margin;
17235		$prev_cell_padding = $this->cell_padding;
17236		$prevPage = $this->page;
17237		$prevlMargin = $this->lMargin;
17238		$prevrMargin = $this->rMargin;
17239		$curfontname = $this->FontFamily;
17240		$curfontstyle = $this->FontStyle;
17241		$curfontsize = $this->FontSizePt;
17242		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17243		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17244		$curfontstretcing = $this->font_stretching;
17245		$curfonttracking = $this->font_spacing;
17246		$this->newline = true;
17247		$newline = true;
17248		$startlinepage = $this->page;
17249		$minstartliney = $this->y;
17250		$maxbottomliney = 0;
17251		$startlinex = $this->x;
17252		$startliney = $this->y;
17253		$yshift = 0;
17254		$loop = 0;
17255		$curpos = 0;
17256		$this_method_vars = array();
17257		$undo = false;
17258		$fontaligned = false;
17259		$reverse_dir = false; // true when the text direction is reversed
17260		$this->premode = false;
17261		if ($this->inxobj) {
17262			// we are inside an XObject template
17263			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17264		} elseif (isset($this->PageAnnots[$this->page])) {
17265			$pask = count($this->PageAnnots[$this->page]);
17266		} else {
17267			$pask = 0;
17268		}
17269		if ($this->inxobj) {
17270			// we are inside an XObject template
17271			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17272		} elseif (!$this->InFooter) {
17273			if (isset($this->footerlen[$this->page])) {
17274				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17275			} else {
17276				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17277			}
17278			$startlinepos = $this->footerpos[$this->page];
17279		} else {
17280			// we are inside the footer
17281			$startlinepos = $this->pagelen[$this->page];
17282		}
17283		$lalign = $align;
17284		$plalign = $align;
17285		if ($this->rtl) {
17286			$w = $this->x - $this->lMargin;
17287		} else {
17288			$w = $this->w - $this->rMargin - $this->x;
17289		}
17290		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17291		if ($cell) {
17292			if ($this->rtl) {
17293				$this->x -= $this->cell_padding['R'];
17294				$this->lMargin += $this->cell_padding['L'];
17295			} else {
17296				$this->x += $this->cell_padding['L'];
17297				$this->rMargin += $this->cell_padding['R'];
17298			}
17299		}
17300		if ($this->customlistindent >= 0) {
17301			$this->listindent = $this->customlistindent;
17302		} else {
17303			$this->listindent = $this->GetStringWidth('000000');
17304		}
17305		$this->listindentlevel = 0;
17306		// save previous states
17307		$prev_cell_height_ratio = $this->cell_height_ratio;
17308		$prev_listnum = $this->listnum;
17309		$prev_listordered = $this->listordered;
17310		$prev_listcount = $this->listcount;
17311		$prev_lispacer = $this->lispacer;
17312		$this->listnum = 0;
17313		$this->listordered = array();
17314		$this->listcount = array();
17315		$this->lispacer = '';
17316		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17317			// reset row height
17318			$this->resetLastH();
17319		}
17320		$dom = $this->getHtmlDomArray($html);
17321		$maxel = count($dom);
17322		$key = 0;
17323		while ($key < $maxel) {
17324			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17325				// store the node key
17326				$hidden_node_key = $key;
17327				if ($dom[$key]['self']) {
17328					// skip just this self-closing tag
17329					++$key;
17330				} else {
17331					// skip this and all children tags
17332					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17333						// skip hidden objects
17334						++$key;
17335					}
17336					++$key;
17337				}
17338			}
17339			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17340				// check for pagebreak
17341				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17342					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17343					$this->checkPageBreak($this->PageBreakTrigger + 1);
17344					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17345				}
17346				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17347					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17348					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17349					$this->checkPageBreak($this->PageBreakTrigger + 1);
17350					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17351				}
17352			}
17353			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17354				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17355					$dom[$key]['attribute']['nobr'] = false;
17356				} else {
17357					// store current object
17358					$this->startTransaction();
17359					// save this method vars
17360					$this_method_vars['html'] = $html;
17361					$this_method_vars['ln'] = $ln;
17362					$this_method_vars['fill'] = $fill;
17363					$this_method_vars['reseth'] = $reseth;
17364					$this_method_vars['cell'] = $cell;
17365					$this_method_vars['align'] = $align;
17366					$this_method_vars['gvars'] = $gvars;
17367					$this_method_vars['prevPage'] = $prevPage;
17368					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17369					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17370					$this_method_vars['prevlMargin'] = $prevlMargin;
17371					$this_method_vars['prevrMargin'] = $prevrMargin;
17372					$this_method_vars['curfontname'] = $curfontname;
17373					$this_method_vars['curfontstyle'] = $curfontstyle;
17374					$this_method_vars['curfontsize'] = $curfontsize;
17375					$this_method_vars['curfontascent'] = $curfontascent;
17376					$this_method_vars['curfontdescent'] = $curfontdescent;
17377					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17378					$this_method_vars['curfonttracking'] = $curfonttracking;
17379					$this_method_vars['minstartliney'] = $minstartliney;
17380					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17381					$this_method_vars['yshift'] = $yshift;
17382					$this_method_vars['startlinepage'] = $startlinepage;
17383					$this_method_vars['startlinepos'] = $startlinepos;
17384					$this_method_vars['startlinex'] = $startlinex;
17385					$this_method_vars['startliney'] = $startliney;
17386					$this_method_vars['newline'] = $newline;
17387					$this_method_vars['loop'] = $loop;
17388					$this_method_vars['curpos'] = $curpos;
17389					$this_method_vars['pask'] = $pask;
17390					$this_method_vars['lalign'] = $lalign;
17391					$this_method_vars['plalign'] = $plalign;
17392					$this_method_vars['w'] = $w;
17393					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17394					$this_method_vars['prev_listnum'] = $prev_listnum;
17395					$this_method_vars['prev_listordered'] = $prev_listordered;
17396					$this_method_vars['prev_listcount'] = $prev_listcount;
17397					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17398					$this_method_vars['fontaligned'] = $fontaligned;
17399					$this_method_vars['key'] = $key;
17400					$this_method_vars['dom'] = $dom;
17401				}
17402			}
17403			// print THEAD block
17404			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17405				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17406					$this->inthead = true;
17407					// print table header (thead)
17408					$this->writeHTML($this->thead, false, false, false, false, '');
17409					// check if we are on a new page or on a new column
17410					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17411						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17412						// restore previous object
17413						$this->rollbackTransaction(true);
17414						// restore previous values
17415						foreach ($this_method_vars as $vkey => $vval) {
17416							$$vkey = $vval;
17417						}
17418						// disable table header
17419						$tmp_thead = $this->thead;
17420						$this->thead = '';
17421						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17422						$pre_y = $this->y;
17423						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17424							// fix for multicolumn mode
17425							$startliney = $this->y;
17426						}
17427						$this->start_transaction_page = $this->page;
17428						$this->start_transaction_y = $this->y;
17429						// restore table header
17430						$this->thead = $tmp_thead;
17431						// fix table border properties
17432						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17433							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17434						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17435							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17436						} else {
17437							$tmp_cellspacing = 0;
17438						}
17439						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17440						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17441						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17442						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17443						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17444						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17445						// print table header (thead)
17446						$this->writeHTML($this->thead, false, false, false, false, '');
17447					}
17448				}
17449				// move $key index forward to skip THEAD block
17450				while ( ($key < $maxel) AND (!(
17451					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17452					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17453					++$key;
17454				}
17455			}
17456			if ($dom[$key]['tag'] OR ($key == 0)) {
17457				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17458					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17459				}
17460				// vertically align image in line
17461				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17462					// get image height
17463					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17464					$autolinebreak = false;
17465					if (!empty($dom[$key]['width'])) {
17466						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17467						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17468							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17469							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17470							// add automatic line break
17471							$autolinebreak = true;
17472							$this->Ln('', $cell);
17473							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17474								// go back to evaluate this line break
17475								--$key;
17476							}
17477						}
17478					}
17479					if (!$autolinebreak) {
17480						if ($this->inPageBody()) {
17481							$pre_y = $this->y;
17482							// check for page break
17483							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17484								// fix for multicolumn mode
17485								$startliney = $this->y;
17486							}
17487						}
17488						if ($this->page > $startlinepage) {
17489							// fix line splitted over two pages
17490							if (isset($this->footerlen[$startlinepage])) {
17491								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17492							}
17493							// line to be moved one page forward
17494							$pagebuff = $this->getPageBuffer($startlinepage);
17495							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17496							$tstart = substr($pagebuff, 0, $startlinepos);
17497							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17498							// remove line from previous page
17499							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17500							$pagebuff = $this->getPageBuffer($this->page);
17501							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17502							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17503							// add line start to current page
17504							$yshift = ($minstartliney - $this->y);
17505							if ($fontaligned) {
17506								$yshift += ($curfontsize / $this->k);
17507							}
17508							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17509							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17510							// shift the annotations and links
17511							if (isset($this->PageAnnots[$this->page])) {
17512								$next_pask = count($this->PageAnnots[$this->page]);
17513							} else {
17514								$next_pask = 0;
17515							}
17516							if (isset($this->PageAnnots[$startlinepage])) {
17517								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17518									if ($pak >= $pask) {
17519										$this->PageAnnots[$this->page][] = $pac;
17520										unset($this->PageAnnots[$startlinepage][$pak]);
17521										$npak = count($this->PageAnnots[$this->page]) - 1;
17522										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17523									}
17524								}
17525							}
17526							$pask = $next_pask;
17527							$startlinepos = $this->cntmrk[$this->page];
17528							$startlinepage = $this->page;
17529							$startliney = $this->y;
17530							$this->newline = false;
17531						}
17532						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17533						$minstartliney = min($this->y, $minstartliney);
17534						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17535					}
17536				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17537					// account for different font size
17538					$pfontname = $curfontname;
17539					$pfontstyle = $curfontstyle;
17540					$pfontsize = $curfontsize;
17541					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17542					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17543					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17544					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17545					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17546					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17547						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17548						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17549						if (($key < ($maxel - 1)) AND (
17550								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17551								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17552								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17553								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17554								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17555							)) {
17556							if ($this->page > $startlinepage) {
17557								// fix lines splitted over two pages
17558								if (isset($this->footerlen[$startlinepage])) {
17559									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17560								}
17561								// line to be moved one page forward
17562								$pagebuff = $this->getPageBuffer($startlinepage);
17563								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17564								$tstart = substr($pagebuff, 0, $startlinepos);
17565								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17566								// remove line start from previous page
17567								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17568								$pagebuff = $this->getPageBuffer($this->page);
17569								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17570								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17571								// add line start to current page
17572								$yshift = ($minstartliney - $this->y);
17573								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17574								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17575								// shift the annotations and links
17576								if (isset($this->PageAnnots[$this->page])) {
17577									$next_pask = count($this->PageAnnots[$this->page]);
17578								} else {
17579									$next_pask = 0;
17580								}
17581								if (isset($this->PageAnnots[$startlinepage])) {
17582									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17583										if ($pak >= $pask) {
17584											$this->PageAnnots[$this->page][] = $pac;
17585											unset($this->PageAnnots[$startlinepage][$pak]);
17586											$npak = count($this->PageAnnots[$this->page]) - 1;
17587											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17588										}
17589									}
17590								}
17591								$pask = $next_pask;
17592								$startlinepos = $this->cntmrk[$this->page];
17593								$startlinepage = $this->page;
17594								$startliney = $this->y;
17595							}
17596							if (!isset($dom[$key]['line-height'])) {
17597								$dom[$key]['line-height'] = $this->cell_height_ratio;
17598							}
17599							if (!$dom[$key]['block']) {
17600								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']))) {
17601									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17602								}
17603								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17604									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17605									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)))) {
17606										$minstartliney = min($this->y, $line_align_data[1]);
17607										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17608									} else {
17609										$minstartliney = min($this->y, $minstartliney);
17610										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17611									}
17612									$line_align_data = $current_line_align_data;
17613								}
17614							}
17615							$this->cell_height_ratio = $dom[$key]['line-height'];
17616							$fontaligned = true;
17617						}
17618						$this->SetFont($fontname, $fontstyle, $fontsize);
17619						// reset row height
17620						$this->resetLastH();
17621						$curfontname = $fontname;
17622						$curfontstyle = $fontstyle;
17623						$curfontsize = $fontsize;
17624						$curfontascent = $fontascent;
17625						$curfontdescent = $fontdescent;
17626					}
17627				}
17628				// set text rendering mode
17629				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17630				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17631				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17632				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17633				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17634					$this->setFontStretching($dom[$key]['font-stretch']);
17635				}
17636				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17637					$this->setFontSpacing($dom[$key]['letter-spacing']);
17638				}
17639				if (($plalign == 'J') AND $dom[$key]['block']) {
17640					$plalign = '';
17641				}
17642				// get current position on page buffer
17643				$curpos = $this->pagelen[$startlinepage];
17644				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17645					$this->SetFillColorArray($dom[$key]['bgcolor']);
17646					$wfill = true;
17647				} else {
17648					$wfill = $fill | false;
17649				}
17650				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17651					$this->SetTextColorArray($dom[$key]['fgcolor']);
17652				}
17653				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17654					$this->SetDrawColorArray($dom[$key]['strokecolor']);
17655				}
17656				if (isset($dom[$key]['align'])) {
17657					$lalign = $dom[$key]['align'];
17658				}
17659				if (TCPDF_STATIC::empty_string($lalign)) {
17660					$lalign = $align;
17661				}
17662			}
17663			// align lines
17664			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17665				$newline = true;
17666				$fontaligned = false;
17667				// we are at the beginning of a new line
17668				if (isset($startlinex)) {
17669					$yshift = ($minstartliney - $startliney);
17670					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17671						$yshift = 0;
17672					}
17673					$t_x = 0;
17674					// the last line must be shifted to be aligned as requested
17675					$linew = abs($this->endlinex - $startlinex);
17676					if ($this->inxobj) {
17677						// we are inside an XObject template
17678						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17679						if (isset($opentagpos)) {
17680							$midpos = $opentagpos;
17681						} else {
17682							$midpos = 0;
17683						}
17684						if ($midpos > 0) {
17685							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17686							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17687						} else {
17688							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17689							$pend = '';
17690						}
17691					} else {
17692						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17693						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17694							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17695							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17696						} elseif (isset($opentagpos)) {
17697							$midpos = $opentagpos;
17698						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17699							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17700							$midpos = $this->footerpos[$startlinepage];
17701						} else {
17702							$midpos = 0;
17703						}
17704						if ($midpos > 0) {
17705							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17706							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17707						} else {
17708							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17709							$pend = '';
17710						}
17711					}
17712					if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17713						// calculate shifting amount
17714						$tw = $w;
17715						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17716							$tw += $this->cell_padding['R'];
17717						}
17718						if ($this->lMargin != $prevlMargin) {
17719							$tw += ($prevlMargin - $this->lMargin);
17720						}
17721						if ($this->rMargin != $prevrMargin) {
17722							$tw += ($prevrMargin - $this->rMargin);
17723						}
17724						$one_space_width = $this->GetStringWidth(chr(32));
17725						$no = 0; // number of spaces on a line contained on a single block
17726						if ($this->isRTLTextDir()) { // RTL
17727							// remove left space if exist
17728							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17729							if ($pos1 > 0) {
17730								$pos1 = intval($pos1);
17731								if ($this->isUnicodeFont()) {
17732									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17733									$spacelen = 2;
17734								} else {
17735									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17736									$spacelen = 1;
17737								}
17738								if ($pos1 == $pos2) {
17739									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17740									if (substr($pmid, $pos1, 4) == '[()]') {
17741										$linew -= $one_space_width;
17742									} elseif ($pos1 == strpos($pmid, '[(')) {
17743										$no = 1;
17744									}
17745								}
17746							}
17747						} else { // LTR
17748							// remove right space if exist
17749							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17750							if ($pos1 > 0) {
17751								$pos1 = intval($pos1);
17752								if ($this->isUnicodeFont()) {
17753									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17754									$spacelen = 2;
17755								} else {
17756									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17757									$spacelen = 1;
17758								}
17759								if ($pos1 == $pos2) {
17760									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17761									$linew -= $one_space_width;
17762								}
17763							}
17764						}
17765						$mdiff = ($tw - $linew);
17766						if ($plalign == 'C') {
17767							if ($this->rtl) {
17768								$t_x = -($mdiff / 2);
17769							} else {
17770								$t_x = ($mdiff / 2);
17771							}
17772						} elseif ($plalign == 'R') {
17773							// right alignment on LTR document
17774							$t_x = $mdiff;
17775						} elseif ($plalign == 'L') {
17776							// left alignment on RTL document
17777							$t_x = -$mdiff;
17778						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17779							// Justification
17780							if ($this->isRTLTextDir()) {
17781								// align text on the left
17782								$t_x = -$mdiff;
17783							}
17784							$ns = 0; // number of spaces
17785							$pmidtemp = $pmid;
17786							// escape special characters
17787							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17788							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17789							// search spaces
17790							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17791								$spacestr = $this->getSpaceString();
17792								$maxkk = count($lnstring[1]) - 1;
17793								for ($kk=0; $kk <= $maxkk; ++$kk) {
17794									// restore special characters
17795									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17796									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17797									// store number of spaces on the strings
17798									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17799									// count total spaces on line
17800									$ns += $lnstring[2][$kk];
17801									$lnstring[3][$kk] = $ns;
17802								}
17803								if ($ns == 0) {
17804									$ns = 1;
17805								}
17806								// calculate additional space to add to each existing space
17807								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17808								if ($this->FontSize <= 0) {
17809									$this->FontSize = 1;
17810								}
17811								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17812								if ($this->font_spacing != 0) {
17813									// fixed spacing mode
17814									$osw = -1000 * $this->font_spacing / $this->FontSize;
17815									$spacewidthu += $osw;
17816								}
17817								$nsmax = $ns;
17818								$ns = 0;
17819								reset($lnstring);
17820								$offset = 0;
17821								$strcount = 0;
17822								$prev_epsposbeg = 0;
17823								$textpos = 0;
17824								if ($this->isRTLTextDir()) {
17825									$textpos = $this->wPt;
17826								}
17827								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17828									// check if we are inside a string section '[( ... )]'
17829									$stroffset = strpos($pmid, '[(', $offset);
17830									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17831										// set offset to the end of string section
17832										$offset = strpos($pmid, ')]', $stroffset);
17833										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17834											$offset = strpos($pmid, ')]', ($offset + 1));
17835										}
17836										if ($offset === false) {
17837											$this->Error('HTML Justification: malformed PDF code.');
17838										}
17839										continue;
17840									}
17841									if ($this->isRTLTextDir()) {
17842										$spacew = ($spacewidth * ($nsmax - $ns));
17843									} else {
17844										$spacew = ($spacewidth * $ns);
17845									}
17846									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17847									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17848									if ($epsposend !== null) {
17849										$epsposend += strlen($this->epsmarker.'Q');
17850										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17851										if ($epsposbeg === null) {
17852											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17853											$prev_epsposbeg = $epsposbeg;
17854										}
17855										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17856											// shift EPS images
17857											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17858											$pmid_b = substr($pmid, 0, $epsposbeg);
17859											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17860											$pmid_e = substr($pmid, $epsposend);
17861											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17862											$offset = $epsposend;
17863											continue;
17864										}
17865									}
17866									$currentxpos = 0;
17867									// shift blocks of code
17868									switch ($strpiece[2][0]) {
17869										case 'Td':
17870										case 'cm':
17871										case 'm':
17872										case 'l': {
17873											// get current X position
17874											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17875											if (!isset($xmatches[1])) {
17876												break;
17877											}
17878											$currentxpos = $xmatches[1];
17879											$textpos = $currentxpos;
17880											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17881												$ns = $lnstring[3][$strcount];
17882												if ($this->isRTLTextDir()) {
17883													$spacew = ($spacewidth * ($nsmax - $ns));
17884												}
17885												++$strcount;
17886											}
17887											// justify block
17888											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17889												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17890												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17891												unset($pmatch, $newpmid);
17892											}
17893											break;
17894										}
17895										case 're': {
17896											// justify block
17897											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17898												$this->lispacer = '';
17899												break;
17900											}
17901											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17902											if (!isset($xmatches[1])) {
17903												break;
17904											}
17905											$currentxpos = $xmatches[1];
17906											$x_diff = 0;
17907											$w_diff = 0;
17908											if ($this->isRTLTextDir()) { // RTL
17909												if ($currentxpos < $textpos) {
17910													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17911													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17912												} else {
17913													if ($strcount > 0) {
17914														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17915														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17916													}
17917												}
17918											} else { // LTR
17919												if ($currentxpos > $textpos) {
17920													if ($strcount > 0) {
17921														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17922													}
17923													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17924												} else {
17925													if ($strcount > 1) {
17926														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17927													}
17928													if ($strcount > 0) {
17929														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17930													}
17931												}
17932											}
17933											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17934												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17935												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17936												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17937												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17938												unset($pmatch, $newpmid, $newx, $neww);
17939											}
17940											break;
17941										}
17942										case 'c': {
17943											// get current X position
17944											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);
17945											if (!isset($xmatches[1])) {
17946												break;
17947											}
17948											$currentxpos = $xmatches[1];
17949											// justify block
17950											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) {
17951												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17952												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17953												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17954												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17955												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17956												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17957											}
17958											break;
17959										}
17960									}
17961									// shift the annotations and links
17962									$cxpos = ($currentxpos / $this->k);
17963									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17964									if ($this->inxobj) {
17965										// we are inside an XObject template
17966										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17967											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17968												if ($cxpos > $lmpos) {
17969													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17970													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17971												} else {
17972													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17973												}
17974												break;
17975											}
17976										}
17977									} elseif (isset($this->PageAnnots[$this->page])) {
17978										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17979											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17980												if ($cxpos > $lmpos) {
17981													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17982													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17983												} else {
17984													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17985												}
17986												break;
17987											}
17988										}
17989									}
17990								} // end of while
17991								// remove markers
17992								$pmid = str_replace('x*#!#*x', '', $pmid);
17993								if ($this->isUnicodeFont()) {
17994									// multibyte characters
17995									$spacew = $spacewidthu;
17996									if ($this->font_stretching != 100) {
17997										// word spacing is affected by stretching
17998										$spacew /= ($this->font_stretching / 100);
17999									}
18000									// escape special characters
18001									$pos = 0;
18002									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18003									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18004									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18005										foreach($pamatch[0] as $pk => $pmatch) {
18006											$replace = $pamatch[1][$pk];
18007											$replace = str_replace('#!#OP#!#', '(', $replace);
18008											$replace = str_replace('#!#CP#!#', ')', $replace);
18009											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18010											$pos = strpos($pmid, $pmatch, $pos);
18011											if ($pos !== FALSE) {
18012												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18013											}
18014											++$pos;
18015										}
18016										unset($pamatch);
18017									}
18018									if ($this->inxobj) {
18019										// we are inside an XObject template
18020										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18021									} else {
18022										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18023									}
18024									$endlinepos = strlen($pstart."\n".$pmid."\n");
18025								} else {
18026									// non-unicode (single-byte characters)
18027									if ($this->font_stretching != 100) {
18028										// word spacing (Tw) is affected by stretching
18029										$spacewidth /= ($this->font_stretching / 100);
18030									}
18031									$rs = sprintf('%F Tw', $spacewidth);
18032									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18033									if ($this->inxobj) {
18034										// we are inside an XObject template
18035										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18036									} else {
18037										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18038									}
18039									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18040								}
18041							}
18042						} // end of J
18043					} // end if $startlinex
18044					if (($t_x != 0) OR ($yshift < 0)) {
18045						// shift the line
18046						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18047						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18048						$endlinepos = strlen($pstart);
18049						if ($this->inxobj) {
18050							// we are inside an XObject template
18051							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18052							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18053								if ($pak >= $pask) {
18054									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18055									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18056								}
18057							}
18058						} else {
18059							$this->setPageBuffer($startlinepage, $pstart.$pend);
18060							// shift the annotations and links
18061							if (isset($this->PageAnnots[$this->page])) {
18062								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18063									if ($pak >= $pask) {
18064										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18065										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18066									}
18067								}
18068							}
18069						}
18070						$this->y -= $yshift;
18071					}
18072				}
18073				$pbrk = $this->checkPageBreak($this->lasth);
18074				$this->newline = false;
18075				$startlinex = $this->x;
18076				$startliney = $this->y;
18077				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18078					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18079				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18080					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
18081				} else {
18082					$minstartliney = $startliney;
18083					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18084				}
18085				$startlinepage = $this->page;
18086				if (isset($endlinepos) AND (!$pbrk)) {
18087					$startlinepos = $endlinepos;
18088				} else {
18089					if ($this->inxobj) {
18090						// we are inside an XObject template
18091						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18092					} elseif (!$this->InFooter) {
18093						if (isset($this->footerlen[$this->page])) {
18094							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18095						} else {
18096							$this->footerpos[$this->page] = $this->pagelen[$this->page];
18097						}
18098						$startlinepos = $this->footerpos[$this->page];
18099					} else {
18100						$startlinepos = $this->pagelen[$this->page];
18101					}
18102				}
18103				unset($endlinepos);
18104				$plalign = $lalign;
18105				if (isset($this->PageAnnots[$this->page])) {
18106					$pask = count($this->PageAnnots[$this->page]);
18107				} else {
18108					$pask = 0;
18109				}
18110				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18111					AND (isset($this->emptypagemrk[$this->page]))
18112					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18113					$this->SetFont($fontname, $fontstyle, $fontsize);
18114					if ($wfill) {
18115						$this->SetFillColorArray($this->bgcolor);
18116					}
18117				}
18118			} // end newline
18119			if (isset($opentagpos)) {
18120				unset($opentagpos);
18121			}
18122			if ($dom[$key]['tag']) {
18123				if ($dom[$key]['opening']) {
18124					// get text indentation (if any)
18125					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18126						$this->textindent = $dom[$key]['text-indent'];
18127						$this->newline = true;
18128					}
18129					// table
18130					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18131						// available page width
18132						if ($this->rtl) {
18133							$wtmp = $this->x - $this->lMargin;
18134						} else {
18135							$wtmp = $this->w - $this->rMargin - $this->x;
18136						}
18137						// get cell spacing
18138						if (isset($dom[$key]['attribute']['cellspacing'])) {
18139							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18140							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18141						} elseif (isset($dom[$key]['border-spacing'])) {
18142							$cellspacing = $dom[$key]['border-spacing'];
18143						} else {
18144							$cellspacing = array('H' => 0, 'V' => 0);
18145						}
18146						// table width
18147						if (isset($dom[$key]['width'])) {
18148							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18149						} else {
18150							$table_width = $wtmp;
18151						}
18152						$table_width -= (2 * $cellspacing['H']);
18153						if (!$this->inthead) {
18154							$this->y += $cellspacing['V'];
18155						}
18156						if ($this->rtl) {
18157							$cellspacingx = -$cellspacing['H'];
18158						} else {
18159							$cellspacingx = $cellspacing['H'];
18160						}
18161						// total table width without cellspaces
18162						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18163						// minimum column width
18164						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18165						// array of custom column widths
18166						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18167					}
18168					// table row
18169					if ($dom[$key]['value'] == 'tr') {
18170						// reset column counter
18171						$colid = 0;
18172					}
18173					// table cell
18174					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18175						$trid = $dom[$key]['parent'];
18176						$table_el = $dom[$trid]['parent'];
18177						if (!isset($dom[$table_el]['cols'])) {
18178							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18179						}
18180						// store border info
18181						$tdborder = 0;
18182						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18183							$tdborder = $dom[$key]['border'];
18184						}
18185						$colspan = intval($dom[$key]['attribute']['colspan']);
18186						if ($colspan <= 0) {
18187							$colspan = 1;
18188						}
18189						$old_cell_padding = $this->cell_padding;
18190						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18191							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18192							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18193						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18194							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18195						} else {
18196							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18197						}
18198						$this->cell_padding = $current_cell_padding;
18199						if (isset($dom[$key]['height'])) {
18200							// minimum cell height
18201							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18202						} else {
18203							$cellh = 0;
18204						}
18205						if (isset($dom[$key]['content'])) {
18206							$cell_content = $dom[$key]['content'];
18207						} else {
18208							$cell_content = '&nbsp;';
18209						}
18210						$tagtype = $dom[$key]['value'];
18211						$parentid = $key;
18212						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18213							// move $key index forward
18214							++$key;
18215						}
18216						if (!isset($dom[$trid]['startpage'])) {
18217							$dom[$trid]['startpage'] = $this->page;
18218						} else {
18219							$this->setPage($dom[$trid]['startpage']);
18220						}
18221						if (!isset($dom[$trid]['startcolumn'])) {
18222							$dom[$trid]['startcolumn'] = $this->current_column;
18223						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18224							$tmpx = $this->x;
18225							$this->selectColumn($dom[$trid]['startcolumn']);
18226							$this->x = $tmpx;
18227						}
18228						if (!isset($dom[$trid]['starty'])) {
18229							$dom[$trid]['starty'] = $this->y;
18230						} else {
18231							$this->y = $dom[$trid]['starty'];
18232						}
18233						if (!isset($dom[$trid]['startx'])) {
18234							$dom[$trid]['startx'] = $this->x;
18235							$this->x += $cellspacingx;
18236						} else {
18237							$this->x += ($cellspacingx / 2);
18238						}
18239						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18240							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18241						} else {
18242							$rowspan = 1;
18243						}
18244						// skip row-spanned cells started on the previous rows
18245						if (isset($dom[$table_el]['rowspans'])) {
18246							$rsk = 0;
18247							$rskmax = count($dom[$table_el]['rowspans']);
18248							while ($rsk < $rskmax) {
18249								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18250								$rsstartx = $trwsp['startx'];
18251								$rsendx = $trwsp['endx'];
18252								// account for margin changes
18253								if ($trwsp['startpage'] < $this->page) {
18254									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18255										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18256										$rsstartx -= $dl;
18257										$rsendx -= $dl;
18258									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18259										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18260										$rsstartx += $dl;
18261										$rsendx += $dl;
18262									}
18263								}
18264								if (($trwsp['rowspan'] > 0)
18265									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18266									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18267									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18268									// set the starting X position of the current cell
18269									$this->x = $rsendx + $cellspacingx;
18270									// increment column indicator
18271									$colid += $trwsp['colspan'];
18272									if (($trwsp['rowspan'] == 1)
18273										AND (isset($dom[$trid]['endy']))
18274										AND (isset($dom[$trid]['endpage']))
18275										AND (isset($dom[$trid]['endcolumn']))
18276										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18277										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18278										// set ending Y position for row
18279										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18280										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18281									}
18282									$rsk = 0;
18283								} else {
18284									++$rsk;
18285								}
18286							}
18287						}
18288						if (isset($dom[$parentid]['width'])) {
18289							// user specified width
18290							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18291							$tmpcw = ($cellw / $colspan);
18292							for ($i = 0; $i < $colspan; ++$i) {
18293								$table_colwidths[($colid + $i)] = $tmpcw;
18294							}
18295						} else {
18296							// inherit column width
18297							$cellw = 0;
18298							for ($i = 0; $i < $colspan; ++$i) {
18299								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18300							}
18301						}
18302						$cellw += (($colspan - 1) * $cellspacing['H']);
18303						// increment column indicator
18304						$colid += $colspan;
18305						// add rowspan information to table element
18306						if ($rowspan > 1) {
18307							$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));
18308						}
18309						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18310						if ($rowspan > 1) {
18311							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18312						}
18313						// push background colors
18314						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18315							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18316						}
18317						// store border info
18318						if (isset($tdborder) AND !empty($tdborder)) {
18319							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18320						}
18321						$prevLastH = $this->lasth;
18322						// store some info for multicolumn mode
18323						if ($this->rtl) {
18324							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18325						} else {
18326							$this->colxshift['x'] = $this->x - $this->lMargin;
18327						}
18328						$this->colxshift['s'] = $cellspacing;
18329						$this->colxshift['p'] = $current_cell_padding;
18330						// ****** write the cell content ******
18331						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18332						// restore some values
18333						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18334						$this->lasth = $prevLastH;
18335						$this->cell_padding = $old_cell_padding;
18336						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18337						// update the end of row position
18338						if ($rowspan <= 1) {
18339							if (isset($dom[$trid]['endy'])) {
18340								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18341									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18342								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18343									$dom[$trid]['endy'] = $this->y;
18344								}
18345							} else {
18346								$dom[$trid]['endy'] = $this->y;
18347							}
18348							if (isset($dom[$trid]['endpage'])) {
18349								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18350							} else {
18351								$dom[$trid]['endpage'] = $this->page;
18352							}
18353							if (isset($dom[$trid]['endcolumn'])) {
18354								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18355							} else {
18356								$dom[$trid]['endcolumn'] = $this->current_column;
18357							}
18358						} else {
18359							// account for row-spanned cells
18360							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18361							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18362							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18363							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18364						}
18365						if (isset($dom[$table_el]['rowspans'])) {
18366							// update endy and endpage on rowspanned cells
18367							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18368								if ($trwsp['rowspan'] > 0) {
18369									if (isset($dom[$trid]['endpage'])) {
18370										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18371											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18372										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18373											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18374											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18375											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18376										} else {
18377											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18378										}
18379									}
18380								}
18381							}
18382						}
18383						$this->x += ($cellspacingx / 2);
18384					} else {
18385						// opening tag (or self-closing tag)
18386						if (!isset($opentagpos)) {
18387							if ($this->inxobj) {
18388								// we are inside an XObject template
18389								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18390							} elseif (!$this->InFooter) {
18391								if (isset($this->footerlen[$this->page])) {
18392									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18393								} else {
18394									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18395								}
18396								$opentagpos = $this->footerpos[$this->page];
18397							}
18398						}
18399						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18400					}
18401				} else { // closing tag
18402					$prev_numpages = $this->numpages;
18403					$old_bordermrk = $this->bordermrk[$this->page];
18404					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18405					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18406						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18407					}
18408					if ($prev_numpages > $this->numpages) {
18409						$startlinepage = $this->page;
18410					}
18411				}
18412			} elseif (strlen($dom[$key]['value']) > 0) {
18413				// print list-item
18414				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18415					$this->SetFont($pfontname, $pfontstyle, $pfontsize);
18416					$this->resetLastH();
18417					$minstartliney = $this->y;
18418					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18419					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18420						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18421					}
18422					$this->SetFont($curfontname, $curfontstyle, $curfontsize);
18423					$this->resetLastH();
18424					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18425						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18426						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18427						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18428						$minstartliney = min($this->y, $minstartliney);
18429						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18430					}
18431				}
18432				// text
18433				$this->htmlvspace = 0;
18434				$isRTLString = preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $dom[$key]['value']) || preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $dom[$key]['value']);
18435				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18436					// reverse spaces order
18437					$lsp = ''; // left spaces
18438					$rsp = ''; // right spaces
18439					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18440						$lsp = $matches[1];
18441					}
18442					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18443						$rsp = $matches[1];
18444					}
18445					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18446				}
18447				if ($newline) {
18448					if (!$this->premode) {
18449						$prelen = strlen($dom[$key]['value']);
18450						if ($this->isRTLTextDir() AND !$isRTLString) {
18451							// right trim except non-breaking space
18452							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18453						} else {
18454							// left trim except non-breaking space
18455							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18456						}
18457						$postlen = strlen($dom[$key]['value']);
18458						if (($postlen == 0) AND ($prelen > 0)) {
18459							$dom[$key]['trimmed_space'] = true;
18460						}
18461					}
18462					$newline = false;
18463					$firstblock = true;
18464				} else {
18465					$firstblock = false;
18466					// replace empty multiple spaces string with a single space
18467					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18468				}
18469				$strrest = '';
18470				if ($this->rtl) {
18471					$this->x -= $this->textindent;
18472				} else {
18473					$this->x += $this->textindent;
18474				}
18475				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18476					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18477					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18478						// HTML <a> Link
18479						$hrefcolor = '';
18480						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18481							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18482						}
18483						$hrefstyle = -1;
18484						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18485							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18486						}
18487						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18488					} else {
18489						$wadj = 0; // space to leave for block continuity
18490						if ($this->rtl) {
18491							$cwa = ($this->x - $this->lMargin);
18492						} else {
18493							$cwa = ($this->w - $this->rMargin - $this->x);
18494						}
18495						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18496							// check the next text blocks for continuity
18497							$nkey = ($key + 1);
18498							$write_block = true;
18499							$same_textdir = true;
18500							$tmp_fontname = $this->FontFamily;
18501							$tmp_fontstyle = $this->FontStyle;
18502							$tmp_fontsize = $this->FontSizePt;
18503							while ($write_block AND isset($dom[$nkey])) {
18504								if ($dom[$nkey]['tag']) {
18505									if ($dom[$nkey]['block']) {
18506										// end of block
18507										$write_block = false;
18508									}
18509									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18510									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18511									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18512									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18513								} else {
18514									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18515									if (isset($nextstr[0]) AND $same_textdir) {
18516										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18517										if (isset($nextstr[1])) {
18518											$write_block = false;
18519										}
18520									}
18521								}
18522								++$nkey;
18523							}
18524						}
18525						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18526							$wadj = 0;
18527							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18528							$numblks = count($nextstr);
18529							if ($numblks > 1) {
18530								// try to split on blank spaces
18531								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18532							} else {
18533								// set the entire block on new line
18534								$wadj = $this->GetStringWidth($nextstr[0]);
18535							}
18536						}
18537						// check for reversed text direction
18538						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18539							// LTR text on RTL direction or RTL text on LTR direction
18540							$reverse_dir = true;
18541							$this->rtl = !$this->rtl;
18542							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18543							if ($this->rtl) {
18544								$this->x += $revshift;
18545							} else {
18546								$this->x -= $revshift;
18547							}
18548							$xws = $this->x;
18549						}
18550						// ****** write only until the end of the line and get the rest ******
18551						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18552						// restore default direction
18553						if ($reverse_dir AND ($wadj == 0)) {
18554							$this->x = $xws;
18555							$this->rtl = !$this->rtl;
18556							$reverse_dir = false;
18557						}
18558					}
18559				}
18560				$this->textindent = 0;
18561				if (strlen($strrest) > 0) {
18562					// store the remaining string on the previous $key position
18563					$this->newline = true;
18564					if ($strrest == $dom[$key]['value']) {
18565						// used to avoid infinite loop
18566						++$loop;
18567					} else {
18568						$loop = 0;
18569					}
18570					$dom[$key]['value'] = $strrest;
18571					if ($cell) {
18572						if ($this->rtl) {
18573							$this->x -= $this->cell_padding['R'];
18574						} else {
18575							$this->x += $this->cell_padding['L'];
18576						}
18577					}
18578					if ($loop < 3) {
18579						--$key;
18580					}
18581				} else {
18582					$loop = 0;
18583					// add the positive font spacing of the last character (if any)
18584					 if ($this->font_spacing > 0) {
18585					 	if ($this->rtl) {
18586							$this->x -= $this->font_spacing;
18587						} else {
18588							$this->x += $this->font_spacing;
18589						}
18590					}
18591				}
18592			}
18593			++$key;
18594			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')) {
18595				// check if we are on a new page or on a new column
18596				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18597					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18598					// restore previous object
18599					$this->rollbackTransaction(true);
18600					// restore previous values
18601					foreach ($this_method_vars as $vkey => $vval) {
18602						$$vkey = $vval;
18603					}
18604					if (!empty($dom[$key]['thead'])) {
18605						$this->inthead = true;
18606					}
18607					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18608					$pre_y = $this->y;
18609					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18610						$startliney = $this->y;
18611					}
18612					$undo = true; // avoid infinite loop
18613				} else {
18614					$undo = false;
18615				}
18616			}
18617		} // end for each $key
18618		// align the last line
18619		if (isset($startlinex)) {
18620			$yshift = ($minstartliney - $startliney);
18621			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18622				$yshift = 0;
18623			}
18624			$t_x = 0;
18625			// the last line must be shifted to be aligned as requested
18626			$linew = abs($this->endlinex - $startlinex);
18627			if ($this->inxobj) {
18628				// we are inside an XObject template
18629				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18630				if (isset($opentagpos)) {
18631					$midpos = $opentagpos;
18632				} else {
18633					$midpos = 0;
18634				}
18635				if ($midpos > 0) {
18636					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18637					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18638				} else {
18639					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18640					$pend = '';
18641				}
18642			} else {
18643				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18644				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18645					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18646					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18647				} elseif (isset($opentagpos)) {
18648					$midpos = $opentagpos;
18649				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18650					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18651					$midpos = $this->footerpos[$startlinepage];
18652				} else {
18653					$midpos = 0;
18654				}
18655				if ($midpos > 0) {
18656					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18657					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18658				} else {
18659					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18660					$pend = '';
18661				}
18662			}
18663			if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18664				// calculate shifting amount
18665				$tw = $w;
18666				if ($this->lMargin != $prevlMargin) {
18667					$tw += ($prevlMargin - $this->lMargin);
18668				}
18669				if ($this->rMargin != $prevrMargin) {
18670					$tw += ($prevrMargin - $this->rMargin);
18671				}
18672				$one_space_width = $this->GetStringWidth(chr(32));
18673				$no = 0; // number of spaces on a line contained on a single block
18674				if ($this->isRTLTextDir()) { // RTL
18675					// remove left space if exist
18676					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18677					if ($pos1 > 0) {
18678						$pos1 = intval($pos1);
18679						if ($this->isUnicodeFont()) {
18680							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18681							$spacelen = 2;
18682						} else {
18683							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18684							$spacelen = 1;
18685						}
18686						if ($pos1 == $pos2) {
18687							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18688							if (substr($pmid, $pos1, 4) == '[()]') {
18689								$linew -= $one_space_width;
18690							} elseif ($pos1 == strpos($pmid, '[(')) {
18691								$no = 1;
18692							}
18693						}
18694					}
18695				} else { // LTR
18696					// remove right space if exist
18697					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18698					if ($pos1 > 0) {
18699						$pos1 = intval($pos1);
18700						if ($this->isUnicodeFont()) {
18701							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18702							$spacelen = 2;
18703						} else {
18704							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18705							$spacelen = 1;
18706						}
18707						if ($pos1 == $pos2) {
18708							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18709							$linew -= $one_space_width;
18710						}
18711					}
18712				}
18713				$mdiff = ($tw - $linew);
18714				if ($plalign == 'C') {
18715					if ($this->rtl) {
18716						$t_x = -($mdiff / 2);
18717					} else {
18718						$t_x = ($mdiff / 2);
18719					}
18720				} elseif ($plalign == 'R') {
18721					// right alignment on LTR document
18722					$t_x = $mdiff;
18723				} elseif ($plalign == 'L') {
18724					// left alignment on RTL document
18725					$t_x = -$mdiff;
18726				}
18727			} // end if startlinex
18728			if (($t_x != 0) OR ($yshift < 0)) {
18729				// shift the line
18730				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18731				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18732				$endlinepos = strlen($pstart);
18733				if ($this->inxobj) {
18734					// we are inside an XObject template
18735					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18736					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18737						if ($pak >= $pask) {
18738							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18739							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18740						}
18741					}
18742				} else {
18743					$this->setPageBuffer($startlinepage, $pstart.$pend);
18744					// shift the annotations and links
18745					if (isset($this->PageAnnots[$this->page])) {
18746						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18747							if ($pak >= $pask) {
18748								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18749								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18750							}
18751						}
18752					}
18753				}
18754				$this->y -= $yshift;
18755				$yshift = 0;
18756			}
18757		}
18758		// restore previous values
18759		$this->setGraphicVars($gvars);
18760		if ($this->num_columns > 1) {
18761			$this->selectColumn();
18762		} elseif ($this->page > $prevPage) {
18763			$this->lMargin = $this->pagedim[$this->page]['olm'];
18764			$this->rMargin = $this->pagedim[$this->page]['orm'];
18765		}
18766		// restore previous list state
18767		$this->cell_height_ratio = $prev_cell_height_ratio;
18768		$this->listnum = $prev_listnum;
18769		$this->listordered = $prev_listordered;
18770		$this->listcount = $prev_listcount;
18771		$this->lispacer = $prev_lispacer;
18772		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18773			$this->Ln($this->lasth);
18774			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18775				$this->y = $maxbottomliney;
18776			}
18777		}
18778		unset($dom);
18779	}
18780
18781	/**
18782	 * Process opening tags.
18783	 * @param array $dom html dom array
18784	 * @param int $key current element id
18785	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18786	 * @return array $dom
18787	 * @protected
18788	 */
18789	protected function openHTMLTagHandler($dom, $key, $cell) {
18790		$tag = $dom[$key];
18791		$parent = $dom[($dom[$key]['parent'])];
18792		$firsttag = ($key == 1);
18793		// check for text direction attribute
18794		if (isset($tag['dir'])) {
18795			$this->setTempRTL($tag['dir']);
18796		} else {
18797			$this->tmprtl = false;
18798		}
18799		if ($tag['block']) {
18800			$hbz = 0; // distance from y to line bottom
18801			$hb = 0; // vertical space between block tags
18802			// calculate vertical space for block tags
18803			if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18804				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18805			} elseif (isset($tag['fontsize'])) {
18806				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18807			} else {
18808				$cur_h = $this->getCellHeight($this->FontSize);
18809			}
18810			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18811				$on = $this->tagvspaces[$tag['value']][0]['n'];
18812			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18813				$on = 0.6;
18814			} else {
18815				$on = 1;
18816			}
18817			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18818				$hb = 0;
18819			} else {
18820				$hb = ($on * $cur_h);
18821			}
18822			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18823				if (isset($parent['fontsize'])) {
18824					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18825				} else {
18826					$hbz = $this->getCellHeight($this->FontSize);
18827				}
18828			}
18829			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18830				// fix vertical space after table
18831				$hbz = 0;
18832			}
18833			// closing vertical space
18834			$hbc = 0;
18835			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18836				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18837			} elseif (isset($parent['fontsize'])) {
18838				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18839			} else {
18840				$pre_h = $this->getCellHeight($this->FontSize);
18841			}
18842			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18843				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18844			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18845				$cn = 0.6;
18846			} else {
18847				$cn = 1;
18848			}
18849			if (isset($this->tagvspaces[$tag['value']][1])) {
18850				$hbc = ($cn * $pre_h);
18851			}
18852		}
18853		// Opening tag
18854		switch($tag['value']) {
18855			case 'table': {
18856				$cp = 0;
18857				$cs = 0;
18858				$dom[$key]['rowspans'] = array();
18859				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18860					$this->htmlvspace = 0;
18861					// set table header
18862					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18863						// set table header
18864						$this->thead = $dom[$key]['thead'];
18865						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18866							$this->theadMargins = array();
18867							$this->theadMargins['cell_padding'] = $this->cell_padding;
18868							$this->theadMargins['lmargin'] = $this->lMargin;
18869							$this->theadMargins['rmargin'] = $this->rMargin;
18870							$this->theadMargins['page'] = $this->page;
18871							$this->theadMargins['cell'] = $cell;
18872							$this->theadMargins['gvars'] = $this->getGraphicVars();
18873						}
18874					}
18875				}
18876				// store current margins and page
18877				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18878				if (isset($tag['attribute']['cellpadding'])) {
18879					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18880					$this->SetCellPadding($pad);
18881				} elseif (isset($tag['padding'])) {
18882					$this->cell_padding = $tag['padding'];
18883				}
18884				if (isset($tag['attribute']['cellspacing'])) {
18885					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18886				} elseif (isset($tag['border-spacing'])) {
18887					$cs = $tag['border-spacing']['V'];
18888				}
18889				$prev_y = $this->y;
18890				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18891					$this->inthead = true;
18892					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18893					$this->checkPageBreak($this->PageBreakTrigger + 1);
18894				}
18895				break;
18896			}
18897			case 'tr': {
18898				// array of columns positions
18899				$dom[$key]['cellpos'] = array();
18900				break;
18901			}
18902			case 'hr': {
18903				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18904					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18905				} else {
18906					$hrHeight = $this->GetLineWidth();
18907				}
18908				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18909				$x = $this->GetX();
18910				$y = $this->GetY();
18911				$wtmp = $this->w - $this->lMargin - $this->rMargin;
18912				if ($cell) {
18913					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18914				}
18915				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18916					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18917				} else {
18918					$hrWidth = $wtmp;
18919				}
18920				$prevlinewidth = $this->GetLineWidth();
18921				$this->SetLineWidth($hrHeight);
18922
18923				$lineStyle = array();
18924                    		if (isset($tag['fgcolor'])) {
18925		                        $lineStyle['color'] = $tag['fgcolor'];
18926                    		}
18927
18928                    		if (isset($tag['fgcolor'])) {
18929                        		$lineStyle['color'] = $tag['fgcolor'];
18930                    		}
18931
18932                    		if (isset($tag['style']['cap'])) {
18933                        		$lineStyle['cap'] = $tag['style']['cap'];
18934                    		}
18935
18936                    		if (isset($tag['style']['join'])) {
18937                        		$lineStyle['join'] = $tag['style']['join'];
18938                    		}
18939
18940                    		if (isset($tag['style']['dash'])) {
18941                        		$lineStyle['dash'] = $tag['style']['dash'];
18942                    		}
18943
18944                    		if (isset($tag['style']['phase'])) {
18945                        		$lineStyle['phase'] = $tag['style']['phase'];
18946                    		}
18947
18948				$lineStyle = array_filter($lineStyle);
18949
18950				$this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
18951				$this->SetLineWidth($prevlinewidth);
18952				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18953				break;
18954			}
18955			case 'a': {
18956				if (array_key_exists('href', $tag['attribute'])) {
18957					$this->HREF['url'] = $tag['attribute']['href'];
18958				}
18959				break;
18960			}
18961			case 'img': {
18962				if (empty($tag['attribute']['src'])) {
18963					break;
18964				}
18965				$imgsrc = $tag['attribute']['src'];
18966				if ($imgsrc[0] === '@') {
18967					// data stream
18968					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
18969					$type = '';
18970				} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
18971                    // get image type from a local file path
18972                    $imgsrc = substr($imgsrc, 7);
18973                    $type = TCPDF_IMAGES::getImageFileType($imgsrc);
18974                } else {
18975					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
18976						// fix image path
18977						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
18978						if (($findroot === false) OR ($findroot > 1)) {
18979							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
18980								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
18981							} else {
18982								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
18983							}
18984						}
18985						$imgsrc = urldecode($imgsrc);
18986						$testscrtype = @parse_url($imgsrc);
18987						if (empty($testscrtype['query'])) {
18988							// convert URL to server path
18989							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
18990						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
18991							// convert URL to server path
18992							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
18993						}
18994					}
18995					// get image type
18996					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
18997				}
18998				if (!isset($tag['width'])) {
18999					$tag['width'] = 0;
19000				}
19001				if (!isset($tag['height'])) {
19002					$tag['height'] = 0;
19003				}
19004				//if (!isset($tag['attribute']['align'])) {
19005					// the only alignment supported is "bottom"
19006					// further development is required for other modes.
19007					$tag['attribute']['align'] = 'bottom';
19008				//}
19009				switch($tag['attribute']['align']) {
19010					case 'top': {
19011						$align = 'T';
19012						break;
19013					}
19014					case 'middle': {
19015						$align = 'M';
19016						break;
19017					}
19018					case 'bottom': {
19019						$align = 'B';
19020						break;
19021					}
19022					default: {
19023						$align = 'B';
19024						break;
19025					}
19026				}
19027				$prevy = $this->y;
19028				$xpos = $this->x;
19029				$imglink = '';
19030				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19031					$imglink = $this->HREF['url'];
19032					if ($imglink[0] == '#') {
19033						// convert url to internal link
19034						$lnkdata = explode(',', $imglink);
19035						if (isset($lnkdata[0])) {
19036							$page = intval(substr($lnkdata[0], 1));
19037							if (empty($page) OR ($page <= 0)) {
19038								$page = $this->page;
19039							}
19040							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19041								$lnky = floatval($lnkdata[1]);
19042							} else {
19043								$lnky = 0;
19044							}
19045							$imglink = $this->AddLink();
19046							$this->SetLink($imglink, $lnky, $page);
19047						}
19048					}
19049				}
19050				$border = 0;
19051				if (isset($tag['border']) AND !empty($tag['border'])) {
19052					// currently only support 1 (frame) or a combination of 'LTRB'
19053					$border = $tag['border'];
19054				}
19055				$iw = '';
19056				if (isset($tag['width'])) {
19057					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19058				}
19059				$ih = '';
19060				if (isset($tag['height'])) {
19061					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19062				}
19063				if (($type == 'eps') OR ($type == 'ai')) {
19064					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19065				} elseif ($type == 'svg') {
19066					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19067				} else {
19068					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19069				}
19070				switch($align) {
19071					case 'T': {
19072						$this->y = $prevy;
19073						break;
19074					}
19075					case 'M': {
19076						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19077						break;
19078					}
19079					case 'B': {
19080						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19081						break;
19082					}
19083				}
19084				break;
19085			}
19086			case 'dl': {
19087				++$this->listnum;
19088				if ($this->listnum == 1) {
19089					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19090				} else {
19091					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19092				}
19093				break;
19094			}
19095			case 'dt': {
19096				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19097				break;
19098			}
19099			case 'dd': {
19100				if ($this->rtl) {
19101					$this->rMargin += $this->listindent;
19102				} else {
19103					$this->lMargin += $this->listindent;
19104				}
19105				++$this->listindentlevel;
19106				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19107				break;
19108			}
19109			case 'ul':
19110			case 'ol': {
19111				++$this->listnum;
19112				if ($tag['value'] == 'ol') {
19113					$this->listordered[$this->listnum] = true;
19114				} else {
19115					$this->listordered[$this->listnum] = false;
19116				}
19117				if (isset($tag['attribute']['start'])) {
19118					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19119				} else {
19120					$this->listcount[$this->listnum] = 0;
19121				}
19122				if ($this->rtl) {
19123					$this->rMargin += $this->listindent;
19124					$this->x -= $this->listindent;
19125				} else {
19126					$this->lMargin += $this->listindent;
19127					$this->x += $this->listindent;
19128				}
19129				++$this->listindentlevel;
19130				if ($this->listnum == 1) {
19131					if ($key > 1) {
19132						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19133					}
19134				} else {
19135					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19136				}
19137				break;
19138			}
19139			case 'li': {
19140				if ($key > 2) {
19141					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19142				}
19143				if ($this->listordered[$this->listnum]) {
19144					// ordered item
19145					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19146						$this->lispacer = $parent['attribute']['type'];
19147					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19148						$this->lispacer = $parent['listtype'];
19149					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19150						$this->lispacer = $this->lisymbol;
19151					} else {
19152						$this->lispacer = '#';
19153					}
19154					++$this->listcount[$this->listnum];
19155					if (isset($tag['attribute']['value'])) {
19156						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19157					}
19158				} else {
19159					// unordered item
19160					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19161						$this->lispacer = $parent['attribute']['type'];
19162					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19163						$this->lispacer = $parent['listtype'];
19164					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19165						$this->lispacer = $this->lisymbol;
19166					} else {
19167						$this->lispacer = '!';
19168					}
19169				}
19170				break;
19171			}
19172			case 'blockquote': {
19173				if ($this->rtl) {
19174					$this->rMargin += $this->listindent;
19175				} else {
19176					$this->lMargin += $this->listindent;
19177				}
19178				++$this->listindentlevel;
19179				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19180				break;
19181			}
19182			case 'br': {
19183				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19184				break;
19185			}
19186			case 'div': {
19187				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19188				break;
19189			}
19190			case 'p': {
19191				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19192				break;
19193			}
19194			case 'pre': {
19195				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19196				$this->premode = true;
19197				break;
19198			}
19199			case 'sup': {
19200				$this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19201				break;
19202			}
19203			case 'sub': {
19204				$this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19205				break;
19206			}
19207			case 'h1':
19208			case 'h2':
19209			case 'h3':
19210			case 'h4':
19211			case 'h5':
19212			case 'h6': {
19213				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19214				break;
19215			}
19216			// Form fields (since 4.8.000 - 2009-09-07)
19217			case 'form': {
19218				if (isset($tag['attribute']['action'])) {
19219					$this->form_action = $tag['attribute']['action'];
19220				} else {
19221					$this->Error('Please explicitly set action attribute path!');
19222				}
19223				if (isset($tag['attribute']['enctype'])) {
19224					$this->form_enctype = $tag['attribute']['enctype'];
19225				} else {
19226					$this->form_enctype = 'application/x-www-form-urlencoded';
19227				}
19228				if (isset($tag['attribute']['method'])) {
19229					$this->form_mode = $tag['attribute']['method'];
19230				} else {
19231					$this->form_mode = 'post';
19232				}
19233				break;
19234			}
19235			case 'input': {
19236				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19237					$name = $tag['attribute']['name'];
19238				} else {
19239					break;
19240				}
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']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19247					$value = $tag['attribute']['value'];
19248				}
19249				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19250					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19251				}
19252				$h = $this->getCellHeight($this->FontSize);
19253				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19254					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19255				} else {
19256					$w = $h;
19257				}
19258				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19259					$checked = true;
19260				} else {
19261					$checked = false;
19262				}
19263				if (isset($tag['align'])) {
19264					switch ($tag['align']) {
19265						case 'C': {
19266							$opt['q'] = 1;
19267							break;
19268						}
19269						case 'R': {
19270							$opt['q'] = 2;
19271							break;
19272						}
19273						case 'L':
19274						default: {
19275							break;
19276						}
19277					}
19278				}
19279				switch ($tag['attribute']['type']) {
19280					case 'text': {
19281						if (isset($value)) {
19282							$opt['v'] = $value;
19283						}
19284						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19285						break;
19286					}
19287					case 'password': {
19288						if (isset($value)) {
19289							$opt['v'] = $value;
19290						}
19291						$prop['password'] = 'true';
19292						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19293						break;
19294					}
19295					case 'checkbox': {
19296						if (!isset($value)) {
19297							break;
19298						}
19299						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19300						break;
19301					}
19302					case 'radio': {
19303						if (!isset($value)) {
19304							break;
19305						}
19306						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19307						break;
19308					}
19309					case 'submit': {
19310						if (!isset($value)) {
19311							$value = 'submit';
19312						}
19313						$w = $this->GetStringWidth($value) * 1.5;
19314						$h *= 1.6;
19315						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19316						$action = array();
19317						$action['S'] = 'SubmitForm';
19318						$action['F'] = $this->form_action;
19319						if ($this->form_enctype != 'FDF') {
19320							$action['Flags'] = array('ExportFormat');
19321						}
19322						if ($this->form_mode == 'get') {
19323							$action['Flags'] = array('GetMethod');
19324						}
19325						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19326						break;
19327					}
19328					case 'reset': {
19329						if (!isset($value)) {
19330							$value = 'reset';
19331						}
19332						$w = $this->GetStringWidth($value) * 1.5;
19333						$h *= 1.6;
19334						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19335						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19336						break;
19337					}
19338					case 'file': {
19339						$prop['fileSelect'] = 'true';
19340						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19341						if (!isset($value)) {
19342							$value = '*';
19343						}
19344						$w = $this->GetStringWidth($value) * 2;
19345						$h *= 1.2;
19346						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19347						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19348						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19349						break;
19350					}
19351					case 'hidden': {
19352						if (isset($value)) {
19353							$opt['v'] = $value;
19354						}
19355						$opt['f'] = array('invisible', 'hidden');
19356						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19357						break;
19358					}
19359					case 'image': {
19360						// THIS TYPE MUST BE FIXED
19361						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19362							$img = $tag['attribute']['src'];
19363						} else {
19364							break;
19365						}
19366						$value = 'img';
19367						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19368						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19369							$jsaction = $tag['attribute']['onclick'];
19370						} else {
19371							$jsaction = '';
19372						}
19373						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19374						break;
19375					}
19376					case 'button': {
19377						if (!isset($value)) {
19378							$value = ' ';
19379						}
19380						$w = $this->GetStringWidth($value) * 1.5;
19381						$h *= 1.6;
19382						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19383						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19384							$jsaction = $tag['attribute']['onclick'];
19385						} else {
19386							$jsaction = '';
19387						}
19388						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19389						break;
19390					}
19391				}
19392				break;
19393			}
19394			case 'textarea': {
19395				$prop = array();
19396				$opt = array();
19397				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19398					$prop['readonly'] = true;
19399				}
19400				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19401					$name = $tag['attribute']['name'];
19402				} else {
19403					break;
19404				}
19405				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19406					$opt['v'] = $tag['attribute']['value'];
19407				}
19408				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19409					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19410				} else {
19411					$w = 40;
19412				}
19413				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19414					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19415				} else {
19416					$h = 10;
19417				}
19418				$prop['multiline'] = 'true';
19419				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19420				break;
19421			}
19422			case 'select': {
19423				$h = $this->getCellHeight($this->FontSize);
19424				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19425					$h *= ($tag['attribute']['size'] + 1);
19426				}
19427				$prop = array();
19428				$opt = array();
19429				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19430					$name = $tag['attribute']['name'];
19431				} else {
19432					break;
19433				}
19434				$w = 0;
19435				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19436					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19437					$values = array();
19438					foreach ($options as $val) {
19439						if (strpos($val, '#!TaB!#') !== false) {
19440							$opts = explode('#!TaB!#', $val);
19441							$values[] = $opts;
19442							$w = max($w, $this->GetStringWidth($opts[1]));
19443						} else {
19444							$values[] = $val;
19445							$w = max($w, $this->GetStringWidth($val));
19446						}
19447					}
19448				} else {
19449					break;
19450				}
19451				$w *= 2;
19452				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19453					$prop['multipleSelection'] = 'true';
19454					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19455				} else {
19456					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19457				}
19458				break;
19459			}
19460			case 'tcpdf': {
19461				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19462					// Special tag used to call TCPDF methods
19463					if (isset($tag['attribute']['method'])) {
19464						$tcpdf_method = $tag['attribute']['method'];
19465						if (method_exists($this, $tcpdf_method)) {
19466							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19467								$params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19468								call_user_func_array(array($this, $tcpdf_method), $params);
19469							} else {
19470								$this->$tcpdf_method();
19471							}
19472							$this->newline = true;
19473						}
19474					}
19475				}
19476				break;
19477			}
19478			default: {
19479				break;
19480			}
19481		}
19482		// define tags that support borders and background colors
19483		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19484		if (in_array($tag['value'], $bordertags)) {
19485			// set border
19486			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19487		}
19488		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19489			$pba = $dom[$key]['attribute']['pagebreakafter'];
19490			// check for pagebreak
19491			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19492				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19493				$this->checkPageBreak($this->PageBreakTrigger + 1);
19494			}
19495			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19496				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19497				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19498				$this->checkPageBreak($this->PageBreakTrigger + 1);
19499			}
19500		}
19501		return $dom;
19502	}
19503
19504	/**
19505	 * Process closing tags.
19506	 * @param array $dom html dom array
19507	 * @param int $key current element id
19508	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19509	 * @param int $maxbottomliney maximum y value of current line
19510	 * @return array $dom
19511	 * @protected
19512	 */
19513	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19514		$tag = $dom[$key];
19515		$parent = $dom[($dom[$key]['parent'])];
19516		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19517		$in_table_head = false;
19518		// maximum x position (used to draw borders)
19519		if ($this->rtl) {
19520			$xmax = $this->w;
19521		} else {
19522			$xmax = 0;
19523		}
19524		if ($tag['block']) {
19525			$hbz = 0; // distance from y to line bottom
19526			$hb = 0; // vertical space between block tags
19527			// calculate vertical space for block tags
19528			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19529				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19530			} elseif (isset($parent['fontsize'])) {
19531				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19532			} else {
19533				$pre_h = $this->getCellHeight($this->FontSize);
19534			}
19535			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19536				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19537			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19538				$cn = 0.6;
19539			} else {
19540				$cn = 1;
19541			}
19542			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19543				$hb = 0;
19544			} else {
19545				$hb = ($cn * $pre_h);
19546			}
19547			if ($maxbottomliney > $this->PageBreakTrigger) {
19548				$hbz = $this->getCellHeight($this->FontSize);
19549			} elseif ($this->y < $maxbottomliney) {
19550				$hbz = ($maxbottomliney - $this->y);
19551			}
19552		}
19553		// Closing tag
19554		switch($tag['value']) {
19555			case 'tr': {
19556				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19557				if (!isset($parent['endy'])) {
19558					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19559					$parent['endy'] = $this->y;
19560				}
19561				if (!isset($parent['endpage'])) {
19562					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19563					$parent['endpage'] = $this->page;
19564				}
19565				if (!isset($parent['endcolumn'])) {
19566					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19567					$parent['endcolumn'] = $this->current_column;
19568				}
19569				// update row-spanned cells
19570				if (isset($dom[$table_el]['rowspans'])) {
19571					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19572						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19573						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19574							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19575								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19576							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19577								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19578								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19579								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19580							}
19581						}
19582					}
19583					// report new endy and endpage to the rowspanned cells
19584					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19585						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19586							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19587							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19588							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19589							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19590							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19591							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19592						}
19593					}
19594					// update remaining rowspanned cells
19595					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19596						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19597							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19598							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19599							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19600						}
19601					}
19602				}
19603				$prev_page = $this->page;
19604				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19605				if ($this->num_columns > 1) {
19606					if (($prev_page < $this->page)
19607						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19608							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19609						// page jump
19610						$this->selectColumn(0);
19611						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19612						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19613					} else {
19614						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19615						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19616					}
19617				} else {
19618					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19619				}
19620				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19621					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19622				} elseif (isset($dom[$table_el]['border-spacing'])) {
19623					$this->y += $dom[$table_el]['border-spacing']['V'];
19624				}
19625				$this->Ln(0, $cell);
19626				if ($this->current_column == $parent['startcolumn']) {
19627					$this->x = $parent['startx'];
19628				}
19629				// account for booklet mode
19630				if ($this->page > $parent['startpage']) {
19631					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19632						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19633					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19634						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19635					}
19636				}
19637				break;
19638			}
19639			case 'tablehead':
19640				// closing tag used for the thead part
19641				$in_table_head = true;
19642				$this->inthead = false;
19643			case 'table': {
19644				$table_el = $parent;
19645				// set default border
19646				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19647					// set default border
19648					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19649				} else {
19650					$border = 0;
19651				}
19652				$default_border = $border;
19653				// fix bottom line alignment of last line before page break
19654				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19655					// update row-spanned cells
19656					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19657						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19658							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19659								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19660							}
19661							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19662								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19663							}
19664						}
19665					}
19666					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19667						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19668						$dom[$prevtrkey]['endy'] = $pgendy;
19669						// update row-spanned cells
19670						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19671							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19672								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19673									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19674									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19675								}
19676							}
19677						}
19678					}
19679					$prevtrkey = $trkey;
19680					$table_el = $dom[($dom[$key]['parent'])];
19681				}
19682				// for each row
19683				if (count($table_el['trids']) > 0) {
19684					unset($xmax);
19685				}
19686				foreach ($table_el['trids'] as $j => $trkey) {
19687					$parent = $dom[$trkey];
19688					if (!isset($xmax)) {
19689						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19690					}
19691					// for each cell on the row
19692					foreach ($parent['cellpos'] as $k => $cellpos) {
19693						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19694							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19695							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19696							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19697							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19698							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19699							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19700							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19701						} else {
19702							$endy = $parent['endy'];
19703							$startpage = $parent['startpage'];
19704							$endpage = $parent['endpage'];
19705							$startcolumn = $parent['startcolumn'];
19706							$endcolumn = $parent['endcolumn'];
19707						}
19708						if ($this->num_columns == 0) {
19709							$this->num_columns = 1;
19710						}
19711						if (isset($cellpos['border'])) {
19712							$border = $cellpos['border'];
19713						}
19714						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19715							$this->SetFillColorArray($cellpos['bgcolor']);
19716							$fill = true;
19717						} else {
19718							$fill = false;
19719						}
19720						$x = $cellpos['startx'];
19721						$y = $parent['starty'];
19722						$starty = $y;
19723						$w = abs($cellpos['endx'] - $cellpos['startx']);
19724						// get border modes
19725						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19726						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19727						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19728						// design borders around HTML cells.
19729						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19730							$ccode = '';
19731							$this->setPage($page);
19732							if ($this->num_columns < 2) {
19733								// single-column mode
19734								$this->x = $x;
19735								$this->y = $this->tMargin;
19736							}
19737							// account for margin changes
19738							if ($page > $startpage) {
19739								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19740									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19741								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19742									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19743								}
19744							}
19745							if ($startpage == $endpage) { // single page
19746								$deltacol = 0;
19747								$deltath = 0;
19748								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19749									$this->selectColumn($column);
19750									if ($startcolumn == $endcolumn) { // single column
19751										$cborder = $border;
19752										$h = $endy - $parent['starty'];
19753										$this->y = $y;
19754										$this->x = $x;
19755									} elseif ($column == $startcolumn) { // first column
19756										$cborder = $border_start;
19757										$this->y = $starty;
19758										$this->x = $x;
19759										$h = $this->h - $this->y - $this->bMargin;
19760										if ($this->rtl) {
19761											$deltacol = $this->x + $this->rMargin - $this->w;
19762										} else {
19763											$deltacol = $this->x - $this->lMargin;
19764										}
19765									} elseif ($column == $endcolumn) { // end column
19766										$cborder = $border_end;
19767										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19768											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19769										}
19770										$this->x += $deltacol;
19771										$h = $endy - $this->y;
19772									} else { // middle column
19773										$cborder = $border_middle;
19774										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19775											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19776										}
19777										$this->x += $deltacol;
19778										$h = $this->h - $this->y - $this->bMargin;
19779									}
19780									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19781								} // end for each column
19782							} elseif ($page == $startpage) { // first page
19783								$deltacol = 0;
19784								$deltath = 0;
19785								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19786									$this->selectColumn($column);
19787									if ($column == $startcolumn) { // first column
19788										$cborder = $border_start;
19789										$this->y = $starty;
19790										$this->x = $x;
19791										$h = $this->h - $this->y - $this->bMargin;
19792										if ($this->rtl) {
19793											$deltacol = $this->x + $this->rMargin - $this->w;
19794										} else {
19795											$deltacol = $this->x - $this->lMargin;
19796										}
19797									} else { // middle column
19798										$cborder = $border_middle;
19799										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19800											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19801										}
19802										$this->x += $deltacol;
19803										$h = $this->h - $this->y - $this->bMargin;
19804									}
19805									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19806								} // end for each column
19807							} elseif ($page == $endpage) { // last page
19808								$deltacol = 0;
19809								$deltath = 0;
19810								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19811									$this->selectColumn($column);
19812									if ($column == $endcolumn) { // end column
19813										$cborder = $border_end;
19814										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19815											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19816										}
19817										$this->x += $deltacol;
19818										$h = $endy - $this->y;
19819									} else { // middle column
19820										$cborder = $border_middle;
19821										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19822											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19823										}
19824										$this->x += $deltacol;
19825										$h = $this->h - $this->y - $this->bMargin;
19826									}
19827									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19828								} // end for each column
19829							} else { // middle page
19830								$deltacol = 0;
19831								$deltath = 0;
19832								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19833									$this->selectColumn($column);
19834									$cborder = $border_middle;
19835									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19836										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19837									}
19838									$this->x += $deltacol;
19839									$h = $this->h - $this->y - $this->bMargin;
19840									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19841								} // end for each column
19842							}
19843							if (!empty($cborder) OR !empty($fill)) {
19844								$offsetlen = strlen($ccode);
19845								// draw border and fill
19846								if ($this->inxobj) {
19847									// we are inside an XObject template
19848									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19849										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19850										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19851										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19852									} else {
19853										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19854										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19855									}
19856									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19857									$pstart = substr($pagebuff, 0, $pagemark);
19858									$pend = substr($pagebuff, $pagemark);
19859									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19860								} else {
19861									// draw border and fill
19862									if (end($this->transfmrk[$this->page]) !== false) {
19863										$pagemarkkey = key($this->transfmrk[$this->page]);
19864										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19865									} elseif ($this->InFooter) {
19866										$pagemark = $this->footerpos[$this->page];
19867									} else {
19868										$pagemark = $this->intmrk[$this->page];
19869									}
19870									$pagebuff = $this->getPageBuffer($this->page);
19871									$pstart = substr($pagebuff, 0, $pagemark);
19872									$pend = substr($pagebuff, $pagemark);
19873									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19874								}
19875							}
19876						} // end for each page
19877						// restore default border
19878						$border = $default_border;
19879					} // end for each cell on the row
19880					if (isset($table_el['attribute']['cellspacing'])) {
19881						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19882					} elseif (isset($table_el['border-spacing'])) {
19883						$this->y += $table_el['border-spacing']['V'];
19884					}
19885					$this->Ln(0, $cell);
19886					$this->x = $parent['startx'];
19887					if ($endpage > $startpage) {
19888						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19889							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19890						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19891							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19892						}
19893					}
19894				}
19895				if (!$in_table_head) { // we are not inside a thead section
19896					$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null;
19897					// reset row height
19898					$this->resetLastH();
19899					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19900						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19901						if (($plendiff > 0) AND ($plendiff < 60)) {
19902							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19903							if (substr($pagediff, 0, 5) == 'BT /F') {
19904								// the difference is only a font setting
19905								$plendiff = 0;
19906							}
19907						}
19908						if ($plendiff == 0) {
19909							// remove last blank page
19910							$this->deletePage($this->numpages);
19911						}
19912					}
19913					if (isset($this->theadMargins['top'])) {
19914						// restore top margin
19915						$this->tMargin = $this->theadMargins['top'];
19916					}
19917					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19918						// reset main table header
19919						$this->thead = '';
19920						$this->theadMargins = array();
19921						$this->pagedim[$this->page]['tm'] = $this->tMargin;
19922					}
19923				}
19924				$parent = $table_el;
19925				break;
19926			}
19927			case 'a': {
19928				$this->HREF = array();
19929				break;
19930			}
19931			case 'sup': {
19932				$this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19933				break;
19934			}
19935			case 'sub': {
19936				$this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19937				break;
19938			}
19939			case 'div': {
19940				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19941				break;
19942			}
19943			case 'blockquote': {
19944				if ($this->rtl) {
19945					$this->rMargin -= $this->listindent;
19946				} else {
19947					$this->lMargin -= $this->listindent;
19948				}
19949				--$this->listindentlevel;
19950				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19951				break;
19952			}
19953			case 'p': {
19954				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19955				break;
19956			}
19957			case 'pre': {
19958				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19959				$this->premode = false;
19960				break;
19961			}
19962			case 'dl': {
19963				--$this->listnum;
19964				if ($this->listnum <= 0) {
19965					$this->listnum = 0;
19966					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19967				} else {
19968					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19969				}
19970				$this->resetLastH();
19971				break;
19972			}
19973			case 'dt': {
19974				$this->lispacer = '';
19975				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19976				break;
19977			}
19978			case 'dd': {
19979				$this->lispacer = '';
19980				if ($this->rtl) {
19981					$this->rMargin -= $this->listindent;
19982				} else {
19983					$this->lMargin -= $this->listindent;
19984				}
19985				--$this->listindentlevel;
19986				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19987				break;
19988			}
19989			case 'ul':
19990			case 'ol': {
19991				--$this->listnum;
19992				$this->lispacer = '';
19993				if ($this->rtl) {
19994					$this->rMargin -= $this->listindent;
19995				} else {
19996					$this->lMargin -= $this->listindent;
19997				}
19998				--$this->listindentlevel;
19999				if ($this->listnum <= 0) {
20000					$this->listnum = 0;
20001					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20002				} else {
20003					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20004				}
20005				$this->resetLastH();
20006				break;
20007			}
20008			case 'li': {
20009				$this->lispacer = '';
20010				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20011				break;
20012			}
20013			case 'h1':
20014			case 'h2':
20015			case 'h3':
20016			case 'h4':
20017			case 'h5':
20018			case 'h6': {
20019				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20020				break;
20021			}
20022			// Form fields (since 4.8.000 - 2009-09-07)
20023			case 'form': {
20024				$this->form_action = '';
20025				$this->form_enctype = 'application/x-www-form-urlencoded';
20026				break;
20027			}
20028			default : {
20029				break;
20030			}
20031		}
20032		// draw border and background (if any)
20033		$this->drawHTMLTagBorder($parent, $xmax);
20034		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20035			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20036			// check for pagebreak
20037			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20038				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20039				$this->checkPageBreak($this->PageBreakTrigger + 1);
20040			}
20041			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20042				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20043				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20044				$this->checkPageBreak($this->PageBreakTrigger + 1);
20045			}
20046		}
20047		$this->tmprtl = false;
20048		return $dom;
20049	}
20050
20051	/**
20052	 * Add vertical spaces if needed.
20053	 * @param string $hbz Distance between current y and line bottom.
20054	 * @param string $hb The height of the break.
20055	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20056	 * @param boolean $firsttag set to true when the tag is the first.
20057	 * @param boolean $lasttag set to true when the tag is the last.
20058	 * @protected
20059	 */
20060	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20061		if ($firsttag) {
20062			$this->Ln(0, $cell);
20063			$this->htmlvspace = 0;
20064			return;
20065		}
20066		if ($lasttag) {
20067			$this->Ln($hbz, $cell);
20068			$this->htmlvspace = 0;
20069			return;
20070		}
20071		if ($hb < $this->htmlvspace) {
20072			$hd = 0;
20073		} else {
20074			$hd = $hb - $this->htmlvspace;
20075			$this->htmlvspace = $hb;
20076		}
20077		$this->Ln(($hbz + $hd), $cell);
20078	}
20079
20080	/**
20081	 * Return the starting coordinates to draw an html border
20082	 * @return array containing top-left border coordinates
20083	 * @protected
20084	 * @since 5.7.000 (2010-08-03)
20085	 */
20086	protected function getBorderStartPosition() {
20087		if ($this->rtl) {
20088			$xmax = $this->lMargin;
20089		} else {
20090			$xmax = $this->w - $this->rMargin;
20091		}
20092		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20093	}
20094
20095	/**
20096	 * Draw an HTML block border and fill
20097	 * @param array $tag array of tag properties.
20098	 * @param int $xmax end X coordinate for border.
20099	 * @protected
20100	 * @since 5.7.000 (2010-08-03)
20101	 */
20102	protected function drawHTMLTagBorder($tag, $xmax) {
20103		if (!isset($tag['borderposition'])) {
20104			// nothing to draw
20105			return;
20106		}
20107		$prev_x = $this->x;
20108		$prev_y = $this->y;
20109		$prev_lasth = $this->lasth;
20110		$border = 0;
20111		$fill = false;
20112		$this->lasth = 0;
20113		if (isset($tag['border']) AND !empty($tag['border'])) {
20114			// get border style
20115			$border = $tag['border'];
20116			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20117				// border for table header
20118				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20119			}
20120		}
20121		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20122			// get background color
20123			$old_bgcolor = $this->bgcolor;
20124			$this->SetFillColorArray($tag['bgcolor']);
20125			$fill = true;
20126		}
20127		if (!$border AND !$fill) {
20128			// nothing to draw
20129			return;
20130		}
20131		if (isset($tag['attribute']['cellspacing'])) {
20132			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20133			$cellspacing = array('H' => $clsp, 'V' => $clsp);
20134		} elseif (isset($tag['border-spacing'])) {
20135			$cellspacing = $tag['border-spacing'];
20136		} else {
20137			$cellspacing = array('H' => 0, 'V' => 0);
20138		}
20139		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20140			// draw the border externally respect the sqare edge.
20141			$border['mode'] = 'ext';
20142		}
20143		if ($this->rtl) {
20144			if ($xmax >= $tag['borderposition']['x']) {
20145				$xmax = $tag['borderposition']['xmax'];
20146			}
20147			$w = ($tag['borderposition']['x'] - $xmax);
20148		} else {
20149			if ($xmax <= $tag['borderposition']['x']) {
20150				$xmax = $tag['borderposition']['xmax'];
20151			}
20152			$w = ($xmax - $tag['borderposition']['x']);
20153		}
20154		if ($w <= 0) {
20155			return;
20156		}
20157		$w += $cellspacing['H'];
20158		$startpage = $tag['borderposition']['page'];
20159		$startcolumn = $tag['borderposition']['column'];
20160		$x = $tag['borderposition']['x'];
20161		$y = $tag['borderposition']['y'];
20162		$endpage = $this->page;
20163		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20164		$currentY = $this->y;
20165		$this->x = $x;
20166		// get latest column
20167		$endcolumn = $this->current_column;
20168		if ($this->num_columns == 0) {
20169			$this->num_columns = 1;
20170		}
20171		// get border modes
20172		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20173		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20174		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20175		// temporary disable page regions
20176		$temp_page_regions = $this->page_regions;
20177		$this->page_regions = array();
20178		// design borders around HTML cells.
20179		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20180			$ccode = '';
20181			$this->setPage($page);
20182			if ($this->num_columns < 2) {
20183				// single-column mode
20184				$this->x = $x;
20185				$this->y = $this->tMargin;
20186			}
20187			// account for margin changes
20188			if ($page > $startpage) {
20189				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20190					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20191				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20192					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20193				}
20194			}
20195			if ($startpage == $endpage) {
20196				// single page
20197				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20198					$this->selectColumn($column);
20199					if ($startcolumn == $endcolumn) { // single column
20200						$cborder = $border;
20201						$h = ($currentY - $y) + $cellspacing['V'];
20202						$this->y = $starty;
20203					} elseif ($column == $startcolumn) { // first column
20204						$cborder = $border_start;
20205						$this->y = $starty;
20206						$h = $this->h - $this->y - $this->bMargin;
20207					} elseif ($column == $endcolumn) { // end column
20208						$cborder = $border_end;
20209						$h = $currentY - $this->y;
20210					} else { // middle column
20211						$cborder = $border_middle;
20212						$h = $this->h - $this->y - $this->bMargin;
20213					}
20214					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20215				} // end for each column
20216			} elseif ($page == $startpage) { // first page
20217				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20218					$this->selectColumn($column);
20219					if ($column == $startcolumn) { // first column
20220						$cborder = $border_start;
20221						$this->y = $starty;
20222						$h = $this->h - $this->y - $this->bMargin;
20223					} else { // middle column
20224						$cborder = $border_middle;
20225						$h = $this->h - $this->y - $this->bMargin;
20226					}
20227					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20228				} // end for each column
20229			} elseif ($page == $endpage) { // last page
20230				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20231					$this->selectColumn($column);
20232					if ($column == $endcolumn) {
20233						// end column
20234						$cborder = $border_end;
20235						$h = $currentY - $this->y;
20236					} else {
20237						// middle column
20238						$cborder = $border_middle;
20239						$h = $this->h - $this->y - $this->bMargin;
20240					}
20241					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20242				} // end for each column
20243			} else { // middle page
20244				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20245					$this->selectColumn($column);
20246					$cborder = $border_middle;
20247					$h = $this->h - $this->y - $this->bMargin;
20248					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20249				} // end for each column
20250			}
20251			if ($cborder OR $fill) {
20252				$offsetlen = strlen($ccode);
20253				// draw border and fill
20254				if ($this->inxobj) {
20255					// we are inside an XObject template
20256					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20257						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20258						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20259						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20260					} else {
20261						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20262						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20263					}
20264					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20265					$pstart = substr($pagebuff, 0, $pagemark);
20266					$pend = substr($pagebuff, $pagemark);
20267					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20268				} else {
20269					if (end($this->transfmrk[$this->page]) !== false) {
20270						$pagemarkkey = key($this->transfmrk[$this->page]);
20271						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20272					} elseif ($this->InFooter) {
20273						$pagemark = $this->footerpos[$this->page];
20274					} else {
20275						$pagemark = $this->intmrk[$this->page];
20276					}
20277					$pagebuff = $this->getPageBuffer($this->page);
20278					$pstart = substr($pagebuff, 0, $pagemark);
20279					$pend = substr($pagebuff, $pagemark);
20280					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20281					$this->bordermrk[$this->page] += $offsetlen;
20282					$this->cntmrk[$this->page] += $offsetlen;
20283				}
20284			}
20285		} // end for each page
20286		// restore page regions
20287		$this->page_regions = $temp_page_regions;
20288		if (isset($old_bgcolor)) {
20289			// restore background color
20290			$this->SetFillColorArray($old_bgcolor);
20291		}
20292		// restore pointer position
20293		$this->x = $prev_x;
20294		$this->y = $prev_y;
20295		$this->lasth = $prev_lasth;
20296	}
20297
20298	/**
20299	 * Set the default bullet to be used as LI bullet symbol
20300	 * @param string $symbol character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20301	 * @public
20302	 * @since 4.0.028 (2008-09-26)
20303	 */
20304	public function setLIsymbol($symbol='!') {
20305		// check for custom image symbol
20306		if (substr($symbol, 0, 4) == 'img|') {
20307			$this->lisymbol = $symbol;
20308			return;
20309		}
20310		$symbol = strtolower($symbol);
20311		$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');
20312		if (in_array($symbol, $valid_symbols)) {
20313			$this->lisymbol = $symbol;
20314		} else {
20315			$this->lisymbol = '';
20316		}
20317	}
20318
20319	/**
20320	 * Set the booklet mode for double-sided pages.
20321	 * @param boolean $booklet true set the booklet mode on, false otherwise.
20322	 * @param float $inner Inner page margin.
20323	 * @param float $outer Outer page margin.
20324	 * @public
20325	 * @since 4.2.000 (2008-10-29)
20326	 */
20327	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20328		$this->booklet = $booklet;
20329		if ($inner >= 0) {
20330			$this->lMargin = $inner;
20331		}
20332		if ($outer >= 0) {
20333			$this->rMargin = $outer;
20334		}
20335	}
20336
20337	/**
20338	 * Swap the left and right margins.
20339	 * @param boolean $reverse if true swap left and right margins.
20340	 * @protected
20341	 * @since 4.2.000 (2008-10-29)
20342	 */
20343	protected function swapMargins($reverse=true) {
20344		if ($reverse) {
20345			// swap left and right margins
20346			$mtemp = $this->original_lMargin;
20347			$this->original_lMargin = $this->original_rMargin;
20348			$this->original_rMargin = $mtemp;
20349			$deltam = $this->original_lMargin - $this->original_rMargin;
20350			$this->lMargin += $deltam;
20351			$this->rMargin -= $deltam;
20352		}
20353	}
20354
20355	/**
20356	 * Set the vertical spaces for HTML tags.
20357	 * The array must have the following structure (example):
20358	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20359	 * The first array level contains the tag names,
20360	 * the second level contains 0 for opening tags or 1 for closing tags,
20361	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20362	 * If the h parameter is not specified, default values are used.
20363	 * @param array $tagvs array of tags and relative vertical spaces.
20364	 * @public
20365	 * @since 4.2.001 (2008-10-30)
20366	 */
20367	public function setHtmlVSpace($tagvs) {
20368		$this->tagvspaces = $tagvs;
20369	}
20370
20371	/**
20372	 * Set custom width for list indentation.
20373	 * @param float $width width of the indentation. Use negative value to disable it.
20374	 * @public
20375	 * @since 4.2.007 (2008-11-12)
20376	 */
20377	public function setListIndentWidth($width) {
20378		return $this->customlistindent = floatval($width);
20379	}
20380
20381	/**
20382	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20383	 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20384	 * @public
20385	 * @since 4.2.010 (2008-11-14)
20386	 */
20387	public function setOpenCell($isopen) {
20388		$this->opencell = $isopen;
20389	}
20390
20391	/**
20392	 * Set the color and font style for HTML links.
20393	 * @param array $color RGB array of colors
20394	 * @param string $fontstyle additional font styles to add
20395	 * @public
20396	 * @since 4.4.003 (2008-12-09)
20397	 */
20398	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20399		$this->htmlLinkColorArray = $color;
20400		$this->htmlLinkFontStyle = $fontstyle;
20401	}
20402
20403	/**
20404	 * Convert HTML string containing value and unit of measure to user's units or points.
20405	 * @param string $htmlval String containing values and unit.
20406	 * @param string $refsize Reference value in points.
20407	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20408	 * @param boolean $points If true returns points, otherwise returns value in user's units.
20409	 * @return float value in user's unit or point if $points=true
20410	 * @public
20411	 * @since 4.4.004 (2008-12-10)
20412	 */
20413	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20414		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20415		$retval = 0;
20416		$value = 0;
20417		$unit = 'px';
20418		if ($points) {
20419			$k = 1;
20420		} else {
20421			$k = $this->k;
20422		}
20423		if (in_array($defaultunit, $supportedunits)) {
20424			$unit = $defaultunit;
20425		}
20426		if (is_numeric($htmlval)) {
20427			$value = floatval($htmlval);
20428		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20429			$value = floatval($mnum[1]);
20430			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20431				if (in_array($munit[1], $supportedunits)) {
20432					$unit = $munit[1];
20433				}
20434			}
20435		}
20436		switch ($unit) {
20437			// percentage
20438			case '%': {
20439				$retval = (($value * $refsize) / 100);
20440				break;
20441			}
20442			// relative-size
20443			case 'em': {
20444				$retval = ($value * $refsize);
20445				break;
20446			}
20447			// height of lower case 'x' (about half the font-size)
20448			case 'ex': {
20449				$retval = ($value * ($refsize / 2));
20450				break;
20451			}
20452			// absolute-size
20453			case 'in': {
20454				$retval = (($value * $this->dpi) / $k);
20455				break;
20456			}
20457			// centimeters
20458			case 'cm': {
20459				$retval = (($value / 2.54 * $this->dpi) / $k);
20460				break;
20461			}
20462			// millimeters
20463			case 'mm': {
20464				$retval = (($value / 25.4 * $this->dpi) / $k);
20465				break;
20466			}
20467			// one pica is 12 points
20468			case 'pc': {
20469				$retval = (($value * 12) / $k);
20470				break;
20471			}
20472			// points
20473			case 'pt': {
20474				$retval = ($value / $k);
20475				break;
20476			}
20477			// pixels
20478			case 'px': {
20479				$retval = $this->pixelsToUnits($value);
20480				if ($points) {
20481					$retval *= $this->k;
20482				}
20483				break;
20484			}
20485		}
20486		return $retval;
20487	}
20488
20489	/**
20490	 * Output an HTML list bullet or ordered item symbol
20491	 * @param int $listdepth list nesting level
20492	 * @param string $listtype type of list
20493	 * @param float $size current font size
20494	 * @protected
20495	 * @since 4.4.004 (2008-12-10)
20496	 */
20497	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20498		if ($this->state != 2) {
20499			return;
20500		}
20501		$size /= $this->k;
20502		$fill = '';
20503		$bgcolor = $this->bgcolor;
20504		$color = $this->fgcolor;
20505		$strokecolor = $this->strokecolor;
20506		$width = 0;
20507		$textitem = '';
20508		$tmpx = $this->x;
20509		$lspace = $this->GetStringWidth('  ');
20510		if ($listtype == '^') {
20511			// special symbol used for avoid justification of rect bullet
20512			$this->lispacer = '';
20513			return;
20514		} elseif ($listtype == '!') {
20515			// set default list type for unordered list
20516			$deftypes = array('disc', 'circle', 'square');
20517			$listtype = $deftypes[($listdepth - 1) % 3];
20518		} elseif ($listtype == '#') {
20519			// set default list type for ordered list
20520			$listtype = 'decimal';
20521		} elseif (substr($listtype, 0, 4) == 'img|') {
20522			// custom image type ('img|type|width|height|image.ext')
20523			$img = explode('|', $listtype);
20524			$listtype = 'img';
20525		}
20526		switch ($listtype) {
20527			// unordered types
20528			case 'none': {
20529				break;
20530			}
20531			case 'disc': {
20532				$r = $size / 6;
20533				$lspace += (2 * $r);
20534				if ($this->rtl) {
20535					$this->x += $lspace;
20536				} else {
20537					$this->x -= $lspace;
20538				}
20539				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20540				break;
20541			}
20542			case 'circle': {
20543				$r = $size / 6;
20544				$lspace += (2 * $r);
20545				if ($this->rtl) {
20546					$this->x += $lspace;
20547				} else {
20548					$this->x -= $lspace;
20549				}
20550				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20551				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20552				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20553				$this->_out($prev_line_style); // restore line settings
20554				break;
20555			}
20556			case 'square': {
20557				$l = $size / 3;
20558				$lspace += $l;
20559				if ($this->rtl) {;
20560					$this->x += $lspace;
20561				} else {
20562					$this->x -= $lspace;
20563				}
20564				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20565				break;
20566			}
20567			case 'img': {
20568				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20569				$lspace += $img[2];
20570				if ($this->rtl) {;
20571					$this->x += $lspace;
20572				} else {
20573					$this->x -= $lspace;
20574				}
20575				$imgtype = strtolower($img[1]);
20576				$prev_y = $this->y;
20577				switch ($imgtype) {
20578					case 'svg': {
20579						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20580						break;
20581					}
20582					case 'ai':
20583					case 'eps': {
20584						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20585						break;
20586					}
20587					default: {
20588						$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);
20589						break;
20590					}
20591				}
20592				$this->y = $prev_y;
20593				break;
20594			}
20595			// ordered types
20596			// $this->listcount[$this->listnum];
20597			// $textitem
20598			case '1':
20599			case 'decimal': {
20600				$textitem = $this->listcount[$this->listnum];
20601				break;
20602			}
20603			case 'decimal-leading-zero': {
20604				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20605				break;
20606			}
20607			case 'i':
20608			case 'lower-roman': {
20609				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20610				break;
20611			}
20612			case 'I':
20613			case 'upper-roman': {
20614				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20615				break;
20616			}
20617			case 'a':
20618			case 'lower-alpha':
20619			case 'lower-latin': {
20620				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20621				break;
20622			}
20623			case 'A':
20624			case 'upper-alpha':
20625			case 'upper-latin': {
20626				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20627				break;
20628			}
20629			case 'lower-greek': {
20630				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20631				break;
20632			}
20633			/*
20634			// Types to be implemented (special handling)
20635			case 'hebrew': {
20636				break;
20637			}
20638			case 'armenian': {
20639				break;
20640			}
20641			case 'georgian': {
20642				break;
20643			}
20644			case 'cjk-ideographic': {
20645				break;
20646			}
20647			case 'hiragana': {
20648				break;
20649			}
20650			case 'katakana': {
20651				break;
20652			}
20653			case 'hiragana-iroha': {
20654				break;
20655			}
20656			case 'katakana-iroha': {
20657				break;
20658			}
20659			*/
20660			default: {
20661				$textitem = $this->listcount[$this->listnum];
20662			}
20663		}
20664		if (!TCPDF_STATIC::empty_string($textitem)) {
20665			// Check whether we need a new page or new column
20666			$prev_y = $this->y;
20667			$h = $this->getCellHeight($this->FontSize);
20668			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20669				$tmpx = $this->x;
20670			}
20671			// print ordered item
20672			if ($this->rtl) {
20673				$textitem = '.'.$textitem;
20674			} else {
20675				$textitem = $textitem.'.';
20676			}
20677			$lspace += $this->GetStringWidth($textitem);
20678			if ($this->rtl) {
20679				$this->x += $lspace;
20680			} else {
20681				$this->x -= $lspace;
20682			}
20683			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20684		}
20685		$this->x = $tmpx;
20686		$this->lispacer = '^';
20687		// restore colors
20688		$this->SetFillColorArray($bgcolor);
20689		$this->SetDrawColorArray($strokecolor);
20690		$this->SettextColorArray($color);
20691	}
20692
20693	/**
20694	 * Returns current graphic variables as array.
20695	 * @return array of graphic variables
20696	 * @protected
20697	 * @since 4.2.010 (2008-11-14)
20698	 */
20699	protected function getGraphicVars() {
20700		$grapvars = array(
20701			'FontFamily' => $this->FontFamily,
20702			'FontStyle' => $this->FontStyle,
20703			'FontSizePt' => $this->FontSizePt,
20704			'rMargin' => $this->rMargin,
20705			'lMargin' => $this->lMargin,
20706			'cell_padding' => $this->cell_padding,
20707			'cell_margin' => $this->cell_margin,
20708			'LineWidth' => $this->LineWidth,
20709			'linestyleWidth' => $this->linestyleWidth,
20710			'linestyleCap' => $this->linestyleCap,
20711			'linestyleJoin' => $this->linestyleJoin,
20712			'linestyleDash' => $this->linestyleDash,
20713			'textrendermode' => $this->textrendermode,
20714			'textstrokewidth' => $this->textstrokewidth,
20715			'DrawColor' => $this->DrawColor,
20716			'FillColor' => $this->FillColor,
20717			'TextColor' => $this->TextColor,
20718			'ColorFlag' => $this->ColorFlag,
20719			'bgcolor' => $this->bgcolor,
20720			'fgcolor' => $this->fgcolor,
20721			'htmlvspace' => $this->htmlvspace,
20722			'listindent' => $this->listindent,
20723			'listindentlevel' => $this->listindentlevel,
20724			'listnum' => $this->listnum,
20725			'listordered' => $this->listordered,
20726			'listcount' => $this->listcount,
20727			'lispacer' => $this->lispacer,
20728			'cell_height_ratio' => $this->cell_height_ratio,
20729			'font_stretching' => $this->font_stretching,
20730			'font_spacing' => $this->font_spacing,
20731			'alpha' => $this->alpha,
20732			// extended
20733			'lasth' => $this->lasth,
20734			'tMargin' => $this->tMargin,
20735			'bMargin' => $this->bMargin,
20736			'AutoPageBreak' => $this->AutoPageBreak,
20737			'PageBreakTrigger' => $this->PageBreakTrigger,
20738			'x' => $this->x,
20739			'y' => $this->y,
20740			'w' => $this->w,
20741			'h' => $this->h,
20742			'wPt' => $this->wPt,
20743			'hPt' => $this->hPt,
20744			'fwPt' => $this->fwPt,
20745			'fhPt' => $this->fhPt,
20746			'page' => $this->page,
20747			'current_column' => $this->current_column,
20748			'num_columns' => $this->num_columns
20749			);
20750		return $grapvars;
20751	}
20752
20753	/**
20754	 * Set graphic variables.
20755	 * @param array $gvars array of graphic variablesto restore
20756	 * @param boolean $extended if true restore extended graphic variables
20757	 * @protected
20758	 * @since 4.2.010 (2008-11-14)
20759	 */
20760	protected function setGraphicVars($gvars, $extended=false) {
20761		if ($this->state != 2) {
20762			 return;
20763		}
20764		$this->FontFamily = $gvars['FontFamily'];
20765		$this->FontStyle = $gvars['FontStyle'];
20766		$this->FontSizePt = $gvars['FontSizePt'];
20767		$this->rMargin = $gvars['rMargin'];
20768		$this->lMargin = $gvars['lMargin'];
20769		$this->cell_padding = $gvars['cell_padding'];
20770		$this->cell_margin = $gvars['cell_margin'];
20771		$this->LineWidth = $gvars['LineWidth'];
20772		$this->linestyleWidth = $gvars['linestyleWidth'];
20773		$this->linestyleCap = $gvars['linestyleCap'];
20774		$this->linestyleJoin = $gvars['linestyleJoin'];
20775		$this->linestyleDash = $gvars['linestyleDash'];
20776		$this->textrendermode = $gvars['textrendermode'];
20777		$this->textstrokewidth = $gvars['textstrokewidth'];
20778		$this->DrawColor = $gvars['DrawColor'];
20779		$this->FillColor = $gvars['FillColor'];
20780		$this->TextColor = $gvars['TextColor'];
20781		$this->ColorFlag = $gvars['ColorFlag'];
20782		$this->bgcolor = $gvars['bgcolor'];
20783		$this->fgcolor = $gvars['fgcolor'];
20784		$this->htmlvspace = $gvars['htmlvspace'];
20785		$this->listindent = $gvars['listindent'];
20786		$this->listindentlevel = $gvars['listindentlevel'];
20787		$this->listnum = $gvars['listnum'];
20788		$this->listordered = $gvars['listordered'];
20789		$this->listcount = $gvars['listcount'];
20790		$this->lispacer = $gvars['lispacer'];
20791		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20792		$this->font_stretching = $gvars['font_stretching'];
20793		$this->font_spacing = $gvars['font_spacing'];
20794		$this->alpha = $gvars['alpha'];
20795		if ($extended) {
20796			// restore extended values
20797			$this->lasth = $gvars['lasth'];
20798			$this->tMargin = $gvars['tMargin'];
20799			$this->bMargin = $gvars['bMargin'];
20800			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20801			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20802			$this->x = $gvars['x'];
20803			$this->y = $gvars['y'];
20804			$this->w = $gvars['w'];
20805			$this->h = $gvars['h'];
20806			$this->wPt = $gvars['wPt'];
20807			$this->hPt = $gvars['hPt'];
20808			$this->fwPt = $gvars['fwPt'];
20809			$this->fhPt = $gvars['fhPt'];
20810			$this->page = $gvars['page'];
20811			$this->current_column = $gvars['current_column'];
20812			$this->num_columns = $gvars['num_columns'];
20813		}
20814		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20815		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20816			$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20817		}
20818	}
20819
20820	/**
20821	 * Outputs the "save graphics state" operator 'q'
20822	 * @protected
20823	 */
20824	protected function _outSaveGraphicsState() {
20825		$this->_out('q');
20826	}
20827
20828	/**
20829	 * Outputs the "restore graphics state" operator 'Q'
20830	 * @protected
20831	 */
20832	protected function _outRestoreGraphicsState() {
20833		$this->_out('Q');
20834	}
20835
20836	/**
20837	 * Set buffer content (always append data).
20838	 * @param string $data data
20839	 * @protected
20840	 * @since 4.5.000 (2009-01-02)
20841	 */
20842	protected function setBuffer($data) {
20843		$this->bufferlen += strlen($data);
20844		$this->buffer .= $data;
20845	}
20846
20847	/**
20848	 * Replace the buffer content
20849	 * @param string $data data
20850	 * @protected
20851	 * @since 5.5.000 (2010-06-22)
20852	 */
20853	protected function replaceBuffer($data) {
20854		$this->bufferlen = strlen($data);
20855		$this->buffer = $data;
20856	}
20857
20858	/**
20859	 * Get buffer content.
20860	 * @return string buffer content
20861	 * @protected
20862	 * @since 4.5.000 (2009-01-02)
20863	 */
20864	protected function getBuffer() {
20865		return $this->buffer;
20866	}
20867
20868	/**
20869	 * Set page buffer content.
20870	 * @param int $page page number
20871	 * @param string $data page data
20872	 * @param boolean $append if true append data, false replace.
20873	 * @protected
20874	 * @since 4.5.000 (2008-12-31)
20875	 */
20876	protected function setPageBuffer($page, $data, $append=false) {
20877		if ($append) {
20878			$this->pages[$page] .= $data;
20879		} else {
20880			$this->pages[$page] = $data;
20881		}
20882		if ($append AND isset($this->pagelen[$page])) {
20883			$this->pagelen[$page] += strlen($data);
20884		} else {
20885			$this->pagelen[$page] = strlen($data);
20886		}
20887	}
20888
20889	/**
20890	 * Get page buffer content.
20891	 * @param int $page page number
20892	 * @return string page buffer content or false in case of error
20893	 * @protected
20894	 * @since 4.5.000 (2008-12-31)
20895	 */
20896	protected function getPageBuffer($page) {
20897		if (isset($this->pages[$page])) {
20898			return $this->pages[$page];
20899		}
20900		return false;
20901	}
20902
20903	/**
20904	 * Set image buffer content.
20905	 * @param string $image image key
20906	 * @param array $data image data
20907	 * @return int image index number
20908	 * @protected
20909	 * @since 4.5.000 (2008-12-31)
20910	 */
20911	protected function setImageBuffer($image, $data) {
20912		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20913			$this->imagekeys[$this->numimages] = $image;
20914			$data['i'] = $this->numimages;
20915			++$this->numimages;
20916		}
20917		$this->images[$image] = $data;
20918		return $data['i'];
20919	}
20920
20921	/**
20922	 * Set image buffer content for a specified sub-key.
20923	 * @param string $image image key
20924	 * @param string $key image sub-key
20925	 * @param array $data image data
20926	 * @protected
20927	 * @since 4.5.000 (2008-12-31)
20928	 */
20929	protected function setImageSubBuffer($image, $key, $data) {
20930		if (!isset($this->images[$image])) {
20931			$this->setImageBuffer($image, array());
20932		}
20933		$this->images[$image][$key] = $data;
20934	}
20935
20936	/**
20937	 * Get image buffer content.
20938	 * @param string $image image key
20939	 * @return string|false image buffer content or false in case of error
20940	 * @protected
20941	 * @since 4.5.000 (2008-12-31)
20942	 */
20943	protected function getImageBuffer($image) {
20944		if (isset($this->images[$image])) {
20945			return $this->images[$image];
20946		}
20947		return false;
20948	}
20949
20950	/**
20951	 * Set font buffer content.
20952	 * @param string $font font key
20953	 * @param array $data font data
20954	 * @protected
20955	 * @since 4.5.000 (2009-01-02)
20956	 */
20957	protected function setFontBuffer($font, $data) {
20958		$this->fonts[$font] = $data;
20959		if (!in_array($font, $this->fontkeys)) {
20960			$this->fontkeys[] = $font;
20961			// store object ID for current font
20962			++$this->n;
20963			$this->font_obj_ids[$font] = $this->n;
20964			$this->setFontSubBuffer($font, 'n', $this->n);
20965		}
20966	}
20967
20968	/**
20969	 * Set font buffer content.
20970	 * @param string $font font key
20971	 * @param string $key font sub-key
20972	 * @param mixed $data font data
20973	 * @protected
20974	 * @since 4.5.000 (2009-01-02)
20975	 */
20976	protected function setFontSubBuffer($font, $key, $data) {
20977		if (!isset($this->fonts[$font])) {
20978			$this->setFontBuffer($font, array());
20979		}
20980		$this->fonts[$font][$key] = $data;
20981	}
20982
20983	/**
20984	 * Get font buffer content.
20985	 * @param string $font font key
20986	 * @return string|false font buffer content or false in case of error
20987	 * @protected
20988	 * @since 4.5.000 (2009-01-02)
20989	 */
20990	protected function getFontBuffer($font) {
20991		if (isset($this->fonts[$font])) {
20992			return $this->fonts[$font];
20993		}
20994		return false;
20995	}
20996
20997	/**
20998	 * Move a page to a previous position.
20999	 * @param int $frompage number of the source page
21000	 * @param int $topage number of the destination page (must be less than $frompage)
21001	 * @return bool true in case of success, false in case of error.
21002	 * @public
21003	 * @since 4.5.000 (2009-01-02)
21004	 */
21005	public function movePage($frompage, $topage) {
21006		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21007			return false;
21008		}
21009		if ($frompage == $this->page) {
21010			// close the page before moving it
21011			$this->endPage();
21012		}
21013		// move all page-related states
21014		$tmppage = $this->getPageBuffer($frompage);
21015		$tmppagedim = $this->pagedim[$frompage];
21016		$tmppagelen = $this->pagelen[$frompage];
21017		$tmpintmrk = $this->intmrk[$frompage];
21018		$tmpbordermrk = $this->bordermrk[$frompage];
21019		$tmpcntmrk = $this->cntmrk[$frompage];
21020		$tmppageobjects = $this->pageobjects[$frompage];
21021		if (isset($this->footerpos[$frompage])) {
21022			$tmpfooterpos = $this->footerpos[$frompage];
21023		}
21024		if (isset($this->footerlen[$frompage])) {
21025			$tmpfooterlen = $this->footerlen[$frompage];
21026		}
21027		if (isset($this->transfmrk[$frompage])) {
21028			$tmptransfmrk = $this->transfmrk[$frompage];
21029		}
21030		if (isset($this->PageAnnots[$frompage])) {
21031			$tmpannots = $this->PageAnnots[$frompage];
21032		}
21033		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21034			for ($i = $frompage; $i > $topage; --$i) {
21035				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21036					--$this->pagegroups[$this->newpagegroup[$i]];
21037					break;
21038				}
21039			}
21040			for ($i = $topage; $i > 0; --$i) {
21041				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21042					++$this->pagegroups[$this->newpagegroup[$i]];
21043					break;
21044				}
21045			}
21046		}
21047		for ($i = $frompage; $i > $topage; --$i) {
21048			$j = $i - 1;
21049			// shift pages down
21050			$this->setPageBuffer($i, $this->getPageBuffer($j));
21051			$this->pagedim[$i] = $this->pagedim[$j];
21052			$this->pagelen[$i] = $this->pagelen[$j];
21053			$this->intmrk[$i] = $this->intmrk[$j];
21054			$this->bordermrk[$i] = $this->bordermrk[$j];
21055			$this->cntmrk[$i] = $this->cntmrk[$j];
21056			$this->pageobjects[$i] = $this->pageobjects[$j];
21057			if (isset($this->footerpos[$j])) {
21058				$this->footerpos[$i] = $this->footerpos[$j];
21059			} elseif (isset($this->footerpos[$i])) {
21060				unset($this->footerpos[$i]);
21061			}
21062			if (isset($this->footerlen[$j])) {
21063				$this->footerlen[$i] = $this->footerlen[$j];
21064			} elseif (isset($this->footerlen[$i])) {
21065				unset($this->footerlen[$i]);
21066			}
21067			if (isset($this->transfmrk[$j])) {
21068				$this->transfmrk[$i] = $this->transfmrk[$j];
21069			} elseif (isset($this->transfmrk[$i])) {
21070				unset($this->transfmrk[$i]);
21071			}
21072			if (isset($this->PageAnnots[$j])) {
21073				$this->PageAnnots[$i] = $this->PageAnnots[$j];
21074			} elseif (isset($this->PageAnnots[$i])) {
21075				unset($this->PageAnnots[$i]);
21076			}
21077			if (isset($this->newpagegroup[$j])) {
21078				$this->newpagegroup[$i] = $this->newpagegroup[$j];
21079				unset($this->newpagegroup[$j]);
21080			}
21081			if ($this->currpagegroup == $j) {
21082				$this->currpagegroup = $i;
21083			}
21084		}
21085		$this->setPageBuffer($topage, $tmppage);
21086		$this->pagedim[$topage] = $tmppagedim;
21087		$this->pagelen[$topage] = $tmppagelen;
21088		$this->intmrk[$topage] = $tmpintmrk;
21089		$this->bordermrk[$topage] = $tmpbordermrk;
21090		$this->cntmrk[$topage] = $tmpcntmrk;
21091		$this->pageobjects[$topage] = $tmppageobjects;
21092		if (isset($tmpfooterpos)) {
21093			$this->footerpos[$topage] = $tmpfooterpos;
21094		} elseif (isset($this->footerpos[$topage])) {
21095			unset($this->footerpos[$topage]);
21096		}
21097		if (isset($tmpfooterlen)) {
21098			$this->footerlen[$topage] = $tmpfooterlen;
21099		} elseif (isset($this->footerlen[$topage])) {
21100			unset($this->footerlen[$topage]);
21101		}
21102		if (isset($tmptransfmrk)) {
21103			$this->transfmrk[$topage] = $tmptransfmrk;
21104		} elseif (isset($this->transfmrk[$topage])) {
21105			unset($this->transfmrk[$topage]);
21106		}
21107		if (isset($tmpannots)) {
21108			$this->PageAnnots[$topage] = $tmpannots;
21109		} elseif (isset($this->PageAnnots[$topage])) {
21110			unset($this->PageAnnots[$topage]);
21111		}
21112		// adjust outlines
21113		$tmpoutlines = $this->outlines;
21114		foreach ($tmpoutlines as $key => $outline) {
21115			if (!$outline['f']) {
21116				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21117					$this->outlines[$key]['p'] = ($outline['p'] + 1);
21118				} elseif ($outline['p'] == $frompage) {
21119					$this->outlines[$key]['p'] = $topage;
21120				}
21121			}
21122		}
21123		// adjust dests
21124		$tmpdests = $this->dests;
21125		foreach ($tmpdests as $key => $dest) {
21126			if (!$dest['f']) {
21127				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21128					$this->dests[$key]['p'] = ($dest['p'] + 1);
21129				} elseif ($dest['p'] == $frompage) {
21130					$this->dests[$key]['p'] = $topage;
21131				}
21132			}
21133		}
21134		// adjust links
21135		$tmplinks = $this->links;
21136		foreach ($tmplinks as $key => $link) {
21137			if (!$link['f']) {
21138				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21139					$this->links[$key]['p'] = ($link['p'] + 1);
21140				} elseif ($link['p'] == $frompage) {
21141					$this->links[$key]['p'] = $topage;
21142				}
21143			}
21144		}
21145		// adjust javascript
21146		$jfrompage = $frompage;
21147		$jtopage = $topage;
21148		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21149			foreach($pamatch[0] as $pk => $pmatch) {
21150				$pagenum = intval($pamatch[3][$pk]) + 1;
21151				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21152					$newpage = ($pagenum + 1);
21153				} elseif ($pagenum == $jfrompage) {
21154					$newpage = $jtopage;
21155				} else {
21156					$newpage = $pagenum;
21157				}
21158				--$newpage;
21159				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21160				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21161			}
21162			unset($pamatch);
21163		}
21164		// return to last page
21165		$this->lastPage(true);
21166		return true;
21167	}
21168
21169	/**
21170	 * Remove the specified page.
21171	 * @param int $page page to remove
21172	 * @return bool true in case of success, false in case of error.
21173	 * @public
21174	 * @since 4.6.004 (2009-04-23)
21175	 */
21176	public function deletePage($page) {
21177		if (($page < 1) OR ($page > $this->numpages)) {
21178			return false;
21179		}
21180		// delete current page
21181		unset($this->pages[$page]);
21182		unset($this->pagedim[$page]);
21183		unset($this->pagelen[$page]);
21184		unset($this->intmrk[$page]);
21185		unset($this->bordermrk[$page]);
21186		unset($this->cntmrk[$page]);
21187		foreach ($this->pageobjects[$page] as $oid) {
21188			if (isset($this->offsets[$oid])){
21189				unset($this->offsets[$oid]);
21190			}
21191		}
21192		unset($this->pageobjects[$page]);
21193		if (isset($this->footerpos[$page])) {
21194			unset($this->footerpos[$page]);
21195		}
21196		if (isset($this->footerlen[$page])) {
21197			unset($this->footerlen[$page]);
21198		}
21199		if (isset($this->transfmrk[$page])) {
21200			unset($this->transfmrk[$page]);
21201		}
21202		if (isset($this->PageAnnots[$page])) {
21203			unset($this->PageAnnots[$page]);
21204		}
21205		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21206			for ($i = $page; $i > 0; --$i) {
21207				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21208					--$this->pagegroups[$this->newpagegroup[$i]];
21209					break;
21210				}
21211			}
21212		}
21213		if (isset($this->pageopen[$page])) {
21214			unset($this->pageopen[$page]);
21215		}
21216		if ($page < $this->numpages) {
21217			// update remaining pages
21218			for ($i = $page; $i < $this->numpages; ++$i) {
21219				$j = $i + 1;
21220				// shift pages
21221				$this->setPageBuffer($i, $this->getPageBuffer($j));
21222				$this->pagedim[$i] = $this->pagedim[$j];
21223				$this->pagelen[$i] = $this->pagelen[$j];
21224				$this->intmrk[$i] = $this->intmrk[$j];
21225				$this->bordermrk[$i] = $this->bordermrk[$j];
21226				$this->cntmrk[$i] = $this->cntmrk[$j];
21227				$this->pageobjects[$i] = $this->pageobjects[$j];
21228				if (isset($this->footerpos[$j])) {
21229					$this->footerpos[$i] = $this->footerpos[$j];
21230				} elseif (isset($this->footerpos[$i])) {
21231					unset($this->footerpos[$i]);
21232				}
21233				if (isset($this->footerlen[$j])) {
21234					$this->footerlen[$i] = $this->footerlen[$j];
21235				} elseif (isset($this->footerlen[$i])) {
21236					unset($this->footerlen[$i]);
21237				}
21238				if (isset($this->transfmrk[$j])) {
21239					$this->transfmrk[$i] = $this->transfmrk[$j];
21240				} elseif (isset($this->transfmrk[$i])) {
21241					unset($this->transfmrk[$i]);
21242				}
21243				if (isset($this->PageAnnots[$j])) {
21244					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21245				} elseif (isset($this->PageAnnots[$i])) {
21246					unset($this->PageAnnots[$i]);
21247				}
21248				if (isset($this->newpagegroup[$j])) {
21249					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21250					unset($this->newpagegroup[$j]);
21251				}
21252				if ($this->currpagegroup == $j) {
21253					$this->currpagegroup = $i;
21254				}
21255				if (isset($this->pageopen[$j])) {
21256					$this->pageopen[$i] = $this->pageopen[$j];
21257				} elseif (isset($this->pageopen[$i])) {
21258					unset($this->pageopen[$i]);
21259				}
21260			}
21261			// remove last page
21262			unset($this->pages[$this->numpages]);
21263			unset($this->pagedim[$this->numpages]);
21264			unset($this->pagelen[$this->numpages]);
21265			unset($this->intmrk[$this->numpages]);
21266			unset($this->bordermrk[$this->numpages]);
21267			unset($this->cntmrk[$this->numpages]);
21268			foreach ($this->pageobjects[$this->numpages] as $oid) {
21269				if (isset($this->offsets[$oid])){
21270					unset($this->offsets[$oid]);
21271				}
21272			}
21273			unset($this->pageobjects[$this->numpages]);
21274			if (isset($this->footerpos[$this->numpages])) {
21275				unset($this->footerpos[$this->numpages]);
21276			}
21277			if (isset($this->footerlen[$this->numpages])) {
21278				unset($this->footerlen[$this->numpages]);
21279			}
21280			if (isset($this->transfmrk[$this->numpages])) {
21281				unset($this->transfmrk[$this->numpages]);
21282			}
21283			if (isset($this->PageAnnots[$this->numpages])) {
21284				unset($this->PageAnnots[$this->numpages]);
21285			}
21286			if (isset($this->newpagegroup[$this->numpages])) {
21287				unset($this->newpagegroup[$this->numpages]);
21288			}
21289			if ($this->currpagegroup == $this->numpages) {
21290				$this->currpagegroup = ($this->numpages - 1);
21291			}
21292			if (isset($this->pagegroups[$this->numpages])) {
21293				unset($this->pagegroups[$this->numpages]);
21294			}
21295			if (isset($this->pageopen[$this->numpages])) {
21296				unset($this->pageopen[$this->numpages]);
21297			}
21298		}
21299		--$this->numpages;
21300		$this->page = $this->numpages;
21301		// adjust outlines
21302		$tmpoutlines = $this->outlines;
21303		foreach ($tmpoutlines as $key => $outline) {
21304			if (!$outline['f']) {
21305				if ($outline['p'] > $page) {
21306					$this->outlines[$key]['p'] = $outline['p'] - 1;
21307				} elseif ($outline['p'] == $page) {
21308					unset($this->outlines[$key]);
21309				}
21310			}
21311		}
21312		// adjust dests
21313		$tmpdests = $this->dests;
21314		foreach ($tmpdests as $key => $dest) {
21315			if (!$dest['f']) {
21316				if ($dest['p'] > $page) {
21317					$this->dests[$key]['p'] = $dest['p'] - 1;
21318				} elseif ($dest['p'] == $page) {
21319					unset($this->dests[$key]);
21320				}
21321			}
21322		}
21323		// adjust links
21324		$tmplinks = $this->links;
21325		foreach ($tmplinks as $key => $link) {
21326			if (!$link['f']) {
21327				if ($link['p'] > $page) {
21328					$this->links[$key]['p'] = $link['p'] - 1;
21329				} elseif ($link['p'] == $page) {
21330					unset($this->links[$key]);
21331				}
21332			}
21333		}
21334		// adjust javascript
21335		$jpage = $page;
21336		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21337			foreach($pamatch[0] as $pk => $pmatch) {
21338				$pagenum = intval($pamatch[3][$pk]) + 1;
21339				if ($pagenum >= $jpage) {
21340					$newpage = ($pagenum - 1);
21341				} elseif ($pagenum == $jpage) {
21342					$newpage = 1;
21343				} else {
21344					$newpage = $pagenum;
21345				}
21346				--$newpage;
21347				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21348				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21349			}
21350			unset($pamatch);
21351		}
21352		// return to last page
21353		if ($this->numpages > 0) {
21354			$this->lastPage(true);
21355		}
21356		return true;
21357	}
21358
21359	/**
21360	 * Clone the specified page to a new page.
21361	 * @param int $page number of page to copy (0 = current page)
21362	 * @return bool true in case of success, false in case of error.
21363	 * @public
21364	 * @since 4.9.015 (2010-04-20)
21365	 */
21366	public function copyPage($page=0) {
21367		if ($page == 0) {
21368			// default value
21369			$page = $this->page;
21370		}
21371		if (($page < 1) OR ($page > $this->numpages)) {
21372			return false;
21373		}
21374		// close the last page
21375		$this->endPage();
21376		// copy all page-related states
21377		++$this->numpages;
21378		$this->page = $this->numpages;
21379		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21380		$this->pagedim[$this->page] = $this->pagedim[$page];
21381		$this->pagelen[$this->page] = $this->pagelen[$page];
21382		$this->intmrk[$this->page] = $this->intmrk[$page];
21383		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21384		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21385		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21386		$this->pageopen[$this->page] = false;
21387		if (isset($this->footerpos[$page])) {
21388			$this->footerpos[$this->page] = $this->footerpos[$page];
21389		}
21390		if (isset($this->footerlen[$page])) {
21391			$this->footerlen[$this->page] = $this->footerlen[$page];
21392		}
21393		if (isset($this->transfmrk[$page])) {
21394			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21395		}
21396		if (isset($this->PageAnnots[$page])) {
21397			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21398		}
21399		if (isset($this->newpagegroup[$page])) {
21400			// start a new group
21401			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21402			$this->currpagegroup = $this->newpagegroup[$this->page];
21403			$this->pagegroups[$this->currpagegroup] = 1;
21404		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21405			++$this->pagegroups[$this->currpagegroup];
21406		}
21407		// copy outlines
21408		$tmpoutlines = $this->outlines;
21409		foreach ($tmpoutlines as $key => $outline) {
21410			if ($outline['p'] == $page) {
21411				$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']);
21412			}
21413		}
21414		// copy links
21415		$tmplinks = $this->links;
21416		foreach ($tmplinks as $key => $link) {
21417			if ($link['p'] == $page) {
21418				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21419			}
21420		}
21421		// return to last page
21422		$this->lastPage(true);
21423		return true;
21424	}
21425
21426	/**
21427	 * Output a Table of Content Index (TOC).
21428	 * This method must be called after all Bookmarks were set.
21429	 * Before calling this method you have to open the page using the addTOCPage() method.
21430	 * After calling this method you have to call endTOCPage() to close the TOC page.
21431	 * You can override this method to achieve different styles.
21432	 * @param int $page page number where this TOC should be inserted (leave empty for current page).
21433	 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21434	 * @param string $filler string used to fill the space between text and page number.
21435	 * @param string $toc_name name to use for TOC bookmark.
21436	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21437	 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21438	 * @public
21439	 * @author Nicola Asuni
21440	 * @since 4.5.000 (2009-01-02)
21441	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21442	 */
21443	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21444		$fontsize = $this->FontSizePt;
21445		$fontfamily = $this->FontFamily;
21446		$fontstyle = $this->FontStyle;
21447		$w = $this->w - $this->lMargin - $this->rMargin;
21448		$spacer = $this->GetStringWidth(chr(32)) * 4;
21449		$lmargin = $this->lMargin;
21450		$rmargin = $this->rMargin;
21451		$x_start = $this->GetX();
21452		$page_first = $this->page;
21453		$current_page = $this->page;
21454		$page_fill_start = false;
21455		$page_fill_end = false;
21456		$current_column = $this->current_column;
21457		if (TCPDF_STATIC::empty_string($numbersfont)) {
21458			$numbersfont = $this->default_monospaced_font;
21459		}
21460		if (TCPDF_STATIC::empty_string($filler)) {
21461			$filler = ' ';
21462		}
21463		if (TCPDF_STATIC::empty_string($page)) {
21464			$gap = ' ';
21465		} else {
21466			$gap = '';
21467			if ($page < 1) {
21468				$page = 1;
21469			}
21470		}
21471		$this->SetFont($numbersfont, $fontstyle, $fontsize);
21472		$numwidth = $this->GetStringWidth('00000');
21473		$maxpage = 0; //used for pages on attached documents
21474		foreach ($this->outlines as $key => $outline) {
21475			// check for extra pages (used for attachments)
21476			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21477				$outline['p'] += ($this->page - $page_first);
21478			}
21479			if ($this->rtl) {
21480				$aligntext = 'R';
21481				$alignnum = 'L';
21482			} else {
21483				$aligntext = 'L';
21484				$alignnum = 'R';
21485			}
21486			if ($outline['l'] == 0) {
21487				$this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21488			} else {
21489				$this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21490			}
21491			$this->SetTextColorArray($outline['c']);
21492			// check for page break
21493			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21494			// set margins and X position
21495			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21496				$this->lMargin = $lmargin;
21497				$this->rMargin = $rmargin;
21498			} else {
21499				if ($this->current_column != $current_column) {
21500					if ($this->rtl) {
21501						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21502					} else {
21503						$x_start = $this->columns[$this->current_column]['x'];
21504					}
21505				}
21506				$lmargin = $this->lMargin;
21507				$rmargin = $this->rMargin;
21508				$current_page = $this->page;
21509				$current_column = $this->current_column;
21510			}
21511			$this->SetX($x_start);
21512			$indent = ($spacer * $outline['l']);
21513			if ($this->rtl) {
21514				$this->x -= $indent;
21515				$this->rMargin = $this->w - $this->x;
21516			} else {
21517				$this->x += $indent;
21518				$this->lMargin = $this->x;
21519			}
21520			$link = $this->AddLink();
21521			$this->SetLink($link, $outline['y'], $outline['p']);
21522			// write the text
21523			if ($this->rtl) {
21524				$txt = ' '.$outline['t'];
21525			} else {
21526				$txt = $outline['t'].' ';
21527			}
21528			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21529			if ($this->rtl) {
21530				$tw = $this->x - $this->lMargin;
21531			} else {
21532				$tw = $this->w - $this->rMargin - $this->x;
21533			}
21534			$this->SetFont($numbersfont, $fontstyle, $fontsize);
21535			if (TCPDF_STATIC::empty_string($page)) {
21536				$pagenum = $outline['p'];
21537			} else {
21538				// placemark to be replaced with the correct number
21539				$pagenum = '{#'.($outline['p']).'}';
21540				if ($this->isUnicodeFont()) {
21541					$pagenum = '{'.$pagenum.'}';
21542				}
21543				$maxpage = max($maxpage, $outline['p']);
21544			}
21545			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21546			$wfiller = $this->GetStringWidth($filler);
21547			if ($wfiller > 0) {
21548				$numfills = floor($fw / $wfiller);
21549			} else {
21550				$numfills = 0;
21551			}
21552			if ($numfills > 0) {
21553				$rowfill = str_repeat($filler, $numfills);
21554			} else {
21555				$rowfill = '';
21556			}
21557			if ($this->rtl) {
21558				$pagenum = $pagenum.$gap.$rowfill;
21559			} else {
21560				$pagenum = $rowfill.$gap.$pagenum;
21561			}
21562			// write the number
21563			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21564		}
21565		$page_last = $this->getPage();
21566		$numpages = ($page_last - $page_first + 1);
21567		// account for booklet mode
21568		if ($this->booklet) {
21569			// check if a blank page is required before TOC
21570			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21571			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21572			if ($page_fill_start) {
21573				// add a page at the end (to be moved before TOC)
21574				$this->addPage();
21575				++$page_last;
21576				++$numpages;
21577			}
21578			if ($page_fill_end) {
21579				// add a page at the end
21580				$this->addPage();
21581				++$page_last;
21582				++$numpages;
21583			}
21584		}
21585		$maxpage = max($maxpage, $page_last);
21586		if (!TCPDF_STATIC::empty_string($page)) {
21587			for ($p = $page_first; $p <= $page_last; ++$p) {
21588				// get page data
21589				$temppage = $this->getPageBuffer($p);
21590				for ($n = 1; $n <= $maxpage; ++$n) {
21591					// update page numbers
21592					$a = '{#'.$n.'}';
21593					// get page number aliases
21594					$pnalias = $this->getInternalPageNumberAliases($a);
21595					// calculate replacement number
21596					if (($n >= $page) AND ($n <= $this->numpages)) {
21597						$np = $n + $numpages;
21598					} else {
21599						$np = $n;
21600					}
21601					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21602					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21603					// replace aliases with numbers
21604					foreach ($pnalias['u'] as $u) {
21605						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21606						if ($this->rtl) {
21607							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21608						} else {
21609							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21610						}
21611						$temppage = str_replace($u, $nr, $temppage);
21612					}
21613					foreach ($pnalias['a'] as $a) {
21614						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21615						if ($this->rtl) {
21616							$nr = $na.' '.$sfill;
21617						} else {
21618							$nr = $sfill.' '.$na;
21619						}
21620						$temppage = str_replace($a, $nr, $temppage);
21621					}
21622				}
21623				// save changes
21624				$this->setPageBuffer($p, $temppage);
21625			}
21626			// move pages
21627			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21628			if ($page_fill_start) {
21629				$this->movePage($page_last, $page_first);
21630			}
21631			for ($i = 0; $i < $numpages; ++$i) {
21632				$this->movePage($page_last, $page);
21633			}
21634		}
21635	}
21636
21637	/**
21638	 * Output a Table Of Content Index (TOC) using HTML templates.
21639	 * This method must be called after all Bookmarks were set.
21640	 * Before calling this method you have to open the page using the addTOCPage() method.
21641	 * After calling this method you have to call endTOCPage() to close the TOC page.
21642	 * @param int $page page number where this TOC should be inserted (leave empty for current page).
21643	 * @param string $toc_name name to use for TOC bookmark.
21644	 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21645	 * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21646	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21647	 * @param array $color RGB color array for title (values from 0 to 255).
21648	 * @public
21649	 * @author Nicola Asuni
21650	 * @since 5.0.001 (2010-05-06)
21651	 * @see addTOCPage(), endTOCPage(), addTOC()
21652	 */
21653	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21654		$filler = ' ';
21655		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21656		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21657		// set new style for link
21658		$this->htmlLinkColorArray = array();
21659		$this->htmlLinkFontStyle = '';
21660		$page_first = $this->getPage();
21661		$page_fill_start = false;
21662		$page_fill_end = false;
21663		// get the font type used for numbers in each template
21664		$current_font = $this->FontFamily;
21665		foreach ($templates as $level => $html) {
21666			$dom = $this->getHtmlDomArray($html);
21667			foreach ($dom as $key => $value) {
21668				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21669					$this->SetFont($dom[($key - 1)]['fontname']);
21670					$templates['F'.$level] = $this->isUnicodeFont();
21671				}
21672			}
21673		}
21674		$this->SetFont($current_font);
21675		$maxpage = 0; //used for pages on attached documents
21676		foreach ($this->outlines as $key => $outline) {
21677			// get HTML template
21678			$row = $templates[$outline['l']];
21679			if (TCPDF_STATIC::empty_string($page)) {
21680				$pagenum = $outline['p'];
21681			} else {
21682				// placemark to be replaced with the correct number
21683				$pagenum = '{#'.($outline['p']).'}';
21684				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21685					$pagenum = '{'.$pagenum.'}';
21686				}
21687				$maxpage = max($maxpage, $outline['p']);
21688			}
21689			// replace templates with current values
21690			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21691			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21692			// add link to page
21693			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21694			// write bookmark entry
21695			$this->writeHTML($row, false, false, true, false, '');
21696		}
21697		// restore link styles
21698		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21699		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21700		// move TOC page and replace numbers
21701		$page_last = $this->getPage();
21702		$numpages = ($page_last - $page_first + 1);
21703		// account for booklet mode
21704		if ($this->booklet) {
21705			// check if a blank page is required before TOC
21706			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21707			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21708			if ($page_fill_start) {
21709				// add a page at the end (to be moved before TOC)
21710				$this->addPage();
21711				++$page_last;
21712				++$numpages;
21713			}
21714			if ($page_fill_end) {
21715				// add a page at the end
21716				$this->addPage();
21717				++$page_last;
21718				++$numpages;
21719			}
21720		}
21721		$maxpage = max($maxpage, $page_last);
21722		if (!TCPDF_STATIC::empty_string($page)) {
21723			for ($p = $page_first; $p <= $page_last; ++$p) {
21724				// get page data
21725				$temppage = $this->getPageBuffer($p);
21726				for ($n = 1; $n <= $maxpage; ++$n) {
21727					// update page numbers
21728					$a = '{#'.$n.'}';
21729					// get page number aliases
21730					$pnalias = $this->getInternalPageNumberAliases($a);
21731					// calculate replacement number
21732					if ($n >= $page) {
21733						$np = $n + $numpages;
21734					} else {
21735						$np = $n;
21736					}
21737					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21738					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21739					// replace aliases with numbers
21740					foreach ($pnalias['u'] as $u) {
21741						if ($correct_align) {
21742							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21743							if ($this->rtl) {
21744								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21745							} else {
21746								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21747							}
21748						} else {
21749							$nr = $nu;
21750						}
21751						$temppage = str_replace($u, $nr, $temppage);
21752					}
21753					foreach ($pnalias['a'] as $a) {
21754						if ($correct_align) {
21755							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21756							if ($this->rtl) {
21757								$nr = $na.' '.$sfill;
21758							} else {
21759								$nr = $sfill.' '.$na;
21760							}
21761						} else {
21762							$nr = $na;
21763						}
21764						$temppage = str_replace($a, $nr, $temppage);
21765					}
21766				}
21767				// save changes
21768				$this->setPageBuffer($p, $temppage);
21769			}
21770			// move pages
21771			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21772			if ($page_fill_start) {
21773				$this->movePage($page_last, $page_first);
21774			}
21775			for ($i = 0; $i < $numpages; ++$i) {
21776				$this->movePage($page_last, $page);
21777			}
21778		}
21779	}
21780
21781	/**
21782	 * Stores a copy of the current TCPDF object used for undo operation.
21783	 * @public
21784	 * @since 4.5.029 (2009-03-19)
21785	 */
21786	public function startTransaction() {
21787		if (isset($this->objcopy)) {
21788			// remove previous copy
21789			$this->commitTransaction();
21790		}
21791		// record current page number and Y position
21792		$this->start_transaction_page = $this->page;
21793		$this->start_transaction_y = $this->y;
21794		// clone current object
21795		$this->objcopy = TCPDF_STATIC::objclone($this);
21796	}
21797
21798	/**
21799	 * Delete the copy of the current TCPDF object used for undo operation.
21800	 * @public
21801	 * @since 4.5.029 (2009-03-19)
21802	 */
21803	public function commitTransaction() {
21804		if (isset($this->objcopy)) {
21805			$this->objcopy->_destroy(true, true);
21806			/* The unique file_id should not be used during cleanup again */
21807			$this->objcopy->file_id = NULL;
21808			unset($this->objcopy);
21809		}
21810	}
21811
21812	/**
21813	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21814	 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21815	 * @return TCPDF object.
21816	 * @public
21817	 * @since 4.5.029 (2009-03-19)
21818	 */
21819	public function rollbackTransaction($self=false) {
21820		if (isset($this->objcopy)) {
21821			$objcopy = $this->objcopy;
21822			$this->_destroy(true, true);
21823			if ($self) {
21824				$objvars = get_object_vars($objcopy);
21825				foreach ($objvars as $key => $value) {
21826					$this->$key = $value;
21827				}
21828				$objcopy->_destroy(true, true);
21829				/* The unique file_id should not be used during cleanup again */
21830				$objcopy->file_id = NULL;
21831				unset($objcopy);
21832				return $this;
21833			}
21834			/* The unique file_id should not be used during cleanup again */
21835			$this->file_id = NULL;
21836			return $objcopy;
21837		}
21838		return $this;
21839	}
21840
21841	// --- MULTI COLUMNS METHODS -----------------------
21842
21843	/**
21844	 * Set multiple columns of the same size
21845	 * @param int $numcols number of columns (set to zero to disable columns mode)
21846	 * @param int $width column width
21847	 * @param int $y column starting Y position (leave empty for current Y position)
21848	 * @public
21849	 * @since 4.9.001 (2010-03-28)
21850	 */
21851	public function setEqualColumns($numcols=0, $width=0, $y='') {
21852		$this->columns = array();
21853		if ($numcols < 2) {
21854			$numcols = 0;
21855			$this->columns = array();
21856		} else {
21857			// maximum column width
21858			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21859			if (($width == 0) OR ($width > $maxwidth)) {
21860				$width = $maxwidth;
21861			}
21862			if (TCPDF_STATIC::empty_string($y)) {
21863				$y = $this->y;
21864			}
21865			// space between columns
21866			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21867			// fill the columns array (with, space, starting Y position)
21868			for ($i = 0; $i < $numcols; ++$i) {
21869				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21870			}
21871		}
21872		$this->num_columns = $numcols;
21873		$this->current_column = 0;
21874		$this->column_start_page = $this->page;
21875		$this->selectColumn(0);
21876	}
21877
21878	/**
21879	 * Remove columns and reset page margins.
21880	 * @public
21881	 * @since 5.9.072 (2011-04-26)
21882	 */
21883	public function resetColumns() {
21884		$this->lMargin = $this->original_lMargin;
21885		$this->rMargin = $this->original_rMargin;
21886		$this->setEqualColumns();
21887	}
21888
21889	/**
21890	 * Set columns array.
21891	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21892	 * @param array $columns
21893	 * @public
21894	 * @since 4.9.001 (2010-03-28)
21895	 */
21896	public function setColumnsArray($columns) {
21897		$this->columns = $columns;
21898		$this->num_columns = count($columns);
21899		$this->current_column = 0;
21900		$this->column_start_page = $this->page;
21901		$this->selectColumn(0);
21902	}
21903
21904	/**
21905	 * Set position at a given column
21906	 * @param int $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21907	 * @public
21908	 * @since 4.9.001 (2010-03-28)
21909	 */
21910	public function selectColumn($col='') {
21911		if (is_string($col)) {
21912			$col = $this->current_column;
21913		} elseif ($col >= $this->num_columns) {
21914			$col = 0;
21915		}
21916		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21917		$enable_thead = false;
21918		if ($this->num_columns > 1) {
21919			if ($col != $this->current_column) {
21920				// move Y pointer at the top of the column
21921				if ($this->column_start_page == $this->page) {
21922					$this->y = $this->columns[$col]['y'];
21923				} else {
21924					$this->y = $this->tMargin;
21925				}
21926				// Avoid to write table headers more than once
21927				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21928					$enable_thead = true;
21929					$this->maxselcol['page'] = $this->page;
21930					$this->maxselcol['column'] = $col;
21931				}
21932			}
21933			$xshift = $this->colxshift;
21934			// set X position of the current column by case
21935			$listindent = ($this->listindentlevel * $this->listindent);
21936			// calculate column X position
21937			$colpos = 0;
21938			for ($i = 0; $i < $col; ++$i) {
21939				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21940			}
21941			if ($this->rtl) {
21942				$x = $this->w - $this->original_rMargin - $colpos;
21943				$this->rMargin = ($this->w - $x + $listindent);
21944				$this->lMargin = ($x - $this->columns[$col]['w']);
21945				$this->x = $x - $listindent;
21946			} else {
21947				$x = $this->original_lMargin + $colpos;
21948				$this->lMargin = ($x + $listindent);
21949				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21950				$this->x = $x + $listindent;
21951			}
21952			$this->columns[$col]['x'] = $x;
21953		}
21954		$this->current_column = $col;
21955		// fix for HTML mode
21956		$this->newline = true;
21957		// print HTML table header (if any)
21958		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21959			if ($enable_thead) {
21960				// print table header
21961				$this->writeHTML($this->thead, false, false, false, false, '');
21962				$this->y += $xshift['s']['V'];
21963				// store end of header position
21964				if (!isset($this->columns[$col]['th'])) {
21965					$this->columns[$col]['th'] = array();
21966				}
21967				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21968				$this->lasth = 0;
21969			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21970				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21971			}
21972		}
21973		// account for an html table cell over multiple columns
21974		if ($this->rtl) {
21975			$this->rMargin += $xshift['x'];
21976			$this->x -= ($xshift['x'] + $xshift['p']['R']);
21977		} else {
21978			$this->lMargin += $xshift['x'];
21979			$this->x += $xshift['x'] + $xshift['p']['L'];
21980		}
21981	}
21982
21983	/**
21984	 * Return the current column number
21985	 * @return int current column number
21986	 * @public
21987	 * @since 5.5.011 (2010-07-08)
21988	 */
21989	public function getColumn() {
21990		return $this->current_column;
21991	}
21992
21993	/**
21994	 * Return the current number of columns.
21995	 * @return int number of columns
21996	 * @public
21997	 * @since 5.8.018 (2010-08-25)
21998	 */
21999	public function getNumberOfColumns() {
22000		return $this->num_columns;
22001	}
22002
22003	/**
22004	 * Set Text rendering mode.
22005	 * @param int $stroke outline size in user units (0 = disable).
22006	 * @param boolean $fill if true fills the text (default).
22007	 * @param boolean $clip if true activate clipping mode
22008	 * @public
22009	 * @since 4.9.008 (2009-04-02)
22010	 */
22011	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22012		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22013		// convert text rendering parameters
22014		if ($stroke < 0) {
22015			$stroke = 0;
22016		}
22017		if ($fill === true) {
22018			if ($stroke > 0) {
22019				if ($clip === true) {
22020					// Fill, then stroke text and add to path for clipping
22021					$textrendermode = 6;
22022				} else {
22023					// Fill, then stroke text
22024					$textrendermode = 2;
22025				}
22026				$textstrokewidth = $stroke;
22027			} else {
22028				if ($clip === true) {
22029					// Fill text and add to path for clipping
22030					$textrendermode = 4;
22031				} else {
22032					// Fill text
22033					$textrendermode = 0;
22034				}
22035			}
22036		} else {
22037			if ($stroke > 0) {
22038				if ($clip === true) {
22039					// Stroke text and add to path for clipping
22040					$textrendermode = 5;
22041				} else {
22042					// Stroke text
22043					$textrendermode = 1;
22044				}
22045				$textstrokewidth = $stroke;
22046			} else {
22047				if ($clip === true) {
22048					// Add text to path for clipping
22049					$textrendermode = 7;
22050				} else {
22051					// Neither fill nor stroke text (invisible)
22052					$textrendermode = 3;
22053				}
22054			}
22055		}
22056		$this->textrendermode = $textrendermode;
22057		$this->textstrokewidth = $stroke;
22058	}
22059
22060	/**
22061	 * Set parameters for drop shadow effect for text.
22062	 * @param array $params Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
22063	 * @since 5.9.174 (2012-07-25)
22064	 * @public
22065	*/
22066	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22067		if (isset($params['enabled'])) {
22068			$this->txtshadow['enabled'] = $params['enabled']?true:false;
22069		} else {
22070			$this->txtshadow['enabled'] = false;
22071		}
22072		if (isset($params['depth_w'])) {
22073			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
22074		} else {
22075			$this->txtshadow['depth_w'] = 0;
22076		}
22077		if (isset($params['depth_h'])) {
22078			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
22079		} else {
22080			$this->txtshadow['depth_h'] = 0;
22081		}
22082		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22083			$this->txtshadow['color'] = $params['color'];
22084		} else {
22085			$this->txtshadow['color'] = $this->strokecolor;
22086		}
22087		if (isset($params['opacity'])) {
22088			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22089		} else {
22090			$this->txtshadow['opacity'] = 1;
22091		}
22092		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'))) {
22093			$this->txtshadow['blend_mode'] = $params['blend_mode'];
22094		} else {
22095			$this->txtshadow['blend_mode'] = 'Normal';
22096		}
22097		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22098			$this->txtshadow['enabled'] = false;
22099		}
22100	}
22101
22102	/**
22103	 * Return the text shadow parameters array.
22104	 * @return array array of parameters.
22105	 * @since 5.9.174 (2012-07-25)
22106	 * @public
22107	 */
22108	public function getTextShadow() {
22109		return $this->txtshadow;
22110	}
22111
22112	/**
22113	 * Returns an array of chars containing soft hyphens.
22114	 * @param array $word array of chars
22115	 * @param array $patterns Array of hypenation patterns.
22116	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22117	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22118	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22119	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22120	 * @param int $charmax Maximum length of broken piece of word.
22121	 * @return array text with soft hyphens
22122	 * @author Nicola Asuni
22123	 * @since 4.9.012 (2010-04-12)
22124	 * @protected
22125	 */
22126	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22127		$hyphenword = array(); // hyphens positions
22128		$numchars = count($word);
22129		if ($numchars <= $charmin) {
22130			return $word;
22131		}
22132		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22133		// some words will be returned as-is
22134		$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})(\]?)$/';
22135		if (preg_match($pattern, $word_string) > 0) {
22136			// email
22137			return $word;
22138		}
22139		$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})(\]?)$/';
22140		if (preg_match($pattern, $word_string) > 0) {
22141			// URL
22142			return $word;
22143		}
22144		if (isset($dictionary[$word_string])) {
22145			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22146		}
22147		// surround word with '_' characters
22148		$tmpword = array_merge(array(46), $word, array(46));
22149		$tmpnumchars = $numchars + 2;
22150		$maxpos = $tmpnumchars - 1;
22151		for ($pos = 0; $pos < $maxpos; ++$pos) {
22152			$imax = min(($tmpnumchars - $pos), $charmax);
22153			for ($i = 1; $i <= $imax; ++$i) {
22154				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22155				if (isset($patterns[$subword])) {
22156					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22157					$pattern_length = count($pattern);
22158					$digits = 1;
22159					for ($j = 0; $j < $pattern_length; ++$j) {
22160						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22161						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22162							if ($j == 0) {
22163								$zero = $pos - 1;
22164							} else {
22165								$zero = $pos + $j - $digits;
22166							}
22167							// get hyphenation level
22168							$level = ($pattern[$j] - 48);
22169							// if two levels from two different patterns match at the same point, the higher one is selected.
22170							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22171								$hyphenword[$zero] = $level;
22172							}
22173							++$digits;
22174						}
22175					}
22176				}
22177			}
22178		}
22179		$inserted = 0;
22180		$maxpos = $numchars - $rightmin;
22181		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22182			// only odd levels indicate allowed hyphenation points
22183			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22184				// 173 = soft hyphen character
22185				array_splice($word, $i + $inserted, 0, 173);
22186				++$inserted;
22187			}
22188		}
22189		return $word;
22190	}
22191
22192	/**
22193	 * Returns text with soft hyphens.
22194	 * @param string $text text to process
22195	 * @param mixed $patterns Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22196	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22197	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22198	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22199	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22200	 * @param int $charmax Maximum length of broken piece of word.
22201	 * @return string text with soft hyphens
22202	 * @author Nicola Asuni
22203	 * @since 4.9.012 (2010-04-12)
22204	 * @public
22205	 */
22206	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22207		$text = $this->unhtmlentities($text);
22208		$word = array(); // last word
22209		$txtarr = array(); // text to be returned
22210		$intag = false; // true if we are inside an HTML tag
22211		$skip = false; // true to skip hyphenation
22212		if (!is_array($patterns)) {
22213			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22214		}
22215		// get array of characters
22216		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22217		// for each char
22218		foreach ($unichars as $char) {
22219			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22220				// letter character
22221				$word[] = $char;
22222			} else {
22223				// other type of character
22224				if (!TCPDF_STATIC::empty_string($word)) {
22225					// hypenate the word
22226					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22227					$word = array();
22228				}
22229				$txtarr[] = $char;
22230				if (chr($char) == '<') {
22231					// we are inside an HTML tag
22232					$intag = true;
22233				} elseif ($intag AND (chr($char) == '>')) {
22234					// end of HTML tag
22235					$intag = false;
22236					// check for style tag
22237					$expected = array(115, 116, 121, 108, 101); // = 'style'
22238					$current = array_slice($txtarr, -6, 5); // last 5 chars
22239					$compare = array_diff($expected, $current);
22240					if (empty($compare)) {
22241						// check if it is a closing tag
22242						$expected = array(47); // = '/'
22243						$current = array_slice($txtarr, -7, 1);
22244						$compare = array_diff($expected, $current);
22245						if (empty($compare)) {
22246							// closing style tag
22247							$skip = false;
22248						} else {
22249							// opening style tag
22250							$skip = true;
22251						}
22252					}
22253				}
22254			}
22255		}
22256		if (!TCPDF_STATIC::empty_string($word)) {
22257			// hypenate the word
22258			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22259		}
22260		// convert char array to string and return
22261		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22262	}
22263
22264	/**
22265	 * Enable/disable rasterization of vector images using ImageMagick library.
22266	 * @param boolean $mode if true enable rasterization, false otherwise.
22267	 * @public
22268	 * @since 5.0.000 (2010-04-27)
22269	 */
22270	public function setRasterizeVectorImages($mode) {
22271		$this->rasterize_vector_images = $mode;
22272	}
22273
22274	/**
22275	 * Enable or disable default option for font subsetting.
22276	 * @param boolean $enable if true enable font subsetting by default.
22277	 * @author Nicola Asuni
22278	 * @public
22279	 * @since 5.3.002 (2010-06-07)
22280	 */
22281	public function setFontSubsetting($enable=true) {
22282		if ($this->pdfa_mode) {
22283			$this->font_subsetting = false;
22284		} else {
22285			$this->font_subsetting = $enable ? true : false;
22286		}
22287	}
22288
22289	/**
22290	 * Return the default option for font subsetting.
22291	 * @return bool default font subsetting state.
22292	 * @author Nicola Asuni
22293	 * @public
22294	 * @since 5.3.002 (2010-06-07)
22295	 */
22296	public function getFontSubsetting() {
22297		return $this->font_subsetting;
22298	}
22299
22300	/**
22301	 * Left trim the input string
22302	 * @param string $str string to trim
22303	 * @param string $replace string that replace spaces.
22304	 * @return string left trimmed string
22305	 * @author Nicola Asuni
22306	 * @public
22307	 * @since 5.8.000 (2010-08-11)
22308	 */
22309	public function stringLeftTrim($str, $replace='') {
22310		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22311	}
22312
22313	/**
22314	 * Right trim the input string
22315	 * @param string $str string to trim
22316	 * @param string $replace string that replace spaces.
22317	 * @return string right trimmed string
22318	 * @author Nicola Asuni
22319	 * @public
22320	 * @since 5.8.000 (2010-08-11)
22321	 */
22322	public function stringRightTrim($str, $replace='') {
22323		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22324	}
22325
22326	/**
22327	 * Trim the input string
22328	 * @param string $str string to trim
22329	 * @param string $replace string that replace spaces.
22330	 * @return string trimmed string
22331	 * @author Nicola Asuni
22332	 * @public
22333	 * @since 5.8.000 (2010-08-11)
22334	 */
22335	public function stringTrim($str, $replace='') {
22336		$str = $this->stringLeftTrim($str, $replace);
22337		$str = $this->stringRightTrim($str, $replace);
22338		return $str;
22339	}
22340
22341	/**
22342	 * Return true if the current font is unicode type.
22343	 * @return true for unicode font, false otherwise.
22344	 * @author Nicola Asuni
22345	 * @public
22346	 * @since 5.8.002 (2010-08-14)
22347	 */
22348	public function isUnicodeFont() {
22349		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22350	}
22351
22352	/**
22353	 * Return normalized font name
22354	 * @param string $fontfamily property string containing font family names
22355	 * @return string normalized font name
22356	 * @author Nicola Asuni
22357	 * @public
22358	 * @since 5.8.004 (2010-08-17)
22359	 */
22360	public function getFontFamilyName($fontfamily) {
22361		// remove spaces and symbols
22362		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22363		// extract all font names
22364		$fontslist = preg_split('/[,]/', $fontfamily);
22365		// find first valid font name
22366		foreach ($fontslist as $font) {
22367			// replace font variations
22368			$font = preg_replace('/regular$/', '', $font);
22369			$font = preg_replace('/italic$/', 'I', $font);
22370			$font = preg_replace('/oblique$/', 'I', $font);
22371			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22372			// replace common family names and core fonts
22373			$pattern = array();
22374			$replacement = array();
22375			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22376			$replacement[] = 'times';
22377			$pattern[] = '/^sansserif/';
22378			$replacement[] = 'helvetica';
22379			$pattern[] = '/^monospace/';
22380			$replacement[] = 'courier';
22381			$font = preg_replace($pattern, $replacement, $font);
22382			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22383				return $font;
22384			}
22385		}
22386		// return current font as default
22387		return $this->CurrentFont['fontkey'];
22388	}
22389
22390	/**
22391	 * Start a new XObject Template.
22392	 * 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).
22393	 * 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.
22394	 * Note: X,Y coordinates will be reset to 0,0.
22395	 * @param int $w Template width in user units (empty string or zero = page width less margins).
22396	 * @param int $h Template height in user units (empty string or zero = page height less margins).
22397	 * @param mixed $group Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22398	 * @return string|false the XObject Template ID in case of success or false in case of error.
22399	 * @author Nicola Asuni
22400	 * @public
22401	 * @since 5.8.017 (2010-08-24)
22402	 * @see endTemplate(), printTemplate()
22403	 */
22404	public function startTemplate($w=0, $h=0, $group=false) {
22405		if ($this->inxobj) {
22406			// we are already inside an XObject template
22407			return false;
22408		}
22409		$this->inxobj = true;
22410		++$this->n;
22411		// XObject ID
22412		$this->xobjid = 'XT'.$this->n;
22413		// object ID
22414		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22415		// store current graphic state
22416		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22417		// initialize data
22418		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22419		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22420		$this->xobjects[$this->xobjid]['outdata'] = '';
22421		$this->xobjects[$this->xobjid]['xobjects'] = array();
22422		$this->xobjects[$this->xobjid]['images'] = array();
22423		$this->xobjects[$this->xobjid]['fonts'] = array();
22424		$this->xobjects[$this->xobjid]['annotations'] = array();
22425		$this->xobjects[$this->xobjid]['extgstates'] = array();
22426		$this->xobjects[$this->xobjid]['gradients'] = array();
22427		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22428		// set new environment
22429		$this->num_columns = 1;
22430		$this->current_column = 0;
22431		$this->SetAutoPageBreak(false);
22432		if (($w === '') OR ($w <= 0)) {
22433			$w = $this->w - $this->lMargin - $this->rMargin;
22434		}
22435		if (($h === '') OR ($h <= 0)) {
22436			$h = $this->h - $this->tMargin - $this->bMargin;
22437		}
22438		$this->xobjects[$this->xobjid]['x'] = 0;
22439		$this->xobjects[$this->xobjid]['y'] = 0;
22440		$this->xobjects[$this->xobjid]['w'] = $w;
22441		$this->xobjects[$this->xobjid]['h'] = $h;
22442		$this->w = $w;
22443		$this->h = $h;
22444		$this->wPt = $this->w * $this->k;
22445		$this->hPt = $this->h * $this->k;
22446		$this->fwPt = $this->wPt;
22447		$this->fhPt = $this->hPt;
22448		$this->x = 0;
22449		$this->y = 0;
22450		$this->lMargin = 0;
22451		$this->rMargin = 0;
22452		$this->tMargin = 0;
22453		$this->bMargin = 0;
22454		// set group mode
22455		$this->xobjects[$this->xobjid]['group'] = $group;
22456		return $this->xobjid;
22457	}
22458
22459	/**
22460	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22461	 * 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).
22462	 * 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.
22463	 * @return string|false the XObject Template ID in case of success or false in case of error.
22464	 * @author Nicola Asuni
22465	 * @public
22466	 * @since 5.8.017 (2010-08-24)
22467	 * @see startTemplate(), printTemplate()
22468	 */
22469	public function endTemplate() {
22470		if (!$this->inxobj) {
22471			// we are not inside a template
22472			return false;
22473		}
22474		$this->inxobj = false;
22475		// restore previous graphic state
22476		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22477		return $this->xobjid;
22478	}
22479
22480	/**
22481	 * Print an XObject Template.
22482	 * You can print an XObject Template inside the currently opened Template.
22483	 * 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).
22484	 * 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.
22485	 * @param string $id The ID of XObject Template to print.
22486	 * @param int $x X position in user units (empty string = current x position)
22487	 * @param int $y Y position in user units (empty string = current y position)
22488	 * @param int $w Width in user units (zero = remaining page width)
22489	 * @param int $h Height in user units (zero = remaining page height)
22490	 * @param string $align Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22491	 * @param string $palign Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22492	 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22493	 * @author Nicola Asuni
22494	 * @public
22495	 * @since 5.8.017 (2010-08-24)
22496	 * @see startTemplate(), endTemplate()
22497	 */
22498	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22499		if ($this->state != 2) {
22500			 return;
22501		}
22502		if (!isset($this->xobjects[$id])) {
22503			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22504		}
22505		if ($this->inxobj) {
22506			if ($id == $this->xobjid) {
22507				// close current template
22508				$this->endTemplate();
22509			} else {
22510				// use the template as resource for the template currently opened
22511				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22512			}
22513		}
22514		// set default values
22515		if ($x === '') {
22516			$x = $this->x;
22517		}
22518		if ($y === '') {
22519			$y = $this->y;
22520		}
22521		// check page for no-write regions and adapt page margins if necessary
22522		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22523		$ow = $this->xobjects[$id]['w'];
22524		if ($ow <= 0) {
22525			$ow = 1;
22526		}
22527		$oh = $this->xobjects[$id]['h'];
22528		if ($oh <= 0) {
22529			$oh = 1;
22530		}
22531		// calculate template width and height on document
22532		if (($w <= 0) AND ($h <= 0)) {
22533			$w = $ow;
22534			$h = $oh;
22535		} elseif ($w <= 0) {
22536			$w = $h * $ow / $oh;
22537		} elseif ($h <= 0) {
22538			$h = $w * $oh / $ow;
22539		}
22540		// fit the template on available space
22541		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22542		// set page alignment
22543		$rb_y = $y + $h;
22544		// set alignment
22545		if ($this->rtl) {
22546			if ($palign == 'L') {
22547				$xt = $this->lMargin;
22548			} elseif ($palign == 'C') {
22549				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22550			} elseif ($palign == 'R') {
22551				$xt = $this->w - $this->rMargin - $w;
22552			} else {
22553				$xt = $x - $w;
22554			}
22555			$rb_x = $xt;
22556		} else {
22557			if ($palign == 'L') {
22558				$xt = $this->lMargin;
22559			} elseif ($palign == 'C') {
22560				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22561			} elseif ($palign == 'R') {
22562				$xt = $this->w - $this->rMargin - $w;
22563			} else {
22564				$xt = $x;
22565			}
22566			$rb_x = $xt + $w;
22567		}
22568		// print XObject Template + Transformation matrix
22569		$this->StartTransform();
22570		// translate and scale
22571		$sx = ($w / $ow);
22572		$sy = ($h / $oh);
22573		$tm = array();
22574		$tm[0] = $sx;
22575		$tm[1] = 0;
22576		$tm[2] = 0;
22577		$tm[3] = $sy;
22578		$tm[4] = $xt * $this->k;
22579		$tm[5] = ($this->h - $h - $y) * $this->k;
22580		$this->Transform($tm);
22581		// set object
22582		$this->_out('/'.$id.' Do');
22583		$this->StopTransform();
22584		// add annotations
22585		if (!empty($this->xobjects[$id]['annotations'])) {
22586			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22587				// transform original coordinates
22588				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22589				$ax = ($coordlt[4] / $this->k);
22590				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22591				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22592				$aw = ($coordrb[4] / $this->k) - $ax;
22593				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22594				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22595			}
22596		}
22597		// set pointer to align the next text/objects
22598		switch($align) {
22599			case 'T': {
22600				$this->y = $y;
22601				$this->x = $rb_x;
22602				break;
22603			}
22604			case 'M': {
22605				$this->y = $y + round($h/2);
22606				$this->x = $rb_x;
22607				break;
22608			}
22609			case 'B': {
22610				$this->y = $rb_y;
22611				$this->x = $rb_x;
22612				break;
22613			}
22614			case 'N': {
22615				$this->SetY($rb_y);
22616				break;
22617			}
22618			default:{
22619				break;
22620			}
22621		}
22622	}
22623
22624	/**
22625	 * Set the percentage of character stretching.
22626	 * @param int $perc percentage of stretching (100 = no stretching)
22627	 * @author Nicola Asuni
22628	 * @public
22629	 * @since 5.9.000 (2010-09-29)
22630	 */
22631	public function setFontStretching($perc=100) {
22632		$this->font_stretching = $perc;
22633	}
22634
22635	/**
22636	 * Get the percentage of character stretching.
22637	 * @return float stretching value
22638	 * @author Nicola Asuni
22639	 * @public
22640	 * @since 5.9.000 (2010-09-29)
22641	 */
22642	public function getFontStretching() {
22643		return $this->font_stretching;
22644	}
22645
22646	/**
22647	 * Set the amount to increase or decrease the space between characters in a text.
22648	 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22649	 * @author Nicola Asuni
22650	 * @public
22651	 * @since 5.9.000 (2010-09-29)
22652	 */
22653	public function setFontSpacing($spacing=0) {
22654		$this->font_spacing = $spacing;
22655	}
22656
22657	/**
22658	 * Get the amount to increase or decrease the space between characters in a text.
22659	 * @return int font spacing (tracking) value
22660	 * @author Nicola Asuni
22661	 * @public
22662	 * @since 5.9.000 (2010-09-29)
22663	 */
22664	public function getFontSpacing() {
22665		return $this->font_spacing;
22666	}
22667
22668	/**
22669	 * Return an array of no-write page regions
22670	 * @return array of no-write page regions
22671	 * @author Nicola Asuni
22672	 * @public
22673	 * @since 5.9.003 (2010-10-13)
22674	 * @see setPageRegions(), addPageRegion()
22675	 */
22676	public function getPageRegions() {
22677		return $this->page_regions;
22678	}
22679
22680	/**
22681	 * Set no-write regions on page.
22682	 * 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.
22683	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22684	 * You can set multiple regions for the same page.
22685	 * @param array $regions array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22686	 * @author Nicola Asuni
22687	 * @public
22688	 * @since 5.9.003 (2010-10-13)
22689	 * @see addPageRegion(), getPageRegions()
22690	 */
22691	public function setPageRegions($regions=array()) {
22692		// empty current regions array
22693		$this->page_regions = array();
22694		// add regions
22695		foreach ($regions as $data) {
22696			$this->addPageRegion($data);
22697		}
22698	}
22699
22700	/**
22701	 * Add a single no-write region on selected page.
22702	 * 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.
22703	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22704	 * You can set multiple regions for the same page.
22705	 * @param array $region array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22706	 * @author Nicola Asuni
22707	 * @public
22708	 * @since 5.9.003 (2010-10-13)
22709	 * @see setPageRegions(), getPageRegions()
22710	 */
22711	public function addPageRegion($region) {
22712		if (!isset($region['page']) OR empty($region['page'])) {
22713			$region['page'] = $this->page;
22714		}
22715		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22716			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22717			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22718			$this->page_regions[] = $region;
22719		}
22720	}
22721
22722	/**
22723	 * Remove a single no-write region.
22724	 * @param int $key region key
22725	 * @author Nicola Asuni
22726	 * @public
22727	 * @since 5.9.003 (2010-10-13)
22728	 * @see setPageRegions(), getPageRegions()
22729	 */
22730	public function removePageRegion($key) {
22731		if (isset($this->page_regions[$key])) {
22732			unset($this->page_regions[$key]);
22733		}
22734	}
22735
22736	/**
22737	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22738	 * 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.
22739	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22740	 * @param float $h height of the text/image/object to print in user units
22741	 * @param float $x current X coordinate in user units
22742	 * @param float $y current Y coordinate in user units
22743	 * @return float[] array($x, $y)
22744	 * @author Nicola Asuni
22745	 * @protected
22746	 * @since 5.9.003 (2010-10-13)
22747	 */
22748	protected function checkPageRegions($h, $x, $y) {
22749		// set default values
22750		if ($x === '') {
22751			$x = $this->x;
22752		}
22753		if ($y === '') {
22754			$y = $this->y;
22755		}
22756		if (!$this->check_page_regions OR empty($this->page_regions)) {
22757			// no page regions defined
22758			return array($x, $y);
22759		}
22760		if (empty($h)) {
22761			$h = $this->getCellHeight($this->FontSize);
22762		}
22763		// check for page break
22764		if ($this->checkPageBreak($h, $y)) {
22765			// the content will be printed on a new page
22766			$x = $this->x;
22767			$y = $this->y;
22768		}
22769		if ($this->num_columns > 1) {
22770			if ($this->rtl) {
22771				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22772			} else {
22773				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22774			}
22775		} else {
22776			if ($this->rtl) {
22777				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22778			} else {
22779				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22780			}
22781		}
22782		// adjust coordinates and page margins
22783		foreach ($this->page_regions as $regid => $regdata) {
22784			if ($regdata['page'] == $this->page) {
22785				// check region boundaries
22786				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22787					// Y is inside the region
22788					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22789					$yt = max($y, $regdata['yt']);
22790					$yb = min(($yt + $h), $regdata['yb']);
22791					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22792					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22793					if ($regdata['side'] == 'L') { // left side
22794						$new_margin = max($xt, $xb);
22795						if ($this->lMargin < $new_margin) {
22796							if ($this->rtl) {
22797								// adjust left page margin
22798								$this->lMargin = max(0, $new_margin);
22799							}
22800							if ($x < $new_margin) {
22801								// adjust x position
22802								$x = $new_margin;
22803								if ($new_margin > ($this->w - $this->rMargin)) {
22804									// adjust y position
22805									$y = $regdata['yb'] - $h;
22806								}
22807							}
22808						}
22809					} elseif ($regdata['side'] == 'R') { // right side
22810						$new_margin = min($xt, $xb);
22811						if (($this->w - $this->rMargin) > $new_margin) {
22812							if (!$this->rtl) {
22813								// adjust right page margin
22814								$this->rMargin = max(0, ($this->w - $new_margin));
22815							}
22816							if ($x > $new_margin) {
22817								// adjust x position
22818								$x = $new_margin;
22819								if ($new_margin > $this->lMargin) {
22820									// adjust y position
22821									$y = $regdata['yb'] - $h;
22822								}
22823							}
22824						}
22825					}
22826				}
22827			}
22828		}
22829		return array($x, $y);
22830	}
22831
22832	// --- SVG METHODS ---------------------------------------------------------
22833
22834	/**
22835	 * Embedd a Scalable Vector Graphics (SVG) image.
22836	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22837	 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
22838	 * @param float $x Abscissa of the upper-left corner.
22839	 * @param float $y Ordinate of the upper-left corner.
22840	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22841	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22842	 * @param mixed $link URL or identifier returned by AddLink().
22843	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
22844	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22845	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22846	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
22847	 * @author Nicola Asuni
22848	 * @since 5.0.000 (2010-05-02)
22849	 * @public
22850	 */
22851	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22852		if ($this->state != 2) {
22853			 return;
22854		}
22855		// reset SVG vars
22856		$this->svggradients = array();
22857		$this->svggradientid = 0;
22858		$this->svgdefsmode = false;
22859		$this->svgdefs = array();
22860		$this->svgclipmode = false;
22861		$this->svgclippaths = array();
22862		$this->svgcliptm = array();
22863		$this->svgclipid = 0;
22864		$this->svgtext = '';
22865		$this->svgtextmode = array();
22866		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22867			// convert SVG to raster image using GD or ImageMagick libraries
22868			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22869		}
22870		if ($file[0] === '@') { // image from string
22871			$this->svgdir = '';
22872			$svgdata = substr($file, 1);
22873		} else { // SVG file
22874			$this->svgdir = dirname($file);
22875            $svgdata = $this->getCachedFileContents($file);
22876		}
22877		if ($svgdata === FALSE) {
22878			$this->Error('SVG file not found: '.$file);
22879		}
22880		if ($x === '') {
22881			$x = $this->x;
22882		}
22883		if ($y === '') {
22884			$y = $this->y;
22885		}
22886		// check page for no-write regions and adapt page margins if necessary
22887		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22888		$k = $this->k;
22889		$ox = 0;
22890		$oy = 0;
22891		$ow = $w;
22892		$oh = $h;
22893		$aspect_ratio_align = 'xMidYMid';
22894		$aspect_ratio_ms = 'meet';
22895		$regs = array();
22896		// get original image width and height
22897		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22898		if (isset($regs[1]) AND !empty($regs[1])) {
22899			$tmp = array();
22900			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22901				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22902			}
22903			$tmp = array();
22904			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22905				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22906			}
22907			$tmp = array();
22908			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22909				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22910			}
22911			$tmp = array();
22912			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22913				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22914			}
22915			$tmp = array();
22916			$view_box = array();
22917			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22918				if (count($tmp) == 5) {
22919					array_shift($tmp);
22920					foreach ($tmp as $key => $val) {
22921						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22922					}
22923					$ox = $view_box[0];
22924					$oy = $view_box[1];
22925				}
22926				// get aspect ratio
22927				$tmp = array();
22928				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22929					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22930					switch (count($aspect_ratio)) {
22931						case 3: {
22932							$aspect_ratio_align = $aspect_ratio[1];
22933							$aspect_ratio_ms = $aspect_ratio[2];
22934							break;
22935						}
22936						case 2: {
22937							$aspect_ratio_align = $aspect_ratio[0];
22938							$aspect_ratio_ms = $aspect_ratio[1];
22939							break;
22940						}
22941						case 1: {
22942							$aspect_ratio_align = $aspect_ratio[0];
22943							$aspect_ratio_ms = 'meet';
22944							break;
22945						}
22946					}
22947				}
22948			}
22949		}
22950		if ($ow <= 0) {
22951			$ow = 1;
22952		}
22953		if ($oh <= 0) {
22954			$oh = 1;
22955		}
22956		// calculate image width and height on document
22957		if (($w <= 0) AND ($h <= 0)) {
22958			// convert image size to document unit
22959			$w = $ow;
22960			$h = $oh;
22961		} elseif ($w <= 0) {
22962			$w = $h * $ow / $oh;
22963		} elseif ($h <= 0) {
22964			$h = $w * $oh / $ow;
22965		}
22966		// fit the image on available space
22967		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22968		if ($this->rasterize_vector_images) {
22969			// convert SVG to raster image using GD or ImageMagick libraries
22970			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22971		}
22972		// set alignment
22973		$this->img_rb_y = $y + $h;
22974		// set alignment
22975		if ($this->rtl) {
22976			if ($palign == 'L') {
22977				$ximg = $this->lMargin;
22978			} elseif ($palign == 'C') {
22979				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22980			} elseif ($palign == 'R') {
22981				$ximg = $this->w - $this->rMargin - $w;
22982			} else {
22983				$ximg = $x - $w;
22984			}
22985			$this->img_rb_x = $ximg;
22986		} else {
22987			if ($palign == 'L') {
22988				$ximg = $this->lMargin;
22989			} elseif ($palign == 'C') {
22990				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22991			} elseif ($palign == 'R') {
22992				$ximg = $this->w - $this->rMargin - $w;
22993			} else {
22994				$ximg = $x;
22995			}
22996			$this->img_rb_x = $ximg + $w;
22997		}
22998		// store current graphic vars
22999		$gvars = $this->getGraphicVars();
23000		// store SVG position and scale factors
23001		$svgoffset_x = ($ximg - $ox) * $this->k;
23002		$svgoffset_y = -($y - $oy) * $this->k;
23003		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23004			$ow = $view_box[2];
23005			$oh = $view_box[3];
23006		} else {
23007			if ($ow <= 0) {
23008				$ow = $w;
23009			}
23010			if ($oh <= 0) {
23011				$oh = $h;
23012			}
23013		}
23014		$svgscale_x = $w / $ow;
23015		$svgscale_y = $h / $oh;
23016		// scaling and alignment
23017		if ($aspect_ratio_align != 'none') {
23018			// store current scaling values
23019			$svgscale_old_x = $svgscale_x;
23020			$svgscale_old_y = $svgscale_y;
23021			// force uniform scaling
23022			if ($aspect_ratio_ms == 'slice') {
23023				// the entire viewport is covered by the viewBox
23024				if ($svgscale_x > $svgscale_y) {
23025					$svgscale_y = $svgscale_x;
23026				} elseif ($svgscale_x < $svgscale_y) {
23027					$svgscale_x = $svgscale_y;
23028				}
23029			} else { // meet
23030				// the entire viewBox is visible within the viewport
23031				if ($svgscale_x < $svgscale_y) {
23032					$svgscale_y = $svgscale_x;
23033				} elseif ($svgscale_x > $svgscale_y) {
23034					$svgscale_x = $svgscale_y;
23035				}
23036			}
23037			// correct X alignment
23038			switch (substr($aspect_ratio_align, 1, 3)) {
23039				case 'Min': {
23040					// do nothing
23041					break;
23042				}
23043				case 'Max': {
23044					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23045					break;
23046				}
23047				default:
23048				case 'Mid': {
23049					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23050					break;
23051				}
23052			}
23053			// correct Y alignment
23054			switch (substr($aspect_ratio_align, 5)) {
23055				case 'Min': {
23056					// do nothing
23057					break;
23058				}
23059				case 'Max': {
23060					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23061					break;
23062				}
23063				default:
23064				case 'Mid': {
23065					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23066					break;
23067				}
23068			}
23069		}
23070		// store current page break mode
23071		$page_break_mode = $this->AutoPageBreak;
23072		$page_break_margin = $this->getBreakMargin();
23073		$cell_padding = $this->cell_padding;
23074		$this->SetCellPadding(0);
23075		$this->SetAutoPageBreak(false);
23076		// save the current graphic state
23077		$this->_out('q'.$this->epsmarker);
23078		// set initial clipping mask
23079		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23080		// scale and translate
23081		$e = $ox * $this->k * (1 - $svgscale_x);
23082		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23083		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23084		// creates a new XML parser to be used by the other XML functions
23085		$parser = xml_parser_create('UTF-8');
23086		// the following function allows to use parser inside object
23087		xml_set_object($parser, $this);
23088		// disable case-folding for this XML parser
23089		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23090		// sets the element handler functions for the XML parser
23091		xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler');
23092		// sets the character data handler function for the XML parser
23093		xml_set_character_data_handler($parser, 'segSVGContentHandler');
23094		// start parsing an XML document
23095		if (!xml_parse($parser, $svgdata)) {
23096			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23097			$this->Error($error_message);
23098		}
23099		// free this XML parser
23100		xml_parser_free($parser);
23101
23102		// >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23103		unset($parser);
23104
23105		// restore previous graphic state
23106		$this->_out($this->epsmarker.'Q');
23107		// restore graphic vars
23108		$this->setGraphicVars($gvars);
23109		$this->lasth = $gvars['lasth'];
23110		if (!empty($border)) {
23111			$bx = $this->x;
23112			$by = $this->y;
23113			$this->x = $ximg;
23114			if ($this->rtl) {
23115				$this->x += $w;
23116			}
23117			$this->y = $y;
23118			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23119			$this->x = $bx;
23120			$this->y = $by;
23121		}
23122		if ($link) {
23123			$this->Link($ximg, $y, $w, $h, $link, 0);
23124		}
23125		// set pointer to align the next text/objects
23126		switch($align) {
23127			case 'T':{
23128				$this->y = $y;
23129				$this->x = $this->img_rb_x;
23130				break;
23131			}
23132			case 'M':{
23133				$this->y = $y + round($h/2);
23134				$this->x = $this->img_rb_x;
23135				break;
23136			}
23137			case 'B':{
23138				$this->y = $this->img_rb_y;
23139				$this->x = $this->img_rb_x;
23140				break;
23141			}
23142			case 'N':{
23143				$this->SetY($this->img_rb_y);
23144				break;
23145			}
23146			default:{
23147				// restore pointer to starting position
23148				$this->x = $gvars['x'];
23149				$this->y = $gvars['y'];
23150				$this->page = $gvars['page'];
23151				$this->current_column = $gvars['current_column'];
23152				$this->tMargin = $gvars['tMargin'];
23153				$this->bMargin = $gvars['bMargin'];
23154				$this->w = $gvars['w'];
23155				$this->h = $gvars['h'];
23156				$this->wPt = $gvars['wPt'];
23157				$this->hPt = $gvars['hPt'];
23158				$this->fwPt = $gvars['fwPt'];
23159				$this->fhPt = $gvars['fhPt'];
23160				break;
23161			}
23162		}
23163		$this->endlinex = $this->img_rb_x;
23164		// restore page break
23165		$this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23166		$this->cell_padding = $cell_padding;
23167	}
23168
23169	/**
23170	 * Convert SVG transformation matrix to PDF.
23171	 * @param array $tm original SVG transformation matrix
23172	 * @return array transformation matrix
23173	 * @protected
23174	 * @since 5.0.000 (2010-05-02)
23175	 */
23176	protected function convertSVGtMatrix($tm) {
23177		$a = $tm[0];
23178		$b = -$tm[1];
23179		$c = -$tm[2];
23180		$d = $tm[3];
23181		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23182		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23183		$x = 0;
23184		$y = $this->h * $this->k;
23185		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23186		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23187		return array($a, $b, $c, $d, $e, $f);
23188	}
23189
23190	/**
23191	 * Apply SVG graphic transformation matrix.
23192	 * @param array $tm original SVG transformation matrix
23193	 * @protected
23194	 * @since 5.0.000 (2010-05-02)
23195	 */
23196	protected function SVGTransform($tm) {
23197		$this->Transform($this->convertSVGtMatrix($tm));
23198	}
23199
23200	/**
23201	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23202	 * @param array $svgstyle array of SVG styles to apply
23203	 * @param array $prevsvgstyle array of previous SVG style
23204	 * @param int $x X origin of the bounding box
23205	 * @param int $y Y origin of the bounding box
23206	 * @param int $w width of the bounding box
23207	 * @param int $h height of the bounding box
23208	 * @param string $clip_function clip function
23209	 * @param array $clip_params array of parameters for clipping function
23210	 * @return string style
23211	 * @author Nicola Asuni
23212	 * @since 5.0.000 (2010-05-02)
23213	 * @protected
23214	 */
23215	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23216		if ($this->state != 2) {
23217			 return;
23218		}
23219		$objstyle = '';
23220		$minlen = (0.01 / $this->k); // minimum acceptable length
23221		if (!isset($svgstyle['opacity'])) {
23222			return $objstyle;
23223		}
23224		// clip-path
23225		$regs = array();
23226		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23227			$clip_path = $this->svgclippaths[$regs[1]];
23228			foreach ($clip_path as $cp) {
23229				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23230			}
23231		}
23232		// opacity
23233		if ($svgstyle['opacity'] != 1) {
23234			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23235		}
23236		// color
23237		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23238		$this->SetFillColorArray($fill_color);
23239		// text color
23240		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23241		$this->SetTextColorArray($text_color);
23242		// clip
23243		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23244			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23245			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23246			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23247			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23248			$cx = $x + $left;
23249			$cy = $y + $top;
23250			$cw = $w - $left - $right;
23251			$ch = $h - $top - $bottom;
23252			if ($svgstyle['clip-rule'] == 'evenodd') {
23253				$clip_rule = 'CNZ';
23254			} else {
23255				$clip_rule = 'CEO';
23256			}
23257			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23258		}
23259		// fill
23260		$regs = array();
23261		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23262			// gradient
23263			$gradient = $this->svggradients[$regs[1]];
23264			if (isset($gradient['xref'])) {
23265				// reference to another gradient definition
23266				$newgradient = $this->svggradients[$gradient['xref']];
23267				$newgradient['coords'] = $gradient['coords'];
23268				$newgradient['mode'] = $gradient['mode'];
23269				$newgradient['type'] = $gradient['type'];
23270				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23271				if (isset($gradient['gradientTransform'])) {
23272					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23273				}
23274				$gradient = $newgradient;
23275			}
23276			//save current Graphic State
23277			$this->_outSaveGraphicsState();
23278			//set clipping area
23279			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23280				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23281				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23282					list($x, $y, $w, $h) = $bbox;
23283				}
23284			}
23285			if ($gradient['mode'] == 'measure') {
23286				if (!isset($gradient['coords'][4])) {
23287					$gradient['coords'][4] = 0.5;
23288				}
23289				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23290					$gtm = $gradient['gradientTransform'];
23291					// apply transformation matrix
23292					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23293					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23294					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23295					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23296					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23297					$gradient['coords'][0] = $xa;
23298					$gradient['coords'][1] = $ya;
23299					$gradient['coords'][2] = $xb;
23300					$gradient['coords'][3] = $yb;
23301					$gradient['coords'][4] = $r;
23302				}
23303				// convert SVG coordinates to user units
23304				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23305				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23306				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23307				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23308				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23309				if ($w <= $minlen) {
23310					$w = $minlen;
23311				}
23312				if ($h <= $minlen) {
23313					$h = $minlen;
23314				}
23315				// shift units
23316				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23317					// convert to SVG coordinate system
23318					$gradient['coords'][0] += $x;
23319					$gradient['coords'][1] += $y;
23320					$gradient['coords'][2] += $x;
23321					$gradient['coords'][3] += $y;
23322				}
23323				// calculate percentages
23324				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23325				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23326				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23327				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23328				$gradient['coords'][4] /= $w;
23329			} elseif ($gradient['mode'] == 'percentage') {
23330				foreach($gradient['coords'] as $key => $val) {
23331					$gradient['coords'][$key] = (intval($val) / 100);
23332					if ($val < 0) {
23333						$gradient['coords'][$key] = 0;
23334					} elseif ($val > 1) {
23335						$gradient['coords'][$key] = 1;
23336					}
23337				}
23338			}
23339			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23340				// single color (no shading)
23341				$gradient['coords'][0] = 1;
23342				$gradient['coords'][1] = 0;
23343				$gradient['coords'][2] = 0.999;
23344				$gradient['coords'][3] = 0;
23345			}
23346			// swap Y coordinates
23347			$tmp = $gradient['coords'][1];
23348			$gradient['coords'][1] = $gradient['coords'][3];
23349			$gradient['coords'][3] = $tmp;
23350			// set transformation map for gradient
23351			$cy = ($this->h - $y);
23352			if ($gradient['type'] == 3) {
23353				// circular gradient
23354				$cy -= ($gradient['coords'][1] * ($w + $h));
23355				$h = $w = max($w, $h);
23356			} else {
23357				$cy -= $h;
23358			}
23359			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23360			if (count($gradient['stops']) > 1) {
23361				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23362			}
23363		} elseif ($svgstyle['fill'] != 'none') {
23364			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23365			if ($svgstyle['fill-opacity'] != 1) {
23366				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23367			}
23368			$this->SetFillColorArray($fill_color);
23369			if ($svgstyle['fill-rule'] == 'evenodd') {
23370				$objstyle .= 'F*';
23371			} else {
23372				$objstyle .= 'F';
23373			}
23374		}
23375		// stroke
23376		if ($svgstyle['stroke'] != 'none') {
23377			if ($svgstyle['stroke-opacity'] != 1) {
23378				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23379			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23380				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23381			}
23382			$stroke_style = array(
23383				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23384				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23385				'cap' => $svgstyle['stroke-linecap'],
23386				'join' => $svgstyle['stroke-linejoin']
23387				);
23388			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23389				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23390			}
23391			$this->SetLineStyle($stroke_style);
23392			$objstyle .= 'D';
23393		}
23394		// font
23395		$regs = array();
23396		if (!empty($svgstyle['font'])) {
23397			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23398				$font_family = $this->getFontFamilyName($regs[1]);
23399			} else {
23400				$font_family = $svgstyle['font-family'];
23401			}
23402			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23403				$font_size = trim($regs[1]);
23404			} else {
23405				$font_size = $svgstyle['font-size'];
23406			}
23407			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23408				$font_style = trim($regs[1]);
23409			} else {
23410				$font_style = $svgstyle['font-style'];
23411			}
23412			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23413				$font_weight = trim($regs[1]);
23414			} else {
23415				$font_weight = $svgstyle['font-weight'];
23416			}
23417			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23418				$font_stretch = trim($regs[1]);
23419			} else {
23420				$font_stretch = $svgstyle['font-stretch'];
23421			}
23422			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23423				$font_spacing = trim($regs[1]);
23424			} else {
23425				$font_spacing = $svgstyle['letter-spacing'];
23426			}
23427		} else {
23428			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23429			$font_size = $svgstyle['font-size'];
23430			$font_style = $svgstyle['font-style'];
23431			$font_weight = $svgstyle['font-weight'];
23432			$font_stretch = $svgstyle['font-stretch'];
23433			$font_spacing = $svgstyle['letter-spacing'];
23434		}
23435		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23436		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23437		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23438		switch ($font_style) {
23439			case 'italic': {
23440				$font_style = 'I';
23441				break;
23442			}
23443			case 'oblique': {
23444				$font_style = 'I';
23445				break;
23446			}
23447			default:
23448			case 'normal': {
23449				$font_style = '';
23450				break;
23451			}
23452		}
23453		switch ($font_weight) {
23454			case 'bold':
23455			case 'bolder': {
23456				$font_style .= 'B';
23457				break;
23458			}
23459			case 'normal': {
23460				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23461					$font_family = substr($font_family, 0, -2).'I';
23462				} elseif (substr($font_family, -1) == 'B') {
23463					$font_family = substr($font_family, 0, -1);
23464				}
23465				break;
23466			}
23467		}
23468		switch ($svgstyle['text-decoration']) {
23469			case 'underline': {
23470				$font_style .= 'U';
23471				break;
23472			}
23473			case 'overline': {
23474				$font_style .= 'O';
23475				break;
23476			}
23477			case 'line-through': {
23478				$font_style .= 'D';
23479				break;
23480			}
23481			default:
23482			case 'none': {
23483				break;
23484			}
23485		}
23486		$this->SetFont($font_family, $font_style, $font_size);
23487		$this->setFontStretching($font_stretch);
23488		$this->setFontSpacing($font_spacing);
23489		return $objstyle;
23490	}
23491
23492	/**
23493	 * Draws an SVG path
23494	 * @param string $d attribute d of the path SVG element
23495	 * @param string $style Style of rendering. Possible values are:
23496	 * <ul>
23497	 *	 <li>D or empty string: Draw (default).</li>
23498	 *	 <li>F: Fill.</li>
23499	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23500	 *	 <li>DF or FD: Draw and fill.</li>
23501	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23502	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23503	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23504	 * </ul>
23505	 * @return array of container box measures (x, y, w, h)
23506	 * @author Nicola Asuni
23507	 * @since 5.0.000 (2010-05-02)
23508	 * @protected
23509	 */
23510	protected function SVGPath($d, $style='') {
23511		if ($this->state != 2) {
23512			return;
23513		}
23514		// set fill/stroke style
23515		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23516		if (empty($op)) {
23517			return;
23518		}
23519		$paths = array();
23520		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23521		$d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23522		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23523		$x = 0;
23524		$y = 0;
23525		$x1 = 0;
23526		$y1 = 0;
23527		$x2 = 0;
23528		$y2 = 0;
23529		$xmin = 2147483647;
23530		$xmax = 0;
23531		$ymin = 2147483647;
23532		$ymax = 0;
23533		$xinitial = 0;
23534		$yinitial = 0;
23535		$relcoord = false;
23536		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23537		$firstcmd = true; // used to print first point
23538		// draw curve pieces
23539		foreach ($paths as $key => $val) {
23540			// get curve type
23541			$cmd = trim($val[1]);
23542			if (strtolower($cmd) == $cmd) {
23543				// use relative coordinated instead of absolute
23544				$relcoord = true;
23545				$xoffset = $x;
23546				$yoffset = $y;
23547			} else {
23548				$relcoord = false;
23549				$xoffset = 0;
23550				$yoffset = 0;
23551			}
23552			$params = array();
23553			if (isset($val[2])) {
23554				// get curve parameters
23555				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23556				$params = array();
23557				foreach ($rawparams as $ck => $cp) {
23558					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23559					if (abs($params[$ck]) < $minlen) {
23560						// approximate little values to zero
23561						$params[$ck] = 0;
23562					}
23563				}
23564			}
23565			// store current origin point
23566			$x0 = $x;
23567			$y0 = $y;
23568			switch (strtoupper($cmd)) {
23569				case 'M': { // moveto
23570					foreach ($params as $ck => $cp) {
23571						if (($ck % 2) == 0) {
23572							$x = $cp + $xoffset;
23573						} else {
23574							$y = $cp + $yoffset;
23575							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23576								if ($ck == 1) {
23577									$this->_outPoint($x, $y);
23578									$firstcmd = false;
23579									$xinitial = $x;
23580									$yinitial = $y;
23581								} else {
23582									$this->_outLine($x, $y);
23583								}
23584								$x0 = $x;
23585								$y0 = $y;
23586							}
23587							$xmin = min($xmin, $x);
23588							$ymin = min($ymin, $y);
23589							$xmax = max($xmax, $x);
23590							$ymax = max($ymax, $y);
23591							if ($relcoord) {
23592								$xoffset = $x;
23593								$yoffset = $y;
23594							}
23595						}
23596					}
23597					break;
23598				}
23599				case 'L': { // lineto
23600					foreach ($params as $ck => $cp) {
23601						if (($ck % 2) == 0) {
23602							$x = $cp + $xoffset;
23603						} else {
23604							$y = $cp + $yoffset;
23605							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23606								$this->_outLine($x, $y);
23607								$x0 = $x;
23608								$y0 = $y;
23609							}
23610							$xmin = min($xmin, $x);
23611							$ymin = min($ymin, $y);
23612							$xmax = max($xmax, $x);
23613							$ymax = max($ymax, $y);
23614							if ($relcoord) {
23615								$xoffset = $x;
23616								$yoffset = $y;
23617							}
23618						}
23619					}
23620					break;
23621				}
23622				case 'H': { // horizontal lineto
23623					foreach ($params as $ck => $cp) {
23624						$x = $cp + $xoffset;
23625						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23626							$this->_outLine($x, $y);
23627							$x0 = $x;
23628							$y0 = $y;
23629						}
23630						$xmin = min($xmin, $x);
23631						$xmax = max($xmax, $x);
23632						if ($relcoord) {
23633							$xoffset = $x;
23634						}
23635					}
23636					break;
23637				}
23638				case 'V': { // vertical lineto
23639					foreach ($params as $ck => $cp) {
23640						$y = $cp + $yoffset;
23641						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23642							$this->_outLine($x, $y);
23643							$x0 = $x;
23644							$y0 = $y;
23645						}
23646						$ymin = min($ymin, $y);
23647						$ymax = max($ymax, $y);
23648						if ($relcoord) {
23649							$yoffset = $y;
23650						}
23651					}
23652					break;
23653				}
23654				case 'C': { // curveto
23655					foreach ($params as $ck => $cp) {
23656						$params[$ck] = $cp;
23657						if ((($ck + 1) % 6) == 0) {
23658							$x1 = $params[($ck - 5)] + $xoffset;
23659							$y1 = $params[($ck - 4)] + $yoffset;
23660							$x2 = $params[($ck - 3)] + $xoffset;
23661							$y2 = $params[($ck - 2)] + $yoffset;
23662							$x = $params[($ck - 1)] + $xoffset;
23663							$y = $params[($ck)] + $yoffset;
23664							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23665							$xmin = min($xmin, $x, $x1, $x2);
23666							$ymin = min($ymin, $y, $y1, $y2);
23667							$xmax = max($xmax, $x, $x1, $x2);
23668							$ymax = max($ymax, $y, $y1, $y2);
23669							if ($relcoord) {
23670								$xoffset = $x;
23671								$yoffset = $y;
23672							}
23673						}
23674					}
23675					break;
23676				}
23677				case 'S': { // shorthand/smooth curveto
23678					foreach ($params as $ck => $cp) {
23679						$params[$ck] = $cp;
23680						if ((($ck + 1) % 4) == 0) {
23681							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23682								$x1 = (2 * $x) - $x2;
23683								$y1 = (2 * $y) - $y2;
23684							} else {
23685								$x1 = $x;
23686								$y1 = $y;
23687							}
23688							$x2 = $params[($ck - 3)] + $xoffset;
23689							$y2 = $params[($ck - 2)] + $yoffset;
23690							$x = $params[($ck - 1)] + $xoffset;
23691							$y = $params[($ck)] + $yoffset;
23692							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23693							$xmin = min($xmin, $x, $x1, $x2);
23694							$ymin = min($ymin, $y, $y1, $y2);
23695							$xmax = max($xmax, $x, $x1, $x2);
23696							$ymax = max($ymax, $y, $y1, $y2);
23697							if ($relcoord) {
23698								$xoffset = $x;
23699								$yoffset = $y;
23700							}
23701						}
23702					}
23703					break;
23704				}
23705				case 'Q': { // quadratic Bezier curveto
23706					foreach ($params as $ck => $cp) {
23707						$params[$ck] = $cp;
23708						if ((($ck + 1) % 4) == 0) {
23709							// convert quadratic points to cubic points
23710							$x1 = $params[($ck - 3)] + $xoffset;
23711							$y1 = $params[($ck - 2)] + $yoffset;
23712							$xa = ($x + (2 * $x1)) / 3;
23713							$ya = ($y + (2 * $y1)) / 3;
23714							$x = $params[($ck - 1)] + $xoffset;
23715							$y = $params[($ck)] + $yoffset;
23716							$xb = ($x + (2 * $x1)) / 3;
23717							$yb = ($y + (2 * $y1)) / 3;
23718							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23719							$xmin = min($xmin, $x, $xa, $xb);
23720							$ymin = min($ymin, $y, $ya, $yb);
23721							$xmax = max($xmax, $x, $xa, $xb);
23722							$ymax = max($ymax, $y, $ya, $yb);
23723							if ($relcoord) {
23724								$xoffset = $x;
23725								$yoffset = $y;
23726							}
23727						}
23728					}
23729					break;
23730				}
23731				case 'T': { // shorthand/smooth quadratic Bezier curveto
23732					foreach ($params as $ck => $cp) {
23733						$params[$ck] = $cp;
23734						if (($ck % 2) != 0) {
23735							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23736								$x1 = (2 * $x) - $x1;
23737								$y1 = (2 * $y) - $y1;
23738							} else {
23739								$x1 = $x;
23740								$y1 = $y;
23741							}
23742							// convert quadratic points to cubic points
23743							$xa = ($x + (2 * $x1)) / 3;
23744							$ya = ($y + (2 * $y1)) / 3;
23745							$x = $params[($ck - 1)] + $xoffset;
23746							$y = $params[($ck)] + $yoffset;
23747							$xb = ($x + (2 * $x1)) / 3;
23748							$yb = ($y + (2 * $y1)) / 3;
23749							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23750							$xmin = min($xmin, $x, $xa, $xb);
23751							$ymin = min($ymin, $y, $ya, $yb);
23752							$xmax = max($xmax, $x, $xa, $xb);
23753							$ymax = max($ymax, $y, $ya, $yb);
23754							if ($relcoord) {
23755								$xoffset = $x;
23756								$yoffset = $y;
23757							}
23758						}
23759					}
23760					break;
23761				}
23762				case 'A': { // elliptical arc
23763					foreach ($params as $ck => $cp) {
23764						$params[$ck] = $cp;
23765						if ((($ck + 1) % 7) == 0) {
23766							$x0 = $x;
23767							$y0 = $y;
23768							$rx = max(abs($params[($ck - 6)]), .000000001);
23769							$ry = max(abs($params[($ck - 5)]), .000000001);
23770							$ang = -$rawparams[($ck - 4)];
23771							$angle = deg2rad($ang);
23772							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23773							$fs = $rawparams[($ck - 2)]; // sweep-flag
23774							$x = $params[($ck - 1)] + $xoffset;
23775							$y = $params[$ck] + $yoffset;
23776							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23777								// endpoints are almost identical
23778								$xmin = min($xmin, $x);
23779								$ymin = min($ymin, $y);
23780								$xmax = max($xmax, $x);
23781								$ymax = max($ymax, $y);
23782							} else {
23783								$cos_ang = cos($angle);
23784								$sin_ang = sin($angle);
23785								$a = (($x0 - $x) / 2);
23786								$b = (($y0 - $y) / 2);
23787								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23788								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23789								$rx2 = $rx * $rx;
23790								$ry2 = $ry * $ry;
23791								$xa2 = $xa * $xa;
23792								$ya2 = $ya * $ya;
23793								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23794								if ($delta > 1) {
23795									$rx *= sqrt($delta);
23796									$ry *= sqrt($delta);
23797									$rx2 = $rx * $rx;
23798									$ry2 = $ry * $ry;
23799								}
23800								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23801								if ($numerator < 0) {
23802									$root = 0;
23803								} else {
23804									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23805								}
23806								if ($fa == $fs){
23807									$root *= -1;
23808								}
23809								$cax = $root * (($rx * $ya) / $ry);
23810								$cay = -$root * (($ry * $xa) / $rx);
23811								// coordinates of ellipse center
23812								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23813								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23814								// get angles
23815								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23816								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23817								if (($fs == 0) AND ($dang > 0)) {
23818									$dang -= (2 * M_PI);
23819								} elseif (($fs == 1) AND ($dang < 0)) {
23820									$dang += (2 * M_PI);
23821								}
23822								$angf = $angs - $dang;
23823								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23824									// reverse angles
23825									$tmp = $angs;
23826									$angs = $angf;
23827									$angf = $tmp;
23828								}
23829								$angs = round(rad2deg($angs), 6);
23830								$angf = round(rad2deg($angf), 6);
23831								// covent angles to positive values
23832								if (($angs < 0) AND ($angf < 0)) {
23833									$angs += 360;
23834									$angf += 360;
23835								}
23836								$pie = false;
23837								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23838									$pie = true;
23839								}
23840								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23841								$xmin = min($xmin, $x, $axmin);
23842								$ymin = min($ymin, $y, $aymin);
23843								$xmax = max($xmax, $x, $axmax);
23844								$ymax = max($ymax, $y, $aymax);
23845							}
23846							if ($relcoord) {
23847								$xoffset = $x;
23848								$yoffset = $y;
23849							}
23850						}
23851					}
23852					break;
23853				}
23854				case 'Z': {
23855					$this->_out('h');
23856					$x = $x0 = $xinitial;
23857					$y = $y0 = $yinitial;
23858					break;
23859				}
23860			}
23861			$firstcmd = false;
23862		} // end foreach
23863		if (!empty($op)) {
23864			$this->_out($op);
23865		}
23866		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23867	}
23868
23869	/**
23870	 * Return the tag name without the namespace
23871	 * @param string $name Tag name
23872	 * @protected
23873	 */
23874	protected function removeTagNamespace($name) {
23875		if(strpos($name, ':') !== false) {
23876			$parts = explode(':', $name);
23877			return $parts[(sizeof($parts) - 1)];
23878		}
23879		return $name;
23880	}
23881
23882	/**
23883	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23884	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
23885	 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
23886	 * @param array $attribs The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
23887	 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
23888	 * @author Nicola Asuni
23889	 * @since 5.0.000 (2010-05-02)
23890	 * @protected
23891	 */
23892	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23893		$name = $this->removeTagNamespace($name);
23894		// check if we are in clip mode
23895		if ($this->svgclipmode) {
23896			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23897			return;
23898		}
23899		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23900			if (isset($attribs['id'])) {
23901				$attribs['child_elements'] = array();
23902				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23903				return;
23904			}
23905			if (end($this->svgdefs) !== FALSE) {
23906				$last_svgdefs_id = key($this->svgdefs);
23907				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23908					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23909					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23910					return;
23911				}
23912			}
23913			return;
23914		}
23915		$clipping = false;
23916		if ($parser == 'clip-path') {
23917			// set clipping mode
23918			$clipping = true;
23919		}
23920		// get styling properties
23921		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23922		$svgstyle = $this->svgstyles[0]; // set default style
23923		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23924			// default fill attribute for clipping
23925			$attribs['fill'] = 'none';
23926		}
23927		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23928			// fix style for regular expression
23929			$attribs['style'] = ';'.$attribs['style'];
23930		}
23931		foreach ($prev_svgstyle as $key => $val) {
23932			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23933				// inherit previous value
23934				$svgstyle[$key] = $val;
23935			}
23936			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23937				// specific attribute settings
23938				if ($attribs[$key] == 'inherit') {
23939					$svgstyle[$key] = $val;
23940				} else {
23941					$svgstyle[$key] = $attribs[$key];
23942				}
23943			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23944				// CSS style syntax
23945				$attrval = array();
23946				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23947					if ($attrval[1] == 'inherit') {
23948						$svgstyle[$key] = $val;
23949					} else {
23950						$svgstyle[$key] = $attrval[1];
23951					}
23952				}
23953			}
23954		}
23955		// transformation matrix
23956		if (!empty($ctm)) {
23957			$tm = $ctm;
23958		} else {
23959			$tm = array(1,0,0,1,0,0);
23960		}
23961		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23962			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23963		}
23964		$svgstyle['transfmatrix'] = $tm;
23965		$invisible = false;
23966		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23967			// the current graphics element is invisible (nothing is painted)
23968			$invisible = true;
23969		}
23970		// process tag
23971		switch($name) {
23972			case 'defs': {
23973				$this->svgdefsmode = true;
23974				break;
23975			}
23976			// clipPath
23977			case 'clipPath': {
23978				if ($invisible) {
23979					break;
23980				}
23981				$this->svgclipmode = true;
23982				if (!isset($attribs['id'])) {
23983					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23984				}
23985				$this->svgclipid = $attribs['id'];
23986				$this->svgclippaths[$this->svgclipid] = array();
23987				$this->svgcliptm[$this->svgclipid] = $tm;
23988				break;
23989			}
23990			case 'svg': {
23991				// start of SVG object
23992				if(++$this->svg_tag_depth <= 1) {
23993					break;
23994				}
23995				// inner SVG
23996				array_push($this->svgstyles, $svgstyle);
23997				$this->StartTransform();
23998				$svgX = (isset($attribs['x'])?$attribs['x']:0);
23999				$svgY = (isset($attribs['y'])?$attribs['y']:0);
24000				$svgW = (isset($attribs['width'])?$attribs['width']:0);
24001				$svgH = (isset($attribs['height'])?$attribs['height']:0);
24002				// set x, y position using transform matrix
24003				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24004				$this->SVGTransform($tm);
24005				// set clipping for width and height
24006				$x = 0;
24007				$y = 0;
24008				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
24009				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
24010				// draw clipping rect
24011				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24012				// parse viewbox, calculate extra transformation matrix
24013				if (isset($attribs['viewBox'])) {
24014					$tmp = array();
24015					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24016					$tmp = $tmp[0];
24017					if (sizeof($tmp) == 4) {
24018						$vx = $tmp[0];
24019						$vy = $tmp[1];
24020						$vw = $tmp[2];
24021						$vh = $tmp[3];
24022						// get aspect ratio
24023						$tmp = array();
24024						$aspectX = 'xMid';
24025						$aspectY = 'YMid';
24026						$fit = 'meet';
24027						if (isset($attribs['preserveAspectRatio'])) {
24028							if($attribs['preserveAspectRatio'] == 'none') {
24029								$fit = 'none';
24030							} else {
24031								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24032								$tmp = $tmp[0];
24033								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24034									$aspectX = substr($tmp[0], 0, 4);
24035									$aspectY = substr($tmp[0], 4, 4);
24036									$fit = $tmp[1];
24037								}
24038							}
24039						}
24040						$wr = ($svgW / $vw);
24041						$hr = ($svgH / $vh);
24042						$ax = $ay = 0;
24043						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24044							if ($aspectX == 'xMax') {
24045								$ax = (($vw * ($wr / $hr)) - $vw);
24046							}
24047							if ($aspectX == 'xMid') {
24048								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24049							}
24050							$wr = $hr;
24051						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24052							if ($aspectY == 'YMax') {
24053								$ay = (($vh * ($hr / $wr)) - $vh);
24054							}
24055							if ($aspectY == 'YMid') {
24056								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24057							}
24058							$hr = $wr;
24059						}
24060						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24061						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24062						$this->SVGTransform($tm);
24063					}
24064				}
24065				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24066				break;
24067			}
24068			case 'g': {
24069				// group together related graphics elements
24070				array_push($this->svgstyles, $svgstyle);
24071				$this->StartTransform();
24072				$x = (isset($attribs['x'])?$attribs['x']:0);
24073				$y = (isset($attribs['y'])?$attribs['y']:0);
24074				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24075				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24076				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24077				$this->SVGTransform($tm);
24078				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24079				break;
24080			}
24081			case 'linearGradient': {
24082				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24083					break;
24084				}
24085				if (!isset($attribs['id'])) {
24086					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24087				}
24088				$this->svggradientid = $attribs['id'];
24089				$this->svggradients[$this->svggradientid] = array();
24090				$this->svggradients[$this->svggradientid]['type'] = 2;
24091				$this->svggradients[$this->svggradientid]['stops'] = array();
24092				if (isset($attribs['gradientUnits'])) {
24093					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24094				} else {
24095					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24096				}
24097				//$attribs['spreadMethod']
24098				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24099					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24100						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24101						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24102						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24103					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24104				} else {
24105					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24106				}
24107				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24108				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24109				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24110				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24111				if (isset($attribs['gradientTransform'])) {
24112					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24113				}
24114				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24115				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24116					// gradient is defined on another place
24117					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24118				}
24119				break;
24120			}
24121			case 'radialGradient': {
24122				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24123					break;
24124				}
24125				if (!isset($attribs['id'])) {
24126					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24127				}
24128				$this->svggradientid = $attribs['id'];
24129				$this->svggradients[$this->svggradientid] = array();
24130				$this->svggradients[$this->svggradientid]['type'] = 3;
24131				$this->svggradients[$this->svggradientid]['stops'] = array();
24132				if (isset($attribs['gradientUnits'])) {
24133					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24134				} else {
24135					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24136				}
24137				//$attribs['spreadMethod']
24138				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24139					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24140					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24141					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24142				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24143					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24144				} else {
24145					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24146				}
24147				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24148				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24149				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24150				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24151				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24152				if (isset($attribs['gradientTransform'])) {
24153					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24154				}
24155				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24156				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24157					// gradient is defined on another place
24158					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24159				}
24160				break;
24161			}
24162			case 'stop': {
24163				// gradient stops
24164				if (substr($attribs['offset'], -1) == '%') {
24165					$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24166				} else {
24167					$offset = floatval($attribs['offset']);
24168					if ($offset > 1) {
24169						$offset /= 100;
24170					}
24171				}
24172				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24173				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24174				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24175				break;
24176			}
24177			// paths
24178			case 'path': {
24179				if ($invisible) {
24180					break;
24181				}
24182				if (isset($attribs['d'])) {
24183					$d = trim($attribs['d']);
24184					if (!empty($d)) {
24185						$x = (isset($attribs['x'])?$attribs['x']:0);
24186						$y = (isset($attribs['y'])?$attribs['y']:0);
24187						$w = (isset($attribs['width'])?$attribs['width']:1);
24188						$h = (isset($attribs['height'])?$attribs['height']:1);
24189						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24190						if ($clipping) {
24191							$this->SVGTransform($tm);
24192							$this->SVGPath($d, 'CNZ');
24193						} else {
24194							$this->StartTransform();
24195							$this->SVGTransform($tm);
24196							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24197							if (!empty($obstyle)) {
24198								$this->SVGPath($d, $obstyle);
24199							}
24200							$this->StopTransform();
24201						}
24202					}
24203				}
24204				break;
24205			}
24206			// shapes
24207			case 'rect': {
24208				if ($invisible) {
24209					break;
24210				}
24211				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24212				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24213				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24214				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24215				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24216				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24217				if ($clipping) {
24218					$this->SVGTransform($tm);
24219					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24220				} else {
24221					$this->StartTransform();
24222					$this->SVGTransform($tm);
24223					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24224					if (!empty($obstyle)) {
24225						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24226					}
24227					$this->StopTransform();
24228				}
24229				break;
24230			}
24231			case 'circle': {
24232				if ($invisible) {
24233					break;
24234				}
24235				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24236				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24237				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24238				$x = ($cx - $r);
24239				$y = ($cy - $r);
24240				$w = (2 * $r);
24241				$h = $w;
24242				if ($clipping) {
24243					$this->SVGTransform($tm);
24244					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24245				} else {
24246					$this->StartTransform();
24247					$this->SVGTransform($tm);
24248					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24249					if (!empty($obstyle)) {
24250						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24251					}
24252					$this->StopTransform();
24253				}
24254				break;
24255			}
24256			case 'ellipse': {
24257				if ($invisible) {
24258					break;
24259				}
24260				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24261				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24262				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24263				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24264				$x = ($cx - $rx);
24265				$y = ($cy - $ry);
24266				$w = (2 * $rx);
24267				$h = (2 * $ry);
24268				if ($clipping) {
24269					$this->SVGTransform($tm);
24270					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24271				} else {
24272					$this->StartTransform();
24273					$this->SVGTransform($tm);
24274					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24275					if (!empty($obstyle)) {
24276						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24277					}
24278					$this->StopTransform();
24279				}
24280				break;
24281			}
24282			case 'line': {
24283				if ($invisible) {
24284					break;
24285				}
24286				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24287				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24288				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24289				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24290				$x = $x1;
24291				$y = $y1;
24292				$w = abs($x2 - $x1);
24293				$h = abs($y2 - $y1);
24294				if (!$clipping) {
24295					$this->StartTransform();
24296					$this->SVGTransform($tm);
24297					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24298					$this->Line($x1, $y1, $x2, $y2);
24299					$this->StopTransform();
24300				}
24301				break;
24302			}
24303			case 'polyline':
24304			case 'polygon': {
24305				if ($invisible) {
24306					break;
24307				}
24308				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24309				$points = trim($points);
24310				// note that point may use a complex syntax not covered here
24311				$points = preg_split('/[\,\s]+/si', $points);
24312				if (count($points) < 4) {
24313					break;
24314				}
24315				$p = array();
24316				$xmin = 2147483647;
24317				$xmax = 0;
24318				$ymin = 2147483647;
24319				$ymax = 0;
24320				foreach ($points as $key => $val) {
24321					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24322					if (($key % 2) == 0) {
24323						// X coordinate
24324						$xmin = min($xmin, $p[$key]);
24325						$xmax = max($xmax, $p[$key]);
24326					} else {
24327						// Y coordinate
24328						$ymin = min($ymin, $p[$key]);
24329						$ymax = max($ymax, $p[$key]);
24330					}
24331				}
24332				$x = $xmin;
24333				$y = $ymin;
24334				$w = ($xmax - $xmin);
24335				$h = ($ymax - $ymin);
24336				if ($name == 'polyline') {
24337					$this->StartTransform();
24338					$this->SVGTransform($tm);
24339					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24340					if (!empty($obstyle)) {
24341						$this->PolyLine($p, $obstyle, array(), array());
24342					}
24343					$this->StopTransform();
24344				} else { // polygon
24345					if ($clipping) {
24346						$this->SVGTransform($tm);
24347						$this->Polygon($p, 'CNZ', array(), array(), true);
24348					} else {
24349						$this->StartTransform();
24350						$this->SVGTransform($tm);
24351						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24352						if (!empty($obstyle)) {
24353							$this->Polygon($p, $obstyle, array(), array(), true);
24354						}
24355						$this->StopTransform();
24356					}
24357				}
24358				break;
24359			}
24360			// image
24361			case 'image': {
24362				if ($invisible) {
24363					break;
24364				}
24365				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24366					break;
24367				}
24368				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24369				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24370				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24371				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24372				$img = $attribs['xlink:href'];
24373				if (!$clipping) {
24374					$this->StartTransform();
24375					$this->SVGTransform($tm);
24376					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24377					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24378						// embedded image encoded as base64
24379						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24380					} else {
24381						// fix image path
24382						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24383							// replace relative path with full server path
24384							$img = $this->svgdir.'/'.$img;
24385						}
24386						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24387							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24388							if (($findroot === false) OR ($findroot > 1)) {
24389								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24390									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24391								} else {
24392									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24393								}
24394							}
24395						}
24396						$img = urldecode($img);
24397						$testscrtype = @parse_url($img);
24398						if (empty($testscrtype['query'])) {
24399							// convert URL to server path
24400							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24401						} elseif (preg_match('|^https?://|', $img) !== 1) {
24402							// convert server path to URL
24403							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24404						}
24405					}
24406					// get image type
24407					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24408					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24409						$this->ImageEps($img, $x, $y, $w, $h);
24410					} elseif ($imgtype == 'svg') {
24411						// store SVG vars
24412						$svggradients = $this->svggradients;
24413						$svggradientid = $this->svggradientid;
24414						$svgdefsmode = $this->svgdefsmode;
24415						$svgdefs = $this->svgdefs;
24416						$svgclipmode = $this->svgclipmode;
24417						$svgclippaths = $this->svgclippaths;
24418						$svgcliptm = $this->svgcliptm;
24419						$svgclipid = $this->svgclipid;
24420						$svgtext = $this->svgtext;
24421						$svgtextmode = $this->svgtextmode;
24422						$this->ImageSVG($img, $x, $y, $w, $h);
24423						// restore SVG vars
24424						$this->svggradients = $svggradients;
24425						$this->svggradientid = $svggradientid;
24426						$this->svgdefsmode = $svgdefsmode;
24427						$this->svgdefs = $svgdefs;
24428						$this->svgclipmode = $svgclipmode;
24429						$this->svgclippaths = $svgclippaths;
24430						$this->svgcliptm = $svgcliptm;
24431						$this->svgclipid = $svgclipid;
24432						$this->svgtext = $svgtext;
24433						$this->svgtextmode = $svgtextmode;
24434					} else {
24435						$this->Image($img, $x, $y, $w, $h);
24436					}
24437					$this->StopTransform();
24438				}
24439				break;
24440			}
24441			// text
24442			case 'text':
24443			case 'tspan': {
24444				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24445					// @TODO: unsupported feature
24446				}
24447				// only basic support - advanced features must be implemented
24448				$this->svgtextmode['invisible'] = $invisible;
24449				if ($invisible) {
24450					break;
24451				}
24452				array_push($this->svgstyles, $svgstyle);
24453				if (isset($attribs['x'])) {
24454					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24455				} elseif ($name == 'tspan') {
24456					$x = $this->x;
24457				} else {
24458					$x = 0;
24459				}
24460				if (isset($attribs['dx'])) {
24461					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24462				}
24463				if (isset($attribs['y'])) {
24464					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24465				} elseif ($name == 'tspan') {
24466					$y = $this->y;
24467				} else {
24468					$y = 0;
24469				}
24470				if (isset($attribs['dy'])) {
24471					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24472				}
24473				$svgstyle['text-color'] = $svgstyle['fill'];
24474				$this->svgtext = '';
24475				if (isset($svgstyle['text-anchor'])) {
24476					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24477				} else {
24478					$this->svgtextmode['text-anchor'] = 'start';
24479				}
24480				if (isset($svgstyle['direction'])) {
24481					if ($svgstyle['direction'] == 'rtl') {
24482						$this->svgtextmode['rtl'] = true;
24483					} else {
24484						$this->svgtextmode['rtl'] = false;
24485					}
24486				} else {
24487					$this->svgtextmode['rtl'] = false;
24488				}
24489				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24490					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24491				} else {
24492					$this->svgtextmode['stroke'] = false;
24493				}
24494				$this->StartTransform();
24495				$this->SVGTransform($tm);
24496				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24497				$this->x = $x;
24498				$this->y = $y;
24499				break;
24500			}
24501			// use
24502			case 'use': {
24503				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24504					$svgdefid = substr($attribs['xlink:href'], 1);
24505					if (isset($this->svgdefs[$svgdefid])) {
24506						$use = $this->svgdefs[$svgdefid];
24507						if (isset($attribs['xlink:href'])) {
24508							unset($attribs['xlink:href']);
24509						}
24510						if (isset($attribs['id'])) {
24511							unset($attribs['id']);
24512						}
24513						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24514							$attribs['x'] += $use['attribs']['x'];
24515						}
24516						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24517							$attribs['y'] += $use['attribs']['y'];
24518						}
24519						if (empty($attribs['style'])) {
24520							$attribs['style'] = '';
24521						}
24522						if (!empty($use['attribs']['style'])) {
24523							// merge styles
24524							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24525						}
24526						$attribs = array_merge($use['attribs'], $attribs);
24527						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24528						return;
24529					}
24530				}
24531				break;
24532			}
24533			default: {
24534				break;
24535			}
24536		} // end of switch
24537		// process child elements
24538		if (!empty($attribs['child_elements'])) {
24539			$child_elements = $attribs['child_elements'];
24540			unset($attribs['child_elements']);
24541			foreach($child_elements as $child_element) {
24542				if (empty($child_element['attribs']['closing_tag'])) {
24543					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24544				} else {
24545					if (isset($child_element['attribs']['content'])) {
24546						$this->svgtext = $child_element['attribs']['content'];
24547					}
24548					$this->endSVGElementHandler('child-tag', $child_element['name']);
24549				}
24550			}
24551		}
24552	}
24553
24554	/**
24555	 * Sets the closing SVG element handler function for the XML parser.
24556	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24557	 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24558	 * @author Nicola Asuni
24559	 * @since 5.0.000 (2010-05-02)
24560	 * @protected
24561	 */
24562	protected function endSVGElementHandler($parser, $name) {
24563		$name = $this->removeTagNamespace($name);
24564		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24565			if (end($this->svgdefs) !== FALSE) {
24566				$last_svgdefs_id = key($this->svgdefs);
24567				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24568					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24569						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24570							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24571							return;
24572						}
24573					}
24574					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24575						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24576						return;
24577					}
24578				}
24579			}
24580			return;
24581		}
24582		switch($name) {
24583			case 'defs': {
24584				$this->svgdefsmode = false;
24585				break;
24586			}
24587			// clipPath
24588			case 'clipPath': {
24589				$this->svgclipmode = false;
24590				break;
24591			}
24592			case 'svg': {
24593				if (--$this->svg_tag_depth <= 0) {
24594					break;
24595				}
24596			}
24597			case 'g': {
24598				// ungroup: remove last style from array
24599				array_pop($this->svgstyles);
24600				$this->StopTransform();
24601				break;
24602			}
24603			case 'text':
24604			case 'tspan': {
24605				if ($this->svgtextmode['invisible']) {
24606					// This implementation must be fixed to following the rule:
24607					// 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.
24608					break;
24609				}
24610				// print text
24611				$text = $this->svgtext;
24612				//$text = $this->stringTrim($text);
24613				$textlen = $this->GetStringWidth($text);
24614				if ($this->svgtextmode['text-anchor'] != 'start') {
24615					// check if string is RTL text
24616					if ($this->svgtextmode['text-anchor'] == 'end') {
24617						if ($this->svgtextmode['rtl']) {
24618							$this->x += $textlen;
24619						} else {
24620							$this->x -= $textlen;
24621						}
24622					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24623						if ($this->svgtextmode['rtl']) {
24624							$this->x += ($textlen / 2);
24625						} else {
24626							$this->x -= ($textlen / 2);
24627						}
24628					}
24629				}
24630				$textrendermode = $this->textrendermode;
24631				$textstrokewidth = $this->textstrokewidth;
24632				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24633				if ($name == 'text') {
24634					// store current coordinates
24635					$tmpx = $this->x;
24636					$tmpy = $this->y;
24637				}
24638				// print the text
24639				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24640				if ($name == 'text') {
24641					// restore coordinates
24642					$this->x = $tmpx;
24643					$this->y = $tmpy;
24644				}
24645				// restore previous rendering mode
24646				$this->textrendermode = $textrendermode;
24647				$this->textstrokewidth = $textstrokewidth;
24648				$this->svgtext = '';
24649				$this->StopTransform();
24650				if (!$this->svgdefsmode) {
24651					array_pop($this->svgstyles);
24652				}
24653				break;
24654			}
24655			default: {
24656				break;
24657			}
24658		}
24659	}
24660
24661	/**
24662	 * Sets the character data handler function for the XML parser.
24663	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24664	 * @param string $data The second parameter, data, contains the character data as a string.
24665	 * @author Nicola Asuni
24666	 * @since 5.0.000 (2010-05-02)
24667	 * @protected
24668	 */
24669	protected function segSVGContentHandler($parser, $data) {
24670		$this->svgtext .= $data;
24671	}
24672
24673	// --- END SVG METHODS -----------------------------------------------------
24674
24675    /**
24676     * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24677     * @param string $file
24678     * @return string
24679     */
24680    protected function getCachedFileContents($file)
24681    {
24682        if (!isset($this->fileContentCache[$file])) {
24683            $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24684        }
24685        return $this->fileContentCache[$file];
24686    }
24687
24688    /**
24689     * Avoid multiple calls to an external server to see if a file exists
24690     * @param string $file
24691     * @return bool
24692     */
24693    protected function fileExists($file)
24694    {
24695        if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24696            return true;
24697        }
24698
24699        return TCPDF_STATIC::file_exists($file);
24700    }
24701
24702} // END OF TCPDF CLASS
24703
24704//============================================================+
24705// END OF FILE
24706//============================================================+
24707