1<?php
2//============================================================+
3// File name   : tcpdf.php
4// Version     : 6.3.2
5// Begin       : 2002-08-03
6// Last Update : 2019-09-20
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	 */
604	protected $header_font;
605
606	/**
607	 * Default font used on page footer.
608	 * @protected
609	 */
610	protected $footer_font;
611
612	/**
613	 * Language templates.
614	 * @protected
615	 */
616	protected $l;
617
618	/**
619	 * Barcode to print on page footer (only if set).
620	 * @protected
621	 */
622	protected $barcode = false;
623
624	/**
625	 * Boolean flag to print/hide page header.
626	 * @protected
627	 */
628	protected $print_header = true;
629
630	/**
631	 * Boolean flag to print/hide page footer.
632	 * @protected
633	 */
634	protected $print_footer = true;
635
636	/**
637	 * Header image logo.
638	 * @protected
639	 */
640	protected $header_logo = '';
641
642	/**
643	 * Width of header image logo in user units.
644	 * @protected
645	 */
646	protected $header_logo_width = 30;
647
648	/**
649	 * Title to be printed on default page header.
650	 * @protected
651	 */
652	protected $header_title = '';
653
654	/**
655	 * String to pring on page header after title.
656	 * @protected
657	 */
658	protected $header_string = '';
659
660	/**
661	 * Color for header text (RGB array).
662	 * @since 5.9.174 (2012-07-25)
663	 * @protected
664	 */
665	protected $header_text_color = array(0,0,0);
666
667	/**
668	 * Color for header line (RGB array).
669	 * @since 5.9.174 (2012-07-25)
670	 * @protected
671	 */
672	protected $header_line_color = array(0,0,0);
673
674	/**
675	 * Color for footer text (RGB array).
676	 * @since 5.9.174 (2012-07-25)
677	 * @protected
678	 */
679	protected $footer_text_color = array(0,0,0);
680
681	/**
682	 * Color for footer line (RGB array).
683	 * @since 5.9.174 (2012-07-25)
684	 * @protected
685	 */
686	protected $footer_line_color = array(0,0,0);
687
688	/**
689	 * Text shadow data array.
690	 * @since 5.9.174 (2012-07-25)
691	 * @protected
692	 */
693	protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
694
695	/**
696	 * Default number of columns for html table.
697	 * @protected
698	 */
699	protected $default_table_columns = 4;
700
701	// variables for html parser
702
703	/**
704	 * HTML PARSER: array to store current link and rendering styles.
705	 * @protected
706	 */
707	protected $HREF = array();
708
709	/**
710	 * List of available fonts on filesystem.
711	 * @protected
712	 */
713	protected $fontlist = array();
714
715	/**
716	 * Current foreground color.
717	 * @protected
718	 */
719	protected $fgcolor;
720
721	/**
722	 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
723	 * @protected
724	 */
725	protected $listordered = array();
726
727	/**
728	 * HTML PARSER: array count list items on nested lists.
729	 * @protected
730	 */
731	protected $listcount = array();
732
733	/**
734	 * HTML PARSER: current list nesting level.
735	 * @protected
736	 */
737	protected $listnum = 0;
738
739	/**
740	 * HTML PARSER: indent amount for lists.
741	 * @protected
742	 */
743	protected $listindent = 0;
744
745	/**
746	 * HTML PARSER: current list indententation level.
747	 * @protected
748	 */
749	protected $listindentlevel = 0;
750
751	/**
752	 * Current background color.
753	 * @protected
754	 */
755	protected $bgcolor;
756
757	/**
758	 * Temporary font size in points.
759	 * @protected
760	 */
761	protected $tempfontsize = 10;
762
763	/**
764	 * Spacer string for LI tags.
765	 * @protected
766	 */
767	protected $lispacer = '';
768
769	/**
770	 * Default encoding.
771	 * @protected
772	 * @since 1.53.0.TC010
773	 */
774	protected $encoding = 'UTF-8';
775
776	/**
777	 * Boolean flag to indicate if the document language is Right-To-Left.
778	 * @protected
779	 * @since 2.0.000
780	 */
781	protected $rtl = false;
782
783	/**
784	 * Boolean flag used to force RTL or LTR string direction.
785	 * @protected
786	 * @since 2.0.000
787	 */
788	protected $tmprtl = false;
789
790	// --- Variables used for document encryption:
791
792	/**
793	 * IBoolean flag indicating whether document is protected.
794	 * @protected
795	 * @since 2.0.000 (2008-01-02)
796	 */
797	protected $encrypted;
798
799	/**
800	 * Array containing encryption settings.
801	 * @protected
802	 * @since 5.0.005 (2010-05-11)
803	 */
804	protected $encryptdata = array();
805
806	/**
807	 * Last RC4 key encrypted (cached for optimisation).
808	 * @protected
809	 * @since 2.0.000 (2008-01-02)
810	 */
811	protected $last_enc_key;
812
813	/**
814	 * Last RC4 computed key.
815	 * @protected
816	 * @since 2.0.000 (2008-01-02)
817	 */
818	protected $last_enc_key_c;
819
820	/**
821	 * File ID (used on document trailer).
822	 * @protected
823	 * @since 5.0.005 (2010-05-12)
824	 */
825	protected $file_id;
826
827	// --- bookmark ---
828
829	/**
830	 * Outlines for bookmark.
831	 * @protected
832	 * @since 2.1.002 (2008-02-12)
833	 */
834	protected $outlines = array();
835
836	/**
837	 * Outline root for bookmark.
838	 * @protected
839	 * @since 2.1.002 (2008-02-12)
840	 */
841	protected $OutlineRoot;
842
843	// --- javascript and form ---
844
845	/**
846	 * Javascript code.
847	 * @protected
848	 * @since 2.1.002 (2008-02-12)
849	 */
850	protected $javascript = '';
851
852	/**
853	 * Javascript counter.
854	 * @protected
855	 * @since 2.1.002 (2008-02-12)
856	 */
857	protected $n_js;
858
859	/**
860	 * line through state
861	 * @protected
862	 * @since 2.8.000 (2008-03-19)
863	 */
864	protected $linethrough;
865
866	/**
867	 * Array with additional document-wide usage rights for the document.
868	 * @protected
869	 * @since 5.8.014 (2010-08-23)
870	 */
871	protected $ur = array();
872
873	/**
874	 * DPI (Dot Per Inch) Document Resolution (do not change).
875	 * @protected
876	 * @since 3.0.000 (2008-03-27)
877	 */
878	protected $dpi = 72;
879
880	/**
881	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
882	 * @protected
883	 * @since 3.0.000 (2008-03-27)
884	 */
885	protected $newpagegroup = array();
886
887	/**
888	 * Array that contains the number of pages in each page group.
889	 * @protected
890	 * @since 3.0.000 (2008-03-27)
891	 */
892	protected $pagegroups = array();
893
894	/**
895	 * Current page group number.
896	 * @protected
897	 * @since 3.0.000 (2008-03-27)
898	 */
899	protected $currpagegroup = 0;
900
901	/**
902	 * Array of transparency objects and parameters.
903	 * @protected
904	 * @since 3.0.000 (2008-03-27)
905	 */
906	protected $extgstates;
907
908	/**
909	 * Set the default JPEG compression quality (1-100).
910	 * @protected
911	 * @since 3.0.000 (2008-03-27)
912	 */
913	protected $jpeg_quality;
914
915	/**
916	 * Default cell height ratio.
917	 * @protected
918	 * @since 3.0.014 (2008-05-23)
919	 */
920	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
921
922	/**
923	 * PDF viewer preferences.
924	 * @protected
925	 * @since 3.1.000 (2008-06-09)
926	 */
927	protected $viewer_preferences;
928
929	/**
930	 * A name object specifying how the document should be displayed when opened.
931	 * @protected
932	 * @since 3.1.000 (2008-06-09)
933	 */
934	protected $PageMode;
935
936	/**
937	 * Array for storing gradient information.
938	 * @protected
939	 * @since 3.1.000 (2008-06-09)
940	 */
941	protected $gradients = array();
942
943	/**
944	 * Array used to store positions inside the pages buffer (keys are the page numbers).
945	 * @protected
946	 * @since 3.2.000 (2008-06-26)
947	 */
948	protected $intmrk = array();
949
950	/**
951	 * Array used to store positions inside the pages buffer (keys are the page numbers).
952	 * @protected
953	 * @since 5.7.000 (2010-08-03)
954	 */
955	protected $bordermrk = array();
956
957	/**
958	 * Array used to store page positions to track empty pages (keys are the page numbers).
959	 * @protected
960	 * @since 5.8.007 (2010-08-18)
961	 */
962	protected $emptypagemrk = array();
963
964	/**
965	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
966	 * @protected
967	 * @since 4.6.021 (2009-07-20)
968	 */
969	protected $cntmrk = array();
970
971	/**
972	 * Array used to store footer positions of each page.
973	 * @protected
974	 * @since 3.2.000 (2008-07-01)
975	 */
976	protected $footerpos = array();
977
978	/**
979	 * Array used to store footer length of each page.
980	 * @protected
981	 * @since 4.0.014 (2008-07-29)
982	 */
983	protected $footerlen = array();
984
985	/**
986	 * Boolean flag to indicate if a new line is created.
987	 * @protected
988	 * @since 3.2.000 (2008-07-01)
989	 */
990	protected $newline = true;
991
992	/**
993	 * End position of the latest inserted line.
994	 * @protected
995	 * @since 3.2.000 (2008-07-01)
996	 */
997	protected $endlinex = 0;
998
999	/**
1000	 * PDF string for width value of the last line.
1001	 * @protected
1002	 * @since 4.0.006 (2008-07-16)
1003	 */
1004	protected $linestyleWidth = '';
1005
1006	/**
1007	 * PDF string for CAP value of the last line.
1008	 * @protected
1009	 * @since 4.0.006 (2008-07-16)
1010	 */
1011	protected $linestyleCap = '0 J';
1012
1013	/**
1014	 * PDF string for join value of the last line.
1015	 * @protected
1016	 * @since 4.0.006 (2008-07-16)
1017	 */
1018	protected $linestyleJoin = '0 j';
1019
1020	/**
1021	 * PDF string for dash value of the last line.
1022	 * @protected
1023	 * @since 4.0.006 (2008-07-16)
1024	 */
1025	protected $linestyleDash = '[] 0 d';
1026
1027	/**
1028	 * Boolean flag to indicate if marked-content sequence is open.
1029	 * @protected
1030	 * @since 4.0.013 (2008-07-28)
1031	 */
1032	protected $openMarkedContent = false;
1033
1034	/**
1035	 * Count the latest inserted vertical spaces on HTML.
1036	 * @protected
1037	 * @since 4.0.021 (2008-08-24)
1038	 */
1039	protected $htmlvspace = 0;
1040
1041	/**
1042	 * Array of Spot colors.
1043	 * @protected
1044	 * @since 4.0.024 (2008-09-12)
1045	 */
1046	protected $spot_colors = array();
1047
1048	/**
1049	 * Symbol used for HTML unordered list items.
1050	 * @protected
1051	 * @since 4.0.028 (2008-09-26)
1052	 */
1053	protected $lisymbol = '';
1054
1055	/**
1056	 * String used to mark the beginning and end of EPS image blocks.
1057	 * @protected
1058	 * @since 4.1.000 (2008-10-18)
1059	 */
1060	protected $epsmarker = 'x#!#EPS#!#x';
1061
1062	/**
1063	 * Array of transformation matrix.
1064	 * @protected
1065	 * @since 4.2.000 (2008-10-29)
1066	 */
1067	protected $transfmatrix = array();
1068
1069	/**
1070	 * Current key for transformation matrix.
1071	 * @protected
1072	 * @since 4.8.005 (2009-09-17)
1073	 */
1074	protected $transfmatrix_key = 0;
1075
1076	/**
1077	 * Booklet mode for double-sided pages.
1078	 * @protected
1079	 * @since 4.2.000 (2008-10-29)
1080	 */
1081	protected $booklet = false;
1082
1083	/**
1084	 * Epsilon value used for float calculations.
1085	 * @protected
1086	 * @since 4.2.000 (2008-10-29)
1087	 */
1088	protected $feps = 0.005;
1089
1090	/**
1091	 * Array used for custom vertical spaces for HTML tags.
1092	 * @protected
1093	 * @since 4.2.001 (2008-10-30)
1094	 */
1095	protected $tagvspaces = array();
1096
1097	/**
1098	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1099	 * @protected
1100	 * @since 4.2.007 (2008-11-12)
1101	 */
1102	protected $customlistindent = -1;
1103
1104	/**
1105	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1106	 * @protected
1107	 * @since 4.2.010 (2008-11-14)
1108	 */
1109	protected $opencell = true;
1110
1111	/**
1112	 * Array of files to embedd.
1113	 * @protected
1114	 * @since 4.4.000 (2008-12-07)
1115	 */
1116	protected $embeddedfiles = array();
1117
1118	/**
1119	 * Boolean flag to indicate if we are inside a PRE tag.
1120	 * @protected
1121	 * @since 4.4.001 (2008-12-08)
1122	 */
1123	protected $premode = false;
1124
1125	/**
1126	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1127	 * keys are the page numbers
1128	 * @protected
1129	 * @since 4.4.002 (2008-12-09)
1130	 */
1131	protected $transfmrk = array();
1132
1133	/**
1134	 * Default color for html links.
1135	 * @protected
1136	 * @since 4.4.003 (2008-12-09)
1137	 */
1138	protected $htmlLinkColorArray = array(0, 0, 255);
1139
1140	/**
1141	 * Default font style to add to html links.
1142	 * @protected
1143	 * @since 4.4.003 (2008-12-09)
1144	 */
1145	protected $htmlLinkFontStyle = 'U';
1146
1147	/**
1148	 * Counts the number of pages.
1149	 * @protected
1150	 * @since 4.5.000 (2008-12-31)
1151	 */
1152	protected $numpages = 0;
1153
1154	/**
1155	 * Array containing page lengths in bytes.
1156	 * @protected
1157	 * @since 4.5.000 (2008-12-31)
1158	 */
1159	protected $pagelen = array();
1160
1161	/**
1162	 * Counts the number of pages.
1163	 * @protected
1164	 * @since 4.5.000 (2008-12-31)
1165	 */
1166	protected $numimages = 0;
1167
1168	/**
1169	 * Store the image keys.
1170	 * @protected
1171	 * @since 4.5.000 (2008-12-31)
1172	 */
1173	protected $imagekeys = array();
1174
1175	/**
1176	 * Length of the buffer in bytes.
1177	 * @protected
1178	 * @since 4.5.000 (2008-12-31)
1179	 */
1180	protected $bufferlen = 0;
1181
1182	/**
1183	 * Counts the number of fonts.
1184	 * @protected
1185	 * @since 4.5.000 (2009-01-02)
1186	 */
1187	protected $numfonts = 0;
1188
1189	/**
1190	 * Store the font keys.
1191	 * @protected
1192	 * @since 4.5.000 (2009-01-02)
1193	 */
1194	protected $fontkeys = array();
1195
1196	/**
1197	 * Store the font object IDs.
1198	 * @protected
1199	 * @since 4.8.001 (2009-09-09)
1200	 */
1201	protected $font_obj_ids = array();
1202
1203	/**
1204	 * Store the fage status (true when opened, false when closed).
1205	 * @protected
1206	 * @since 4.5.000 (2009-01-02)
1207	 */
1208	protected $pageopen = array();
1209
1210	/**
1211	 * Default monospace font.
1212	 * @protected
1213	 * @since 4.5.025 (2009-03-10)
1214	 */
1215	protected $default_monospaced_font = 'courier';
1216
1217	/**
1218	 * Cloned copy of the current class object.
1219	 * @protected
1220	 * @since 4.5.029 (2009-03-19)
1221	 */
1222	protected $objcopy;
1223
1224	/**
1225	 * Array used to store the lengths of cache files.
1226	 * @protected
1227	 * @since 4.5.029 (2009-03-19)
1228	 */
1229	protected $cache_file_length = array();
1230
1231	/**
1232	 * Table header content to be repeated on each new page.
1233	 * @protected
1234	 * @since 4.5.030 (2009-03-20)
1235	 */
1236	protected $thead = '';
1237
1238	/**
1239	 * Margins used for table header.
1240	 * @protected
1241	 * @since 4.5.030 (2009-03-20)
1242	 */
1243	protected $theadMargins = array();
1244
1245	/**
1246	 * Boolean flag to enable document digital signature.
1247	 * @protected
1248	 * @since 4.6.005 (2009-04-24)
1249	 */
1250	protected $sign = false;
1251
1252	/**
1253	 * Digital signature data.
1254	 * @protected
1255	 * @since 4.6.005 (2009-04-24)
1256	 */
1257	protected $signature_data = array();
1258
1259	/**
1260	 * Digital signature max length.
1261	 * @protected
1262	 * @since 4.6.005 (2009-04-24)
1263	 */
1264	protected $signature_max_length = 11742;
1265
1266	/**
1267	 * Data for digital signature appearance.
1268	 * @protected
1269	 * @since 5.3.011 (2010-06-16)
1270	 */
1271	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1272
1273	/**
1274	 * Array of empty digital signature appearances.
1275	 * @protected
1276	 * @since 5.9.101 (2011-07-06)
1277	 */
1278	protected $empty_signature_appearance = array();
1279
1280	/**
1281	 * Boolean flag to enable document timestamping with TSA.
1282	 * @protected
1283	 * @since 6.0.085 (2014-06-19)
1284	 */
1285	protected $tsa_timestamp = false;
1286
1287	/**
1288	 * Timestamping data.
1289	 * @protected
1290	 * @since 6.0.085 (2014-06-19)
1291	 */
1292	protected $tsa_data = array();
1293
1294	/**
1295	 * Regular expression used to find blank characters (required for word-wrapping).
1296	 * @protected
1297	 * @since 4.6.006 (2009-04-28)
1298	 */
1299	protected $re_spaces = '/[^\S\xa0]/';
1300
1301	/**
1302	 * Array of $re_spaces parts.
1303	 * @protected
1304	 * @since 5.5.011 (2010-07-09)
1305	 */
1306	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1307
1308	/**
1309	 * Digital signature object ID.
1310	 * @protected
1311	 * @since 4.6.022 (2009-06-23)
1312	 */
1313	protected $sig_obj_id = 0;
1314
1315	/**
1316	 * ID of page objects.
1317	 * @protected
1318	 * @since 4.7.000 (2009-08-29)
1319	 */
1320	protected $page_obj_id = array();
1321
1322	/**
1323	 * List of form annotations IDs.
1324	 * @protected
1325	 * @since 4.8.000 (2009-09-07)
1326	 */
1327	protected $form_obj_id = array();
1328
1329	/**
1330	 * 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.
1331	 * @protected
1332	 * @since 4.8.000 (2009-09-07)
1333	 */
1334	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1335
1336	/**
1337	 * Javascript objects array.
1338	 * @protected
1339	 * @since 4.8.000 (2009-09-07)
1340	 */
1341	protected $js_objects = array();
1342
1343	/**
1344	 * Current form action (used during XHTML rendering).
1345	 * @protected
1346	 * @since 4.8.000 (2009-09-07)
1347	 */
1348	protected $form_action = '';
1349
1350	/**
1351	 * Current form encryption type (used during XHTML rendering).
1352	 * @protected
1353	 * @since 4.8.000 (2009-09-07)
1354	 */
1355	protected $form_enctype = 'application/x-www-form-urlencoded';
1356
1357	/**
1358	 * Current method to submit forms.
1359	 * @protected
1360	 * @since 4.8.000 (2009-09-07)
1361	 */
1362	protected $form_mode = 'post';
1363
1364	/**
1365	 * List of fonts used on form fields (fontname => fontkey).
1366	 * @protected
1367	 * @since 4.8.001 (2009-09-09)
1368	 */
1369	protected $annotation_fonts = array();
1370
1371	/**
1372	 * List of radio buttons parent objects.
1373	 * @protected
1374	 * @since 4.8.001 (2009-09-09)
1375	 */
1376	protected $radiobutton_groups = array();
1377
1378	/**
1379	 * List of radio group objects IDs.
1380	 * @protected
1381	 * @since 4.8.001 (2009-09-09)
1382	 */
1383	protected $radio_groups = array();
1384
1385	/**
1386	 * Text indentation value (used for text-indent CSS attribute).
1387	 * @protected
1388	 * @since 4.8.006 (2009-09-23)
1389	 */
1390	protected $textindent = 0;
1391
1392	/**
1393	 * Store page number when startTransaction() is called.
1394	 * @protected
1395	 * @since 4.8.006 (2009-09-23)
1396	 */
1397	protected $start_transaction_page = 0;
1398
1399	/**
1400	 * Store Y position when startTransaction() is called.
1401	 * @protected
1402	 * @since 4.9.001 (2010-03-28)
1403	 */
1404	protected $start_transaction_y = 0;
1405
1406	/**
1407	 * True when we are printing the thead section on a new page.
1408	 * @protected
1409	 * @since 4.8.027 (2010-01-25)
1410	 */
1411	protected $inthead = false;
1412
1413	/**
1414	 * Array of column measures (width, space, starting Y position).
1415	 * @protected
1416	 * @since 4.9.001 (2010-03-28)
1417	 */
1418	protected $columns = array();
1419
1420	/**
1421	 * Number of colums.
1422	 * @protected
1423	 * @since 4.9.001 (2010-03-28)
1424	 */
1425	protected $num_columns = 1;
1426
1427	/**
1428	 * Current column number.
1429	 * @protected
1430	 * @since 4.9.001 (2010-03-28)
1431	 */
1432	protected $current_column = 0;
1433
1434	/**
1435	 * Starting page for columns.
1436	 * @protected
1437	 * @since 4.9.001 (2010-03-28)
1438	 */
1439	protected $column_start_page = 0;
1440
1441	/**
1442	 * Maximum page and column selected.
1443	 * @protected
1444	 * @since 5.8.000 (2010-08-11)
1445	 */
1446	protected $maxselcol = array('page' => 0, 'column' => 0);
1447
1448	/**
1449	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1450	 * @protected
1451	 * @since 5.8.000 (2010-08-11)
1452	 */
1453	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1454
1455	/**
1456	 * 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.
1457	 * @protected
1458	 * @since 4.9.008 (2010-04-03)
1459	 */
1460	protected $textrendermode = 0;
1461
1462	/**
1463	 * Text stroke width in doc units.
1464	 * @protected
1465	 * @since 4.9.008 (2010-04-03)
1466	 */
1467	protected $textstrokewidth = 0;
1468
1469	/**
1470	 * Current stroke color.
1471	 * @protected
1472	 * @since 4.9.008 (2010-04-03)
1473	 */
1474	protected $strokecolor;
1475
1476	/**
1477	 * Default unit of measure for document.
1478	 * @protected
1479	 * @since 5.0.000 (2010-04-22)
1480	 */
1481	protected $pdfunit = 'mm';
1482
1483	/**
1484	 * Boolean flag true when we are on TOC (Table Of Content) page.
1485	 * @protected
1486	 */
1487	protected $tocpage = false;
1488
1489	/**
1490	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1491	 * @protected
1492	 * @since 5.0.000 (2010-04-26)
1493	 */
1494	protected $rasterize_vector_images = false;
1495
1496	/**
1497	 * Boolean flag: if true enables font subsetting by default.
1498	 * @protected
1499	 * @since 5.3.002 (2010-06-07)
1500	 */
1501	protected $font_subsetting = true;
1502
1503	/**
1504	 * Array of default graphic settings.
1505	 * @protected
1506	 * @since 5.5.008 (2010-07-02)
1507	 */
1508	protected $default_graphic_vars = array();
1509
1510	/**
1511	 * Array of XObjects.
1512	 * @protected
1513	 * @since 5.8.014 (2010-08-23)
1514	 */
1515	protected $xobjects = array();
1516
1517	/**
1518	 * Boolean value true when we are inside an XObject.
1519	 * @protected
1520	 * @since 5.8.017 (2010-08-24)
1521	 */
1522	protected $inxobj = false;
1523
1524	/**
1525	 * Current XObject ID.
1526	 * @protected
1527	 * @since 5.8.017 (2010-08-24)
1528	 */
1529	protected $xobjid = '';
1530
1531	/**
1532	 * Percentage of character stretching.
1533	 * @protected
1534	 * @since 5.9.000 (2010-09-29)
1535	 */
1536	protected $font_stretching = 100;
1537
1538	/**
1539	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1540	 * @protected
1541	 * @since 5.9.000 (2010-09-29)
1542	 */
1543	protected $font_spacing = 0;
1544
1545	/**
1546	 * Array of no-write regions.
1547	 * ('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)
1548	 * @protected
1549	 * @since 5.9.003 (2010-10-14)
1550	 */
1551	protected $page_regions = array();
1552
1553	/**
1554	 * Boolean value true when page region check is active.
1555	 * @protected
1556	 */
1557	protected $check_page_regions = true;
1558
1559	/**
1560	 * Array of PDF layers data.
1561	 * @protected
1562	 * @since 5.9.102 (2011-07-13)
1563	 */
1564	protected $pdflayers = array();
1565
1566	/**
1567	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1568	 * @protected
1569	 * @since 5.9.097 (2011-06-23)
1570	 */
1571	protected $dests = array();
1572
1573	/**
1574	 * Object ID for Named Destinations
1575	 * @protected
1576	 * @since 5.9.097 (2011-06-23)
1577	 */
1578	protected $n_dests;
1579
1580	/**
1581	 * Embedded Files Names
1582	 * @protected
1583	 * @since 5.9.204 (2013-01-23)
1584	 */
1585	protected $efnames = array();
1586
1587	/**
1588	 * Directory used for the last SVG image.
1589	 * @protected
1590	 * @since 5.0.000 (2010-05-05)
1591	 */
1592	protected $svgdir = '';
1593
1594	/**
1595	 *  Deafult unit of measure for SVG.
1596	 * @protected
1597	 * @since 5.0.000 (2010-05-02)
1598	 */
1599	protected $svgunit = 'px';
1600
1601	/**
1602	 * Array of SVG gradients.
1603	 * @protected
1604	 * @since 5.0.000 (2010-05-02)
1605	 */
1606	protected $svggradients = array();
1607
1608	/**
1609	 * ID of last SVG gradient.
1610	 * @protected
1611	 * @since 5.0.000 (2010-05-02)
1612	 */
1613	protected $svggradientid = 0;
1614
1615	/**
1616	 * Boolean value true when in SVG defs group.
1617	 * @protected
1618	 * @since 5.0.000 (2010-05-02)
1619	 */
1620	protected $svgdefsmode = false;
1621
1622	/**
1623	 * Array of SVG defs.
1624	 * @protected
1625	 * @since 5.0.000 (2010-05-02)
1626	 */
1627	protected $svgdefs = array();
1628
1629	/**
1630	 * Boolean value true when in SVG clipPath tag.
1631	 * @protected
1632	 * @since 5.0.000 (2010-04-26)
1633	 */
1634	protected $svgclipmode = false;
1635
1636	/**
1637	 * Array of SVG clipPath commands.
1638	 * @protected
1639	 * @since 5.0.000 (2010-05-02)
1640	 */
1641	protected $svgclippaths = array();
1642
1643	/**
1644	 * Array of SVG clipPath tranformation matrix.
1645	 * @protected
1646	 * @since 5.8.022 (2010-08-31)
1647	 */
1648	protected $svgcliptm = array();
1649
1650	/**
1651	 * ID of last SVG clipPath.
1652	 * @protected
1653	 * @since 5.0.000 (2010-05-02)
1654	 */
1655	protected $svgclipid = 0;
1656
1657	/**
1658	 * SVG text.
1659	 * @protected
1660	 * @since 5.0.000 (2010-05-02)
1661	 */
1662	protected $svgtext = '';
1663
1664	/**
1665	 * SVG text properties.
1666	 * @protected
1667	 * @since 5.8.013 (2010-08-23)
1668	 */
1669	protected $svgtextmode = array();
1670
1671	/**
1672	 * Array of SVG properties.
1673	 * @protected
1674	 * @since 5.0.000 (2010-05-02)
1675	 */
1676	protected $svgstyles = array(array(
1677		'alignment-baseline' => 'auto',
1678		'baseline-shift' => 'baseline',
1679		'clip' => 'auto',
1680		'clip-path' => 'none',
1681		'clip-rule' => 'nonzero',
1682		'color' => 'black',
1683		'color-interpolation' => 'sRGB',
1684		'color-interpolation-filters' => 'linearRGB',
1685		'color-profile' => 'auto',
1686		'color-rendering' => 'auto',
1687		'cursor' => 'auto',
1688		'direction' => 'ltr',
1689		'display' => 'inline',
1690		'dominant-baseline' => 'auto',
1691		'enable-background' => 'accumulate',
1692		'fill' => 'black',
1693		'fill-opacity' => 1,
1694		'fill-rule' => 'nonzero',
1695		'filter' => 'none',
1696		'flood-color' => 'black',
1697		'flood-opacity' => 1,
1698		'font' => '',
1699		'font-family' => 'helvetica',
1700		'font-size' => 'medium',
1701		'font-size-adjust' => 'none',
1702		'font-stretch' => 'normal',
1703		'font-style' => 'normal',
1704		'font-variant' => 'normal',
1705		'font-weight' => 'normal',
1706		'glyph-orientation-horizontal' => '0deg',
1707		'glyph-orientation-vertical' => 'auto',
1708		'image-rendering' => 'auto',
1709		'kerning' => 'auto',
1710		'letter-spacing' => 'normal',
1711		'lighting-color' => 'white',
1712		'marker' => '',
1713		'marker-end' => 'none',
1714		'marker-mid' => 'none',
1715		'marker-start' => 'none',
1716		'mask' => 'none',
1717		'opacity' => 1,
1718		'overflow' => 'auto',
1719		'pointer-events' => 'visiblePainted',
1720		'shape-rendering' => 'auto',
1721		'stop-color' => 'black',
1722		'stop-opacity' => 1,
1723		'stroke' => 'none',
1724		'stroke-dasharray' => 'none',
1725		'stroke-dashoffset' => 0,
1726		'stroke-linecap' => 'butt',
1727		'stroke-linejoin' => 'miter',
1728		'stroke-miterlimit' => 4,
1729		'stroke-opacity' => 1,
1730		'stroke-width' => 1,
1731		'text-anchor' => 'start',
1732		'text-decoration' => 'none',
1733		'text-rendering' => 'auto',
1734		'unicode-bidi' => 'normal',
1735		'visibility' => 'visible',
1736		'word-spacing' => 'normal',
1737		'writing-mode' => 'lr-tb',
1738		'text-color' => 'black',
1739		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1740		));
1741
1742	/**
1743	 * If true force sRGB color profile for all document.
1744	 * @protected
1745	 * @since 5.9.121 (2011-09-28)
1746	 */
1747	protected $force_srgb = false;
1748
1749	/**
1750	 * If true set the document to PDF/A mode.
1751	 * @protected
1752	 * @since 5.9.121 (2011-09-27)
1753	 */
1754	protected $pdfa_mode = false;
1755
1756	/**
1757	 * version of PDF/A mode (1 - 3).
1758	 * @protected
1759	 * @since 6.2.26 (2019-03-12)
1760	 */
1761	protected $pdfa_version = 1;
1762
1763	/**
1764	 * Document creation date-time
1765	 * @protected
1766	 * @since 5.9.152 (2012-03-22)
1767	 */
1768	protected $doc_creation_timestamp;
1769
1770	/**
1771	 * Document modification date-time
1772	 * @protected
1773	 * @since 5.9.152 (2012-03-22)
1774	 */
1775	protected $doc_modification_timestamp;
1776
1777	/**
1778	 * Custom XMP data.
1779	 * @protected
1780	 * @since 5.9.128 (2011-10-06)
1781	 */
1782	protected $custom_xmp = '';
1783
1784	/**
1785	 * Custom XMP RDF data.
1786	 * @protected
1787	 * @since 6.3.0 (2019-09-19)
1788	 */
1789	protected $custom_xmp_rdf = '';
1790
1791	/**
1792	 * Overprint mode array.
1793	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1794	 * @protected
1795	 * @since 5.9.152 (2012-03-23)
1796	 */
1797	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1798
1799	/**
1800	 * Alpha mode array.
1801	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1802	 * @protected
1803	 * @since 5.9.152 (2012-03-23)
1804	 */
1805	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1806
1807	/**
1808	 * Define the page boundaries boxes to be set on document.
1809	 * @protected
1810	 * @since 5.9.152 (2012-03-23)
1811	 */
1812	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1813
1814	/**
1815	 * If true print TCPDF meta link.
1816	 * @protected
1817	 * @since 5.9.152 (2012-03-23)
1818	 */
1819	protected $tcpdflink = true;
1820
1821	/**
1822	 * Cache array for computed GD gamma values.
1823	 * @protected
1824	 * @since 5.9.1632 (2012-06-05)
1825	 */
1826	protected $gdgammacache = array();
1827
1828    /**
1829     * Cache array for file content
1830     * @protected
1831     * @var array
1832     * @sinde 6.3.5 (2020-09-28)
1833     */
1834	protected $fileContentCache = array();
1835
1836	/**
1837	 * Whether to allow local file path in image html tags, when prefixed with file://
1838	 *
1839	 * @var bool
1840	 * @protected
1841	 * @since 6.4 (2020-07-23)
1842	 */
1843	protected $allowLocalFiles = false;
1844
1845	//------------------------------------------------------------
1846	// METHODS
1847	//------------------------------------------------------------
1848
1849	/**
1850	 * This is the class constructor.
1851	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1852	 *
1853	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1854	 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1855	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1856	 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1857	 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1858	 * @param $diskcache (boolean) DEPRECATED FEATURE
1859	 * @param $pdfa (integer) If not false, set the document to PDF/A mode and the good version (1 or 3).
1860	 * @public
1861	 * @see getPageSizeFromFormat(), setPageFormat()
1862	 */
1863	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1864		// set file ID for trailer
1865		$serformat = (is_array($format) ? json_encode($format) : $format);
1866		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1867		$this->font_obj_ids = array();
1868		$this->page_obj_id = array();
1869		$this->form_obj_id = array();
1870
1871		// set pdf/a mode
1872		if ($pdfa != false) {
1873			$this->pdfa_mode = true;
1874			$this->pdfa_version = $pdfa;  // 1 or 3
1875		} else
1876			$this->pdfa_mode = false;
1877
1878		$this->force_srgb = false;
1879		// set language direction
1880		$this->rtl = false;
1881		$this->tmprtl = false;
1882		// some checks
1883		$this->_dochecks();
1884		// initialization of properties
1885		$this->isunicode = $unicode;
1886		$this->page = 0;
1887		$this->transfmrk[0] = array();
1888		$this->pagedim = array();
1889		$this->n = 2;
1890		$this->buffer = '';
1891		$this->pages = array();
1892		$this->state = 0;
1893		$this->fonts = array();
1894		$this->FontFiles = array();
1895		$this->diffs = array();
1896		$this->images = array();
1897		$this->links = array();
1898		$this->gradients = array();
1899		$this->InFooter = false;
1900		$this->lasth = 0;
1901		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1902		$this->FontStyle = '';
1903		$this->FontSizePt = 12;
1904		$this->underline = false;
1905		$this->overline = false;
1906		$this->linethrough = false;
1907		$this->DrawColor = '0 G';
1908		$this->FillColor = '0 g';
1909		$this->TextColor = '0 g';
1910		$this->ColorFlag = false;
1911		$this->pdflayers = array();
1912		// encryption values
1913		$this->encrypted = false;
1914		$this->last_enc_key = '';
1915		// standard Unicode fonts
1916		$this->CoreFonts = array(
1917			'courier'=>'Courier',
1918			'courierB'=>'Courier-Bold',
1919			'courierI'=>'Courier-Oblique',
1920			'courierBI'=>'Courier-BoldOblique',
1921			'helvetica'=>'Helvetica',
1922			'helveticaB'=>'Helvetica-Bold',
1923			'helveticaI'=>'Helvetica-Oblique',
1924			'helveticaBI'=>'Helvetica-BoldOblique',
1925			'times'=>'Times-Roman',
1926			'timesB'=>'Times-Bold',
1927			'timesI'=>'Times-Italic',
1928			'timesBI'=>'Times-BoldItalic',
1929			'symbol'=>'Symbol',
1930			'zapfdingbats'=>'ZapfDingbats'
1931		);
1932		// set scale factor
1933		$this->setPageUnit($unit);
1934		// set page format and orientation
1935		$this->setPageFormat($format, $orientation);
1936		// page margins (1 cm)
1937		$margin = 28.35 / $this->k;
1938		$this->SetMargins($margin, $margin);
1939		$this->clMargin = $this->lMargin;
1940		$this->crMargin = $this->rMargin;
1941		// internal cell padding
1942		$cpadding = $margin / 10;
1943		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1944		// cell margins
1945		$this->setCellMargins(0, 0, 0, 0);
1946		// line width (0.2 mm)
1947		$this->LineWidth = 0.57 / $this->k;
1948		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1949		$this->linestyleCap = '0 J';
1950		$this->linestyleJoin = '0 j';
1951		$this->linestyleDash = '[] 0 d';
1952		// automatic page break
1953		$this->SetAutoPageBreak(true, (2 * $margin));
1954		// full width display mode
1955		$this->SetDisplayMode('fullwidth');
1956		// compression
1957		$this->SetCompression();
1958		// set default PDF version number
1959		$this->setPDFVersion();
1960		$this->tcpdflink = true;
1961		$this->encoding = $encoding;
1962		$this->HREF = array();
1963		$this->getFontsList();
1964		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1965		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1966		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1967		$this->extgstates = array();
1968		$this->setTextShadow();
1969		// signature
1970		$this->sign = false;
1971		$this->tsa_timestamp = false;
1972		$this->tsa_data = array();
1973		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1974		$this->empty_signature_appearance = array();
1975		// user's rights
1976		$this->ur['enabled'] = false;
1977		$this->ur['document'] = '/FullSave';
1978		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1979		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1980		$this->ur['signature'] = '/Modify';
1981		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1982		$this->ur['formex'] = '';
1983		// set default JPEG quality
1984		$this->jpeg_quality = 75;
1985		// initialize some settings
1986		TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
1987		// set default font
1988		$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1989		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1990		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1991		// check if PCRE Unicode support is enabled
1992		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1993			// PCRE unicode support is turned ON
1994			// \s     : any whitespace character
1995			// \p{Z}  : any separator
1996			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1997			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1998			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1999			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2000		} else {
2001			// PCRE unicode support is turned OFF
2002			$this->setSpacesRE('/[^\S\xa0]/');
2003		}
2004		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2005		// set document creation and modification timestamp
2006		$this->doc_creation_timestamp = time();
2007		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
2008		// get default graphic vars
2009		$this->default_graphic_vars = $this->getGraphicVars();
2010		$this->header_xobj_autoreset = false;
2011		$this->custom_xmp = '';
2012		$this->custom_xmp_rdf = '';
2013		// Call cleanup method after script execution finishes or exit() is called.
2014		// NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
2015		register_shutdown_function(array($this, '_destroy'), true);
2016	}
2017
2018	/**
2019	 * Default destructor.
2020	 * @public
2021	 * @since 1.53.0.TC016
2022	 */
2023	public function __destruct() {
2024		// cleanup
2025		$this->_destroy(true);
2026	}
2027
2028	/**
2029	 * Set the units of measure for the document.
2030	 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2031	 * @public
2032	 * @since 3.0.015 (2008-06-06)
2033	 */
2034	public function setPageUnit($unit) {
2035		$unit = strtolower($unit);
2036		//Set scale factor
2037		switch ($unit) {
2038			// points
2039			case 'px':
2040			case 'pt': {
2041				$this->k = 1;
2042				break;
2043			}
2044			// millimeters
2045			case 'mm': {
2046				$this->k = $this->dpi / 25.4;
2047				break;
2048			}
2049			// centimeters
2050			case 'cm': {
2051				$this->k = $this->dpi / 2.54;
2052				break;
2053			}
2054			// inches
2055			case 'in': {
2056				$this->k = $this->dpi;
2057				break;
2058			}
2059			// unsupported unit
2060			default : {
2061				$this->Error('Incorrect unit: '.$unit);
2062				break;
2063			}
2064		}
2065		$this->pdfunit = $unit;
2066		if (isset($this->CurOrientation)) {
2067			$this->setPageOrientation($this->CurOrientation);
2068		}
2069	}
2070
2071	/**
2072	 * Change the format of the current page
2073	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2074	 * <li>['format'] = page format name (one of the above);</li>
2075	 * <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>
2076	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2077	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2078	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2079	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2080	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2081	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2082	 * <li>['CropBox'] : the visible region of default user space:</li>
2083	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2084	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2085	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2086	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2087	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2088	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2089	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2090	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2091	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2092	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2093	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2094	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2095	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2096	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2097	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2098	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2099	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2100	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2101	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2102	 * <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>
2103	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2104	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2105	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2106	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2107	 * <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>
2108	 * <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>
2109	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2110	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2111	 * <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>
2112	 * <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>
2113	 * <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>
2114	 * <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>
2115	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2116	 * </ul>
2117	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2118	 * <li>P or Portrait (default)</li>
2119	 * <li>L or Landscape</li>
2120	 * <li>'' (empty string) for automatic orientation</li>
2121	 * </ul>
2122	 * @protected
2123	 * @since 3.0.015 (2008-06-06)
2124	 * @see getPageSizeFromFormat()
2125	 */
2126	protected function setPageFormat($format, $orientation='P') {
2127		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2128			// remove inherited values
2129			unset($this->pagedim[$this->page]);
2130		}
2131		if (is_string($format)) {
2132			// get page measures from format name
2133			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2134			$this->fwPt = $pf[0];
2135			$this->fhPt = $pf[1];
2136		} else {
2137			// the boundaries of the physical medium on which the page shall be displayed or printed
2138			if (isset($format['MediaBox'])) {
2139				$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);
2140				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2141				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2142			} else {
2143				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2144					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2145				} else {
2146					if (!isset($format['format'])) {
2147						// default value
2148						$format['format'] = 'A4';
2149					}
2150					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2151				}
2152				$this->fwPt = $pf[0];
2153				$this->fhPt = $pf[1];
2154				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2155			}
2156			// the visible region of default user space
2157			if (isset($format['CropBox'])) {
2158				$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);
2159			}
2160			// the region to which the contents of the page shall be clipped when output in a production environment
2161			if (isset($format['BleedBox'])) {
2162				$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);
2163			}
2164			// the intended dimensions of the finished page after trimming
2165			if (isset($format['TrimBox'])) {
2166				$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);
2167			}
2168			// the page's meaningful content (including potential white space)
2169			if (isset($format['ArtBox'])) {
2170				$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);
2171			}
2172			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2173			if (isset($format['BoxColorInfo'])) {
2174				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2175			}
2176			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2177				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2178				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2179			}
2180			if (isset($format['PZ'])) {
2181				// The page's preferred zoom (magnification) factor
2182				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2183			}
2184			if (isset($format['trans'])) {
2185				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2186				if (isset($format['trans']['Dur'])) {
2187					// The page's display duration
2188					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2189				}
2190				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2191				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2192					// The transition style that shall be used when moving to this page from another during a presentation
2193					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2194					$valid_effect = array('Split', 'Blinds');
2195					$valid_vals = array('H', 'V');
2196					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2197						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2198					}
2199					$valid_effect = array('Split', 'Box', 'Fly');
2200					$valid_vals = array('I', 'O');
2201					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2202						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2203					}
2204					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2205					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2206						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2207							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2208							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2209							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2210						}
2211					}
2212					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2213						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2214					}
2215					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2216						$this->pagedim[$this->page]['trans']['B'] = 'true';
2217					}
2218				} else {
2219					$this->pagedim[$this->page]['trans']['S'] = 'R';
2220				}
2221				if (isset($format['trans']['D'])) {
2222					// The duration of the transition effect, in seconds
2223					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2224				} else {
2225					$this->pagedim[$this->page]['trans']['D'] = 1;
2226				}
2227			}
2228		}
2229		$this->setPageOrientation($orientation);
2230	}
2231
2232	/**
2233	 * Set page orientation.
2234	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2235	 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2236	 * @param $bottommargin (float) bottom margin of the page.
2237	 * @public
2238	 * @since 3.0.015 (2008-06-06)
2239	 */
2240	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2241		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2242			// the boundaries of the physical medium on which the page shall be displayed or printed
2243			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2244		}
2245		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2246			// the visible region of default user space
2247			$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);
2248		}
2249		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2250			// the region to which the contents of the page shall be clipped when output in a production environment
2251			$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);
2252		}
2253		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2254			// the intended dimensions of the finished page after trimming
2255			$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);
2256		}
2257		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2258			// the page's meaningful content (including potential white space)
2259			$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);
2260		}
2261		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2262			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2263			$this->pagedim[$this->page]['Rotate'] = 0;
2264		}
2265		if (!isset($this->pagedim[$this->page]['PZ'])) {
2266			// The page's preferred zoom (magnification) factor
2267			$this->pagedim[$this->page]['PZ'] = 1;
2268		}
2269		if ($this->fwPt > $this->fhPt) {
2270			// landscape
2271			$default_orientation = 'L';
2272		} else {
2273			// portrait
2274			$default_orientation = 'P';
2275		}
2276		$valid_orientations = array('P', 'L');
2277		if (empty($orientation)) {
2278			$orientation = $default_orientation;
2279		} else {
2280			$orientation = strtoupper($orientation[0]);
2281		}
2282		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2283			$this->CurOrientation = $orientation;
2284			$this->wPt = $this->fhPt;
2285			$this->hPt = $this->fwPt;
2286		} else {
2287			$this->CurOrientation = $default_orientation;
2288			$this->wPt = $this->fwPt;
2289			$this->hPt = $this->fhPt;
2290		}
2291		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2292			// swap X and Y coordinates (change page orientation)
2293			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2294		}
2295		$this->w = ($this->wPt / $this->k);
2296		$this->h = ($this->hPt / $this->k);
2297		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2298			if (isset($this->AutoPageBreak)) {
2299				$autopagebreak = $this->AutoPageBreak;
2300			} else {
2301				$autopagebreak = true;
2302			}
2303		}
2304		if (TCPDF_STATIC::empty_string($bottommargin)) {
2305			if (isset($this->bMargin)) {
2306				$bottommargin = $this->bMargin;
2307			} else {
2308				// default value = 2 cm
2309				$bottommargin = 2 * 28.35 / $this->k;
2310			}
2311		}
2312		$this->SetAutoPageBreak($autopagebreak, $bottommargin);
2313		// store page dimensions
2314		$this->pagedim[$this->page]['w'] = $this->wPt;
2315		$this->pagedim[$this->page]['h'] = $this->hPt;
2316		$this->pagedim[$this->page]['wk'] = $this->w;
2317		$this->pagedim[$this->page]['hk'] = $this->h;
2318		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2319		$this->pagedim[$this->page]['bm'] = $bottommargin;
2320		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2321		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2322		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2323		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2324		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2325		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2326	}
2327
2328	/**
2329	 * Set regular expression to detect withespaces or word separators.
2330	 * The pattern delimiter must be the forward-slash character "/".
2331	 * Some example patterns are:
2332	 * <pre>
2333	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2334	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2335	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2336	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2337	 *      \s     : any whitespace character
2338	 *      \p{Z}  : any separator
2339	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2340	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2341	 * </pre>
2342	 * @param $re (string) regular expression (leave empty for default).
2343	 * @public
2344	 * @since 4.6.016 (2009-06-15)
2345	 */
2346	public function setSpacesRE($re='/[^\S\xa0]/') {
2347		$this->re_spaces = $re;
2348		$re_parts = explode('/', $re);
2349		// get pattern parts
2350		$this->re_space = array();
2351		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2352			$this->re_space['p'] = $re_parts[1];
2353		} else {
2354			$this->re_space['p'] = '[\s]';
2355		}
2356		// set pattern modifiers
2357		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2358			$this->re_space['m'] = $re_parts[2];
2359		} else {
2360			$this->re_space['m'] = '';
2361		}
2362	}
2363
2364	/**
2365	 * Enable or disable Right-To-Left language mode
2366	 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2367	 * @param $resetx (Boolean) if true reset the X position on direction change.
2368	 * @public
2369	 * @since 2.0.000 (2008-01-03)
2370	 */
2371	public function setRTL($enable, $resetx=true) {
2372		$enable = $enable ? true : false;
2373		$resetx = ($resetx AND ($enable != $this->rtl));
2374		$this->rtl = $enable;
2375		$this->tmprtl = false;
2376		if ($resetx) {
2377			$this->Ln(0);
2378		}
2379	}
2380
2381	/**
2382	 * Return the RTL status
2383	 * @return boolean
2384	 * @public
2385	 * @since 4.0.012 (2008-07-24)
2386	 */
2387	public function getRTL() {
2388		return $this->rtl;
2389	}
2390
2391	/**
2392	 * Force temporary RTL language direction
2393	 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2394	 * @public
2395	 * @since 2.1.000 (2008-01-09)
2396	 */
2397	public function setTempRTL($mode) {
2398		$newmode = false;
2399		switch (strtoupper($mode)) {
2400			case 'LTR':
2401			case 'L': {
2402				if ($this->rtl) {
2403					$newmode = 'L';
2404				}
2405				break;
2406			}
2407			case 'RTL':
2408			case 'R': {
2409				if (!$this->rtl) {
2410					$newmode = 'R';
2411				}
2412				break;
2413			}
2414			case false:
2415			default: {
2416				$newmode = false;
2417				break;
2418			}
2419		}
2420		$this->tmprtl = $newmode;
2421	}
2422
2423	/**
2424	 * Return the current temporary RTL status
2425	 * @return boolean
2426	 * @public
2427	 * @since 4.8.014 (2009-11-04)
2428	 */
2429	public function isRTLTextDir() {
2430		return ($this->rtl OR ($this->tmprtl == 'R'));
2431	}
2432
2433	/**
2434	 * Set the last cell height.
2435	 * @param $h (float) cell height.
2436	 * @author Nicola Asuni
2437	 * @public
2438	 * @since 1.53.0.TC034
2439	 */
2440	public function setLastH($h) {
2441		$this->lasth = $h;
2442	}
2443
2444	/**
2445	 * Return the cell height
2446	 * @param $fontsize (int) Font size in internal units
2447	 * @param $padding (boolean) If true add cell padding
2448	 * @public
2449	 */
2450	public function getCellHeight($fontsize, $padding=TRUE) {
2451		$height = ($fontsize * $this->cell_height_ratio);
2452		if ($padding) {
2453			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2454		}
2455		return round($height, 6);
2456	}
2457
2458	/**
2459	 * Reset the last cell height.
2460	 * @public
2461	 * @since 5.9.000 (2010-10-03)
2462	 */
2463	public function resetLastH() {
2464		$this->lasth = $this->getCellHeight($this->FontSize);
2465	}
2466
2467	/**
2468	 * Get the last cell height.
2469	 * @return last cell height
2470	 * @public
2471	 * @since 4.0.017 (2008-08-05)
2472	 */
2473	public function getLastH() {
2474		return $this->lasth;
2475	}
2476
2477	/**
2478	 * Set the adjusting factor to convert pixels to user units.
2479	 * @param $scale (float) adjusting factor to convert pixels to user units.
2480	 * @author Nicola Asuni
2481	 * @public
2482	 * @since 1.5.2
2483	 */
2484	public function setImageScale($scale) {
2485		$this->imgscale = $scale;
2486	}
2487
2488	/**
2489	 * Returns the adjusting factor to convert pixels to user units.
2490	 * @return float adjusting factor to convert pixels to user units.
2491	 * @author Nicola Asuni
2492	 * @public
2493	 * @since 1.5.2
2494	 */
2495	public function getImageScale() {
2496		return $this->imgscale;
2497	}
2498
2499	/**
2500	 * Returns an array of page dimensions:
2501	 * <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>
2502	 * @param $pagenum (int) page number (empty = current page)
2503	 * @return array of page dimensions.
2504	 * @author Nicola Asuni
2505	 * @public
2506	 * @since 4.5.027 (2009-03-16)
2507	 */
2508	public function getPageDimensions($pagenum='') {
2509		if (empty($pagenum)) {
2510			$pagenum = $this->page;
2511		}
2512		return $this->pagedim[$pagenum];
2513	}
2514
2515	/**
2516	 * Returns the page width in units.
2517	 * @param $pagenum (int) page number (empty = current page)
2518	 * @return int page width.
2519	 * @author Nicola Asuni
2520	 * @public
2521	 * @since 1.5.2
2522	 * @see getPageDimensions()
2523	 */
2524	public function getPageWidth($pagenum='') {
2525		if (empty($pagenum)) {
2526			return $this->w;
2527		}
2528		return $this->pagedim[$pagenum]['w'];
2529	}
2530
2531	/**
2532	 * Returns the page height in units.
2533	 * @param $pagenum (int) page number (empty = current page)
2534	 * @return int page height.
2535	 * @author Nicola Asuni
2536	 * @public
2537	 * @since 1.5.2
2538	 * @see getPageDimensions()
2539	 */
2540	public function getPageHeight($pagenum='') {
2541		if (empty($pagenum)) {
2542			return $this->h;
2543		}
2544		return $this->pagedim[$pagenum]['h'];
2545	}
2546
2547	/**
2548	 * Returns the page break margin.
2549	 * @param $pagenum (int) page number (empty = current page)
2550	 * @return int page break margin.
2551	 * @author Nicola Asuni
2552	 * @public
2553	 * @since 1.5.2
2554	 * @see getPageDimensions()
2555	 */
2556	public function getBreakMargin($pagenum='') {
2557		if (empty($pagenum)) {
2558			return $this->bMargin;
2559		}
2560		return $this->pagedim[$pagenum]['bm'];
2561	}
2562
2563	/**
2564	 * Returns the scale factor (number of points in user unit).
2565	 * @return int scale factor.
2566	 * @author Nicola Asuni
2567	 * @public
2568	 * @since 1.5.2
2569	 */
2570	public function getScaleFactor() {
2571		return $this->k;
2572	}
2573
2574	/**
2575	 * Defines the left, top and right margins.
2576	 * @param $left (float) Left margin.
2577	 * @param $top (float) Top margin.
2578	 * @param $right (float) Right margin. Default value is the left one.
2579	 * @param $keepmargins (boolean) if true overwrites the default page margins
2580	 * @public
2581	 * @since 1.0
2582	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2583	 */
2584	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2585		//Set left, top and right margins
2586		$this->lMargin = $left;
2587		$this->tMargin = $top;
2588		if ($right == -1) {
2589			$right = $left;
2590		}
2591		$this->rMargin = $right;
2592		if ($keepmargins) {
2593			// overwrite original values
2594			$this->original_lMargin = $this->lMargin;
2595			$this->original_rMargin = $this->rMargin;
2596		}
2597	}
2598
2599	/**
2600	 * 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.
2601	 * @param $margin (float) The margin.
2602	 * @public
2603	 * @since 1.4
2604	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2605	 */
2606	public function SetLeftMargin($margin) {
2607		//Set left margin
2608		$this->lMargin = $margin;
2609		if (($this->page > 0) AND ($this->x < $margin)) {
2610			$this->x = $margin;
2611		}
2612	}
2613
2614	/**
2615	 * Defines the top margin. The method can be called before creating the first page.
2616	 * @param $margin (float) The margin.
2617	 * @public
2618	 * @since 1.5
2619	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2620	 */
2621	public function SetTopMargin($margin) {
2622		//Set top margin
2623		$this->tMargin = $margin;
2624		if (($this->page > 0) AND ($this->y < $margin)) {
2625			$this->y = $margin;
2626		}
2627	}
2628
2629	/**
2630	 * Defines the right margin. The method can be called before creating the first page.
2631	 * @param $margin (float) The margin.
2632	 * @public
2633	 * @since 1.5
2634	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2635	 */
2636	public function SetRightMargin($margin) {
2637		$this->rMargin = $margin;
2638		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2639			$this->x = $this->w - $margin;
2640		}
2641	}
2642
2643	/**
2644	 * Set the same internal Cell padding for top, right, bottom, left-
2645	 * @param $pad (float) internal padding.
2646	 * @public
2647	 * @since 2.1.000 (2008-01-09)
2648	 * @see getCellPaddings(), setCellPaddings()
2649	 */
2650	public function SetCellPadding($pad) {
2651		if ($pad >= 0) {
2652			$this->cell_padding['L'] = $pad;
2653			$this->cell_padding['T'] = $pad;
2654			$this->cell_padding['R'] = $pad;
2655			$this->cell_padding['B'] = $pad;
2656		}
2657	}
2658
2659	/**
2660	 * Set the internal Cell paddings.
2661	 * @param $left (float) left padding
2662	 * @param $top (float) top padding
2663	 * @param $right (float) right padding
2664	 * @param $bottom (float) bottom padding
2665	 * @public
2666	 * @since 5.9.000 (2010-10-03)
2667	 * @see getCellPaddings(), SetCellPadding()
2668	 */
2669	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2670		if (($left !== '') AND ($left >= 0)) {
2671			$this->cell_padding['L'] = $left;
2672		}
2673		if (($top !== '') AND ($top >= 0)) {
2674			$this->cell_padding['T'] = $top;
2675		}
2676		if (($right !== '') AND ($right >= 0)) {
2677			$this->cell_padding['R'] = $right;
2678		}
2679		if (($bottom !== '') AND ($bottom >= 0)) {
2680			$this->cell_padding['B'] = $bottom;
2681		}
2682	}
2683
2684	/**
2685	 * Get the internal Cell padding array.
2686	 * @return array of padding values
2687	 * @public
2688	 * @since 5.9.000 (2010-10-03)
2689	 * @see setCellPaddings(), SetCellPadding()
2690	 */
2691	public function getCellPaddings() {
2692		return $this->cell_padding;
2693	}
2694
2695	/**
2696	 * Set the internal Cell margins.
2697	 * @param $left (float) left margin
2698	 * @param $top (float) top margin
2699	 * @param $right (float) right margin
2700	 * @param $bottom (float) bottom margin
2701	 * @public
2702	 * @since 5.9.000 (2010-10-03)
2703	 * @see getCellMargins()
2704	 */
2705	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2706		if (($left !== '') AND ($left >= 0)) {
2707			$this->cell_margin['L'] = $left;
2708		}
2709		if (($top !== '') AND ($top >= 0)) {
2710			$this->cell_margin['T'] = $top;
2711		}
2712		if (($right !== '') AND ($right >= 0)) {
2713			$this->cell_margin['R'] = $right;
2714		}
2715		if (($bottom !== '') AND ($bottom >= 0)) {
2716			$this->cell_margin['B'] = $bottom;
2717		}
2718	}
2719
2720	/**
2721	 * Get the internal Cell margin array.
2722	 * @return array of margin values
2723	 * @public
2724	 * @since 5.9.000 (2010-10-03)
2725	 * @see setCellMargins()
2726	 */
2727	public function getCellMargins() {
2728		return $this->cell_margin;
2729	}
2730
2731	/**
2732	 * Adjust the internal Cell padding array to take account of the line width.
2733	 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2734	 * @return void|array of adjustments
2735	 * @public
2736	 * @since 5.9.000 (2010-10-03)
2737	 */
2738	protected function adjustCellPadding($brd=0) {
2739		if (empty($brd)) {
2740			return;
2741		}
2742		if (is_string($brd)) {
2743			// convert string to array
2744			$slen = strlen($brd);
2745			$newbrd = array();
2746			for ($i = 0; $i < $slen; ++$i) {
2747				$newbrd[$brd[$i]] = true;
2748			}
2749			$brd = $newbrd;
2750		} elseif (
2751			($brd === 1)
2752			|| ($brd === true)
2753			|| (is_numeric($brd) && ((int)$brd > 0))
2754		) {
2755			$brd = array('LRTB' => true);
2756		}
2757		if (!is_array($brd)) {
2758			return;
2759		}
2760		// store current cell padding
2761		$cp = $this->cell_padding;
2762		// select border mode
2763		if (isset($brd['mode'])) {
2764			$mode = $brd['mode'];
2765			unset($brd['mode']);
2766		} else {
2767			$mode = 'normal';
2768		}
2769		// process borders
2770		foreach ($brd as $border => $style) {
2771			$line_width = $this->LineWidth;
2772			if (is_array($style) && isset($style['width'])) {
2773				// get border width
2774				$line_width = $style['width'];
2775			}
2776			$adj = 0; // line width inside the cell
2777			switch ($mode) {
2778				case 'ext': {
2779					$adj = 0;
2780					break;
2781				}
2782				case 'int': {
2783					$adj = $line_width;
2784					break;
2785				}
2786				case 'normal':
2787				default: {
2788					$adj = ($line_width / 2);
2789					break;
2790				}
2791			}
2792			// correct internal cell padding if required to avoid overlap between text and lines
2793			if (
2794				is_numeric($this->cell_padding['T'])
2795				&& ($this->cell_padding['T'] < $adj)
2796				&& (strpos($border, 'T') !== false)
2797			) {
2798				$this->cell_padding['T'] = $adj;
2799			}
2800			if (
2801				is_numeric($this->cell_padding['R'])
2802				&& ($this->cell_padding['R'] < $adj)
2803				&& (strpos($border, 'R') !== false)
2804			) {
2805				$this->cell_padding['R'] = $adj;
2806			}
2807			if (
2808				is_numeric($this->cell_padding['B'])
2809				&& ($this->cell_padding['B'] < $adj)
2810				&& (strpos($border, 'B') !== false)
2811			) {
2812				$this->cell_padding['B'] = $adj;
2813			}
2814			if (
2815				is_numeric($this->cell_padding['L'])
2816				&& ($this->cell_padding['L'] < $adj)
2817				&& (strpos($border, 'L') !== false)
2818			) {
2819				$this->cell_padding['L'] = $adj;
2820			}
2821
2822		}
2823
2824		return array(
2825			'T' => ($this->cell_padding['T'] - $cp['T']),
2826			'R' => ($this->cell_padding['R'] - $cp['R']),
2827			'B' => ($this->cell_padding['B'] - $cp['B']),
2828			'L' => ($this->cell_padding['L'] - $cp['L']),
2829		);
2830	}
2831
2832	/**
2833	 * 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.
2834	 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2835	 * @param $margin (float) Distance from the bottom of the page.
2836	 * @public
2837	 * @since 1.0
2838	 * @see Cell(), MultiCell(), AcceptPageBreak()
2839	 */
2840	public function SetAutoPageBreak($auto, $margin=0) {
2841		$this->AutoPageBreak = $auto ? true : false;
2842		$this->bMargin = $margin;
2843		$this->PageBreakTrigger = $this->h - $margin;
2844	}
2845
2846	/**
2847	 * Return the auto-page-break mode (true or false).
2848	 * @return boolean auto-page-break mode
2849	 * @public
2850	 * @since 5.9.088
2851	 */
2852	public function getAutoPageBreak() {
2853		return $this->AutoPageBreak;
2854	}
2855
2856	/**
2857	 * Defines the way the document is to be displayed by the viewer.
2858	 * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2859	 * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2860	 * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2861	 * @public
2862	 * @since 1.2
2863	 */
2864	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2865		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2866			$this->ZoomMode = $zoom;
2867		} else {
2868			$this->Error('Incorrect zoom display mode: '.$zoom);
2869		}
2870		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2871		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2872	}
2873
2874	/**
2875	 * 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.
2876	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2877	 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2878	 * @public
2879	 * @since 1.4
2880	 */
2881	public function SetCompression($compress=true) {
2882		$this->compress = false;
2883		if (function_exists('gzcompress')) {
2884			if ($compress) {
2885				if ( !$this->pdfa_mode) {
2886					$this->compress = true;
2887				}
2888			}
2889		}
2890	}
2891
2892	/**
2893	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2894	 * @param $mode (boolean) If true force sRGB output intent.
2895	 * @public
2896	 * @since 5.9.121 (2011-09-28)
2897	 */
2898	public function setSRGBmode($mode=false) {
2899		$this->force_srgb = $mode ? true : false;
2900	}
2901
2902	/**
2903	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2904	 * This has effect only when unicode mode is set to false.
2905	 * @param $unicode (boolean) if true set the meta information in Unicode
2906	 * @since 5.9.027 (2010-12-01)
2907	 * @public
2908	 */
2909	public function SetDocInfoUnicode($unicode=true) {
2910		$this->docinfounicode = $unicode ? true : false;
2911	}
2912
2913	/**
2914	 * Defines the title of the document.
2915	 * @param $title (string) The title.
2916	 * @public
2917	 * @since 1.2
2918	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2919	 */
2920	public function SetTitle($title) {
2921		$this->title = $title;
2922	}
2923
2924	/**
2925	 * Defines the subject of the document.
2926	 * @param $subject (string) The subject.
2927	 * @public
2928	 * @since 1.2
2929	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2930	 */
2931	public function SetSubject($subject) {
2932		$this->subject = $subject;
2933	}
2934
2935	/**
2936	 * Defines the author of the document.
2937	 * @param $author (string) The name of the author.
2938	 * @public
2939	 * @since 1.2
2940	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2941	 */
2942	public function SetAuthor($author) {
2943		$this->author = $author;
2944	}
2945
2946	/**
2947	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2948	 * @param $keywords (string) The list of keywords.
2949	 * @public
2950	 * @since 1.2
2951	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2952	 */
2953	public function SetKeywords($keywords) {
2954		$this->keywords = $keywords;
2955	}
2956
2957	/**
2958	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2959	 * @param $creator (string) The name of the creator.
2960	 * @public
2961	 * @since 1.2
2962	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2963	 */
2964	public function SetCreator($creator) {
2965		$this->creator = $creator;
2966	}
2967
2968	/**
2969	 * Whether to allow local file path in image html tags, when prefixed with file://
2970	 *
2971	 * @param $allowLocalFiles bool true, when local files should be allowed. Otherwise false.
2972	 * @public
2973	 * @since 6.4
2974	 */
2975	public function SetAllowLocalFiles($allowLocalFiles) {
2976		$this->allowLocalFiles = (bool) $allowLocalFiles;
2977	}
2978
2979
2980	/**
2981	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2982	 * @param $msg (string) The error message
2983	 * @public
2984	 * @since 1.0
2985	 */
2986	public function Error($msg) {
2987		// unset all class variables
2988		$this->_destroy(true);
2989		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2990			die('<strong>TCPDF ERROR: </strong>'.$msg);
2991		} else {
2992			throw new Exception('TCPDF ERROR: '.$msg);
2993		}
2994	}
2995
2996	/**
2997	 * This method begins the generation of the PDF document.
2998	 * It is not necessary to call it explicitly because AddPage() does it automatically.
2999	 * Note: no page is created by this method
3000	 * @public
3001	 * @since 1.0
3002	 * @see AddPage(), Close()
3003	 */
3004	public function Open() {
3005		$this->state = 1;
3006	}
3007
3008	/**
3009	 * Terminates the PDF document.
3010	 * It is not necessary to call this method explicitly because Output() does it automatically.
3011	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3012	 * @public
3013	 * @since 1.0
3014	 * @see Open(), Output()
3015	 */
3016	public function Close() {
3017		if ($this->state == 3) {
3018			return;
3019		}
3020		if ($this->page == 0) {
3021			$this->AddPage();
3022		}
3023		$this->endLayer();
3024		if ($this->tcpdflink) {
3025			// save current graphic settings
3026			$gvars = $this->getGraphicVars();
3027			$this->setEqualColumns();
3028			$this->lastpage(true);
3029			$this->SetAutoPageBreak(false);
3030			$this->x = 0;
3031			$this->y = $this->h - (1 / $this->k);
3032			$this->lMargin = 0;
3033			$this->_outSaveGraphicsState();
3034			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3035			$this->SetFont($font, '', 1);
3036			$this->setTextRenderingMode(0, false, false);
3037			$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";
3038			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3039			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3040			$this->_outRestoreGraphicsState();
3041			// restore graphic settings
3042			$this->setGraphicVars($gvars);
3043		}
3044		// close page
3045		$this->endPage();
3046		// close document
3047		$this->_enddoc();
3048		// unset all class variables (except critical ones)
3049		$this->_destroy(false);
3050	}
3051
3052	/**
3053	 * Move pointer at the specified document page and update page dimensions.
3054	 * @param $pnum (int) page number (1 ... numpages)
3055	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3056	 * @public
3057	 * @since 2.1.000 (2008-01-07)
3058	 * @see getPage(), lastpage(), getNumPages()
3059	 */
3060	public function setPage($pnum, $resetmargins=false) {
3061		if (($pnum == $this->page) AND ($this->state == 2)) {
3062			return;
3063		}
3064		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3065			$this->state = 2;
3066			// save current graphic settings
3067			//$gvars = $this->getGraphicVars();
3068			$oldpage = $this->page;
3069			$this->page = $pnum;
3070			$this->wPt = $this->pagedim[$this->page]['w'];
3071			$this->hPt = $this->pagedim[$this->page]['h'];
3072			$this->w = $this->pagedim[$this->page]['wk'];
3073			$this->h = $this->pagedim[$this->page]['hk'];
3074			$this->tMargin = $this->pagedim[$this->page]['tm'];
3075			$this->bMargin = $this->pagedim[$this->page]['bm'];
3076			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3077			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3078			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3079			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3080			$this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3081			// restore graphic settings
3082			//$this->setGraphicVars($gvars);
3083			if ($resetmargins) {
3084				$this->lMargin = $this->pagedim[$this->page]['olm'];
3085				$this->rMargin = $this->pagedim[$this->page]['orm'];
3086				$this->SetY($this->tMargin);
3087			} else {
3088				// account for booklet mode
3089				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3090					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3091					$this->lMargin += $deltam;
3092					$this->rMargin -= $deltam;
3093				}
3094			}
3095		} else {
3096			$this->Error('Wrong page number on setPage() function: '.$pnum);
3097		}
3098	}
3099
3100	/**
3101	 * Reset pointer to the last document page.
3102	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3103	 * @public
3104	 * @since 2.0.000 (2008-01-04)
3105	 * @see setPage(), getPage(), getNumPages()
3106	 */
3107	public function lastPage($resetmargins=false) {
3108		$this->setPage($this->getNumPages(), $resetmargins);
3109	}
3110
3111	/**
3112	 * Get current document page number.
3113	 * @return int page number
3114	 * @public
3115	 * @since 2.1.000 (2008-01-07)
3116	 * @see setPage(), lastpage(), getNumPages()
3117	 */
3118	public function getPage() {
3119		return $this->page;
3120	}
3121
3122	/**
3123	 * Get the total number of insered pages.
3124	 * @return int number of pages
3125	 * @public
3126	 * @since 2.1.000 (2008-01-07)
3127	 * @see setPage(), getPage(), lastpage()
3128	 */
3129	public function getNumPages() {
3130		return $this->numpages;
3131	}
3132
3133	/**
3134	 * Adds a new TOC (Table Of Content) page to the document.
3135	 * @param $orientation (string) page orientation.
3136	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3137	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3138	 * @public
3139	 * @since 5.0.001 (2010-05-06)
3140	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3141	 */
3142	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3143		$this->AddPage($orientation, $format, $keepmargins, true);
3144	}
3145
3146	/**
3147	 * Terminate the current TOC (Table Of Content) page
3148	 * @public
3149	 * @since 5.0.001 (2010-05-06)
3150	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3151	 */
3152	public function endTOCPage() {
3153		$this->endPage(true);
3154	}
3155
3156	/**
3157	 * 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).
3158	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3159	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3160	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3161	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3162	 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3163	 * @public
3164	 * @since 1.0
3165	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3166	 */
3167	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3168		if ($this->inxobj) {
3169			// we are inside an XObject template
3170			return;
3171		}
3172		if (!isset($this->original_lMargin) OR $keepmargins) {
3173			$this->original_lMargin = $this->lMargin;
3174		}
3175		if (!isset($this->original_rMargin) OR $keepmargins) {
3176			$this->original_rMargin = $this->rMargin;
3177		}
3178		// terminate previous page
3179		$this->endPage();
3180		// start new page
3181		$this->startPage($orientation, $format, $tocpage);
3182	}
3183
3184	/**
3185	 * Terminate the current page
3186	 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3187	 * @public
3188	 * @since 4.2.010 (2008-11-14)
3189	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3190	 */
3191	public function endPage($tocpage=false) {
3192		// check if page is already closed
3193		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3194			return;
3195		}
3196		// print page footer
3197		$this->setFooter();
3198		// close page
3199		$this->_endpage();
3200		// mark page as closed
3201		$this->pageopen[$this->page] = false;
3202		if ($tocpage) {
3203			$this->tocpage = false;
3204		}
3205	}
3206
3207	/**
3208	 * Starts a new page to the document. The page must be closed using the endPage() function.
3209	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3210	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3211	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3212	 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3213	 * @since 4.2.010 (2008-11-14)
3214	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3215	 * @public
3216	 */
3217	public function startPage($orientation='', $format='', $tocpage=false) {
3218		if ($tocpage) {
3219			$this->tocpage = true;
3220		}
3221		// move page numbers of documents to be attached
3222		if ($this->tocpage) {
3223			// move reference to unexistent pages (used for page attachments)
3224			// adjust outlines
3225			$tmpoutlines = $this->outlines;
3226			foreach ($tmpoutlines as $key => $outline) {
3227				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3228					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3229				}
3230			}
3231			// adjust dests
3232			$tmpdests = $this->dests;
3233			foreach ($tmpdests as $key => $dest) {
3234				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3235					$this->dests[$key]['p'] = ($dest['p'] + 1);
3236				}
3237			}
3238			// adjust links
3239			$tmplinks = $this->links;
3240			foreach ($tmplinks as $key => $link) {
3241				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3242					$this->links[$key]['p'] = ($link['p'] + 1);
3243				}
3244			}
3245		}
3246		if ($this->numpages > $this->page) {
3247			// this page has been already added
3248			$this->setPage($this->page + 1);
3249			$this->SetY($this->tMargin);
3250			return;
3251		}
3252		// start a new page
3253		if ($this->state == 0) {
3254			$this->Open();
3255		}
3256		++$this->numpages;
3257		$this->swapMargins($this->booklet);
3258		// save current graphic settings
3259		$gvars = $this->getGraphicVars();
3260		// start new page
3261		$this->_beginpage($orientation, $format);
3262		// mark page as open
3263		$this->pageopen[$this->page] = true;
3264		// restore graphic settings
3265		$this->setGraphicVars($gvars);
3266		// mark this point
3267		$this->setPageMark();
3268		// print page header
3269		$this->setHeader();
3270		// restore graphic settings
3271		$this->setGraphicVars($gvars);
3272		// mark this point
3273		$this->setPageMark();
3274		// print table header (if any)
3275		$this->setTableHeader();
3276		// set mark for empty page check
3277		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3278	}
3279
3280	/**
3281	 * Set start-writing mark on current page stream used to put borders and fills.
3282	 * Borders and fills are always created after content and inserted on the position marked by this method.
3283	 * This function must be called after calling Image() function for a background image.
3284	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3285	 * @public
3286	 * @since 4.0.016 (2008-07-30)
3287	 */
3288	public function setPageMark() {
3289		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3290		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3291		$this->setContentMark();
3292	}
3293
3294	/**
3295	 * Set start-writing mark on selected page.
3296	 * Borders and fills are always created after content and inserted on the position marked by this method.
3297	 * @param $page (int) page number (default is the current page)
3298	 * @protected
3299	 * @since 4.6.021 (2009-07-20)
3300	 */
3301	protected function setContentMark($page=0) {
3302		if ($page <= 0) {
3303			$page = $this->page;
3304		}
3305		if (isset($this->footerlen[$page])) {
3306			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3307		} else {
3308			$this->cntmrk[$page] = $this->pagelen[$page];
3309		}
3310	}
3311
3312	/**
3313	 * Set header data.
3314	 * @param $ln (string) header image logo
3315	 * @param $lw (string) header image logo width in mm
3316	 * @param $ht (string) string to print as title on document header
3317	 * @param $hs (string) string to print on document header
3318	 * @param $tc (array) RGB array color for text.
3319	 * @param $lc (array) RGB array color for line.
3320	 * @public
3321	 */
3322	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3323		$this->header_logo = $ln;
3324		$this->header_logo_width = $lw;
3325		$this->header_title = $ht;
3326		$this->header_string = $hs;
3327		$this->header_text_color = $tc;
3328		$this->header_line_color = $lc;
3329	}
3330
3331	/**
3332	 * Set footer data.
3333	 * @param $tc (array) RGB array color for text.
3334	 * @param $lc (array) RGB array color for line.
3335	 * @public
3336	 */
3337	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3338		$this->footer_text_color = $tc;
3339		$this->footer_line_color = $lc;
3340	}
3341
3342	/**
3343	 * Returns header data:
3344	 * <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>
3345	 * @return array()
3346	 * @public
3347	 * @since 4.0.012 (2008-07-24)
3348	 */
3349	public function getHeaderData() {
3350		$ret = array();
3351		$ret['logo'] = $this->header_logo;
3352		$ret['logo_width'] = $this->header_logo_width;
3353		$ret['title'] = $this->header_title;
3354		$ret['string'] = $this->header_string;
3355		$ret['text_color'] = $this->header_text_color;
3356		$ret['line_color'] = $this->header_line_color;
3357		return $ret;
3358	}
3359
3360	/**
3361	 * Set header margin.
3362	 * (minimum distance between header and top page margin)
3363	 * @param $hm (int) distance in user units
3364	 * @public
3365	 */
3366	public function setHeaderMargin($hm=10) {
3367		$this->header_margin = $hm;
3368	}
3369
3370	/**
3371	 * Returns header margin in user units.
3372	 * @return float
3373	 * @since 4.0.012 (2008-07-24)
3374	 * @public
3375	 */
3376	public function getHeaderMargin() {
3377		return $this->header_margin;
3378	}
3379
3380	/**
3381	 * Set footer margin.
3382	 * (minimum distance between footer and bottom page margin)
3383	 * @param $fm (int) distance in user units
3384	 * @public
3385	 */
3386	public function setFooterMargin($fm=10) {
3387		$this->footer_margin = $fm;
3388	}
3389
3390	/**
3391	 * Returns footer margin in user units.
3392	 * @return float
3393	 * @since 4.0.012 (2008-07-24)
3394	 * @public
3395	 */
3396	public function getFooterMargin() {
3397		return $this->footer_margin;
3398	}
3399	/**
3400	 * Set a flag to print page header.
3401	 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3402	 * @public
3403	 */
3404	public function setPrintHeader($val=true) {
3405		$this->print_header = $val ? true : false;
3406	}
3407
3408	/**
3409	 * Set a flag to print page footer.
3410	 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3411	 * @public
3412	 */
3413	public function setPrintFooter($val=true) {
3414		$this->print_footer = $val ? true : false;
3415	}
3416
3417	/**
3418	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3419	 * @return float
3420	 * @public
3421	 */
3422	public function getImageRBX() {
3423		return $this->img_rb_x;
3424	}
3425
3426	/**
3427	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3428	 * @return float
3429	 * @public
3430	 */
3431	public function getImageRBY() {
3432		return $this->img_rb_y;
3433	}
3434
3435	/**
3436	 * Reset the xobject template used by Header() method.
3437	 * @public
3438	 */
3439	public function resetHeaderTemplate() {
3440		$this->header_xobjid = false;
3441	}
3442
3443	/**
3444	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3445	 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3446	 * @public
3447	 */
3448	public function setHeaderTemplateAutoreset($val=true) {
3449		$this->header_xobj_autoreset = $val ? true : false;
3450	}
3451
3452	/**
3453	 * This method is used to render the page header.
3454	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3455	 * @public
3456	 */
3457	public function Header() {
3458		if ($this->header_xobjid === false) {
3459			// start a new XObject Template
3460			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3461			$headerfont = $this->getHeaderFont();
3462			$headerdata = $this->getHeaderData();
3463			$this->y = $this->header_margin;
3464			if ($this->rtl) {
3465				$this->x = $this->w - $this->original_rMargin;
3466			} else {
3467				$this->x = $this->original_lMargin;
3468			}
3469			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3470				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3471				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3472					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3473				} elseif ($imgtype == 'svg') {
3474					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3475				} else {
3476					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3477				}
3478				$imgy = $this->getImageRBY();
3479			} else {
3480				$imgy = $this->y;
3481			}
3482			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3483			// set starting margin for text data cell
3484			if ($this->getRTL()) {
3485				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3486			} else {
3487				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3488			}
3489			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3490			$this->SetTextColorArray($this->header_text_color);
3491			// header title
3492			$this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3493			$this->SetX($header_x);
3494			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3495			// header string
3496			$this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3497			$this->SetX($header_x);
3498			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3499			// print an ending header line
3500			$this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3501			$this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3502			if ($this->rtl) {
3503				$this->SetX($this->original_rMargin);
3504			} else {
3505				$this->SetX($this->original_lMargin);
3506			}
3507			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3508			$this->endTemplate();
3509		}
3510		// print header template
3511		$x = 0;
3512		$dx = 0;
3513		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3514			// adjust margins for booklet mode
3515			$dx = ($this->original_lMargin - $this->original_rMargin);
3516		}
3517		if ($this->rtl) {
3518			$x = $this->w + $dx;
3519		} else {
3520			$x = 0 + $dx;
3521		}
3522		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3523		if ($this->header_xobj_autoreset) {
3524			// reset header xobject template at each page
3525			$this->header_xobjid = false;
3526		}
3527	}
3528
3529	/**
3530	 * This method is used to render the page footer.
3531	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3532	 * @public
3533	 */
3534	public function Footer() {
3535		$cur_y = $this->y;
3536		$this->SetTextColorArray($this->footer_text_color);
3537		//set style for cell border
3538		$line_width = (0.85 / $this->k);
3539		$this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3540		//print document barcode
3541		$barcode = $this->getBarcode();
3542		if (!empty($barcode)) {
3543			$this->Ln($line_width);
3544			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3545			$style = array(
3546				'position' => $this->rtl?'R':'L',
3547				'align' => $this->rtl?'R':'L',
3548				'stretch' => false,
3549				'fitwidth' => true,
3550				'cellfitalign' => '',
3551				'border' => false,
3552				'padding' => 0,
3553				'fgcolor' => array(0,0,0),
3554				'bgcolor' => false,
3555				'text' => false
3556			);
3557			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3558		}
3559		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3560		if (empty($this->pagegroups)) {
3561			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3562		} else {
3563			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3564		}
3565		$this->SetY($cur_y);
3566		//Print page number
3567		if ($this->getRTL()) {
3568			$this->SetX($this->original_rMargin);
3569			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3570		} else {
3571			$this->SetX($this->original_lMargin);
3572			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3573		}
3574	}
3575
3576	/**
3577	 * This method is used to render the page header.
3578	 * @protected
3579	 * @since 4.0.012 (2008-07-24)
3580	 */
3581	protected function setHeader() {
3582		if (!$this->print_header OR ($this->state != 2)) {
3583			return;
3584		}
3585		$this->InHeader = true;
3586		$this->setGraphicVars($this->default_graphic_vars);
3587		$temp_thead = $this->thead;
3588		$temp_theadMargins = $this->theadMargins;
3589		$lasth = $this->lasth;
3590		$newline = $this->newline;
3591		$this->_outSaveGraphicsState();
3592		$this->rMargin = $this->original_rMargin;
3593		$this->lMargin = $this->original_lMargin;
3594		$this->SetCellPadding(0);
3595		//set current position
3596		if ($this->rtl) {
3597			$this->SetXY($this->original_rMargin, $this->header_margin);
3598		} else {
3599			$this->SetXY($this->original_lMargin, $this->header_margin);
3600		}
3601		$this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3602		$this->Header();
3603		//restore position
3604		if ($this->rtl) {
3605			$this->SetXY($this->original_rMargin, $this->tMargin);
3606		} else {
3607			$this->SetXY($this->original_lMargin, $this->tMargin);
3608		}
3609		$this->_outRestoreGraphicsState();
3610		$this->lasth = $lasth;
3611		$this->thead = $temp_thead;
3612		$this->theadMargins = $temp_theadMargins;
3613		$this->newline = $newline;
3614		$this->InHeader = false;
3615	}
3616
3617	/**
3618	 * This method is used to render the page footer.
3619	 * @protected
3620	 * @since 4.0.012 (2008-07-24)
3621	 */
3622	protected function setFooter() {
3623		if ($this->state != 2) {
3624			return;
3625		}
3626		$this->InFooter = true;
3627		// save current graphic settings
3628		$gvars = $this->getGraphicVars();
3629		// mark this point
3630		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3631		$this->_out("\n");
3632		if ($this->print_footer) {
3633			$this->setGraphicVars($this->default_graphic_vars);
3634			$this->current_column = 0;
3635			$this->num_columns = 1;
3636			$temp_thead = $this->thead;
3637			$temp_theadMargins = $this->theadMargins;
3638			$lasth = $this->lasth;
3639			$this->_outSaveGraphicsState();
3640			$this->rMargin = $this->original_rMargin;
3641			$this->lMargin = $this->original_lMargin;
3642			$this->SetCellPadding(0);
3643			//set current position
3644			$footer_y = $this->h - $this->footer_margin;
3645			if ($this->rtl) {
3646				$this->SetXY($this->original_rMargin, $footer_y);
3647			} else {
3648				$this->SetXY($this->original_lMargin, $footer_y);
3649			}
3650			$this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3651			$this->Footer();
3652			//restore position
3653			if ($this->rtl) {
3654				$this->SetXY($this->original_rMargin, $this->tMargin);
3655			} else {
3656				$this->SetXY($this->original_lMargin, $this->tMargin);
3657			}
3658			$this->_outRestoreGraphicsState();
3659			$this->lasth = $lasth;
3660			$this->thead = $temp_thead;
3661			$this->theadMargins = $temp_theadMargins;
3662		}
3663		// restore graphic settings
3664		$this->setGraphicVars($gvars);
3665		$this->current_column = $gvars['current_column'];
3666		$this->num_columns = $gvars['num_columns'];
3667		// calculate footer length
3668		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3669		$this->InFooter = false;
3670	}
3671
3672	/**
3673	 * Check if we are on the page body (excluding page header and footer).
3674	 * @return true if we are not in page header nor in page footer, false otherwise.
3675	 * @protected
3676	 * @since 5.9.091 (2011-06-15)
3677	 */
3678	protected function inPageBody() {
3679		return (($this->InHeader === false) AND ($this->InFooter === false));
3680	}
3681
3682	/**
3683	 * This method is used to render the table header on new page (if any).
3684	 * @protected
3685	 * @since 4.5.030 (2009-03-25)
3686	 */
3687	protected function setTableHeader() {
3688		if ($this->num_columns > 1) {
3689			// multi column mode
3690			return;
3691		}
3692		if (isset($this->theadMargins['top'])) {
3693			// restore the original top-margin
3694			$this->tMargin = $this->theadMargins['top'];
3695			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3696			$this->y = $this->tMargin;
3697		}
3698		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3699			// set margins
3700			$prev_lMargin = $this->lMargin;
3701			$prev_rMargin = $this->rMargin;
3702			$prev_cell_padding = $this->cell_padding;
3703			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3704			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3705			$this->cell_padding = $this->theadMargins['cell_padding'];
3706			if ($this->rtl) {
3707				$this->x = $this->w - $this->rMargin;
3708			} else {
3709				$this->x = $this->lMargin;
3710			}
3711			// account for special "cell" mode
3712			if ($this->theadMargins['cell']) {
3713				if ($this->rtl) {
3714					$this->x -= $this->cell_padding['R'];
3715				} else {
3716					$this->x += $this->cell_padding['L'];
3717				}
3718			}
3719			$gvars = $this->getGraphicVars();
3720			if (!empty($this->theadMargins['gvars'])) {
3721				// set the correct graphic style
3722				$this->setGraphicVars($this->theadMargins['gvars']);
3723				$this->rMargin = $gvars['rMargin'];
3724				$this->lMargin = $gvars['lMargin'];
3725			}
3726			// print table header
3727			$this->writeHTML($this->thead, false, false, false, false, '');
3728			$this->setGraphicVars($gvars);
3729			// set new top margin to skip the table headers
3730			if (!isset($this->theadMargins['top'])) {
3731				$this->theadMargins['top'] = $this->tMargin;
3732			}
3733			// store end of header position
3734			if (!isset($this->columns[0]['th'])) {
3735				$this->columns[0]['th'] = array();
3736			}
3737			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3738			$this->tMargin = $this->y;
3739			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3740			$this->lasth = 0;
3741			$this->lMargin = $prev_lMargin;
3742			$this->rMargin = $prev_rMargin;
3743			$this->cell_padding = $prev_cell_padding;
3744		}
3745	}
3746
3747	/**
3748	 * Returns the current page number.
3749	 * @return int page number
3750	 * @public
3751	 * @since 1.0
3752	 * @see getAliasNbPages()
3753	 */
3754	public function PageNo() {
3755		return $this->page;
3756	}
3757
3758	/**
3759	 * Returns the array of spot colors.
3760	 * @return (array) Spot colors array.
3761	 * @public
3762	 * @since 6.0.038 (2013-09-30)
3763	 */
3764	public function getAllSpotColors() {
3765		return $this->spot_colors;
3766	}
3767
3768	/**
3769	 * Defines a new spot color.
3770	 * It can be expressed in RGB components or gray scale.
3771	 * The method can be called before the first page is created and the value is retained from page to page.
3772	 * @param $name (string) Full name of the spot color.
3773	 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3774	 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3775	 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3776	 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3777	 * @public
3778	 * @since 4.0.024 (2008-09-12)
3779	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3780	 */
3781	public function AddSpotColor($name, $c, $m, $y, $k) {
3782		if (!isset($this->spot_colors[$name])) {
3783			$i = (1 + count($this->spot_colors));
3784			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3785		}
3786	}
3787
3788	/**
3789	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3790	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3791	 * @param $name (string) Name of the spot color.
3792	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3793	 * @return (string) PDF color command.
3794	 * @public
3795	 * @since 5.9.125 (2011-10-03)
3796	 */
3797	public function setSpotColor($type, $name, $tint=100) {
3798		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3799		if ($spotcolor === false) {
3800			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3801		}
3802		$tint = (max(0, min(100, $tint)) / 100);
3803		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3804		switch ($type) {
3805			case 'draw': {
3806				$pdfcolor .= sprintf('CS %F SCN', $tint);
3807				$this->DrawColor = $pdfcolor;
3808				$this->strokecolor = $spotcolor;
3809				break;
3810			}
3811			case 'fill': {
3812				$pdfcolor .= sprintf('cs %F scn', $tint);
3813				$this->FillColor = $pdfcolor;
3814				$this->bgcolor = $spotcolor;
3815				break;
3816			}
3817			case 'text': {
3818				$pdfcolor .= sprintf('cs %F scn', $tint);
3819				$this->TextColor = $pdfcolor;
3820				$this->fgcolor = $spotcolor;
3821				break;
3822			}
3823		}
3824		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3825		if ($this->state == 2) {
3826			$this->_out($pdfcolor);
3827		}
3828		if ($this->inxobj) {
3829			// we are inside an XObject template
3830			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3831		}
3832		return $pdfcolor;
3833	}
3834
3835	/**
3836	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3837	 * @param $name (string) Name of the spot color.
3838	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3839	 * @public
3840	 * @since 4.0.024 (2008-09-12)
3841	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3842	 */
3843	public function SetDrawSpotColor($name, $tint=100) {
3844		$this->setSpotColor('draw', $name, $tint);
3845	}
3846
3847	/**
3848	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3849	 * @param $name (string) Name of the spot color.
3850	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3851	 * @public
3852	 * @since 4.0.024 (2008-09-12)
3853	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3854	 */
3855	public function SetFillSpotColor($name, $tint=100) {
3856		$this->setSpotColor('fill', $name, $tint);
3857	}
3858
3859	/**
3860	 * Defines the spot color used for text.
3861	 * @param $name (string) Name of the spot color.
3862	 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3863	 * @public
3864	 * @since 4.0.024 (2008-09-12)
3865	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3866	 */
3867	public function SetTextSpotColor($name, $tint=100) {
3868		$this->setSpotColor('text', $name, $tint);
3869	}
3870
3871	/**
3872	 * Set the color array for the specified type ('draw', 'fill', 'text').
3873	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3874	 * The method can be called before the first page is created and the value is retained from page to page.
3875	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3876	 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3877	 * @param $ret (boolean) If true do not send the PDF command.
3878	 * @return (string) The PDF command or empty string.
3879	 * @public
3880	 * @since 3.1.000 (2008-06-11)
3881	 */
3882	public function setColorArray($type, $color, $ret=false) {
3883		if (is_array($color)) {
3884			$color = array_values($color);
3885			// component: grey, RGB red or CMYK cyan
3886			$c = isset($color[0]) ? $color[0] : -1;
3887			// component: RGB green or CMYK magenta
3888			$m = isset($color[1]) ? $color[1] : -1;
3889			// component: RGB blue or CMYK yellow
3890			$y = isset($color[2]) ? $color[2] : -1;
3891			// component: CMYK black
3892			$k = isset($color[3]) ? $color[3] : -1;
3893			// color name
3894			$name = isset($color[4]) ? $color[4] : '';
3895			if ($c >= 0) {
3896				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3897			}
3898		}
3899		return '';
3900	}
3901
3902	/**
3903	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3904	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3905	 * The method can be called before the first page is created and the value is retained from page to page.
3906	 * @param $color (array) Array of colors (1, 3 or 4 values).
3907	 * @param $ret (boolean) If true do not send the PDF command.
3908	 * @return string the PDF command
3909	 * @public
3910	 * @since 3.1.000 (2008-06-11)
3911	 * @see SetDrawColor()
3912	 */
3913	public function SetDrawColorArray($color, $ret=false) {
3914		return $this->setColorArray('draw', $color, $ret);
3915	}
3916
3917	/**
3918	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3919	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3920	 * The method can be called before the first page is created and the value is retained from page to page.
3921	 * @param $color (array) Array of colors (1, 3 or 4 values).
3922	 * @param $ret (boolean) If true do not send the PDF command.
3923	 * @public
3924	 * @since 3.1.000 (2008-6-11)
3925	 * @see SetFillColor()
3926	 */
3927	public function SetFillColorArray($color, $ret=false) {
3928		return $this->setColorArray('fill', $color, $ret);
3929	}
3930
3931	/**
3932	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3933	 * The method can be called before the first page is created and the value is retained from page to page.
3934	 * @param $color (array) Array of colors (1, 3 or 4 values).
3935	 * @param $ret (boolean) If true do not send the PDF command.
3936	 * @public
3937	 * @since 3.1.000 (2008-6-11)
3938	 * @see SetFillColor()
3939	 */
3940	public function SetTextColorArray($color, $ret=false) {
3941		return $this->setColorArray('text', $color, $ret);
3942	}
3943
3944	/**
3945	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3946	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3947	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3948	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3949	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3950	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3951	 * @param $ret (boolean) If true do not send the command.
3952	 * @param $name (string) spot color name (if any)
3953	 * @return (string) The PDF command or empty string.
3954	 * @public
3955	 * @since 5.9.125 (2011-10-03)
3956	 */
3957	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3958		// set default values
3959		if (!is_numeric($col1)) {
3960			$col1 = 0;
3961		}
3962		if (!is_numeric($col2)) {
3963			$col2 = -1;
3964		}
3965		if (!is_numeric($col3)) {
3966			$col3 = -1;
3967		}
3968		if (!is_numeric($col4)) {
3969			$col4 = -1;
3970		}
3971		// set color by case
3972		$suffix = '';
3973		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3974			// Grey scale
3975			$col1 = max(0, min(255, $col1));
3976			$intcolor = array('G' => $col1);
3977			$pdfcolor = sprintf('%F ', ($col1 / 255));
3978			$suffix = 'g';
3979		} elseif ($col4 == -1) {
3980			// RGB
3981			$col1 = max(0, min(255, $col1));
3982			$col2 = max(0, min(255, $col2));
3983			$col3 = max(0, min(255, $col3));
3984			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3985			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3986			$suffix = 'rg';
3987		} else {
3988			$col1 = max(0, min(100, $col1));
3989			$col2 = max(0, min(100, $col2));
3990			$col3 = max(0, min(100, $col3));
3991			$col4 = max(0, min(100, $col4));
3992			if (empty($name)) {
3993				// CMYK
3994				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3995				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3996				$suffix = 'k';
3997			} else {
3998				// SPOT COLOR
3999				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4000				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4001				$pdfcolor = $this->setSpotColor($type, $name, 100);
4002			}
4003		}
4004		switch ($type) {
4005			case 'draw': {
4006				$pdfcolor .= strtoupper($suffix);
4007				$this->DrawColor = $pdfcolor;
4008				$this->strokecolor = $intcolor;
4009				break;
4010			}
4011			case 'fill': {
4012				$pdfcolor .= $suffix;
4013				$this->FillColor = $pdfcolor;
4014				$this->bgcolor = $intcolor;
4015				break;
4016			}
4017			case 'text': {
4018				$pdfcolor .= $suffix;
4019				$this->TextColor = $pdfcolor;
4020				$this->fgcolor = $intcolor;
4021				break;
4022			}
4023		}
4024		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4025		if (($type != 'text') AND ($this->state == 2)) {
4026			if (!$ret) {
4027				$this->_out($pdfcolor);
4028			}
4029			return $pdfcolor;
4030		}
4031		return '';
4032	}
4033
4034	/**
4035	 * 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.
4036	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4037	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4038	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4039	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4040	 * @param $ret (boolean) If true do not send the command.
4041	 * @param $name (string) spot color name (if any)
4042	 * @return string the PDF command
4043	 * @public
4044	 * @since 1.3
4045	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4046	 */
4047	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4048		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4049	}
4050
4051	/**
4052	 * 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.
4053	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4054	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4055	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4056	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4057	 * @param $ret (boolean) If true do not send the command.
4058	 * @param $name (string) Spot color name (if any).
4059	 * @return (string) The PDF command.
4060	 * @public
4061	 * @since 1.3
4062	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4063	 */
4064	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4065		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4066	}
4067
4068	/**
4069	 * 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.
4070	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4071	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4072	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4073	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4074	 * @param $ret (boolean) If true do not send the command.
4075	 * @param $name (string) Spot color name (if any).
4076	 * @return (string) Empty string.
4077	 * @public
4078	 * @since 1.3
4079	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4080	 */
4081	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4082		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4083	}
4084
4085	/**
4086	 * Returns the length of a string in user unit. A font must be selected.<br>
4087	 * @param $s (string) The string whose length is to be computed
4088	 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4089	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4090	 * @param $fontsize (float) Font size in points. The default value is the current size.
4091	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4092	 * @return mixed int total string length or array of characted widths
4093	 * @author Nicola Asuni
4094	 * @public
4095	 * @since 1.2
4096	 */
4097	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4098		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);
4099	}
4100
4101	/**
4102	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4103	 * @param $sa (string) The array of chars whose total length is to be computed
4104	 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4105	 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4106	 * @param $fontsize (float) Font size in points. The default value is the current size.
4107	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4108	 * @return mixed int total string length or array of characted widths
4109	 * @author Nicola Asuni
4110	 * @public
4111	 * @since 2.4.000 (2008-03-06)
4112	 */
4113	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4114		// store current values
4115		if (!TCPDF_STATIC::empty_string($fontname)) {
4116			$prev_FontFamily = $this->FontFamily;
4117			$prev_FontStyle = $this->FontStyle;
4118			$prev_FontSizePt = $this->FontSizePt;
4119			$this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4120		}
4121		// convert UTF-8 array to Latin1 if required
4122		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4123			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4124		}
4125		$w = 0; // total width
4126		$wa = array(); // array of characters widths
4127		foreach ($sa as $ck => $char) {
4128			// character width
4129			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4130			$wa[] = $cw;
4131			$w += $cw;
4132		}
4133		// restore previous values
4134		if (!TCPDF_STATIC::empty_string($fontname)) {
4135			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4136		}
4137		if ($getarray) {
4138			return $wa;
4139		}
4140		return $w;
4141	}
4142
4143	/**
4144	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4145	 * @param $char (int) The char code whose length is to be returned
4146	 * @param $notlast (boolean) If false ignore the font-spacing.
4147	 * @return float char width
4148	 * @author Nicola Asuni
4149	 * @public
4150	 * @since 2.4.000 (2008-03-06)
4151	 */
4152	public function GetCharWidth($char, $notlast=true) {
4153		// get raw width
4154		$chw = $this->getRawCharWidth($char);
4155		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4156			// increase/decrease font spacing
4157			$chw += $this->font_spacing;
4158		}
4159		if ($this->font_stretching != 100) {
4160			// fixed stretching mode
4161			$chw *= ($this->font_stretching / 100);
4162		}
4163		return $chw;
4164	}
4165
4166	/**
4167	 * Returns the length of the char in user unit for the current font.
4168	 * @param $char (int) The char code whose length is to be returned
4169	 * @return float char width
4170	 * @author Nicola Asuni
4171	 * @public
4172	 * @since 5.9.000 (2010-09-28)
4173	 */
4174	public function getRawCharWidth($char) {
4175		if ($char == 173) {
4176			// SHY character will not be printed
4177			return (0);
4178		}
4179		if (isset($this->CurrentFont['cw'][$char])) {
4180			$w = $this->CurrentFont['cw'][$char];
4181		} elseif (isset($this->CurrentFont['dw'])) {
4182			// default width
4183			$w = $this->CurrentFont['dw'];
4184		} elseif (isset($this->CurrentFont['cw'][32])) {
4185			// default width
4186			$w = $this->CurrentFont['cw'][32];
4187		} else {
4188			$w = 600;
4189		}
4190		return $this->getAbsFontMeasure($w);
4191	}
4192
4193	/**
4194	 * Returns the numbero of characters in a string.
4195	 * @param $s (string) The input string.
4196	 * @return int number of characters
4197	 * @public
4198	 * @since 2.0.0001 (2008-01-07)
4199	 */
4200	public function GetNumChars($s) {
4201		if ($this->isUnicodeFont()) {
4202			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4203		}
4204		return strlen($s);
4205	}
4206
4207	/**
4208	 * Fill the list of available fonts ($this->fontlist).
4209	 * @protected
4210	 * @since 4.0.013 (2008-07-28)
4211	 */
4212	protected function getFontsList() {
4213		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4214			while (($file = readdir($fontsdir)) !== false) {
4215				if (substr($file, -4) == '.php') {
4216					array_push($this->fontlist, strtolower(basename($file, '.php')));
4217				}
4218			}
4219			closedir($fontsdir);
4220		}
4221	}
4222
4223	/**
4224	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4225	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4226	 * 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.
4227	 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4228	 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4229	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4230	 * @return array containing the font data, or false in case of error.
4231	 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4232	 * @public
4233	 * @since 1.5
4234	 * @see SetFont(), setFontSubsetting()
4235	 */
4236	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4237		if ($subset === 'default') {
4238			$subset = $this->font_subsetting;
4239		}
4240		if ($this->pdfa_mode) {
4241			$subset = false;
4242		}
4243		if (TCPDF_STATIC::empty_string($family)) {
4244			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4245				$family = $this->FontFamily;
4246			} else {
4247				$this->Error('Empty font family');
4248			}
4249		}
4250		// move embedded styles on $style
4251		if (substr($family, -1) == 'I') {
4252			$style .= 'I';
4253			$family = substr($family, 0, -1);
4254		}
4255		if (substr($family, -1) == 'B') {
4256			$style .= 'B';
4257			$family = substr($family, 0, -1);
4258		}
4259		// normalize family name
4260		$family = strtolower($family);
4261		if ((!$this->isunicode) AND ($family == 'arial')) {
4262			$family = 'helvetica';
4263		}
4264		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4265			$style = '';
4266		}
4267		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4268			// all fonts must be embedded
4269			$family = 'pdfa'.$family;
4270		}
4271		$tempstyle = strtoupper($style);
4272		$style = '';
4273		// underline
4274		if (strpos($tempstyle, 'U') !== false) {
4275			$this->underline = true;
4276		} else {
4277			$this->underline = false;
4278		}
4279		// line-through (deleted)
4280		if (strpos($tempstyle, 'D') !== false) {
4281			$this->linethrough = true;
4282		} else {
4283			$this->linethrough = false;
4284		}
4285		// overline
4286		if (strpos($tempstyle, 'O') !== false) {
4287			$this->overline = true;
4288		} else {
4289			$this->overline = false;
4290		}
4291		// bold
4292		if (strpos($tempstyle, 'B') !== false) {
4293			$style .= 'B';
4294		}
4295		// oblique
4296		if (strpos($tempstyle, 'I') !== false) {
4297			$style .= 'I';
4298		}
4299		$bistyle = $style;
4300		$fontkey = $family.$style;
4301		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4302		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4303		// check if the font has been already added
4304		$fb = $this->getFontBuffer($fontkey);
4305		if ($fb !== false) {
4306			if ($this->inxobj) {
4307				// we are inside an XObject template
4308				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4309			}
4310			return $fontdata;
4311		}
4312		// get specified font directory (if any)
4313		$fontdir = false;
4314		if (!TCPDF_STATIC::empty_string($fontfile)) {
4315			$fontdir = dirname($fontfile);
4316			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4317				$fontdir = '';
4318			} else {
4319				$fontdir .= '/';
4320			}
4321		}
4322		// true when the font style variation is missing
4323		$missing_style = false;
4324		// search and include font file
4325		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4326			// build a standard filenames for specified font
4327			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4328			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4329			if (TCPDF_STATIC::empty_string($fontfile)) {
4330				$missing_style = true;
4331				// try to remove the style part
4332				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4333				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4334			}
4335		}
4336		// include font file
4337		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4338			include($fontfile);
4339		} else {
4340			$this->Error('Could not include font definition file: '.$family.'');
4341		}
4342		// check font parameters
4343		if ((!isset($type)) OR (!isset($cw))) {
4344			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4345		}
4346		// SET default parameters
4347		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4348			$file = '';
4349		}
4350		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4351			$enc = '';
4352		}
4353		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4354			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4355			$cidinfo['uni2cid'] = array();
4356		}
4357		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4358			$ctg = '';
4359		}
4360		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4361			$desc = array();
4362		}
4363		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4364			$up = -100;
4365		}
4366		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4367			$ut = 50;
4368		}
4369		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4370			$cw = array();
4371		}
4372		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4373			// set default width
4374			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4375				$dw = $desc['MissingWidth'];
4376			} elseif (isset($cw[32])) {
4377				$dw = $cw[32];
4378			} else {
4379				$dw = 600;
4380			}
4381		}
4382		++$this->numfonts;
4383		if ($type == 'core') {
4384			$name = $this->CoreFonts[$fontkey];
4385			$subset = false;
4386		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4387			$subset = false;
4388		} elseif ($type == 'TrueTypeUnicode') {
4389			$enc = 'Identity-H';
4390		} elseif ($type == 'cidfont0') {
4391			if ($this->pdfa_mode) {
4392				$this->Error('All fonts must be embedded in PDF/A mode!');
4393			}
4394		} else {
4395			$this->Error('Unknow font type: '.$type.'');
4396		}
4397		// set name if unset
4398		if (!isset($name) OR empty($name)) {
4399			$name = $fontkey;
4400		}
4401		// create artificial font style variations if missing (only works with non-embedded fonts)
4402		if (($type != 'core') AND $missing_style) {
4403			// style variations
4404			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4405			$name .= $styles[$bistyle];
4406			// artificial bold
4407			if (strpos($bistyle, 'B') !== false) {
4408				if (isset($desc['StemV'])) {
4409					// from normal to bold
4410					$desc['StemV'] = round($desc['StemV'] * 1.75);
4411				} else {
4412					// bold
4413					$desc['StemV'] = 123;
4414				}
4415			}
4416			// artificial italic
4417			if (strpos($bistyle, 'I') !== false) {
4418				if (isset($desc['ItalicAngle'])) {
4419					$desc['ItalicAngle'] -= 11;
4420				} else {
4421					$desc['ItalicAngle'] = -11;
4422				}
4423				if (isset($desc['Flags'])) {
4424					$desc['Flags'] |= 64; //bit 7
4425				} else {
4426					$desc['Flags'] = 64;
4427				}
4428			}
4429		}
4430		// check if the array of characters bounding boxes is defined
4431		if (!isset($cbbox)) {
4432			$cbbox = array();
4433		}
4434		// initialize subsetchars
4435		$subsetchars = array_fill(0, 255, true);
4436		$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));
4437		if ($this->inxobj) {
4438			// we are inside an XObject template
4439			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4440		}
4441		if (isset($diff) AND (!empty($diff))) {
4442			//Search existing encodings
4443			$d = 0;
4444			$nb = count($this->diffs);
4445			for ($i=1; $i <= $nb; ++$i) {
4446				if ($this->diffs[$i] == $diff) {
4447					$d = $i;
4448					break;
4449				}
4450			}
4451			if ($d == 0) {
4452				$d = $nb + 1;
4453				$this->diffs[$d] = $diff;
4454			}
4455			$this->setFontSubBuffer($fontkey, 'diff', $d);
4456		}
4457		if (!TCPDF_STATIC::empty_string($file)) {
4458			if (!isset($this->FontFiles[$file])) {
4459				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4460					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4461				} elseif ($type != 'core') {
4462					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4463				}
4464			} else {
4465				// update fontkeys that are sharing this font file
4466				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4467				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4468					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4469				}
4470			}
4471		}
4472		return $fontdata;
4473	}
4474
4475	/**
4476	 * Sets the font used to print character strings.
4477	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4478	 * The method can be called before the first page is created and the font is retained from page to page.
4479	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4480	 * 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 />
4481	 * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4482	 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4483	 * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4484	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4485	 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4486	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4487	 * @author Nicola Asuni
4488	 * @public
4489	 * @since 1.0
4490	 * @see AddFont(), SetFontSize()
4491	 */
4492	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4493		//Select a font; size given in points
4494		if ($size === null) {
4495			$size = $this->FontSizePt;
4496		}
4497		if ($size < 0) {
4498			$size = 0;
4499		}
4500		// try to add font (if not already added)
4501		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4502		$this->FontFamily = $fontdata['family'];
4503		$this->FontStyle = $fontdata['style'];
4504		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4505			// save subset chars of the previous font
4506			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4507		}
4508		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4509		$this->SetFontSize($size, $out);
4510	}
4511
4512	/**
4513	 * Defines the size of the current font.
4514	 * @param $size (float) The font size in points.
4515	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4516	 * @public
4517	 * @since 1.0
4518	 * @see SetFont()
4519	 */
4520	public function SetFontSize($size, $out=true) {
4521		$size = (float)$size;
4522		// font size in points
4523		$this->FontSizePt = $size;
4524		// font size in user units
4525		$this->FontSize = $size / $this->k;
4526		// calculate some font metrics
4527		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4528			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4529			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4530		} else {
4531			$font_height = $size * 1.219;
4532		}
4533		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4534			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4535		}
4536		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4537			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4538		}
4539		if (!isset($font_ascent) AND !isset($font_descent)) {
4540			// core font
4541			$font_ascent = 0.76 * $font_height;
4542			$font_descent = $font_height - $font_ascent;
4543		} elseif (!isset($font_descent)) {
4544			$font_descent = $font_height - $font_ascent;
4545		} elseif (!isset($font_ascent)) {
4546			$font_ascent = $font_height - $font_descent;
4547		}
4548		$this->FontAscent = ($font_ascent / $this->k);
4549		$this->FontDescent = ($font_descent / $this->k);
4550		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4551			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4552		}
4553	}
4554
4555	/**
4556	 * Returns the bounding box of the current font in user units.
4557	 * @return array
4558	 * @public
4559	 * @since 5.9.152 (2012-03-23)
4560	 */
4561	public function getFontBBox() {
4562		$fbbox = array();
4563		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4564			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4565			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4566		} else {
4567			// Find max width
4568			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4569				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4570			} else {
4571				$maxw = 0;
4572				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4573					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4574				}
4575				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4576					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4577				}
4578				if (isset($this->CurrentFont['dw'])) {
4579					$maxw = max($maxw, $this->CurrentFont['dw']);
4580				}
4581				foreach ($this->CurrentFont['cw'] as $char => $w) {
4582					$maxw = max($maxw, $w);
4583				}
4584				if ($maxw == 0) {
4585					$maxw = 600;
4586				}
4587				$maxw = $this->getAbsFontMeasure($maxw);
4588			}
4589			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4590		}
4591		return $fbbox;
4592	}
4593
4594	/**
4595	 * Convert a relative font measure into absolute value.
4596	 * @param $s (int) Font measure.
4597	 * @return float Absolute measure.
4598	 * @since 5.9.186 (2012-09-13)
4599	 */
4600	public function getAbsFontMeasure($s) {
4601		return ($s * $this->FontSize / 1000);
4602	}
4603
4604	/**
4605	 * Returns the glyph bounding box of the specified character in the current font in user units.
4606	 * @param $char (int) Input character code.
4607	 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4608	 * @since 5.9.186 (2012-09-13)
4609	 */
4610	public function getCharBBox($char) {
4611		$c = intval($char);
4612		if (isset($this->CurrentFont['cw'][$c])) {
4613			// glyph is defined ... use zero width & height for glyphs without outlines
4614			$result = array(0,0,0,0);
4615			if (isset($this->CurrentFont['cbbox'][$c])) {
4616				$result = $this->CurrentFont['cbbox'][$c];
4617			}
4618			return array_map(array($this,'getAbsFontMeasure'), $result);
4619		}
4620		return false;
4621	}
4622
4623	/**
4624	 * Return the font descent value
4625	 * @param $font (string) font name
4626	 * @param $style (string) font style
4627	 * @param $size (float) The size (in points)
4628	 * @return int font descent
4629	 * @public
4630	 * @author Nicola Asuni
4631	 * @since 4.9.003 (2010-03-30)
4632	 */
4633	public function getFontDescent($font, $style='', $size=0) {
4634		$fontdata = $this->AddFont($font, $style);
4635		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4636		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4637			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4638		} else {
4639			$descent = (1.219 * 0.24 * $size);
4640		}
4641		return ($descent / $this->k);
4642	}
4643
4644	/**
4645	 * Return the font ascent value.
4646	 * @param $font (string) font name
4647	 * @param $style (string) font style
4648	 * @param $size (float) The size (in points)
4649	 * @return int font ascent
4650	 * @public
4651	 * @author Nicola Asuni
4652	 * @since 4.9.003 (2010-03-30)
4653	 */
4654	public function getFontAscent($font, $style='', $size=0) {
4655		$fontdata = $this->AddFont($font, $style);
4656		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4657		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4658			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4659		} else {
4660			$ascent = 1.219 * 0.76 * $size;
4661		}
4662		return ($ascent / $this->k);
4663	}
4664
4665	/**
4666	 * Return true in the character is present in the specified font.
4667	 * @param $char (mixed) Character to check (integer value or string)
4668	 * @param $font (string) Font name (family name).
4669	 * @param $style (string) Font style.
4670	 * @return (boolean) true if the char is defined, false otherwise.
4671	 * @public
4672	 * @since 5.9.153 (2012-03-28)
4673	 */
4674	public function isCharDefined($char, $font='', $style='') {
4675		if (is_string($char)) {
4676			// get character code
4677			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4678			$char = $char[0];
4679		}
4680		if (TCPDF_STATIC::empty_string($font)) {
4681			if (TCPDF_STATIC::empty_string($style)) {
4682				return (isset($this->CurrentFont['cw'][intval($char)]));
4683			}
4684			$font = $this->FontFamily;
4685		}
4686		$fontdata = $this->AddFont($font, $style);
4687		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4688		return (isset($fontinfo['cw'][intval($char)]));
4689	}
4690
4691	/**
4692	 * Replace missing font characters on selected font with specified substitutions.
4693	 * @param $text (string) Text to process.
4694	 * @param $font (string) Font name (family name).
4695	 * @param $style (string) Font style.
4696	 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4697	 * @return (string) Processed text.
4698	 * @public
4699	 * @since 5.9.153 (2012-03-28)
4700	 */
4701	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4702		if (empty($subs)) {
4703			return $text;
4704		}
4705		if (TCPDF_STATIC::empty_string($font)) {
4706			$font = $this->FontFamily;
4707		}
4708		$fontdata = $this->AddFont($font, $style);
4709		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4710		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4711		foreach ($uniarr as $k => $chr) {
4712			if (!isset($fontinfo['cw'][$chr])) {
4713				// this character is missing on the selected font
4714				if (isset($subs[$chr])) {
4715					// we have available substitutions
4716					if (is_array($subs[$chr])) {
4717						foreach($subs[$chr] as $s) {
4718							if (isset($fontinfo['cw'][$s])) {
4719								$uniarr[$k] = $s;
4720								break;
4721							}
4722						}
4723					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4724						$uniarr[$k] = $subs[$chr];
4725					}
4726				}
4727			}
4728		}
4729		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4730	}
4731
4732	/**
4733	 * Defines the default monospaced font.
4734	 * @param $font (string) Font name.
4735	 * @public
4736	 * @since 4.5.025
4737	 */
4738	public function SetDefaultMonospacedFont($font) {
4739		$this->default_monospaced_font = $font;
4740	}
4741
4742	/**
4743	 * 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 />
4744	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4745	 * @public
4746	 * @since 1.5
4747	 * @see Cell(), Write(), Image(), Link(), SetLink()
4748	 */
4749	public function AddLink() {
4750		// create a new internal link
4751		$n = count($this->links) + 1;
4752		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4753		return $n;
4754	}
4755
4756	/**
4757	 * Defines the page and position a link points to.
4758	 * @param $link (int) The link identifier returned by AddLink()
4759	 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4760	 * @param $page (int|string) 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.
4761	 * @public
4762	 * @since 1.5
4763	 * @see AddLink()
4764	 */
4765	public function SetLink($link, $y=0, $page=-1) {
4766		$fixed = false;
4767		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4768			$page = intval(substr($page, 1));
4769			// this page number will not be changed when moving/add/deleting pages
4770			$fixed = true;
4771		}
4772		if ($page < 0) {
4773			$page = $this->page;
4774		}
4775		if ($y == -1) {
4776			$y = $this->y;
4777		}
4778		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4779	}
4780
4781	/**
4782	 * Puts a link on a rectangular area of the page.
4783	 * 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.
4784	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4785	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4786	 * @param $w (float) Width of the rectangle
4787	 * @param $h (float) Height of the rectangle
4788	 * @param $link (mixed) URL or identifier returned by AddLink()
4789	 * @param $spaces (int) number of spaces on the text to link
4790	 * @public
4791	 * @since 1.5
4792	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4793	 */
4794	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4795		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4796	}
4797
4798	/**
4799	 * Puts a markup annotation on a rectangular area of the page.
4800	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4801	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4802	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4803	 * @param $w (float) Width of the rectangle
4804	 * @param $h (float) Height of the rectangle
4805	 * @param $text (string) annotation text or alternate content
4806	 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4807	 * @param $spaces (int) number of spaces on the text to link
4808	 * @public
4809	 * @since 4.0.018 (2008-08-06)
4810	 */
4811	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4812		if ($this->inxobj) {
4813			// store parameters for later use on template
4814			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4815			return;
4816		}
4817		if ($x === '') {
4818			$x = $this->x;
4819		}
4820		if ($y === '') {
4821			$y = $this->y;
4822		}
4823		// check page for no-write regions and adapt page margins if necessary
4824		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4825		// recalculate coordinates to account for graphic transformations
4826		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4827			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4828				$maxid = count($this->transfmatrix[$i]) - 1;
4829				for ($j=$maxid; $j >= 0; --$j) {
4830					$ctm = $this->transfmatrix[$i][$j];
4831					if (isset($ctm['a'])) {
4832						$x = $x * $this->k;
4833						$y = ($this->h - $y) * $this->k;
4834						$w = $w * $this->k;
4835						$h = $h * $this->k;
4836						// top left
4837						$xt = $x;
4838						$yt = $y;
4839						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4840						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4841						// top right
4842						$xt = $x + $w;
4843						$yt = $y;
4844						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4845						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4846						// bottom left
4847						$xt = $x;
4848						$yt = $y - $h;
4849						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4850						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4851						// bottom right
4852						$xt = $x + $w;
4853						$yt = $y - $h;
4854						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4855						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4856						// new coordinates (rectangle area)
4857						$x = min($x1, $x2, $x3, $x4);
4858						$y = max($y1, $y2, $y3, $y4);
4859						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4860						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4861						$x = $x / $this->k;
4862						$y = $this->h - ($y / $this->k);
4863					}
4864				}
4865			}
4866		}
4867		if ($this->page <= 0) {
4868			$page = 1;
4869		} else {
4870			$page = $this->page;
4871		}
4872		if (!isset($this->PageAnnots[$page])) {
4873			$this->PageAnnots[$page] = array();
4874		}
4875		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4876		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4877			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4878				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4879				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4880				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4881			}
4882		}
4883		// Add widgets annotation's icons
4884		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4885			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4886		}
4887		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4888			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4889		}
4890		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4891			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4892		}
4893	}
4894
4895	/**
4896	 * Embedd the attached files.
4897	 * @since 4.4.000 (2008-12-07)
4898	 * @protected
4899	 * @see Annotation()
4900	 */
4901	protected function _putEmbeddedFiles() {
4902		if ($this->pdfa_mode && $this->pdfa_version != 3)  {
4903			// embedded files are not allowed in PDF/A mode version 1 and 2
4904			return;
4905		}
4906		reset($this->embeddedfiles);
4907		foreach ($this->embeddedfiles as $filename => $filedata) {
4908		    $data = $this->getCachedFileContents($filedata['file']);
4909			if ($data !== FALSE) {
4910				$rawsize = strlen($data);
4911				if ($rawsize > 0) {
4912					// update name tree
4913					$this->efnames[$filename] = $filedata['f'].' 0 R';
4914					// embedded file specification object
4915					$out = $this->_getobj($filedata['f'])."\n";
4916					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4917					$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4918					$out .= ' /AFRelationship /Source';
4919					$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4920					$out .= "\n".'endobj';
4921					$this->_out($out);
4922					// embedded file object
4923					$filter = '';
4924					if ($this->compress) {
4925						$data = gzcompress($data);
4926						$filter = ' /Filter /FlateDecode';
4927					}
4928
4929					if ($this->pdfa_version == 3) {
4930						$filter = ' /Subtype /text#2Fxml';
4931					}
4932
4933					$stream = $this->_getrawstream($data, $filedata['n']);
4934					$out = $this->_getobj($filedata['n'])."\n";
4935					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4936					$out .= ' stream'."\n".$stream."\n".'endstream';
4937					$out .= "\n".'endobj';
4938					$this->_out($out);
4939				}
4940			}
4941		}
4942	}
4943
4944	/**
4945	 * Prints a text cell at the specified position.
4946	 * This method allows to place a string precisely on the page.
4947	 * @param $x (float) Abscissa of the cell origin
4948	 * @param $y (float) Ordinate of the cell origin
4949	 * @param $txt (string) String to print
4950	 * @param $fstroke (int) outline size in user units (false = disable)
4951	 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4952	 * @param $ffill (boolean) if true fills the text
4953	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4954	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4955	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4956	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4957	 * @param $link (mixed) URL or identifier returned by AddLink().
4958	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4959	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4960	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4961	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4962	 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4963	 * @public
4964	 * @since 1.0
4965	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4966	 */
4967	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) {
4968		$textrendermode = $this->textrendermode;
4969		$textstrokewidth = $this->textstrokewidth;
4970		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
4971		$this->SetXY($x, $y, $rtloff);
4972		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4973		// restore previous rendering mode
4974		$this->textrendermode = $textrendermode;
4975		$this->textstrokewidth = $textstrokewidth;
4976	}
4977
4978	/**
4979	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4980	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4981	 * This method is called automatically and should not be called directly by the application.
4982	 * @return boolean
4983	 * @public
4984	 * @since 1.4
4985	 * @see SetAutoPageBreak()
4986	 */
4987	public function AcceptPageBreak() {
4988		if ($this->num_columns > 1) {
4989			// multi column mode
4990			if ($this->current_column < ($this->num_columns - 1)) {
4991				// go to next column
4992				$this->selectColumn($this->current_column + 1);
4993			} elseif ($this->AutoPageBreak) {
4994				// add a new page
4995				$this->AddPage();
4996				// set first column
4997				$this->selectColumn(0);
4998			}
4999			// avoid page breaking from checkPageBreak()
5000			return false;
5001		}
5002		return $this->AutoPageBreak;
5003	}
5004
5005	/**
5006	 * Add page if needed.
5007	 * @param $h (float) Cell height. Default value: 0.
5008	 * @param $y (mixed) starting y position, leave empty for current position.
5009	 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
5010	 * @return boolean true in case of page break, false otherwise.
5011	 * @since 3.2.000 (2008-07-01)
5012	 * @protected
5013	 */
5014	protected function checkPageBreak($h=0, $y='', $addpage=true) {
5015		if (TCPDF_STATIC::empty_string($y)) {
5016			$y = $this->y;
5017		}
5018		$current_page = $this->page;
5019		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5020			if ($addpage) {
5021				//Automatic page break
5022				$x = $this->x;
5023				$this->AddPage($this->CurOrientation);
5024				$this->y = $this->tMargin;
5025				$oldpage = $this->page - 1;
5026				if ($this->rtl) {
5027					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5028						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5029					} else {
5030						$this->x = $x;
5031					}
5032				} else {
5033					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5034						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5035					} else {
5036						$this->x = $x;
5037					}
5038				}
5039			}
5040			return true;
5041		}
5042		if ($current_page != $this->page) {
5043			// account for columns mode
5044			return true;
5045		}
5046		return false;
5047	}
5048
5049	/**
5050	 * 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 />
5051	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5052	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5053	 * @param $h (float) Cell height. Default value: 0.
5054	 * @param $txt (string) String to print. Default value: empty string.
5055	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5056	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5057	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5058	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5059	 * @param $link (mixed) URL or identifier returned by AddLink().
5060	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5061	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5062	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5063	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5064	 * @public
5065	 * @since 1.0
5066	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5067	 */
5068	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') {
5069		$prev_cell_margin = $this->cell_margin;
5070		$prev_cell_padding = $this->cell_padding;
5071		$this->adjustCellPadding($border);
5072		if (!$ignore_min_height) {
5073			$min_cell_height = $this->getCellHeight($this->FontSize);
5074			if ($h < $min_cell_height) {
5075				$h = $min_cell_height;
5076			}
5077		}
5078		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5079		// apply text shadow if enabled
5080		if ($this->txtshadow['enabled']) {
5081			// save data
5082			$x = $this->x;
5083			$y = $this->y;
5084			$bc = $this->bgcolor;
5085			$fc = $this->fgcolor;
5086			$sc = $this->strokecolor;
5087			$alpha = $this->alpha;
5088			// print shadow
5089			$this->x += $this->txtshadow['depth_w'];
5090			$this->y += $this->txtshadow['depth_h'];
5091			$this->SetFillColorArray($this->txtshadow['color']);
5092			$this->SetTextColorArray($this->txtshadow['color']);
5093			$this->SetDrawColorArray($this->txtshadow['color']);
5094			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5095				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5096			}
5097			if ($this->state == 2) {
5098				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5099			}
5100			//restore data
5101			$this->x = $x;
5102			$this->y = $y;
5103			$this->SetFillColorArray($bc);
5104			$this->SetTextColorArray($fc);
5105			$this->SetDrawColorArray($sc);
5106			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5107				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5108			}
5109		}
5110		if ($this->state == 2) {
5111			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5112		}
5113		$this->cell_padding = $prev_cell_padding;
5114		$this->cell_margin = $prev_cell_margin;
5115	}
5116
5117	/**
5118	 * 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 />
5119	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5120	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5121	 * @param $h (float) Cell height. Default value: 0.
5122	 * @param $txt (string) String to print. Default value: empty string.
5123	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5124	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5125	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5126	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5127	 * @param $link (mixed) URL or identifier returned by AddLink().
5128	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5129	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5130	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5131	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5132	 * @return string containing cell code
5133	 * @protected
5134	 * @since 1.0
5135	 * @see Cell()
5136	 */
5137	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') {
5138		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5139		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5140		$prev_cell_margin = $this->cell_margin;
5141		$prev_cell_padding = $this->cell_padding;
5142		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5143		$rs = ''; //string to be returned
5144		$this->adjustCellPadding($border);
5145		if (!$ignore_min_height) {
5146			$min_cell_height = $this->getCellHeight($this->FontSize);
5147			if ($h < $min_cell_height) {
5148				$h = $min_cell_height;
5149			}
5150		}
5151		$k = $this->k;
5152		// check page for no-write regions and adapt page margins if necessary
5153		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5154		if ($this->rtl) {
5155			$x = $this->x - $this->cell_margin['R'];
5156		} else {
5157			$x = $this->x + $this->cell_margin['L'];
5158		}
5159		$y = $this->y + $this->cell_margin['T'];
5160		$prev_font_stretching = $this->font_stretching;
5161		$prev_font_spacing = $this->font_spacing;
5162		// cell vertical alignment
5163		switch ($calign) {
5164			case 'A': {
5165				// font top
5166				switch ($valign) {
5167					case 'T': {
5168						// top
5169						$y -= $this->cell_padding['T'];
5170						break;
5171					}
5172					case 'B': {
5173						// bottom
5174						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5175						break;
5176					}
5177					default:
5178					case 'C':
5179					case 'M': {
5180						// center
5181						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5182						break;
5183					}
5184				}
5185				break;
5186			}
5187			case 'L': {
5188				// font baseline
5189				switch ($valign) {
5190					case 'T': {
5191						// top
5192						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5193						break;
5194					}
5195					case 'B': {
5196						// bottom
5197						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5198						break;
5199					}
5200					default:
5201					case 'C':
5202					case 'M': {
5203						// center
5204						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5205						break;
5206					}
5207				}
5208				break;
5209			}
5210			case 'D': {
5211				// font bottom
5212				switch ($valign) {
5213					case 'T': {
5214						// top
5215						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5216						break;
5217					}
5218					case 'B': {
5219						// bottom
5220						$y -= ($h - $this->cell_padding['B']);
5221						break;
5222					}
5223					default:
5224					case 'C':
5225					case 'M': {
5226						// center
5227						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5228						break;
5229					}
5230				}
5231				break;
5232			}
5233			case 'B': {
5234				// cell bottom
5235				$y -= $h;
5236				break;
5237			}
5238			case 'C':
5239			case 'M': {
5240				// cell center
5241				$y -= ($h / 2);
5242				break;
5243			}
5244			default:
5245			case 'T': {
5246				// cell top
5247				break;
5248			}
5249		}
5250		// text vertical alignment
5251		switch ($valign) {
5252			case 'T': {
5253				// top
5254				$yt = $y + $this->cell_padding['T'];
5255				break;
5256			}
5257			case 'B': {
5258				// bottom
5259				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5260				break;
5261			}
5262			default:
5263			case 'C':
5264			case 'M': {
5265				// center
5266				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5267				break;
5268			}
5269		}
5270		$basefonty = $yt + $this->FontAscent;
5271		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5272			if ($this->rtl) {
5273				$w = $x - $this->lMargin;
5274			} else {
5275				$w = $this->w - $this->rMargin - $x;
5276			}
5277		}
5278		$s = '';
5279		// fill and borders
5280		if (is_string($border) AND (strlen($border) == 4)) {
5281			// full border
5282			$border = 1;
5283		}
5284		if ($fill OR ($border == 1)) {
5285			if ($fill) {
5286				$op = ($border == 1) ? 'B' : 'f';
5287			} else {
5288				$op = 'S';
5289			}
5290			if ($this->rtl) {
5291				$xk = (($x - $w) * $k);
5292			} else {
5293				$xk = ($x * $k);
5294			}
5295			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5296		}
5297		// draw borders
5298		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5299		if ($txt != '') {
5300			$txt2 = $txt;
5301			if ($this->isunicode) {
5302				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5303					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5304				} else {
5305					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5306					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5307					// replace thai chars (if any)
5308					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5309						// number of chars
5310						$numchars = count($unicode);
5311						// po pla, for far, for fan
5312						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5313						// do chada, to patak
5314						$lowtail = array(0x0e0e, 0x0e0f);
5315						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5316						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5317						// mai ek, mai tho, mai tri, mai chattawa, karan
5318						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5319						// sara u, sara uu, pinthu
5320						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5321						$output = array();
5322						for ($i = 0; $i < $numchars; $i++) {
5323							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5324								$ch0 = $unicode[$i];
5325								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5326								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5327								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5328								if (in_array($ch0, $tonemark)) {
5329									if ($chn == 0x0e33) {
5330										// sara um
5331										if (in_array($ch1, $longtail)) {
5332											// tonemark at upper left
5333											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5334										} else {
5335											// tonemark at upper right (normal position)
5336											$output[] = $ch0;
5337										}
5338									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5339										// tonemark at lower left
5340										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5341									} elseif (in_array($ch1, $upvowel)) {
5342										if (in_array($ch2, $longtail)) {
5343											// tonemark at upper left
5344											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5345										} else {
5346											// tonemark at upper right (normal position)
5347											$output[] = $ch0;
5348										}
5349									} else {
5350										// tonemark at lower right
5351										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5352									}
5353								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5354									// add lower left nikhahit and sara aa
5355									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5356										$output[] = 0xf711;
5357										$this->CurrentFont['subsetchars'][0xf711] = true;
5358										$output[] = 0x0e32;
5359										$this->CurrentFont['subsetchars'][0x0e32] = true;
5360									} else {
5361										$output[] = $ch0;
5362									}
5363								} elseif (in_array($ch1, $longtail)) {
5364									if ($ch0 == 0x0e31) {
5365										// lower left mai hun arkad
5366										$output[] = $this->replaceChar($ch0, 0xf710);
5367									} elseif (in_array($ch0, $upvowel)) {
5368										// lower left
5369										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5370									} elseif ($ch0 == 0x0e47) {
5371										// lower left mai tai koo
5372										$output[] = $this->replaceChar($ch0, 0xf712);
5373									} else {
5374										// normal character
5375										$output[] = $ch0;
5376									}
5377								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5378									// lower vowel
5379									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5380								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5381									// yo ying without lower part
5382									$output[] = $this->replaceChar($ch0, 0xf70f);
5383								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5384									// tho santan without lower part
5385									$output[] = $this->replaceChar($ch0, 0xf700);
5386								} else {
5387									$output[] = $ch0;
5388								}
5389							} else {
5390								// non-thai character
5391								$output[] = $unicode[$i];
5392							}
5393						}
5394						$unicode = $output;
5395						// update font subsetchars
5396						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5397					} // end of K_THAI_TOPCHARS
5398					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5399				}
5400			}
5401			$txt2 = TCPDF_STATIC::_escape($txt2);
5402			// get current text width (considering general font stretching and spacing)
5403			$txwidth = $this->GetStringWidth($txt);
5404			$width = $txwidth;
5405			// check for stretch mode
5406			if ($stretch > 0) {
5407				// calculate ratio between cell width and text width
5408				if ($width <= 0) {
5409					$ratio = 1;
5410				} else {
5411					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5412				}
5413				// check if stretching is required
5414				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5415					// the text will be stretched to fit cell width
5416					if ($stretch > 2) {
5417						// set new character spacing
5418						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5419					} else {
5420						// set new horizontal stretching
5421						$this->font_stretching *= $ratio;
5422					}
5423					// recalculate text width (the text fills the entire cell)
5424					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5425					// reset alignment
5426					$align = '';
5427				}
5428			}
5429			if ($this->font_stretching != 100) {
5430				// apply font stretching
5431				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5432			}
5433			if ($this->font_spacing != 0) {
5434				// increase/decrease font spacing
5435				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5436			}
5437			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5438				$s .= 'q '.$this->TextColor.' ';
5439			}
5440			// rendering mode
5441			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5442			// count number of spaces
5443			$ns = substr_count($txt, chr(32));
5444			// Justification
5445			$spacewidth = 0;
5446			if (($align == 'J') AND ($ns > 0)) {
5447				if ($this->isUnicodeFont()) {
5448					// get string width without spaces
5449					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5450					// calculate average space width
5451					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5452					if ($this->font_stretching != 100) {
5453						// word spacing is affected by stretching
5454						$spacewidth /= ($this->font_stretching / 100);
5455					}
5456					// set word position to be used with TJ operator
5457					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5458					$unicode_justification = true;
5459				} else {
5460					// get string width
5461					$width = $txwidth;
5462					// new space width
5463					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5464					if ($this->font_stretching != 100) {
5465						// word spacing (Tw) is affected by stretching
5466						$spacewidth /= ($this->font_stretching / 100);
5467					}
5468					// set word spacing
5469					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5470				}
5471				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5472			}
5473			// replace carriage return characters
5474			$txt2 = str_replace("\r", ' ', $txt2);
5475			switch ($align) {
5476				case 'C': {
5477					$dx = ($w - $width) / 2;
5478					break;
5479				}
5480				case 'R': {
5481					if ($this->rtl) {
5482						$dx = $this->cell_padding['R'];
5483					} else {
5484						$dx = $w - $width - $this->cell_padding['R'];
5485					}
5486					break;
5487				}
5488				case 'L': {
5489					if ($this->rtl) {
5490						$dx = $w - $width - $this->cell_padding['L'];
5491					} else {
5492						$dx = $this->cell_padding['L'];
5493					}
5494					break;
5495				}
5496				case 'J':
5497				default: {
5498					if ($this->rtl) {
5499						$dx = $this->cell_padding['R'];
5500					} else {
5501						$dx = $this->cell_padding['L'];
5502					}
5503					break;
5504				}
5505			}
5506			if ($this->rtl) {
5507				$xdx = $x - $dx - $width;
5508			} else {
5509				$xdx = $x + $dx;
5510			}
5511			$xdk = $xdx * $k;
5512			// print text
5513			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5514			if (isset($uniblock)) {
5515				// print overlapping characters as separate string
5516				$xshift = 0; // horizontal shift
5517				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5518				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5519				foreach ($uniblock as $uk => $uniarr) {
5520					if (($uk % 2) == 0) {
5521						// x space to skip
5522						if ($spacewidth != 0) {
5523							// justification shift
5524							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5525						}
5526						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5527					} else {
5528						// character to print
5529						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5530						$topchr = TCPDF_STATIC::_escape($topchr);
5531						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5532					}
5533				}
5534			}
5535			if ($this->underline) {
5536				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5537			}
5538			if ($this->linethrough) {
5539				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5540			}
5541			if ($this->overline) {
5542				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5543			}
5544			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5545				$s .= ' Q';
5546			}
5547			if ($link) {
5548				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5549			}
5550		}
5551		// output cell
5552		if ($s) {
5553			// output cell
5554			$rs .= $s;
5555			if ($this->font_spacing != 0) {
5556				// reset font spacing mode
5557				$rs .= ' BT 0 Tc ET';
5558			}
5559			if ($this->font_stretching != 100) {
5560				// reset font stretching mode
5561				$rs .= ' BT 100 Tz ET';
5562			}
5563		}
5564		// reset word spacing
5565		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5566			$rs .= ' BT 0 Tw ET';
5567		}
5568		// reset stretching and spacing
5569		$this->font_stretching = $prev_font_stretching;
5570		$this->font_spacing = $prev_font_spacing;
5571		$this->lasth = $h;
5572		if ($ln > 0) {
5573			//Go to the beginning of the next line
5574			$this->y = $y + $h + $this->cell_margin['B'];
5575			if ($ln == 1) {
5576				if ($this->rtl) {
5577					$this->x = $this->w - $this->rMargin;
5578				} else {
5579					$this->x = $this->lMargin;
5580				}
5581			}
5582		} else {
5583			// go left or right by case
5584			if ($this->rtl) {
5585				$this->x = $x - $w - $this->cell_margin['L'];
5586			} else {
5587				$this->x = $x + $w + $this->cell_margin['R'];
5588			}
5589		}
5590		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5591		$rs = $gstyles.$rs;
5592		$this->cell_padding = $prev_cell_padding;
5593		$this->cell_margin = $prev_cell_margin;
5594		return $rs;
5595	}
5596
5597	/**
5598	 * Replace a char if is defined on the current font.
5599	 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5600	 * @param $newchar (int) Integer code (unicode) of the new character.
5601	 * @return int the replaced char or the old char in case the new char i not defined
5602	 * @protected
5603	 * @since 5.9.167 (2012-06-22)
5604	 */
5605	protected function replaceChar($oldchar, $newchar) {
5606		if ($this->isCharDefined($newchar)) {
5607			// add the new char on the subset list
5608			$this->CurrentFont['subsetchars'][$newchar] = true;
5609			// return the new character
5610			return $newchar;
5611		}
5612		// return the old char
5613		return $oldchar;
5614	}
5615
5616	/**
5617	 * Returns the code to draw the cell border
5618	 * @param $x (float) X coordinate.
5619	 * @param $y (float) Y coordinate.
5620	 * @param $w (float) Cell width.
5621	 * @param $h (float) Cell height.
5622	 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5623	 * @return string containing cell border code
5624	 * @protected
5625	 * @see SetLineStyle()
5626	 * @since 5.7.000 (2010-08-02)
5627	 */
5628	protected function getCellBorder($x, $y, $w, $h, $brd) {
5629		$s = ''; // string to be returned
5630		if (empty($brd)) {
5631			return $s;
5632		}
5633		if ($brd == 1) {
5634			$brd = array('LRTB' => true);
5635		}
5636		// calculate coordinates for border
5637		$k = $this->k;
5638		if ($this->rtl) {
5639			$xeL = ($x - $w) * $k;
5640			$xeR = $x * $k;
5641		} else {
5642			$xeL = $x * $k;
5643			$xeR = ($x + $w) * $k;
5644		}
5645		$yeL = (($this->h - ($y + $h)) * $k);
5646		$yeT = (($this->h - $y) * $k);
5647		$xeT = $xeL;
5648		$xeB = $xeR;
5649		$yeR = $yeT;
5650		$yeB = $yeL;
5651		if (is_string($brd)) {
5652			// convert string to array
5653			$slen = strlen($brd);
5654			$newbrd = array();
5655			for ($i = 0; $i < $slen; ++$i) {
5656				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5657			}
5658			$brd = $newbrd;
5659		}
5660		if (isset($brd['mode'])) {
5661			$mode = $brd['mode'];
5662			unset($brd['mode']);
5663		} else {
5664			$mode = 'normal';
5665		}
5666		foreach ($brd as $border => $style) {
5667			if (is_array($style) AND !empty($style)) {
5668				// apply border style
5669				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5670				$s .= $this->SetLineStyle($style, true)."\n";
5671			}
5672			switch ($mode) {
5673				case 'ext': {
5674					$off = (($this->LineWidth / 2) * $k);
5675					$xL = $xeL - $off;
5676					$xR = $xeR + $off;
5677					$yT = $yeT + $off;
5678					$yL = $yeL - $off;
5679					$xT = $xL;
5680					$xB = $xR;
5681					$yR = $yT;
5682					$yB = $yL;
5683					$w += $this->LineWidth;
5684					$h += $this->LineWidth;
5685					break;
5686				}
5687				case 'int': {
5688					$off = ($this->LineWidth / 2) * $k;
5689					$xL = $xeL + $off;
5690					$xR = $xeR - $off;
5691					$yT = $yeT - $off;
5692					$yL = $yeL + $off;
5693					$xT = $xL;
5694					$xB = $xR;
5695					$yR = $yT;
5696					$yB = $yL;
5697					$w -= $this->LineWidth;
5698					$h -= $this->LineWidth;
5699					break;
5700				}
5701				case 'normal':
5702				default: {
5703					$xL = $xeL;
5704					$xT = $xeT;
5705					$xB = $xeB;
5706					$xR = $xeR;
5707					$yL = $yeL;
5708					$yT = $yeT;
5709					$yB = $yeB;
5710					$yR = $yeR;
5711					break;
5712				}
5713			}
5714			// draw borders by case
5715			if (strlen($border) == 4) {
5716				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5717			} elseif (strlen($border) == 3) {
5718				if (strpos($border,'B') === false) { // LTR
5719					$s .= sprintf('%F %F m ', $xL, $yL);
5720					$s .= sprintf('%F %F l ', $xT, $yT);
5721					$s .= sprintf('%F %F l ', $xR, $yR);
5722					$s .= sprintf('%F %F l ', $xB, $yB);
5723					$s .= 'S ';
5724				} elseif (strpos($border,'L') === false) { // TRB
5725					$s .= sprintf('%F %F m ', $xT, $yT);
5726					$s .= sprintf('%F %F l ', $xR, $yR);
5727					$s .= sprintf('%F %F l ', $xB, $yB);
5728					$s .= sprintf('%F %F l ', $xL, $yL);
5729					$s .= 'S ';
5730				} elseif (strpos($border,'T') === false) { // RBL
5731					$s .= sprintf('%F %F m ', $xR, $yR);
5732					$s .= sprintf('%F %F l ', $xB, $yB);
5733					$s .= sprintf('%F %F l ', $xL, $yL);
5734					$s .= sprintf('%F %F l ', $xT, $yT);
5735					$s .= 'S ';
5736				} elseif (strpos($border,'R') === false) { // BLT
5737					$s .= sprintf('%F %F m ', $xB, $yB);
5738					$s .= sprintf('%F %F l ', $xL, $yL);
5739					$s .= sprintf('%F %F l ', $xT, $yT);
5740					$s .= sprintf('%F %F l ', $xR, $yR);
5741					$s .= 'S ';
5742				}
5743			} elseif (strlen($border) == 2) {
5744				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5745					$s .= sprintf('%F %F m ', $xL, $yL);
5746					$s .= sprintf('%F %F l ', $xT, $yT);
5747					$s .= sprintf('%F %F l ', $xR, $yR);
5748					$s .= 'S ';
5749				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5750					$s .= sprintf('%F %F m ', $xT, $yT);
5751					$s .= sprintf('%F %F l ', $xR, $yR);
5752					$s .= sprintf('%F %F l ', $xB, $yB);
5753					$s .= 'S ';
5754				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5755					$s .= sprintf('%F %F m ', $xR, $yR);
5756					$s .= sprintf('%F %F l ', $xB, $yB);
5757					$s .= sprintf('%F %F l ', $xL, $yL);
5758					$s .= 'S ';
5759				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5760					$s .= sprintf('%F %F m ', $xB, $yB);
5761					$s .= sprintf('%F %F l ', $xL, $yL);
5762					$s .= sprintf('%F %F l ', $xT, $yT);
5763					$s .= 'S ';
5764				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5765					$s .= sprintf('%F %F m ', $xL, $yL);
5766					$s .= sprintf('%F %F l ', $xT, $yT);
5767					$s .= 'S ';
5768					$s .= sprintf('%F %F m ', $xR, $yR);
5769					$s .= sprintf('%F %F l ', $xB, $yB);
5770					$s .= 'S ';
5771				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5772					$s .= sprintf('%F %F m ', $xT, $yT);
5773					$s .= sprintf('%F %F l ', $xR, $yR);
5774					$s .= 'S ';
5775					$s .= sprintf('%F %F m ', $xB, $yB);
5776					$s .= sprintf('%F %F l ', $xL, $yL);
5777					$s .= 'S ';
5778				}
5779			} else { // strlen($border) == 1
5780				if (strpos($border,'L') !== false) { // L
5781					$s .= sprintf('%F %F m ', $xL, $yL);
5782					$s .= sprintf('%F %F l ', $xT, $yT);
5783					$s .= 'S ';
5784				} elseif (strpos($border,'T') !== false) { // T
5785					$s .= sprintf('%F %F m ', $xT, $yT);
5786					$s .= sprintf('%F %F l ', $xR, $yR);
5787					$s .= 'S ';
5788				} elseif (strpos($border,'R') !== false) { // R
5789					$s .= sprintf('%F %F m ', $xR, $yR);
5790					$s .= sprintf('%F %F l ', $xB, $yB);
5791					$s .= 'S ';
5792				} elseif (strpos($border,'B') !== false) { // B
5793					$s .= sprintf('%F %F m ', $xB, $yB);
5794					$s .= sprintf('%F %F l ', $xL, $yL);
5795					$s .= 'S ';
5796				}
5797			}
5798			if (is_array($style) AND !empty($style)) {
5799				// reset border style to previous value
5800				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5801			}
5802		}
5803		return $s;
5804	}
5805
5806	/**
5807	 * This method allows printing text with line breaks.
5808	 * 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 />
5809	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5810	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5811	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5812	 * @param $txt (string) String to print
5813	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5814	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5815	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5816	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5817	 * @param $x (float) x position in user units
5818	 * @param $y (float) y position in user units
5819	 * @param $reseth (boolean) if true reset the last cell height (default true).
5820	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5821	 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5822	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5823	 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5824	 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5825	 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5826	 * @return int Return the number of cells or 1 for html mode.
5827	 * @public
5828	 * @since 1.3
5829	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5830	 */
5831	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) {
5832		$prev_cell_margin = $this->cell_margin;
5833		$prev_cell_padding = $this->cell_padding;
5834		// adjust internal padding
5835		$this->adjustCellPadding($border);
5836		$mc_padding = $this->cell_padding;
5837		$mc_margin = $this->cell_margin;
5838		$this->cell_padding['T'] = 0;
5839		$this->cell_padding['B'] = 0;
5840		$this->setCellMargins(0, 0, 0, 0);
5841		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5842			// reset row height
5843			$this->resetLastH();
5844		}
5845		if (!TCPDF_STATIC::empty_string($y)) {
5846			$this->SetY($y); // set y in order to convert negative y values to positive ones
5847		}
5848		$y = $this->GetY();
5849		$resth = 0;
5850		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5851			// spit cell in more pages/columns
5852			$newh = ($this->PageBreakTrigger - $y);
5853			$resth = ($h - $newh); // cell to be printed on the next page/column
5854			$h = $newh;
5855		}
5856		// get current page number
5857		$startpage = $this->page;
5858		// get current column
5859		$startcolumn = $this->current_column;
5860		if (!TCPDF_STATIC::empty_string($x)) {
5861			$this->SetX($x);
5862		} else {
5863			$x = $this->GetX();
5864		}
5865		// check page for no-write regions and adapt page margins if necessary
5866		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5867		// apply margins
5868		$oy = $y + $mc_margin['T'];
5869		if ($this->rtl) {
5870			$ox = ($this->w - $x - $mc_margin['R']);
5871		} else {
5872			$ox = ($x + $mc_margin['L']);
5873		}
5874		$this->x = $ox;
5875		$this->y = $oy;
5876		// set width
5877		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5878			if ($this->rtl) {
5879				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5880			} else {
5881				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5882			}
5883		}
5884		// store original margin values
5885		$lMargin = $this->lMargin;
5886		$rMargin = $this->rMargin;
5887		if ($this->rtl) {
5888			$this->rMargin = ($this->w - $this->x);
5889			$this->lMargin = ($this->x - $w);
5890		} else {
5891			$this->lMargin = ($this->x);
5892			$this->rMargin = ($this->w - $this->x - $w);
5893		}
5894		$this->clMargin = $this->lMargin;
5895		$this->crMargin = $this->rMargin;
5896		if ($autopadding) {
5897			// add top padding
5898			$this->y += $mc_padding['T'];
5899		}
5900		if ($ishtml) { // ******* Write HTML text
5901			$this->writeHTML($txt, true, false, $reseth, true, $align);
5902			$nl = 1;
5903		} else { // ******* Write simple text
5904			$prev_FontSizePt = $this->FontSizePt;
5905			if ($fitcell) {
5906				// ajust height values
5907				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5908				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5909			}
5910			// vertical alignment
5911			if ($maxh > 0) {
5912				// get text height
5913				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5914				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5915					// try to reduce font size to fit text on cell (use a quick search algorithm)
5916					$fmin = 1;
5917					$fmax = $this->FontSizePt;
5918					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5919					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5920					while ($maxit >= 0) {
5921						$fmid = (($fmax + $fmin) / 2);
5922						$this->SetFontSize($fmid, false);
5923						$this->resetLastH();
5924						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5925						$diff = ($maxh - $text_height);
5926						if ($diff >= 0) {
5927							if ($diff <= $diff_epsilon) {
5928								break;
5929							}
5930							$fmin = $fmid;
5931						} else {
5932							$fmax = $fmid;
5933						}
5934						--$maxit;
5935					}
5936					if ($maxit < 0) {
5937						// premature exit, we get the minimum font value to fit the cell
5938						$this->SetFontSize($fmin);
5939						$this->resetLastH();
5940						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5941					} else {
5942						$this->SetFontSize($fmid);
5943						$this->resetLastH();
5944					}
5945				}
5946				if ($text_height < $maxh) {
5947					if ($valign == 'M') {
5948						// text vertically centered
5949						$this->y += (($maxh - $text_height) / 2);
5950					} elseif ($valign == 'B') {
5951						// text vertically aligned on bottom
5952						$this->y += ($maxh - $text_height);
5953					}
5954				}
5955			}
5956			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5957			if ($fitcell) {
5958				// restore font size
5959				$this->SetFontSize($prev_FontSizePt);
5960			}
5961		}
5962		if ($autopadding) {
5963			// add bottom padding
5964			$this->y += $mc_padding['B'];
5965		}
5966		// Get end-of-text Y position
5967		$currentY = $this->y;
5968		// get latest page number
5969		$endpage = $this->page;
5970		if ($resth > 0) {
5971			$skip = ($endpage - $startpage);
5972			$tmpresth = $resth;
5973			while ($tmpresth > 0) {
5974				if ($skip <= 0) {
5975					// add a page (or trig AcceptPageBreak() for multicolumn mode)
5976					$this->checkPageBreak($this->PageBreakTrigger + 1);
5977				}
5978				if ($this->num_columns > 1) {
5979					$tmpresth -= ($this->h - $this->y - $this->bMargin);
5980				} else {
5981					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5982				}
5983				--$skip;
5984			}
5985			$currentY = $this->y;
5986			$endpage = $this->page;
5987		}
5988		// get latest column
5989		$endcolumn = $this->current_column;
5990		if ($this->num_columns == 0) {
5991			$this->num_columns = 1;
5992		}
5993		// disable page regions check
5994		$check_page_regions = $this->check_page_regions;
5995		$this->check_page_regions = false;
5996		// get border modes
5997		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5998		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5999		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6000		// design borders around HTML cells.
6001		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6002			$ccode = '';
6003			$this->setPage($page);
6004			if ($this->num_columns < 2) {
6005				// single-column mode
6006				$this->SetX($x);
6007				$this->y = $this->tMargin;
6008			}
6009			// account for margin changes
6010			if ($page > $startpage) {
6011				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6012					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6013				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6014					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6015				}
6016			}
6017			if ($startpage == $endpage) {
6018				// single page
6019				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6020					if ($column != $this->current_column) {
6021						$this->selectColumn($column);
6022					}
6023					if ($this->rtl) {
6024						$this->x -= $mc_margin['R'];
6025					} else {
6026						$this->x += $mc_margin['L'];
6027					}
6028					if ($startcolumn == $endcolumn) { // single column
6029						$cborder = $border;
6030						$h = max($h, ($currentY - $oy));
6031						$this->y = $oy;
6032					} elseif ($column == $startcolumn) { // first column
6033						$cborder = $border_start;
6034						$this->y = $oy;
6035						$h = $this->h - $this->y - $this->bMargin;
6036					} elseif ($column == $endcolumn) { // end column
6037						$cborder = $border_end;
6038						$h = $currentY - $this->y;
6039						if ($resth > $h) {
6040							$h = $resth;
6041						}
6042					} else { // middle column
6043						$cborder = $border_middle;
6044						$h = $this->h - $this->y - $this->bMargin;
6045						$resth -= $h;
6046					}
6047					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6048				} // end for each column
6049			} elseif ($page == $startpage) { // first page
6050				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6051					if ($column != $this->current_column) {
6052						$this->selectColumn($column);
6053					}
6054					if ($this->rtl) {
6055						$this->x -= $mc_margin['R'];
6056					} else {
6057						$this->x += $mc_margin['L'];
6058					}
6059					if ($column == $startcolumn) { // first column
6060						$cborder = $border_start;
6061						$this->y = $oy;
6062						$h = $this->h - $this->y - $this->bMargin;
6063					} else { // middle column
6064						$cborder = $border_middle;
6065						$h = $this->h - $this->y - $this->bMargin;
6066						$resth -= $h;
6067					}
6068					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6069				} // end for each column
6070			} elseif ($page == $endpage) { // last page
6071				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6072					if ($column != $this->current_column) {
6073						$this->selectColumn($column);
6074					}
6075					if ($this->rtl) {
6076						$this->x -= $mc_margin['R'];
6077					} else {
6078						$this->x += $mc_margin['L'];
6079					}
6080					if ($column == $endcolumn) {
6081						// end column
6082						$cborder = $border_end;
6083						$h = $currentY - $this->y;
6084						if ($resth > $h) {
6085							$h = $resth;
6086						}
6087					} else {
6088						// middle column
6089						$cborder = $border_middle;
6090						$h = $this->h - $this->y - $this->bMargin;
6091						$resth -= $h;
6092					}
6093					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6094				} // end for each column
6095			} else { // middle page
6096				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6097					$this->selectColumn($column);
6098					if ($this->rtl) {
6099						$this->x -= $mc_margin['R'];
6100					} else {
6101						$this->x += $mc_margin['L'];
6102					}
6103					$cborder = $border_middle;
6104					$h = $this->h - $this->y - $this->bMargin;
6105					$resth -= $h;
6106					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6107				} // end for each column
6108			}
6109			if ($cborder OR $fill) {
6110				$offsetlen = strlen($ccode);
6111				// draw border and fill
6112				if ($this->inxobj) {
6113					// we are inside an XObject template
6114					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6115						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6116						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6117						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6118					} else {
6119						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6120						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6121					}
6122					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6123					$pstart = substr($pagebuff, 0, $pagemark);
6124					$pend = substr($pagebuff, $pagemark);
6125					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6126				} else {
6127					if (end($this->transfmrk[$this->page]) !== false) {
6128						$pagemarkkey = key($this->transfmrk[$this->page]);
6129						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6130						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6131					} elseif ($this->InFooter) {
6132						$pagemark = $this->footerpos[$this->page];
6133						$this->footerpos[$this->page] += $offsetlen;
6134					} else {
6135						$pagemark = $this->intmrk[$this->page];
6136						$this->intmrk[$this->page] += $offsetlen;
6137					}
6138					$pagebuff = $this->getPageBuffer($this->page);
6139					$pstart = substr($pagebuff, 0, $pagemark);
6140					$pend = substr($pagebuff, $pagemark);
6141					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6142				}
6143			}
6144		} // end for each page
6145		// restore page regions check
6146		$this->check_page_regions = $check_page_regions;
6147		// Get end-of-cell Y position
6148		$currentY = $this->GetY();
6149		// restore previous values
6150		if ($this->num_columns > 1) {
6151			$this->selectColumn();
6152		} else {
6153			// restore original margins
6154			$this->lMargin = $lMargin;
6155			$this->rMargin = $rMargin;
6156			if ($this->page > $startpage) {
6157				// check for margin variations between pages (i.e. booklet mode)
6158				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6159				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6160				if (($dl != 0) OR ($dr != 0)) {
6161					$this->lMargin += $dl;
6162					$this->rMargin += $dr;
6163				}
6164			}
6165		}
6166		if ($ln > 0) {
6167			//Go to the beginning of the next line
6168			$this->SetY($currentY + $mc_margin['B']);
6169			if ($ln == 2) {
6170				$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6171			}
6172		} else {
6173			// go left or right by case
6174			$this->setPage($startpage);
6175			$this->y = $y;
6176			$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6177		}
6178		$this->setContentMark();
6179		$this->cell_padding = $prev_cell_padding;
6180		$this->cell_margin = $prev_cell_margin;
6181		$this->clMargin = $this->lMargin;
6182		$this->crMargin = $this->rMargin;
6183		return $nl;
6184	}
6185
6186	/**
6187	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6188	 * @param $txt (string) String for calculating his height
6189	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6190	 * @param $reseth (boolean) if true reset the last cell height (default false).
6191	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6192	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6193	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6194	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6195	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6196	 * @public
6197	 * @since 4.5.011
6198	 */
6199	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6200		if ($txt === NULL) {
6201			return 0;
6202		}
6203		if ($txt === '') {
6204			// empty string
6205			return 1;
6206		}
6207		// adjust internal padding
6208		$prev_cell_padding = $this->cell_padding;
6209		$prev_lasth = $this->lasth;
6210		if (is_array($cellpadding)) {
6211			$this->cell_padding = $cellpadding;
6212		}
6213		$this->adjustCellPadding($border);
6214		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6215			if ($this->rtl) {
6216				$w = $this->x - $this->lMargin;
6217			} else {
6218				$w = $this->w - $this->rMargin - $this->x;
6219			}
6220		}
6221		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6222		if ($reseth) {
6223			// reset row height
6224			$this->resetLastH();
6225		}
6226		$lines = 1;
6227		$sum = 0;
6228		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6229		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6230		$length = count($chars);
6231		$lastSeparator = -1;
6232		for ($i = 0; $i < $length; ++$i) {
6233			$c = $chars[$i];
6234			$charWidth = $charsWidth[$i];
6235			if (($c != 160)
6236					AND (($c == 173)
6237						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6238						OR (($c == 45)
6239							AND ($i > 0) AND ($i < ($length - 1))
6240							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6241							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6242						)
6243					)
6244				) {
6245				$lastSeparator = $i;
6246			}
6247			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6248				++$lines;
6249				if ($c == 10) {
6250					$lastSeparator = -1;
6251					$sum = 0;
6252				} elseif ($lastSeparator != -1) {
6253					$i = $lastSeparator;
6254					$lastSeparator = -1;
6255					$sum = 0;
6256				} else {
6257					$sum = $charWidth;
6258				}
6259			} else {
6260				$sum += $charWidth;
6261			}
6262		}
6263		if ($chars[($length - 1)] == 10) {
6264			--$lines;
6265		}
6266		$this->cell_padding = $prev_cell_padding;
6267		$this->lasth = $prev_lasth;
6268		return $lines;
6269	}
6270
6271	/**
6272	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6273	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6274	 * @pre
6275	 *  // store current object
6276	 *  $pdf->startTransaction();
6277	 *  // store starting values
6278	 *  $start_y = $pdf->GetY();
6279	 *  $start_page = $pdf->getPage();
6280	 *  // call your printing functions with your parameters
6281	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6282	 *  $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);
6283	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6284	 *  // get the new Y
6285	 *  $end_y = $pdf->GetY();
6286	 *  $end_page = $pdf->getPage();
6287	 *  // calculate height
6288	 *  $height = 0;
6289	 *  if ($end_page == $start_page) {
6290	 *  	$height = $end_y - $start_y;
6291	 *  } else {
6292	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6293	 *  		$this->setPage($page);
6294	 *  		if ($page == $start_page) {
6295	 *  			// first page
6296	 *  			$height += $this->h - $start_y - $this->bMargin;
6297	 *  		} elseif ($page == $end_page) {
6298	 *  			// last page
6299	 *  			$height += $end_y - $this->tMargin;
6300	 *  		} else {
6301	 *  			$height += $this->h - $this->tMargin - $this->bMargin;
6302	 *  		}
6303	 *  	}
6304	 *  }
6305	 *  // restore previous object
6306	 *  $pdf = $pdf->rollbackTransaction();
6307	 *
6308	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6309	 * @param $txt (string) String for calculating his height
6310	 * @param $reseth (boolean) if true reset the last cell height (default false).
6311	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6312	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6313	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6314	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6315	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6316	 * @public
6317	 */
6318	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6319		// adjust internal padding
6320		$prev_cell_padding = $this->cell_padding;
6321		$prev_lasth = $this->lasth;
6322		if (is_array($cellpadding)) {
6323			$this->cell_padding = $cellpadding;
6324		}
6325		$this->adjustCellPadding($border);
6326		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6327		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6328		$this->cell_padding = $prev_cell_padding;
6329		$this->lasth = $prev_lasth;
6330		return $height;
6331	}
6332
6333	/**
6334	 * This method prints text from the current position.<br />
6335	 * @param $h (float) Line height
6336	 * @param $txt (string) String to print
6337	 * @param $link (mixed) URL or identifier returned by AddLink()
6338	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6339	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6340	 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6341	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6342	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6343	 * @param $firstblock (boolean) if true the string is the starting of a line.
6344	 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6345	 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6346	 * @param $margin (array) margin array of the parent container
6347	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6348	 * @public
6349	 * @since 1.5
6350	 */
6351	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6352		// check page for no-write regions and adapt page margins if necessary
6353		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6354		if (strlen($txt) == 0) {
6355			// fix empty text
6356			$txt = ' ';
6357		}
6358		if ($margin === '') {
6359			// set default margins
6360			$margin = $this->cell_margin;
6361		}
6362		// remove carriage returns
6363		$s = str_replace("\r", '', $txt);
6364		// check if string contains arabic text
6365		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6366			$arabic = true;
6367		} else {
6368			$arabic = false;
6369		}
6370		// check if string contains RTL text
6371		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6372			$rtlmode = true;
6373		} else {
6374			$rtlmode = false;
6375		}
6376		// get a char width
6377		$chrwidth = $this->GetCharWidth(46); // dot character
6378		// get array of unicode values
6379		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6380		// calculate maximum width for a single character on string
6381		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6382		array_walk($chrw, array($this, 'getRawCharWidth'));
6383		$maxchwidth = max($chrw);
6384		// get array of chars
6385		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6386		// get the number of characters
6387		$nb = count($chars);
6388		// replacement for SHY character (minus symbol)
6389		$shy_replacement = 45;
6390		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6391		// widht for SHY replacement
6392		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6393		// page width
6394		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6395		// calculate remaining line width ($w)
6396		if ($this->rtl) {
6397			$w = $this->x - $this->lMargin;
6398		} else {
6399			$w = $this->w - $this->rMargin - $this->x;
6400		}
6401		// max column width
6402		$wmax = ($w - $wadj);
6403		if (!$firstline) {
6404			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6405		}
6406		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6407			// the maximum width character do not fit on column
6408			return '';
6409		}
6410		// minimum row height
6411		$row_height = max($h, $this->getCellHeight($this->FontSize));
6412		// max Y
6413		$maxy = $this->y + $maxh - max($row_height, $h);
6414		$start_page = $this->page;
6415		$i = 0; // character position
6416		$j = 0; // current starting position
6417		$sep = -1; // position of the last blank space
6418		$prevsep = $sep; // previous separator
6419		$shy = false; // true if the last blank is a soft hypen (SHY)
6420		$prevshy = $shy; // previous shy mode
6421		$l = 0; // current string length
6422		$nl = 0; //number of lines
6423		$linebreak = false;
6424		$pc = 0; // previous character
6425		// for each character
6426		while ($i < $nb) {
6427			if (($maxh > 0) AND ($this->y > $maxy) ) {
6428				break;
6429			}
6430			//Get the current character
6431			$c = $chars[$i];
6432			if ($c == 10) { // 10 = "\n" = new line
6433				//Explicit line break
6434				if ($align == 'J') {
6435					if ($this->rtl) {
6436						$talign = 'R';
6437					} else {
6438						$talign = 'L';
6439					}
6440				} else {
6441					$talign = $align;
6442				}
6443				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6444				if ($firstline) {
6445					$startx = $this->x;
6446					$tmparr = array_slice($chars, $j, ($i - $j));
6447					if ($rtlmode) {
6448						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6449					}
6450					$linew = $this->GetArrStringWidth($tmparr);
6451					unset($tmparr);
6452					if ($this->rtl) {
6453						$this->endlinex = $startx - $linew;
6454					} else {
6455						$this->endlinex = $startx + $linew;
6456					}
6457					$w = $linew;
6458					$tmpcellpadding = $this->cell_padding;
6459					if ($maxh == 0) {
6460						$this->SetCellPadding(0);
6461					}
6462				}
6463				if ($firstblock AND $this->isRTLTextDir()) {
6464					$tmpstr = $this->stringRightTrim($tmpstr);
6465				}
6466				// Skip newlines at the beginning of a page or column
6467				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6468					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6469				}
6470				unset($tmpstr);
6471				if ($firstline) {
6472					$this->cell_padding = $tmpcellpadding;
6473					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6474				}
6475				++$nl;
6476				$j = $i + 1;
6477				$l = 0;
6478				$sep = -1;
6479				$prevsep = $sep;
6480				$shy = false;
6481				// account for margin changes
6482				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6483					$this->AcceptPageBreak();
6484					if ($this->rtl) {
6485						$this->x -= $margin['R'];
6486					} else {
6487						$this->x += $margin['L'];
6488					}
6489					$this->lMargin += $margin['L'];
6490					$this->rMargin += $margin['R'];
6491				}
6492				$w = $this->getRemainingWidth();
6493				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6494			} else {
6495				// 160 is the non-breaking space.
6496				// 173 is SHY (Soft Hypen).
6497				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6498				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6499				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6500				if (($c != 160)
6501					AND (($c == 173)
6502						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6503						OR (($c == 45)
6504							AND ($i < ($nb - 1))
6505							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6506							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6507						)
6508					)
6509				) {
6510					// update last blank space position
6511					$prevsep = $sep;
6512					$sep = $i;
6513					// check if is a SHY
6514					if (($c == 173) OR ($c == 45)) {
6515						$prevshy = $shy;
6516						$shy = true;
6517						if ($pc == 45) {
6518							$tmp_shy_replacement_width = 0;
6519							$tmp_shy_replacement_char = '';
6520						} else {
6521							$tmp_shy_replacement_width = $shy_replacement_width;
6522							$tmp_shy_replacement_char = $shy_replacement_char;
6523						}
6524					} else {
6525						$shy = false;
6526					}
6527				}
6528				// update string length
6529				if ($this->isUnicodeFont() AND ($arabic)) {
6530					// with bidirectional algorithm some chars may be changed affecting the line length
6531					// *** very slow ***
6532					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6533				} else {
6534					$l += $this->GetCharWidth($c, ($i+1 < $nb));
6535				}
6536				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6537					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6538						$sep = $prevsep;
6539						$shy = $prevshy;
6540					}
6541					// we have reached the end of column
6542					if ($sep == -1) {
6543						// check if the line was already started
6544						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6545							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6546							// print a void cell and go to next line
6547							$this->Cell($w, $h, '', 0, 1);
6548							$linebreak = true;
6549							if ($firstline) {
6550								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6551							}
6552						} else {
6553							// truncate the word because do not fit on column
6554							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6555							if ($firstline) {
6556								$startx = $this->x;
6557								$tmparr = array_slice($chars, $j, ($i - $j));
6558								if ($rtlmode) {
6559									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6560								}
6561								$linew = $this->GetArrStringWidth($tmparr);
6562								unset($tmparr);
6563								if ($this->rtl) {
6564									$this->endlinex = $startx - $linew;
6565								} else {
6566									$this->endlinex = $startx + $linew;
6567								}
6568								$w = $linew;
6569								$tmpcellpadding = $this->cell_padding;
6570								if ($maxh == 0) {
6571									$this->SetCellPadding(0);
6572								}
6573							}
6574							if ($firstblock AND $this->isRTLTextDir()) {
6575								$tmpstr = $this->stringRightTrim($tmpstr);
6576							}
6577							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6578							unset($tmpstr);
6579							if ($firstline) {
6580								$this->cell_padding = $tmpcellpadding;
6581								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6582							}
6583							$j = $i;
6584							--$i;
6585						}
6586					} else {
6587						// word wrapping
6588						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6589							$endspace = 1;
6590						} else {
6591							$endspace = 0;
6592						}
6593						// check the length of the next string
6594						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6595						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6596						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6597							// truncate the word because do not fit on a full page width
6598							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6599							if ($firstline) {
6600								$startx = $this->x;
6601								$tmparr = array_slice($chars, $j, ($i - $j));
6602								if ($rtlmode) {
6603									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6604								}
6605								$linew = $this->GetArrStringWidth($tmparr);
6606								unset($tmparr);
6607								if ($this->rtl) {
6608									$this->endlinex = ($startx - $linew);
6609								} else {
6610									$this->endlinex = ($startx + $linew);
6611								}
6612								$w = $linew;
6613								$tmpcellpadding = $this->cell_padding;
6614								if ($maxh == 0) {
6615									$this->SetCellPadding(0);
6616								}
6617							}
6618							if ($firstblock AND $this->isRTLTextDir()) {
6619								$tmpstr = $this->stringRightTrim($tmpstr);
6620							}
6621							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6622							unset($tmpstr);
6623							if ($firstline) {
6624								$this->cell_padding = $tmpcellpadding;
6625								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6626							}
6627							$j = $i;
6628							--$i;
6629						} else {
6630							// word wrapping
6631							if ($shy) {
6632								// add hypen (minus symbol) at the end of the line
6633								$shy_width = $tmp_shy_replacement_width;
6634								if ($this->rtl) {
6635									$shy_char_left = $tmp_shy_replacement_char;
6636									$shy_char_right = '';
6637								} else {
6638									$shy_char_left = '';
6639									$shy_char_right = $tmp_shy_replacement_char;
6640								}
6641							} else {
6642								$shy_width = 0;
6643								$shy_char_left = '';
6644								$shy_char_right = '';
6645							}
6646							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6647							if ($firstline) {
6648								$startx = $this->x;
6649								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6650								if ($rtlmode) {
6651									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6652								}
6653								$linew = $this->GetArrStringWidth($tmparr);
6654								unset($tmparr);
6655								if ($this->rtl) {
6656									$this->endlinex = $startx - $linew - $shy_width;
6657								} else {
6658									$this->endlinex = $startx + $linew + $shy_width;
6659								}
6660								$w = $linew;
6661								$tmpcellpadding = $this->cell_padding;
6662								if ($maxh == 0) {
6663									$this->SetCellPadding(0);
6664								}
6665							}
6666							// print the line
6667							if ($firstblock AND $this->isRTLTextDir()) {
6668								$tmpstr = $this->stringRightTrim($tmpstr);
6669							}
6670							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6671							unset($tmpstr);
6672							if ($firstline) {
6673								if ($chars[$sep] == 45) {
6674									$endspace += 1;
6675								}
6676								// return the remaining text
6677								$this->cell_padding = $tmpcellpadding;
6678								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6679							}
6680							$i = $sep;
6681							$sep = -1;
6682							$shy = false;
6683							$j = ($i + 1);
6684						}
6685					}
6686					// account for margin changes
6687					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6688						$this->AcceptPageBreak();
6689						if ($this->rtl) {
6690							$this->x -= $margin['R'];
6691						} else {
6692							$this->x += $margin['L'];
6693						}
6694						$this->lMargin += $margin['L'];
6695						$this->rMargin += $margin['R'];
6696					}
6697					$w = $this->getRemainingWidth();
6698					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6699					if ($linebreak) {
6700						$linebreak = false;
6701					} else {
6702						++$nl;
6703						$l = 0;
6704					}
6705				}
6706			}
6707			// save last character
6708			$pc = $c;
6709			++$i;
6710		} // end while i < nb
6711		// print last substring (if any)
6712		if ($l > 0) {
6713			switch ($align) {
6714				case 'J':
6715				case 'C': {
6716					$w = $w;
6717					break;
6718				}
6719				case 'L': {
6720					if ($this->rtl) {
6721						$w = $w;
6722					} else {
6723						$w = $l;
6724					}
6725					break;
6726				}
6727				case 'R': {
6728					if ($this->rtl) {
6729						$w = $l;
6730					} else {
6731						$w = $w;
6732					}
6733					break;
6734				}
6735				default: {
6736					$w = $l;
6737					break;
6738				}
6739			}
6740			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6741			if ($firstline) {
6742				$startx = $this->x;
6743				$tmparr = array_slice($chars, $j, ($nb - $j));
6744				if ($rtlmode) {
6745					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6746				}
6747				$linew = $this->GetArrStringWidth($tmparr);
6748				unset($tmparr);
6749				if ($this->rtl) {
6750					$this->endlinex = $startx - $linew;
6751				} else {
6752					$this->endlinex = $startx + $linew;
6753				}
6754				$w = $linew;
6755				$tmpcellpadding = $this->cell_padding;
6756				if ($maxh == 0) {
6757					$this->SetCellPadding(0);
6758				}
6759			}
6760			if ($firstblock AND $this->isRTLTextDir()) {
6761				$tmpstr = $this->stringRightTrim($tmpstr);
6762			}
6763			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6764			unset($tmpstr);
6765			if ($firstline) {
6766				$this->cell_padding = $tmpcellpadding;
6767				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6768			}
6769			++$nl;
6770		}
6771		if ($firstline) {
6772			return '';
6773		}
6774		return $nl;
6775	}
6776
6777	/**
6778	 * Returns the remaining width between the current position and margins.
6779	 * @return int Return the remaining width
6780	 * @protected
6781	 */
6782	protected function getRemainingWidth() {
6783		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6784		if ($this->rtl) {
6785			return ($this->x - $this->lMargin);
6786		} else {
6787			return ($this->w - $this->rMargin - $this->x);
6788		}
6789	}
6790
6791	/**
6792	 * Set the block dimensions accounting for page breaks and page/column fitting
6793	 * @param $w (float) width
6794	 * @param $h (float) height
6795	 * @param $x (float) X coordinate
6796	 * @param $y (float) Y coodiante
6797	 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6798	 * @return array($w, $h, $x, $y)
6799	 * @protected
6800	 * @since 5.5.009 (2010-07-05)
6801	 */
6802	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6803		if ($w <= 0) {
6804			// set maximum width
6805			$w = ($this->w - $this->lMargin - $this->rMargin);
6806			if ($w <= 0) {
6807				$w = 1;
6808			}
6809		}
6810		if ($h <= 0) {
6811			// set maximum height
6812			$h = ($this->PageBreakTrigger - $this->tMargin);
6813			if ($h <= 0) {
6814				$h = 1;
6815			}
6816		}
6817		// resize the block to be vertically contained on a single page or single column
6818		if ($fitonpage OR $this->AutoPageBreak) {
6819			$ratio_wh = ($w / $h);
6820			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6821				$h = $this->PageBreakTrigger - $this->tMargin;
6822				$w = ($h * $ratio_wh);
6823			}
6824			// resize the block to be horizontally contained on a single page or single column
6825			if ($fitonpage) {
6826				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6827				if ($w > $maxw) {
6828					$w = $maxw;
6829					$h = ($w / $ratio_wh);
6830				}
6831			}
6832		}
6833		// Check whether we need a new page or new column first as this does not fit
6834		$prev_x = $this->x;
6835		$prev_y = $this->y;
6836		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6837			$y = $this->y;
6838			if ($this->rtl) {
6839				$x += ($prev_x - $this->x);
6840			} else {
6841				$x += ($this->x - $prev_x);
6842			}
6843			$this->newline = true;
6844		}
6845		// resize the block to be contained on the remaining available page or column space
6846		if ($fitonpage) {
6847			$ratio_wh = ($w / $h);
6848			if (($y + $h) > $this->PageBreakTrigger) {
6849				$h = $this->PageBreakTrigger - $y;
6850				$w = ($h * $ratio_wh);
6851			}
6852			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6853				$w = $this->w - $this->rMargin - $x;
6854				$h = ($w / $ratio_wh);
6855			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6856				$w = $x - $this->lMargin;
6857				$h = ($w / $ratio_wh);
6858			}
6859		}
6860		return array($w, $h, $x, $y);
6861	}
6862
6863	/**
6864	 * Puts an image in the page.
6865	 * The upper-left corner must be given.
6866	 * The dimensions can be specified in different ways:<ul>
6867	 * <li>explicit width and height (expressed in user unit)</li>
6868	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6869	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6870	 * 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;
6871	 * The format can be specified explicitly or inferred from the file extension.<br />
6872	 * It is possible to put a link on the image.<br />
6873	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6874	 * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6875	 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6876	 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6877	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6878	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6879	 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6880	 * @param $link (mixed) URL or identifier returned by AddLink().
6881	 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6882	 * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6883	 * @param $dpi (int) dot-per-inch resolution used on resize
6884	 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6885	 * @param $ismask (boolean) true if this image is a mask, false otherwise
6886	 * @param $imgmask (mixed) image object returned by this function or false
6887	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6888	 * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6889	 * @param $hidden (boolean) If true do not display the image.
6890	 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6891	 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6892	 * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6893	 * @return image information
6894	 * @public
6895	 * @since 1.1
6896	 */
6897	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()) {
6898		if ($this->state != 2) {
6899			return;
6900		}
6901		if (strcmp($x, '') === 0) {
6902			$x = $this->x;
6903		}
6904		if (strcmp($y, '') === 0) {
6905			$y = $this->y;
6906		}
6907		// check page for no-write regions and adapt page margins if necessary
6908		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6909		$exurl = ''; // external streams
6910		$imsize = FALSE;
6911
6912        // Make sure the file variable is not empty or null because accessing $file[0] later
6913        // results in error when running PHP 7.4
6914        if (empty($file)) {
6915            return false;
6916        }
6917		// check if we are passing an image as file or string
6918		if ($file[0] === '@') {
6919			// image from string
6920			$imgdata = substr($file, 1);
6921		} else { // image file
6922			if ($file[0] === '*') {
6923				// image as external stream
6924				$file = substr($file, 1);
6925				$exurl = $file;
6926			}
6927			// check if file exist and it is valid
6928			if (!@$this->fileExists($file)) {
6929				return false;
6930			}
6931            if (false !== $info = $this->getImageBuffer($file)) {
6932                $imsize = array($info['w'], $info['h']);
6933            } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
6934                $imgdata = $this->getCachedFileContents($file);
6935            }
6936		}
6937		if (!empty($imgdata)) {
6938			// copy image to cache
6939			$original_file = $file;
6940			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6941			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6942			if (!$fp) {
6943				$this->Error('Unable to write file: '.$file);
6944			}
6945			fwrite($fp, $imgdata);
6946			fclose($fp);
6947			unset($imgdata);
6948			$imsize = @getimagesize($file);
6949			if ($imsize === FALSE) {
6950				unlink($file);
6951				$file = $original_file;
6952			}
6953		}
6954		if ($imsize === FALSE) {
6955			if (($w > 0) AND ($h > 0)) {
6956				// get measures from specified data
6957				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6958				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6959				$imsize = array($pw, $ph);
6960			} else {
6961				$this->Error('[Image] Unable to get the size of the image: '.$file);
6962			}
6963		}
6964		// file hash
6965		$filehash = md5($file);
6966		// get original image width and height in pixels
6967		list($pixw, $pixh) = $imsize;
6968		// calculate image width and height on document
6969		if (($w <= 0) AND ($h <= 0)) {
6970			// convert image size to document unit
6971			$w = $this->pixelsToUnits($pixw);
6972			$h = $this->pixelsToUnits($pixh);
6973		} elseif ($w <= 0) {
6974			$w = $h * $pixw / $pixh;
6975		} elseif ($h <= 0) {
6976			$h = $w * $pixh / $pixw;
6977		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6978			if (strlen($fitbox) !== 2) {
6979				// set default alignment
6980				$fitbox = '--';
6981			}
6982			// scale image dimensions proportionally to fit within the ($w, $h) box
6983			if ((($w * $pixh) / ($h * $pixw)) < 1) {
6984				// store current height
6985				$oldh = $h;
6986				// calculate new height
6987				$h = $w * $pixh / $pixw;
6988				// height difference
6989				$hdiff = ($oldh - $h);
6990				// vertical alignment
6991				switch (strtoupper($fitbox[1])) {
6992					case 'T': {
6993						break;
6994					}
6995					case 'M': {
6996						$y += ($hdiff / 2);
6997						break;
6998					}
6999					case 'B': {
7000						$y += $hdiff;
7001						break;
7002					}
7003				}
7004			} else {
7005				// store current width
7006				$oldw = $w;
7007				// calculate new width
7008				$w = $h * $pixw / $pixh;
7009				// width difference
7010				$wdiff = ($oldw - $w);
7011				// horizontal alignment
7012				switch (strtoupper($fitbox[0])) {
7013					case 'L': {
7014						if ($this->rtl) {
7015							$x -= $wdiff;
7016						}
7017						break;
7018					}
7019					case 'C': {
7020						if ($this->rtl) {
7021							$x -= ($wdiff / 2);
7022						} else {
7023							$x += ($wdiff / 2);
7024						}
7025						break;
7026					}
7027					case 'R': {
7028						if (!$this->rtl) {
7029							$x += $wdiff;
7030						}
7031						break;
7032					}
7033				}
7034			}
7035		}
7036		// fit the image on available space
7037		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7038		// calculate new minimum dimensions in pixels
7039		$neww = round($w * $this->k * $dpi / $this->dpi);
7040		$newh = round($h * $this->k * $dpi / $this->dpi);
7041		// check if resize is necessary (resize is used only to reduce the image)
7042		$newsize = ($neww * $newh);
7043		$pixsize = ($pixw * $pixh);
7044		if (intval($resize) == 2) {
7045			$resize = true;
7046		} elseif ($newsize >= $pixsize) {
7047			$resize = false;
7048		}
7049		// check if image has been already added on document
7050		$newimage = true;
7051		if (in_array($file, $this->imagekeys)) {
7052			$newimage = false;
7053			// get existing image data
7054			$info = $this->getImageBuffer($file);
7055			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7056				// check if the newer image is larger
7057				$oldsize = ($info['w'] * $info['h']);
7058				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7059					$newimage = true;
7060				}
7061			}
7062		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7063			// create temp image file (without alpha channel)
7064			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7065			// create temp alpha file
7066			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7067			// check for cached images
7068			if (in_array($tempfile_plain, $this->imagekeys)) {
7069				// get existing image data
7070				$info = $this->getImageBuffer($tempfile_plain);
7071				// check if the newer image is larger
7072				$oldsize = ($info['w'] * $info['h']);
7073				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7074					$newimage = true;
7075				} else {
7076					$newimage = false;
7077					// embed mask image
7078					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7079					// embed image, masked with previously embedded mask
7080					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7081				}
7082			}
7083		}
7084		if ($newimage) {
7085			//First use of image, get info
7086			$type = strtolower($type);
7087			if ($type == '') {
7088				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7089			} elseif ($type == 'jpg') {
7090				$type = 'jpeg';
7091			}
7092			$mqr = TCPDF_STATIC::get_mqr();
7093			TCPDF_STATIC::set_mqr(false);
7094			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7095			$mtd = '_parse'.$type;
7096			// GD image handler function
7097			$gdfunction = 'imagecreatefrom'.$type;
7098			$info = false;
7099			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7100				// TCPDF image functions
7101				$info = TCPDF_IMAGES::$mtd($file);
7102				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7103					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7104					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7105				}
7106			}
7107			if (($info === false) AND function_exists($gdfunction)) {
7108				try {
7109					// GD library
7110					$img = $gdfunction($file);
7111					if ($img !== false) {
7112						if ($resize) {
7113							$imgr = imagecreatetruecolor($neww, $newh);
7114							if (($type == 'gif') OR ($type == 'png')) {
7115								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7116							}
7117							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7118							$img = $imgr;
7119						}
7120						if (($type == 'gif') OR ($type == 'png')) {
7121							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7122						} else {
7123							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7124						}
7125					}
7126				} catch(Exception $e) {
7127					$info = false;
7128				}
7129			}
7130			if (($info === false) AND extension_loaded('imagick')) {
7131				try {
7132					// ImageMagick library
7133					$img = new Imagick();
7134					if ($type == 'svg') {
7135						if ($file[0] === '@') {
7136							// image from string
7137							$svgimg = substr($file, 1);
7138						} else {
7139							// get SVG file content
7140                            $svgimg = $this->getCachedFileContents($file);
7141						}
7142						if ($svgimg !== FALSE) {
7143							// get width and height
7144							$regs = array();
7145							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7146								$svgtag = $regs[1];
7147								$tmp = array();
7148								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7149									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7150									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7151									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7152								} else {
7153									$ow = $w;
7154								}
7155								$tmp = array();
7156								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7157									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7158									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7159									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7160								} else {
7161									$oh = $h;
7162								}
7163								$tmp = array();
7164								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7165									$vbw = ($ow * $this->imgscale * $this->k);
7166									$vbh = ($oh * $this->imgscale * $this->k);
7167									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7168									$svgtag = $vbox.$svgtag;
7169								}
7170								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7171							}
7172							$img->readImageBlob($svgimg);
7173						}
7174					} else {
7175						$img->readImage($file);
7176					}
7177					if ($resize) {
7178						$img->resizeImage($neww, $newh, 10, 1, false);
7179					}
7180					$img->setCompressionQuality($this->jpeg_quality);
7181					$img->setImageFormat('jpeg');
7182					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7183					$img->writeImage($tempname);
7184					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7185					unlink($tempname);
7186					$img->destroy();
7187				} catch(Exception $e) {
7188					$info = false;
7189				}
7190			}
7191			if ($info === false) {
7192				// unable to process image
7193				return;
7194			}
7195			TCPDF_STATIC::set_mqr($mqr);
7196			if ($ismask) {
7197				// force grayscale
7198				$info['cs'] = 'DeviceGray';
7199			}
7200			if ($imgmask !== false) {
7201				$info['masked'] = $imgmask;
7202			}
7203			if (!empty($exurl)) {
7204				$info['exurl'] = $exurl;
7205			}
7206			// array of alternative images
7207			$info['altimgs'] = $altimgs;
7208			// add image to document
7209			$info['i'] = $this->setImageBuffer($file, $info);
7210		}
7211		// set alignment
7212		$this->img_rb_x = $x + $w;
7213		$this->img_rb_y = $y + $h;
7214
7215		// set alignment
7216		if ($palign == 'L') {
7217			$ximg = $this->lMargin;
7218		} elseif ($palign == 'C') {
7219			$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7220		} elseif ($palign == 'R') {
7221			$ximg = $this->w - $this->rMargin - $w;
7222		} else {
7223			$ximg = $x;
7224		}
7225
7226		if ($ismask OR $hidden) {
7227			// image is not displayed
7228			return $info['i'];
7229		}
7230		$xkimg = $ximg * $this->k;
7231		if (!$alt) {
7232			// only non-alternative immages will be set
7233			$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']));
7234		}
7235		if (!empty($border)) {
7236			$bx = $this->x;
7237			$by = $this->y;
7238			$this->x = $ximg;
7239			if ($this->rtl) {
7240				$this->x += $w;
7241			}
7242			$this->y = $y;
7243			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7244			$this->x = $bx;
7245			$this->y = $by;
7246		}
7247		if ($link) {
7248			$this->Link($ximg, $y, $w, $h, $link, 0);
7249		}
7250		// set pointer to align the next text/objects
7251		switch($align) {
7252			case 'T': {
7253				$this->y = $y;
7254				$this->x = $this->img_rb_x;
7255				break;
7256			}
7257			case 'M': {
7258				$this->y = $y + round($h/2);
7259				$this->x = $this->img_rb_x;
7260				break;
7261			}
7262			case 'B': {
7263				$this->y = $this->img_rb_y;
7264				$this->x = $this->img_rb_x;
7265				break;
7266			}
7267			case 'N': {
7268				$this->SetY($this->img_rb_y);
7269				break;
7270			}
7271			default:{
7272				break;
7273			}
7274		}
7275		$this->endlinex = $this->img_rb_x;
7276		if ($this->inxobj) {
7277			// we are inside an XObject template
7278			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7279		}
7280		return $info['i'];
7281	}
7282
7283	/**
7284	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7285	 * @param $file (string) Name of the file containing the image.
7286	 * @param $x (float) Abscissa of the upper-left corner.
7287	 * @param $y (float) Ordinate of the upper-left corner.
7288	 * @param $wpx (float) Original width of the image in pixels.
7289	 * @param $hpx (float) original height of the image in pixels.
7290	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7291	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7292	 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7293	 * @param $link (mixed) URL or identifier returned by AddLink().
7294	 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7295	 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7296	 * @param $dpi (int) dot-per-inch resolution used on resize
7297	 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7298	 * @param $filehash (string) File hash used to build unique file names.
7299	 * @author Nicola Asuni
7300	 * @protected
7301	 * @since 4.3.007 (2008-12-04)
7302	 * @see Image()
7303	 */
7304	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7305		// create temp images
7306		if (empty($filehash)) {
7307			$filehash = md5($file);
7308		}
7309		// create temp image file (without alpha channel)
7310		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7311		// create temp alpha file
7312		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7313		$parsed = false;
7314		$parse_error = '';
7315		// ImageMagick extension
7316		if (($parsed === false) AND extension_loaded('imagick')) {
7317			try {
7318				// ImageMagick library
7319				$img = new Imagick();
7320				$img->readImage($file);
7321				// clone image object
7322				$imga = TCPDF_STATIC::objclone($img);
7323				// extract alpha channel
7324				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7325					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7326				} else {
7327					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7328					$img->negateImage(true);
7329				}
7330				$img->setImageFormat('png');
7331				$img->writeImage($tempfile_alpha);
7332				// remove alpha channel
7333				if (method_exists($imga, 'setImageMatte')) {
7334					$imga->setImageMatte(false);
7335				} else {
7336					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7337				}
7338				$imga->setImageFormat('png');
7339				$imga->writeImage($tempfile_plain);
7340				$parsed = true;
7341			} catch (Exception $e) {
7342				// Imagemagick fails, try with GD
7343				$parse_error = 'Imagick library error: '.$e->getMessage();
7344			}
7345		}
7346		// GD extension
7347		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7348			try {
7349				// generate images
7350				$img = imagecreatefrompng($file);
7351				$imgalpha = imagecreate($wpx, $hpx);
7352				// generate gray scale palette (0 -> 255)
7353				for ($c = 0; $c < 256; ++$c) {
7354					ImageColorAllocate($imgalpha, $c, $c, $c);
7355				}
7356				// extract alpha channel
7357				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7358					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7359						$color = imagecolorat($img, $xpx, $ypx);
7360						// get and correct gamma color
7361						$alpha = $this->getGDgamma($img, $color);
7362						imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7363					}
7364				}
7365				imagepng($imgalpha, $tempfile_alpha);
7366				imagedestroy($imgalpha);
7367				// extract image without alpha channel
7368				$imgplain = imagecreatetruecolor($wpx, $hpx);
7369				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7370				imagepng($imgplain, $tempfile_plain);
7371				imagedestroy($imgplain);
7372				$parsed = true;
7373			} catch (Exception $e) {
7374				// GD fails
7375				$parse_error = 'GD library error: '.$e->getMessage();
7376			}
7377		}
7378		if ($parsed === false) {
7379			if (empty($parse_error)) {
7380				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7381			} else {
7382				$this->Error($parse_error);
7383			}
7384		}
7385		// embed mask image
7386		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7387		// embed image, masked with previously embedded mask
7388		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7389	}
7390
7391	/**
7392	 * Get the GD-corrected PNG gamma value from alpha color
7393	 * @param $img (int) GD image Resource ID.
7394	 * @param $c (int) alpha color
7395	 * @protected
7396	 * @since 4.3.007 (2008-12-04)
7397	 */
7398	protected function getGDgamma($img, $c) {
7399		if (!isset($this->gdgammacache['#'.$c])) {
7400			$colors = imagecolorsforindex($img, $c);
7401			// GD alpha is only 7 bit (0 -> 127)
7402			$this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7403			// correct gamma
7404			$this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7405			// store the latest values on cache to improve performances
7406			if (count($this->gdgammacache) > 8) {
7407				// remove one element from the cache array
7408				array_shift($this->gdgammacache);
7409			}
7410		}
7411		return $this->gdgammacache['#'.$c];
7412	}
7413
7414	/**
7415	 * Performs a line break.
7416	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7417	 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7418	 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7419	 * @public
7420	 * @since 1.0
7421	 * @see Cell()
7422	 */
7423	public function Ln($h='', $cell=false) {
7424		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'])) {
7425			// revove vertical space from the top of the column
7426			return;
7427		}
7428		if ($cell) {
7429			if ($this->rtl) {
7430				$cellpadding = $this->cell_padding['R'];
7431			} else {
7432				$cellpadding = $this->cell_padding['L'];
7433			}
7434		} else {
7435			$cellpadding = 0;
7436		}
7437		if ($this->rtl) {
7438			$this->x = $this->w - $this->rMargin - $cellpadding;
7439		} else {
7440			$this->x = $this->lMargin + $cellpadding;
7441		}
7442		if (is_string($h)) {
7443			$h = $this->lasth;
7444		}
7445		$this->y += $h;
7446		$this->newline = true;
7447	}
7448
7449	/**
7450	 * Returns the relative X value of current position.
7451	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7452	 * @return float
7453	 * @public
7454	 * @since 1.2
7455	 * @see SetX(), GetY(), SetY()
7456	 */
7457	public function GetX() {
7458		//Get x position
7459		if ($this->rtl) {
7460			return ($this->w - $this->x);
7461		} else {
7462			return $this->x;
7463		}
7464	}
7465
7466	/**
7467	 * Returns the absolute X value of current position.
7468	 * @return float
7469	 * @public
7470	 * @since 1.2
7471	 * @see SetX(), GetY(), SetY()
7472	 */
7473	public function GetAbsX() {
7474		return $this->x;
7475	}
7476
7477	/**
7478	 * Returns the ordinate of the current position.
7479	 * @return float
7480	 * @public
7481	 * @since 1.0
7482	 * @see SetY(), GetX(), SetX()
7483	 */
7484	public function GetY() {
7485		return $this->y;
7486	}
7487
7488	/**
7489	 * Defines the abscissa of the current position.
7490	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7491	 * @param $x (float) The value of the abscissa in user units.
7492	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7493	 * @public
7494	 * @since 1.2
7495	 * @see GetX(), GetY(), SetY(), SetXY()
7496	 */
7497	public function SetX($x, $rtloff=false) {
7498		$x = floatval($x);
7499		if (!$rtloff AND $this->rtl) {
7500			if ($x >= 0) {
7501				$this->x = $this->w - $x;
7502			} else {
7503				$this->x = abs($x);
7504			}
7505		} else {
7506			if ($x >= 0) {
7507				$this->x = $x;
7508			} else {
7509				$this->x = $this->w + $x;
7510			}
7511		}
7512		if ($this->x < 0) {
7513			$this->x = 0;
7514		}
7515		if ($this->x > $this->w) {
7516			$this->x = $this->w;
7517		}
7518	}
7519
7520	/**
7521	 * Moves the current abscissa back to the left margin and sets the ordinate.
7522	 * If the passed value is negative, it is relative to the bottom of the page.
7523	 * @param $y (float) The value of the ordinate in user units.
7524	 * @param $resetx (bool) if true (default) reset the X position.
7525	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7526	 * @public
7527	 * @since 1.0
7528	 * @see GetX(), GetY(), SetY(), SetXY()
7529	 */
7530	public function SetY($y, $resetx=true, $rtloff=false) {
7531		$y = floatval($y);
7532		if ($resetx) {
7533			//reset x
7534			if (!$rtloff AND $this->rtl) {
7535				$this->x = $this->w - $this->rMargin;
7536			} else {
7537				$this->x = $this->lMargin;
7538			}
7539		}
7540		if ($y >= 0) {
7541			$this->y = $y;
7542		} else {
7543			$this->y = $this->h + $y;
7544		}
7545		if ($this->y < 0) {
7546			$this->y = 0;
7547		}
7548		if ($this->y > $this->h) {
7549			$this->y = $this->h;
7550		}
7551	}
7552
7553	/**
7554	 * Defines the abscissa and ordinate of the current position.
7555	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7556	 * @param $x (float) The value of the abscissa.
7557	 * @param $y (float) The value of the ordinate.
7558	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7559	 * @public
7560	 * @since 1.2
7561	 * @see SetX(), SetY()
7562	 */
7563	public function SetXY($x, $y, $rtloff=false) {
7564		$this->SetY($y, false, $rtloff);
7565		$this->SetX($x, $rtloff);
7566	}
7567
7568	/**
7569	 * Set the absolute X coordinate of the current pointer.
7570	 * @param $x (float) The value of the abscissa in user units.
7571	 * @public
7572	 * @since 5.9.186 (2012-09-13)
7573	 * @see setAbsX(), setAbsY(), SetAbsXY()
7574	 */
7575	public function SetAbsX($x) {
7576		$this->x = floatval($x);
7577	}
7578
7579	/**
7580	 * Set the absolute Y coordinate of the current pointer.
7581	 * @param $y (float) (float) The value of the ordinate in user units.
7582	 * @public
7583	 * @since 5.9.186 (2012-09-13)
7584	 * @see setAbsX(), setAbsY(), SetAbsXY()
7585	 */
7586	public function SetAbsY($y) {
7587		$this->y = floatval($y);
7588	}
7589
7590	/**
7591	 * Set the absolute X and Y coordinates of the current pointer.
7592	 * @param $x (float) The value of the abscissa in user units.
7593	 * @param $y (float) (float) The value of the ordinate in user units.
7594	 * @public
7595	 * @since 5.9.186 (2012-09-13)
7596	 * @see setAbsX(), setAbsY(), SetAbsXY()
7597	 */
7598	public function SetAbsXY($x, $y) {
7599		$this->SetAbsX($x);
7600		$this->SetAbsY($y);
7601	}
7602
7603	/**
7604	 * Send the document to a given destination: string, local file or browser.
7605	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7606	 * The method first calls Close() if necessary to terminate the document.
7607	 * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
7608	 * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7609	 * @return string
7610	 * @public
7611	 * @since 1.0
7612	 * @see Close()
7613	 */
7614	public function Output($name='doc.pdf', $dest='I') {
7615		//Output PDF to some destination
7616		//Finish document if necessary
7617		if ($this->state < 3) {
7618			$this->Close();
7619		}
7620		//Normalize parameters
7621		if (is_bool($dest)) {
7622			$dest = $dest ? 'D' : 'F';
7623		}
7624		$dest = strtoupper($dest);
7625		if ($dest[0] != 'F') {
7626			$name = preg_replace('/[\s]+/', '_', $name);
7627			$name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7628		}
7629		if ($this->sign) {
7630			// *** apply digital signature to the document ***
7631			// get the document content
7632			$pdfdoc = $this->getBuffer();
7633			// remove last newline
7634			$pdfdoc = substr($pdfdoc, 0, -1);
7635			// remove filler space
7636			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7637			// define the ByteRange
7638			$byte_range = array();
7639			$byte_range[0] = 0;
7640			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7641			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7642			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7643			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7644			// replace the ByteRange
7645			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7646			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7647			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7648			// write the document to a temporary folder
7649			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7650			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7651			if (!$f) {
7652				$this->Error('Unable to create temporary file: '.$tempdoc);
7653			}
7654			$pdfdoc_length = strlen($pdfdoc);
7655			fwrite($f, $pdfdoc, $pdfdoc_length);
7656			fclose($f);
7657			// get digital signature via openssl library
7658			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7659			if (empty($this->signature_data['extracerts'])) {
7660				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7661			} else {
7662				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']);
7663			}
7664			// read signature
7665			$signature = file_get_contents($tempsign);
7666			// extract signature
7667			$signature = substr($signature, $pdfdoc_length);
7668			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7669			$tmparr = explode("\n\n", $signature);
7670			$signature = $tmparr[1];
7671			// decode signature
7672			$signature = base64_decode(trim($signature));
7673			// add TSA timestamp to signature
7674			$signature = $this->applyTSA($signature);
7675			// convert signature to hex
7676			$signature = current(unpack('H*', $signature));
7677			$signature = str_pad($signature, $this->signature_max_length, '0');
7678			// Add signature to the document
7679			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7680			$this->bufferlen = strlen($this->buffer);
7681		}
7682		switch($dest) {
7683			case 'I': {
7684				// Send PDF to the standard output
7685				if (ob_get_contents()) {
7686					$this->Error('Some data has already been output, can\'t send PDF file');
7687				}
7688				if (php_sapi_name() != 'cli') {
7689					// send output to a browser
7690					header('Content-Type: application/pdf');
7691					if (headers_sent()) {
7692						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7693					}
7694					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7695					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7696					header('Pragma: public');
7697					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7698					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7699					header('Content-Disposition: inline; filename="'.basename($name).'"');
7700					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7701				} else {
7702					echo $this->getBuffer();
7703				}
7704				break;
7705			}
7706			case 'D': {
7707				// download PDF as file
7708				if (ob_get_contents()) {
7709					$this->Error('Some data has already been output, can\'t send PDF file');
7710				}
7711				header('Content-Description: File Transfer');
7712				if (headers_sent()) {
7713					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7714				}
7715				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7716				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7717				header('Pragma: public');
7718				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7719				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7720				// force download dialog
7721				if (strpos(php_sapi_name(), 'cgi') === false) {
7722					header('Content-Type: application/force-download');
7723					header('Content-Type: application/octet-stream', false);
7724					header('Content-Type: application/download', false);
7725					header('Content-Type: application/pdf', false);
7726				} else {
7727					header('Content-Type: application/pdf');
7728				}
7729				// use the Content-Disposition header to supply a recommended filename
7730				header('Content-Disposition: attachment; filename="'.basename($name).'"');
7731				header('Content-Transfer-Encoding: binary');
7732				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7733				break;
7734			}
7735			case 'F':
7736			case 'FI':
7737			case 'FD': {
7738				// save PDF to a local file
7739				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7740				if (!$f) {
7741					$this->Error('Unable to create output file: '.$name);
7742				}
7743				fwrite($f, $this->getBuffer(), $this->bufferlen);
7744				fclose($f);
7745				if ($dest == 'FI') {
7746					// send headers to browser
7747					header('Content-Type: application/pdf');
7748					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7749					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7750					header('Pragma: public');
7751					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7752					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7753					header('Content-Disposition: inline; filename="'.basename($name).'"');
7754					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7755				} elseif ($dest == 'FD') {
7756					// send headers to browser
7757					if (ob_get_contents()) {
7758						$this->Error('Some data has already been output, can\'t send PDF file');
7759					}
7760					header('Content-Description: File Transfer');
7761					if (headers_sent()) {
7762						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7763					}
7764					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7765					header('Pragma: public');
7766					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7767					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7768					// force download dialog
7769					if (strpos(php_sapi_name(), 'cgi') === false) {
7770						header('Content-Type: application/force-download');
7771						header('Content-Type: application/octet-stream', false);
7772						header('Content-Type: application/download', false);
7773						header('Content-Type: application/pdf', false);
7774					} else {
7775						header('Content-Type: application/pdf');
7776					}
7777					// use the Content-Disposition header to supply a recommended filename
7778					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7779					header('Content-Transfer-Encoding: binary');
7780					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7781				}
7782				break;
7783			}
7784			case 'E': {
7785				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7786				$retval = 'Content-Type: application/pdf;'."\r\n";
7787				$retval .= ' name="'.$name.'"'."\r\n";
7788				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7789				$retval .= 'Content-Disposition: attachment;'."\r\n";
7790				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7791				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7792				return $retval;
7793			}
7794			case 'S': {
7795				// returns PDF as a string
7796				return $this->getBuffer();
7797			}
7798			default: {
7799				$this->Error('Incorrect output destination: '.$dest);
7800			}
7801		}
7802		return '';
7803	}
7804
7805	protected static $cleaned_ids = array();
7806	/**
7807	 * Unset all class variables except the following critical variables.
7808	 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7809	 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7810	 * @public
7811	 * @since 4.5.016 (2009-02-24)
7812	 */
7813	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7814		if (isset(self::$cleaned_ids[$this->file_id])) {
7815			$destroyall = false;
7816		}
7817		if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7818			self::$cleaned_ids[$this->file_id] = true;
7819			// remove all temporary files
7820			if ($handle = @opendir(K_PATH_CACHE)) {
7821				while ( false !== ( $file_name = readdir( $handle ) ) ) {
7822					if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7823						unlink(K_PATH_CACHE.$file_name);
7824					}
7825				}
7826				closedir($handle);
7827			}
7828			if (isset($this->imagekeys)) {
7829				foreach($this->imagekeys as $file) {
7830					if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
7831						@unlink($file);
7832					}
7833				}
7834			}
7835		}
7836		$preserve = array(
7837			'file_id',
7838			'state',
7839			'bufferlen',
7840			'buffer',
7841			'cached_files',
7842			'imagekeys',
7843			'sign',
7844			'signature_data',
7845			'signature_max_length',
7846			'byterange_string',
7847			'tsa_timestamp',
7848			'tsa_data'
7849		);
7850		foreach (array_keys(get_object_vars($this)) as $val) {
7851			if ($destroyall OR !in_array($val, $preserve)) {
7852				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7853					unset($this->$val);
7854				}
7855			}
7856		}
7857	}
7858
7859	/**
7860	 * Check for locale-related bug
7861	 * @protected
7862	 */
7863	protected function _dochecks() {
7864		//Check for locale-related bug
7865		if (1.1 == 1) {
7866			$this->Error('Don\'t alter the locale before including class file');
7867		}
7868		//Check for decimal separator
7869		if (sprintf('%.1F', 1.0) != '1.0') {
7870			setlocale(LC_NUMERIC, 'C');
7871		}
7872	}
7873
7874	/**
7875	 * Return an array containing variations for the basic page number alias.
7876	 * @param $a (string) Base alias.
7877	 * @return array of page number aliases
7878	 * @protected
7879	 */
7880	protected function getInternalPageNumberAliases($a= '') {
7881		$alias = array();
7882		// build array of Unicode + ASCII variants (the order is important)
7883		$alias = array('u' => array(), 'a' => array());
7884		$u = '{'.$a.'}';
7885		$alias['u'][] = TCPDF_STATIC::_escape($u);
7886		if ($this->isunicode) {
7887			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7888			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7889			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7890			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7891		}
7892		$alias['a'][] = TCPDF_STATIC::_escape($a);
7893		return $alias;
7894	}
7895
7896	/**
7897	 * Return an array containing all internal page aliases.
7898	 * @return array of page number aliases
7899	 * @protected
7900	 */
7901	protected function getAllInternalPageNumberAliases() {
7902		$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);
7903		$pnalias = array();
7904		foreach($basic_alias as $k => $a) {
7905			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7906		}
7907		return $pnalias;
7908	}
7909
7910	/**
7911	 * Replace right shift page number aliases with spaces to correct right alignment.
7912	 * This works perfectly only when using monospaced fonts.
7913	 * @param $page (string) Page content.
7914	 * @param $aliases (array) Array of page aliases.
7915	 * @param $diff (int) initial difference to add.
7916	 * @return replaced page content.
7917	 * @protected
7918	 */
7919	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7920		foreach ($aliases as $type => $alias) {
7921			foreach ($alias as $a) {
7922				// find position of compensation factor
7923				$startnum = (strpos($a, ':') + 1);
7924				$a = substr($a, 0, $startnum);
7925				if (($pos = strpos($page, $a)) !== false) {
7926					// end of alias
7927					$endnum = strpos($page, '}', $pos);
7928					// string to be replaced
7929					$aa = substr($page, $pos, ($endnum - $pos + 1));
7930					// get compensation factor
7931					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7932					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7933					$ratio = floatval($ratio);
7934					if ($type == 'u') {
7935						$chrdiff = floor(($diff + 12) * $ratio);
7936						$shift = str_repeat(' ', $chrdiff);
7937						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7938					} else {
7939						$chrdiff = floor(($diff + 11) * $ratio);
7940						$shift = str_repeat(' ', $chrdiff);
7941					}
7942					$page = str_replace($aa, $shift, $page);
7943				}
7944			}
7945		}
7946		return $page;
7947	}
7948
7949	/**
7950	 * Set page boxes to be included on page descriptions.
7951	 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7952	 * @protected
7953	 */
7954	protected function setPageBoxTypes($boxes) {
7955		$this->page_boxes = array();
7956		foreach ($boxes as $box) {
7957			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7958				$this->page_boxes[] = $box;
7959			}
7960		}
7961	}
7962
7963	/**
7964	 * Output pages (and replace page number aliases).
7965	 * @protected
7966	 */
7967	protected function _putpages() {
7968		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7969		// get internal aliases for page numbers
7970		$pnalias = $this->getAllInternalPageNumberAliases();
7971		$num_pages = $this->numpages;
7972		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7973		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7974		$ptp_num_chars = $this->GetNumChars($ptpa);
7975		$pagegroupnum = 0;
7976		$groupnum = 0;
7977		$ptgu = 1;
7978		$ptga = 1;
7979		$ptg_num_chars = 1;
7980		for ($n = 1; $n <= $num_pages; ++$n) {
7981			// get current page
7982			$temppage = $this->getPageBuffer($n);
7983			$pagelen = strlen($temppage);
7984			// set replacements for total pages number
7985			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7986			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7987			$pnp_num_chars = $this->GetNumChars($pnpa);
7988			$pdiff = 0; // difference used for right shift alignment of page numbers
7989			$gdiff = 0; // difference used for right shift alignment of page group numbers
7990			if (!empty($this->pagegroups)) {
7991				if (isset($this->newpagegroup[$n])) {
7992					$pagegroupnum = 0;
7993					++$groupnum;
7994					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7995					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7996					$ptg_num_chars = $this->GetNumChars($ptga);
7997				}
7998				++$pagegroupnum;
7999				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
8000				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
8001				$png_num_chars = $this->GetNumChars($pnga);
8002				// replace page numbers
8003				$replace = array();
8004				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8005				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8006				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8007				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8008				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8009			}
8010			// replace page numbers
8011			$replace = array();
8012			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8013			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8014			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8015			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8016			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8017			// replace right shift alias
8018			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8019			// replace EPS marker
8020			$temppage = str_replace($this->epsmarker, '', $temppage);
8021			//Page
8022			$this->page_obj_id[$n] = $this->_newobj();
8023			$out = '<<';
8024			$out .= ' /Type /Page';
8025			$out .= ' /Parent 1 0 R';
8026			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8027				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8028			}
8029			$out .= ' /Resources 2 0 R';
8030			foreach ($this->page_boxes as $box) {
8031				$out .= ' /'.$box;
8032				$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']);
8033			}
8034			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8035				$out .= ' /BoxColorInfo <<';
8036				foreach ($this->page_boxes as $box) {
8037					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8038						$out .= ' /'.$box.' <<';
8039						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8040							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8041							$out .= ' /C [';
8042							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8043							$out .= ' ]';
8044						}
8045						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8046							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8047						}
8048						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8049							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8050						}
8051						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8052							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8053							$out .= ' /D [';
8054							foreach ($dashes as $dash) {
8055								$out .= sprintf(' %F', ($dash * $this->k));
8056							}
8057							$out .= ' ]';
8058						}
8059						$out .= ' >>';
8060					}
8061				}
8062				$out .= ' >>';
8063			}
8064			$out .= ' /Contents '.($this->n + 1).' 0 R';
8065			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8066			if (!$this->pdfa_mode) {
8067				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8068			}
8069			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8070				// page transitions
8071				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8072					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8073				}
8074				$out .= ' /Trans <<';
8075				$out .= ' /Type /Trans';
8076				if (isset($this->pagedim[$n]['trans']['S'])) {
8077					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8078				}
8079				if (isset($this->pagedim[$n]['trans']['D'])) {
8080					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8081				}
8082				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8083					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8084				}
8085				if (isset($this->pagedim[$n]['trans']['M'])) {
8086					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8087				}
8088				if (isset($this->pagedim[$n]['trans']['Di'])) {
8089					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8090				}
8091				if (isset($this->pagedim[$n]['trans']['SS'])) {
8092					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8093				}
8094				if (isset($this->pagedim[$n]['trans']['B'])) {
8095					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8096				}
8097				$out .= ' >>';
8098			}
8099			$out .= $this->_getannotsrefs($n);
8100			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8101			$out .= ' >>';
8102			$out .= "\n".'endobj';
8103			$this->_out($out);
8104			//Page content
8105			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8106			$this->_newobj();
8107			$p = $this->_getrawstream($p);
8108			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8109		}
8110		//Pages root
8111		$out = $this->_getobj(1)."\n";
8112		$out .= '<< /Type /Pages /Kids [';
8113		foreach($this->page_obj_id as $page_obj) {
8114			$out .= ' '.$page_obj.' 0 R';
8115		}
8116		$out .= ' ] /Count '.$num_pages.' >>';
8117		$out .= "\n".'endobj';
8118		$this->_out($out);
8119	}
8120
8121	/**
8122	 * Get references to page annotations.
8123	 * @param $n (int) page number
8124	 * @return string
8125	 * @protected
8126	 * @author Nicola Asuni
8127	 * @since 5.0.010 (2010-05-17)
8128	 */
8129	protected function _getannotsrefs($n) {
8130		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8131			return '';
8132		}
8133		$out = ' /Annots [';
8134		if (isset($this->PageAnnots[$n])) {
8135			foreach ($this->PageAnnots[$n] as $key => $val) {
8136				if (!in_array($val['n'], $this->radio_groups)) {
8137					$out .= ' '.$val['n'].' 0 R';
8138				}
8139			}
8140			// add radiobutton groups
8141			if (isset($this->radiobutton_groups[$n])) {
8142				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8143					if (isset($data['n'])) {
8144						$out .= ' '.$data['n'].' 0 R';
8145					}
8146				}
8147			}
8148		}
8149		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8150			// set reference for signature object
8151			$out .= ' '.$this->sig_obj_id.' 0 R';
8152		}
8153		if (!empty($this->empty_signature_appearance)) {
8154			foreach ($this->empty_signature_appearance as $esa) {
8155				if ($esa['page'] == $n) {
8156					// set reference for empty signature objects
8157					$out .= ' '.$esa['objid'].' 0 R';
8158				}
8159			}
8160		}
8161		$out .= ' ]';
8162		return $out;
8163	}
8164
8165	/**
8166	 * Output annotations objects for all pages.
8167	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8168	 * See section 12.5 of PDF 32000_2008 reference.
8169	 * @protected
8170	 * @author Nicola Asuni
8171	 * @since 4.0.018 (2008-08-06)
8172	 */
8173	protected function _putannotsobjs() {
8174		// reset object counter
8175		for ($n=1; $n <= $this->numpages; ++$n) {
8176			if (isset($this->PageAnnots[$n])) {
8177				// set page annotations
8178				foreach ($this->PageAnnots[$n] as $key => $pl) {
8179					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8180					// create annotation object for grouping radiobuttons
8181					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8182						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8183						$annots = '<<';
8184						$annots .= ' /Type /Annot';
8185						$annots .= ' /Subtype /Widget';
8186						$annots .= ' /Rect [0 0 0 0]';
8187						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8188							// read only
8189							$annots .= ' /F 68';
8190							$annots .= ' /Ff 49153';
8191						} else {
8192							$annots .= ' /F 4'; // default print for PDF/A
8193							$annots .= ' /Ff 49152';
8194						}
8195						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8196						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8197							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8198						}
8199						$annots .= ' /FT /Btn';
8200						$annots .= ' /Kids [';
8201						$defval = '';
8202						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8203							if (isset($data['kid'])) {
8204								$annots .= ' '.$data['kid'].' 0 R';
8205								if ($data['def'] !== 'Off') {
8206									$defval = $data['def'];
8207								}
8208							}
8209						}
8210						$annots .= ' ]';
8211						if (!empty($defval)) {
8212							$annots .= ' /V /'.$defval;
8213						}
8214						$annots .= ' >>';
8215						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8216						$this->form_obj_id[] = $radio_button_obj_id;
8217						// store object id to be used on Parent entry of Kids
8218						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8219					}
8220					$formfield = false;
8221					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8222					$a = $pl['x'] * $this->k;
8223					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8224					$c = $pl['w'] * $this->k;
8225					$d = $pl['h'] * $this->k;
8226					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8227					// create new annotation object
8228					$annots = '<</Type /Annot';
8229					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8230					$annots .= ' /Rect ['.$rect.']';
8231					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8232					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8233						$annots .= ' /FT /'.$pl['opt']['ft'];
8234						$formfield = true;
8235					}
8236					if ($pl['opt']['subtype'] !== 'Link') {
8237						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8238					}
8239					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8240					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8241					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8242					if (isset($pl['opt']['f'])) {
8243						$fval = 0;
8244						if (is_array($pl['opt']['f'])) {
8245							foreach ($pl['opt']['f'] as $f) {
8246								switch (strtolower($f)) {
8247									case 'invisible': {
8248										$fval += 1 << 0;
8249										break;
8250									}
8251									case 'hidden': {
8252										$fval += 1 << 1;
8253										break;
8254									}
8255									case 'print': {
8256										$fval += 1 << 2;
8257										break;
8258									}
8259									case 'nozoom': {
8260										$fval += 1 << 3;
8261										break;
8262									}
8263									case 'norotate': {
8264										$fval += 1 << 4;
8265										break;
8266									}
8267									case 'noview': {
8268										$fval += 1 << 5;
8269										break;
8270									}
8271									case 'readonly': {
8272										$fval += 1 << 6;
8273										break;
8274									}
8275									case 'locked': {
8276										$fval += 1 << 8;
8277										break;
8278									}
8279									case 'togglenoview': {
8280										$fval += 1 << 9;
8281										break;
8282									}
8283									case 'lockedcontents': {
8284										$fval += 1 << 10;
8285										break;
8286									}
8287									default: {
8288										break;
8289									}
8290								}
8291							}
8292						} else {
8293							$fval = intval($pl['opt']['f']);
8294						}
8295					} else {
8296						$fval = 4;
8297					}
8298					if ($this->pdfa_mode) {
8299						// force print flag for PDF/A mode
8300						$fval |= 4;
8301					}
8302					$annots .= ' /F '.intval($fval);
8303					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8304						$annots .= ' /AS /'.$pl['opt']['as'];
8305					}
8306					if (isset($pl['opt']['ap'])) {
8307						// appearance stream
8308						$annots .= ' /AP <<';
8309						if (is_array($pl['opt']['ap'])) {
8310							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8311								// $apmode can be: n = normal; r = rollover; d = down;
8312								$annots .= ' /'.strtoupper($apmode);
8313								if (is_array($apdef)) {
8314									$annots .= ' <<';
8315									foreach ($apdef as $apstate => $stream) {
8316										// reference to XObject that define the appearance for this mode-state
8317										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8318										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8319									}
8320									$annots .= ' >>';
8321								} else {
8322									// reference to XObject that define the appearance for this mode
8323									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8324									$annots .= ' '.$apsobjid.' 0 R';
8325								}
8326							}
8327						} else {
8328							$annots .= $pl['opt']['ap'];
8329						}
8330						$annots .= ' >>';
8331					}
8332					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8333						$annots .= ' /BS <<';
8334						$annots .= ' /Type /Border';
8335						if (isset($pl['opt']['bs']['w'])) {
8336							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8337						}
8338						$bstyles = array('S', 'D', 'B', 'I', 'U');
8339						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8340							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8341						}
8342						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8343							$annots .= ' /D [';
8344							foreach ($pl['opt']['bs']['d'] as $cord) {
8345								$annots .= ' '.intval($cord);
8346							}
8347							$annots .= ']';
8348						}
8349						$annots .= ' >>';
8350					} else {
8351						$annots .= ' /Border [';
8352						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8353							$annots .= intval($pl['opt']['border'][0]).' ';
8354							$annots .= intval($pl['opt']['border'][1]).' ';
8355							$annots .= intval($pl['opt']['border'][2]);
8356							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8357								$annots .= ' [';
8358								foreach ($pl['opt']['border'][3] as $dash) {
8359									$annots .= intval($dash).' ';
8360								}
8361								$annots .= ']';
8362							}
8363						} else {
8364							$annots .= '0 0 0';
8365						}
8366						$annots .= ']';
8367					}
8368					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8369						$annots .= ' /BE <<';
8370						$bstyles = array('S', 'C');
8371						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8372							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8373						} else {
8374							$annots .= ' /S /S';
8375						}
8376						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8377							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8378						}
8379						$annots .= '>>';
8380					}
8381					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8382						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8383					}
8384					//$annots .= ' /StructParent ';
8385					//$annots .= ' /OC ';
8386					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8387					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8388						// this is a markup type
8389						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8390							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8391						}
8392						//$annots .= ' /Popup ';
8393						if (isset($pl['opt']['ca'])) {
8394							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8395						}
8396						if (isset($pl['opt']['rc'])) {
8397							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8398						}
8399						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8400						//$annots .= ' /IRT ';
8401						if (isset($pl['opt']['subj'])) {
8402							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8403						}
8404						//$annots .= ' /RT ';
8405						//$annots .= ' /IT ';
8406						//$annots .= ' /ExData ';
8407					}
8408					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8409					// Annotation types
8410					switch (strtolower($pl['opt']['subtype'])) {
8411						case 'text': {
8412							if (isset($pl['opt']['open'])) {
8413								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8414							}
8415							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8416							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8417								$annots .= ' /Name /'.$pl['opt']['name'];
8418							} else {
8419								$annots .= ' /Name /Note';
8420							}
8421							$statemodels = array('Marked', 'Review');
8422							if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8423								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8424							} else {
8425								$pl['opt']['statemodel'] = 'Marked';
8426								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8427							}
8428							if ($pl['opt']['statemodel'] == 'Marked') {
8429								$states = array('Accepted', 'Unmarked');
8430							} else {
8431								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8432							}
8433							if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8434								$annots .= ' /State /'.$pl['opt']['state'];
8435							} else {
8436								if ($pl['opt']['statemodel'] == 'Marked') {
8437									$annots .= ' /State /Unmarked';
8438								} else {
8439									$annots .= ' /State /None';
8440								}
8441							}
8442							break;
8443						}
8444						case 'link': {
8445							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8446								if ($pl['txt'][0] == '#') {
8447									// internal destination
8448									$annots .= ' /A <</S /GoTo /D '.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8449								} elseif ($pl['txt'][0] == '%') {
8450									// embedded PDF file
8451									$filename = basename(substr($pl['txt'], 1));
8452									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8453								} elseif ($pl['txt'][0] == '*') {
8454									// embedded generic file
8455									$filename = basename(substr($pl['txt'], 1));
8456									$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});';
8457									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8458								} else {
8459									$parsedUrl = parse_url($pl['txt']);
8460									if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8461										// relative link to a PDF file
8462										$dest = '[0 /Fit]'; // default page 0
8463										if (!empty($parsedUrl['fragment'])) {
8464											// check for named destination
8465											$tmp = explode('=', $parsedUrl['fragment']);
8466											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8467										}
8468										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8469									} else {
8470										// external URI link
8471										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8472									}
8473								}
8474							} elseif (isset($this->links[$pl['txt']])) {
8475								// internal link ID
8476								$l = $this->links[$pl['txt']];
8477								if (isset($this->page_obj_id[($l['p'])])) {
8478									$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)));
8479								}
8480							}
8481							$hmodes = array('N', 'I', 'O', 'P');
8482							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8483								$annots .= ' /H /'.$pl['opt']['h'];
8484							} else {
8485								$annots .= ' /H /I';
8486							}
8487							//$annots .= ' /PA ';
8488							//$annots .= ' /Quadpoints ';
8489							break;
8490						}
8491						case 'freetext': {
8492							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8493								$annots .= ' /DA ('.$pl['opt']['da'].')';
8494							}
8495							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8496								$annots .= ' /Q '.intval($pl['opt']['q']);
8497							}
8498							if (isset($pl['opt']['rc'])) {
8499								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8500							}
8501							if (isset($pl['opt']['ds'])) {
8502								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8503							}
8504							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8505								$annots .= ' /CL [';
8506								foreach ($pl['opt']['cl'] as $cl) {
8507									$annots .= sprintf('%F ', $cl * $this->k);
8508								}
8509								$annots .= ']';
8510							}
8511							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8512							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8513								$annots .= ' /IT /'.$pl['opt']['it'];
8514							}
8515							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8516								$l = $pl['opt']['rd'][0] * $this->k;
8517								$r = $pl['opt']['rd'][1] * $this->k;
8518								$t = $pl['opt']['rd'][2] * $this->k;
8519								$b = $pl['opt']['rd'][3] * $this->k;
8520								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8521							}
8522							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8523								$annots .= ' /LE /'.$pl['opt']['le'];
8524							}
8525							break;
8526						}
8527						case 'line': {
8528							break;
8529						}
8530						case 'square': {
8531							break;
8532						}
8533						case 'circle': {
8534							break;
8535						}
8536						case 'polygon': {
8537							break;
8538						}
8539						case 'polyline': {
8540							break;
8541						}
8542						case 'highlight': {
8543							break;
8544						}
8545						case 'underline': {
8546							break;
8547						}
8548						case 'squiggly': {
8549							break;
8550						}
8551						case 'strikeout': {
8552							break;
8553						}
8554						case 'stamp': {
8555							break;
8556						}
8557						case 'caret': {
8558							break;
8559						}
8560						case 'ink': {
8561							break;
8562						}
8563						case 'popup': {
8564							break;
8565						}
8566						case 'fileattachment': {
8567							if ($this->pdfa_mode && $this->pdfa_version != 3) {
8568								// embedded files are not allowed in PDF/A mode version 1 and 2
8569								break;
8570							}
8571							if (!isset($pl['opt']['fs'])) {
8572								break;
8573							}
8574							$filename = basename($pl['opt']['fs']);
8575							if (isset($this->embeddedfiles[$filename]['f'])) {
8576								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8577								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8578								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8579									$annots .= ' /Name /'.$pl['opt']['name'];
8580								} else {
8581									$annots .= ' /Name /PushPin';
8582								}
8583								// index (zero-based) of the annotation in the Annots array of this page
8584								$this->embeddedfiles[$filename]['a'] = $key;
8585							}
8586							break;
8587						}
8588						case 'sound': {
8589							if (!isset($pl['opt']['fs'])) {
8590								break;
8591							}
8592							$filename = basename($pl['opt']['fs']);
8593							if (isset($this->embeddedfiles[$filename]['f'])) {
8594								// ... TO BE COMPLETED ...
8595								// /R /C /B /E /CO /CP
8596								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8597								$iconsapp = array('Speaker', 'Mic');
8598								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8599									$annots .= ' /Name /'.$pl['opt']['name'];
8600								} else {
8601									$annots .= ' /Name /Speaker';
8602								}
8603							}
8604							break;
8605						}
8606						case 'movie': {
8607							break;
8608						}
8609						case 'widget': {
8610							$hmode = array('N', 'I', 'O', 'P', 'T');
8611							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8612								$annots .= ' /H /'.$pl['opt']['h'];
8613							}
8614							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8615								$annots .= ' /MK <<';
8616								if (isset($pl['opt']['mk']['r'])) {
8617									$annots .= ' /R '.$pl['opt']['mk']['r'];
8618								}
8619								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8620									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8621								}
8622								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8623									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8624								}
8625								if (isset($pl['opt']['mk']['ca'])) {
8626									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8627								}
8628								if (isset($pl['opt']['mk']['rc'])) {
8629									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8630								}
8631								if (isset($pl['opt']['mk']['ac'])) {
8632									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8633								}
8634								if (isset($pl['opt']['mk']['i'])) {
8635									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8636									if ($info !== false) {
8637										$annots .= ' /I '.$info['n'].' 0 R';
8638									}
8639								}
8640								if (isset($pl['opt']['mk']['ri'])) {
8641									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8642									if ($info !== false) {
8643										$annots .= ' /RI '.$info['n'].' 0 R';
8644									}
8645								}
8646								if (isset($pl['opt']['mk']['ix'])) {
8647									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8648									if ($info !== false) {
8649										$annots .= ' /IX '.$info['n'].' 0 R';
8650									}
8651								}
8652								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8653									$annots .= ' /IF <<';
8654									$if_sw = array('A', 'B', 'S', 'N');
8655									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8656										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8657									}
8658									$if_s = array('A', 'P');
8659									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8660										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8661									}
8662									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8663										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8664									}
8665									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8666										$annots .= ' /FB true';
8667									}
8668									$annots .= '>>';
8669								}
8670								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8671									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8672								}
8673								$annots .= '>>';
8674							} // end MK
8675							// --- Entries for field dictionaries ---
8676							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8677								// set parent
8678								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8679							}
8680							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8681								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8682							}
8683							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8684								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8685							}
8686							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8687								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8688							}
8689							if (isset($pl['opt']['ff'])) {
8690								if (is_array($pl['opt']['ff'])) {
8691									// array of bit settings
8692									$flag = 0;
8693									foreach($pl['opt']['ff'] as $val) {
8694										$flag += 1 << ($val - 1);
8695									}
8696								} else {
8697									$flag = intval($pl['opt']['ff']);
8698								}
8699								$annots .= ' /Ff '.$flag;
8700							}
8701							if (isset($pl['opt']['maxlen'])) {
8702								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8703							}
8704							if (isset($pl['opt']['v'])) {
8705								$annots .= ' /V';
8706								if (is_array($pl['opt']['v'])) {
8707									foreach ($pl['opt']['v'] AS $optval) {
8708										if (is_float($optval)) {
8709											$optval = sprintf('%F', $optval);
8710										}
8711										$annots .= ' '.$optval;
8712									}
8713								} else {
8714									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8715								}
8716							}
8717							if (isset($pl['opt']['dv'])) {
8718								$annots .= ' /DV';
8719								if (is_array($pl['opt']['dv'])) {
8720									foreach ($pl['opt']['dv'] AS $optval) {
8721										if (is_float($optval)) {
8722											$optval = sprintf('%F', $optval);
8723										}
8724										$annots .= ' '.$optval;
8725									}
8726								} else {
8727									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8728								}
8729							}
8730							if (isset($pl['opt']['rv'])) {
8731								$annots .= ' /RV';
8732								if (is_array($pl['opt']['rv'])) {
8733									foreach ($pl['opt']['rv'] AS $optval) {
8734										if (is_float($optval)) {
8735											$optval = sprintf('%F', $optval);
8736										}
8737										$annots .= ' '.$optval;
8738									}
8739								} else {
8740									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8741								}
8742							}
8743							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8744								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8745							}
8746							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8747								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8748							}
8749							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8750								$annots .= ' /DA ('.$pl['opt']['da'].')';
8751							}
8752							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8753								$annots .= ' /Q '.intval($pl['opt']['q']);
8754							}
8755							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8756								$annots .= ' /Opt [';
8757								foreach($pl['opt']['opt'] AS $copt) {
8758									if (is_array($copt)) {
8759										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8760									} else {
8761										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8762									}
8763								}
8764								$annots .= ']';
8765							}
8766							if (isset($pl['opt']['ti'])) {
8767								$annots .= ' /TI '.intval($pl['opt']['ti']);
8768							}
8769							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8770								$annots .= ' /I [';
8771								foreach($pl['opt']['i'] AS $copt) {
8772									$annots .= intval($copt).' ';
8773								}
8774								$annots .= ']';
8775							}
8776							break;
8777						}
8778						case 'screen': {
8779							break;
8780						}
8781						case 'printermark': {
8782							break;
8783						}
8784						case 'trapnet': {
8785							break;
8786						}
8787						case 'watermark': {
8788							break;
8789						}
8790						case '3d': {
8791							break;
8792						}
8793						default: {
8794							break;
8795						}
8796					}
8797					$annots .= '>>';
8798					// create new annotation object
8799					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8800					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8801						// store reference of form object
8802						$this->form_obj_id[] = $annot_obj_id;
8803					}
8804				}
8805			}
8806		} // end for each page
8807	}
8808
8809	/**
8810	 * Put appearance streams XObject used to define annotation's appearance states.
8811	 * @param $w (int) annotation width
8812	 * @param $h (int) annotation height
8813	 * @param $stream (string) appearance stream
8814	 * @return int object ID
8815	 * @protected
8816	 * @since 4.8.001 (2009-09-09)
8817	 */
8818	protected function _putAPXObject($w=0, $h=0, $stream='') {
8819		$stream = trim($stream);
8820		$out = $this->_getobj()."\n";
8821		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8822		$out .= '<<';
8823		$out .= ' /Type /XObject';
8824		$out .= ' /Subtype /Form';
8825		$out .= ' /FormType 1';
8826		if ($this->compress) {
8827			$stream = gzcompress($stream);
8828			$out .= ' /Filter /FlateDecode';
8829		}
8830		$rect = sprintf('%F %F', $w, $h);
8831		$out .= ' /BBox [0 0 '.$rect.']';
8832		$out .= ' /Matrix [1 0 0 1 0 0]';
8833		$out .= ' /Resources 2 0 R';
8834		$stream = $this->_getrawstream($stream);
8835		$out .= ' /Length '.strlen($stream);
8836		$out .= ' >>';
8837		$out .= ' stream'."\n".$stream."\n".'endstream';
8838		$out .= "\n".'endobj';
8839		$this->_out($out);
8840		return $this->n;
8841	}
8842
8843	/**
8844	 * Output fonts.
8845	 * @author Nicola Asuni
8846	 * @protected
8847	 */
8848	protected function _putfonts() {
8849		$nf = $this->n;
8850		foreach ($this->diffs as $diff) {
8851			//Encodings
8852			$this->_newobj();
8853			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8854		}
8855		$mqr = TCPDF_STATIC::get_mqr();
8856		TCPDF_STATIC::set_mqr(false);
8857		foreach ($this->FontFiles as $file => $info) {
8858			// search and get font file to embedd
8859			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8860			if (!TCPDF_STATIC::empty_string($fontfile)) {
8861				$font = file_get_contents($fontfile);
8862				$compressed = (substr($file, -2) == '.z');
8863				if ((!$compressed) AND (isset($info['length2']))) {
8864					$header = (ord($font[0]) == 128);
8865					if ($header) {
8866						// strip first binary header
8867						$font = substr($font, 6);
8868					}
8869					if ($header AND (ord($font[$info['length1']]) == 128)) {
8870						// strip second binary header
8871						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8872					}
8873				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8874					if ($compressed) {
8875						// uncompress font
8876						$font = gzuncompress($font);
8877					}
8878					// merge subset characters
8879					$subsetchars = array(); // used chars
8880					foreach ($info['fontkeys'] as $fontkey) {
8881						$fontinfo = $this->getFontBuffer($fontkey);
8882						$subsetchars += $fontinfo['subsetchars'];
8883					}
8884					// rebuild a font subset
8885					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8886					// calculate new font length
8887					$info['length1'] = strlen($font);
8888					if ($compressed) {
8889						// recompress font
8890						$font = gzcompress($font);
8891					}
8892				}
8893				$this->_newobj();
8894				$this->FontFiles[$file]['n'] = $this->n;
8895				$stream = $this->_getrawstream($font);
8896				$out = '<< /Length '.strlen($stream);
8897				if ($compressed) {
8898					$out .= ' /Filter /FlateDecode';
8899				}
8900				$out .= ' /Length1 '.$info['length1'];
8901				if (isset($info['length2'])) {
8902					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8903				}
8904				$out .= ' >>';
8905				$out .= ' stream'."\n".$stream."\n".'endstream';
8906				$out .= "\n".'endobj';
8907				$this->_out($out);
8908			}
8909		}
8910		TCPDF_STATIC::set_mqr($mqr);
8911		foreach ($this->fontkeys as $k) {
8912			//Font objects
8913			$font = $this->getFontBuffer($k);
8914			$type = $font['type'];
8915			$name = $font['name'];
8916			if ($type == 'core') {
8917				// standard core font
8918				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8919				$out .= '<</Type /Font';
8920				$out .= ' /Subtype /Type1';
8921				$out .= ' /BaseFont /'.$name;
8922				$out .= ' /Name /F'.$font['i'];
8923				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8924					$out .= ' /Encoding /WinAnsiEncoding';
8925				}
8926				if ($k == 'helvetica') {
8927					// add default font for annotations
8928					$this->annotation_fonts[$k] = $font['i'];
8929				}
8930				$out .= ' >>';
8931				$out .= "\n".'endobj';
8932				$this->_out($out);
8933			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8934				// additional Type1 or TrueType font
8935				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8936				$out .= '<</Type /Font';
8937				$out .= ' /Subtype /'.$type;
8938				$out .= ' /BaseFont /'.$name;
8939				$out .= ' /Name /F'.$font['i'];
8940				$out .= ' /FirstChar 32 /LastChar 255';
8941				$out .= ' /Widths '.($this->n + 1).' 0 R';
8942				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8943				if ($font['enc']) {
8944					if (isset($font['diff'])) {
8945						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8946					} else {
8947						$out .= ' /Encoding /WinAnsiEncoding';
8948					}
8949				}
8950				$out .= ' >>';
8951				$out .= "\n".'endobj';
8952				$this->_out($out);
8953				// Widths
8954				$this->_newobj();
8955				$s = '[';
8956				for ($i = 32; $i < 256; ++$i) {
8957					if (isset($font['cw'][$i])) {
8958						$s .= $font['cw'][$i].' ';
8959					} else {
8960						$s .= $font['dw'].' ';
8961					}
8962				}
8963				$s .= ']';
8964				$s .= "\n".'endobj';
8965				$this->_out($s);
8966				//Descriptor
8967				$this->_newobj();
8968				$s = '<</Type /FontDescriptor /FontName /'.$name;
8969				foreach ($font['desc'] as $fdk => $fdv) {
8970					if (is_float($fdv)) {
8971						$fdv = sprintf('%F', $fdv);
8972					}
8973					$s .= ' /'.$fdk.' '.$fdv.'';
8974				}
8975				if (!TCPDF_STATIC::empty_string($font['file'])) {
8976					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8977				}
8978				$s .= '>>';
8979				$s .= "\n".'endobj';
8980				$this->_out($s);
8981			} else {
8982				// additional types
8983				$mtd = '_put'.strtolower($type);
8984				if (!method_exists($this, $mtd)) {
8985					$this->Error('Unsupported font type: '.$type);
8986				}
8987				$this->$mtd($font);
8988			}
8989		}
8990	}
8991
8992	/**
8993	 * Adds unicode fonts.<br>
8994	 * Based on PDF Reference 1.3 (section 5)
8995	 * @param $font (array) font data
8996	 * @protected
8997	 * @author Nicola Asuni
8998	 * @since 1.52.0.TC005 (2005-01-05)
8999	 */
9000	protected function _puttruetypeunicode($font) {
9001		$fontname = '';
9002		if ($font['subset']) {
9003			// change name for font subsetting
9004			$subtag = sprintf('%06u', $font['i']);
9005			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9006			$fontname .= $subtag.'+';
9007		}
9008		$fontname .= $font['name'];
9009		// Type0 Font
9010		// A composite font composed of other fonts, organized hierarchically
9011		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9012		$out .= '<< /Type /Font';
9013		$out .= ' /Subtype /Type0';
9014		$out .= ' /BaseFont /'.$fontname;
9015		$out .= ' /Name /F'.$font['i'];
9016		$out .= ' /Encoding /'.$font['enc'];
9017		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9018		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9019		$out .= ' >>';
9020		$out .= "\n".'endobj';
9021		$this->_out($out);
9022		// ToUnicode map for Identity-H
9023		$stream = TCPDF_FONT_DATA::$uni_identity_h;
9024		// ToUnicode Object
9025		$this->_newobj();
9026		$stream = ($this->compress) ? gzcompress($stream) : $stream;
9027		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9028		$stream = $this->_getrawstream($stream);
9029		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9030		// CIDFontType2
9031		// A CIDFont whose glyph descriptions are based on TrueType font technology
9032		$oid = $this->_newobj();
9033		$out = '<< /Type /Font';
9034		$out .= ' /Subtype /CIDFontType2';
9035		$out .= ' /BaseFont /'.$fontname;
9036		// A dictionary containing entries that define the character collection of the CIDFont.
9037		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9038		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9039		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9040		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9041		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9042		$out .= ' /DW '.$font['dw']; // default width
9043		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9044		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9045			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9046		}
9047		$out .= ' >>';
9048		$out .= "\n".'endobj';
9049		$this->_out($out);
9050		// Font descriptor
9051		// A font descriptor describing the CIDFont default metrics other than its glyph widths
9052		$this->_newobj();
9053		$out = '<< /Type /FontDescriptor';
9054		$out .= ' /FontName /'.$fontname;
9055		foreach ($font['desc'] as $key => $value) {
9056			if (is_float($value)) {
9057				$value = sprintf('%F', $value);
9058			}
9059			$out .= ' /'.$key.' '.$value;
9060		}
9061		$fontdir = false;
9062		if (!TCPDF_STATIC::empty_string($font['file'])) {
9063			// A stream containing a TrueType font
9064			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9065			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
9066		}
9067		$out .= ' >>';
9068		$out .= "\n".'endobj';
9069		$this->_out($out);
9070		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9071			$this->_newobj();
9072			// Embed CIDToGIDMap
9073			// A specification of the mapping from CIDs to glyph indices
9074			// search and get CTG font file to embedd
9075			$ctgfile = strtolower($font['ctg']);
9076			// search and get ctg font file to embedd
9077			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9078			if (TCPDF_STATIC::empty_string($fontfile)) {
9079				$this->Error('Font file not found: '.$ctgfile);
9080			}
9081			$stream = $this->_getrawstream(file_get_contents($fontfile));
9082			$out = '<< /Length '.strlen($stream).'';
9083			if (substr($fontfile, -2) == '.z') { // check file extension
9084				// Decompresses data encoded using the public-domain
9085				// zlib/deflate compression method, reproducing the
9086				// original text or binary data
9087				$out .= ' /Filter /FlateDecode';
9088			}
9089			$out .= ' >>';
9090			$out .= ' stream'."\n".$stream."\n".'endstream';
9091			$out .= "\n".'endobj';
9092			$this->_out($out);
9093		}
9094	}
9095
9096	/**
9097	 * Output CID-0 fonts.
9098	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9099	 * @param $font (array) font data
9100	 * @protected
9101	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9102	 * @since 3.2.000 (2008-06-23)
9103	 */
9104	protected function _putcidfont0($font) {
9105		$cidoffset = 0;
9106		if (!isset($font['cw'][1])) {
9107			$cidoffset = 31;
9108		}
9109		if (isset($font['cidinfo']['uni2cid'])) {
9110			// convert unicode to cid.
9111			$uni2cid = $font['cidinfo']['uni2cid'];
9112			$cw = array();
9113			foreach ($font['cw'] as $uni => $width) {
9114				if (isset($uni2cid[$uni])) {
9115					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9116				} elseif ($uni < 256) {
9117					$cw[$uni] = $width;
9118				} // else unknown character
9119			}
9120			$font = array_merge($font, array('cw' => $cw));
9121		}
9122		$name = $font['name'];
9123		$enc = $font['enc'];
9124		if ($enc) {
9125			$longname = $name.'-'.$enc;
9126		} else {
9127			$longname = $name;
9128		}
9129		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9130		$out .= '<</Type /Font';
9131		$out .= ' /Subtype /Type0';
9132		$out .= ' /BaseFont /'.$longname;
9133		$out .= ' /Name /F'.$font['i'];
9134		if ($enc) {
9135			$out .= ' /Encoding /'.$enc;
9136		}
9137		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9138		$out .= ' >>';
9139		$out .= "\n".'endobj';
9140		$this->_out($out);
9141		$oid = $this->_newobj();
9142		$out = '<</Type /Font';
9143		$out .= ' /Subtype /CIDFontType0';
9144		$out .= ' /BaseFont /'.$name;
9145		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9146		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9147		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9148		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9149		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9150		$out .= ' /DW '.$font['dw'];
9151		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9152		$out .= ' >>';
9153		$out .= "\n".'endobj';
9154		$this->_out($out);
9155		$this->_newobj();
9156		$s = '<</Type /FontDescriptor /FontName /'.$name;
9157		foreach ($font['desc'] as $k => $v) {
9158			if ($k != 'Style') {
9159				if (is_float($v)) {
9160					$v = sprintf('%F', $v);
9161				}
9162				$s .= ' /'.$k.' '.$v.'';
9163			}
9164		}
9165		$s .= '>>';
9166		$s .= "\n".'endobj';
9167		$this->_out($s);
9168	}
9169
9170	/**
9171	 * Output images.
9172	 * @protected
9173	 */
9174	protected function _putimages() {
9175		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9176		foreach ($this->imagekeys as $file) {
9177			$info = $this->getImageBuffer($file);
9178			// set object for alternate images array
9179			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9180				$altoid = $this->_newobj();
9181				$out = '[';
9182				foreach ($info['altimgs'] as $altimage) {
9183					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9184						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9185						$out .= ' /DefaultForPrinting';
9186						if ($altimage[1] === true) {
9187							$out .= ' true';
9188						} else {
9189							$out .= ' false';
9190						}
9191						$out .= ' >>';
9192					}
9193				}
9194				$out .= ' ]';
9195				$out .= "\n".'endobj';
9196				$this->_out($out);
9197			}
9198			// set image object
9199			$oid = $this->_newobj();
9200			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9201			$this->setImageSubBuffer($file, 'n', $this->n);
9202			$out = '<</Type /XObject';
9203			$out .= ' /Subtype /Image';
9204			$out .= ' /Width '.$info['w'];
9205			$out .= ' /Height '.$info['h'];
9206			if (array_key_exists('masked', $info)) {
9207				$out .= ' /SMask '.($this->n - 1).' 0 R';
9208			}
9209			// set color space
9210			$icc = false;
9211			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9212				// ICC Colour Space
9213				$icc = true;
9214				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9215			} elseif ($info['cs'] == 'Indexed') {
9216				// Indexed Colour Space
9217				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9218			} else {
9219				// Device Colour Space
9220				$out .= ' /ColorSpace /'.$info['cs'];
9221			}
9222			if ($info['cs'] == 'DeviceCMYK') {
9223				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9224			}
9225			$out .= ' /BitsPerComponent '.$info['bpc'];
9226			if (isset($altoid) AND ($altoid > 0)) {
9227				// reference to alternate images dictionary
9228				$out .= ' /Alternates '.$altoid.' 0 R';
9229			}
9230			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9231				// external stream
9232				$out .= ' /Length 0';
9233				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9234				if (isset($info['f'])) {
9235					$out .= ' /FFilter /'.$info['f'];
9236				}
9237				$out .= ' >>';
9238				$out .= ' stream'."\n".'endstream';
9239			} else {
9240				if (isset($info['f'])) {
9241					$out .= ' /Filter /'.$info['f'];
9242				}
9243				if (isset($info['parms'])) {
9244					$out .= ' '.$info['parms'];
9245				}
9246				if (isset($info['trns']) AND is_array($info['trns'])) {
9247					$trns = '';
9248					$count_info = count($info['trns']);
9249					if ($info['cs'] == 'Indexed') {
9250						$maxval =(pow(2, $info['bpc']) - 1);
9251						for ($i = 0; $i < $count_info; ++$i) {
9252							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9253								// this is not a binary type mask @TODO: create a SMask
9254								$trns = '';
9255								break;
9256							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9257								// store the first fully transparent value
9258								$trns .= $i.' '.$i.' ';
9259							}
9260						}
9261					} else {
9262						// grayscale or RGB
9263						for ($i = 0; $i < $count_info; ++$i) {
9264							if ($info['trns'][$i] == 0) {
9265								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9266							}
9267						}
9268					}
9269					// Colour Key Masking
9270					if (!empty($trns)) {
9271						$out .= ' /Mask ['.$trns.']';
9272					}
9273				}
9274				$stream = $this->_getrawstream($info['data']);
9275				$out .= ' /Length '.strlen($stream).' >>';
9276				$out .= ' stream'."\n".$stream."\n".'endstream';
9277			}
9278			$out .= "\n".'endobj';
9279			$this->_out($out);
9280			if ($icc) {
9281				// ICC colour profile
9282				$this->_newobj();
9283				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9284				$icc = $this->_getrawstream($icc);
9285				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9286			} elseif ($info['cs'] == 'Indexed') {
9287				// colour palette
9288				$this->_newobj();
9289				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9290				$pal = $this->_getrawstream($pal);
9291				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9292			}
9293		}
9294	}
9295
9296	/**
9297	 * Output Form XObjects Templates.
9298	 * @author Nicola Asuni
9299	 * @since 5.8.017 (2010-08-24)
9300	 * @protected
9301	 * @see startTemplate(), endTemplate(), printTemplate()
9302	 */
9303	protected function _putxobjects() {
9304		foreach ($this->xobjects as $key => $data) {
9305			if (isset($data['outdata'])) {
9306				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9307				$out = $this->_getobj($data['n'])."\n";
9308				$out .= '<<';
9309				$out .= ' /Type /XObject';
9310				$out .= ' /Subtype /Form';
9311				$out .= ' /FormType 1';
9312				if ($this->compress) {
9313					$stream = gzcompress($stream);
9314					$out .= ' /Filter /FlateDecode';
9315				}
9316				$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));
9317				$out .= ' /Matrix [1 0 0 1 0 0]';
9318				$out .= ' /Resources <<';
9319				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9320				if (!$this->pdfa_mode) {
9321					// transparency
9322					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9323						$out .= ' /ExtGState <<';
9324						foreach ($data['extgstates'] as $k => $extgstate) {
9325							if (isset($this->extgstates[$k]['name'])) {
9326								$out .= ' /'.$this->extgstates[$k]['name'];
9327							} else {
9328								$out .= ' /GS'.$k;
9329							}
9330							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9331						}
9332						$out .= ' >>';
9333					}
9334					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9335						$gp = '';
9336						$gs = '';
9337						foreach ($data['gradients'] as $id => $grad) {
9338							// gradient patterns
9339							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9340							// gradient shadings
9341							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9342						}
9343						$out .= ' /Pattern <<'.$gp.' >>';
9344						$out .= ' /Shading <<'.$gs.' >>';
9345					}
9346				}
9347				// spot colors
9348				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9349					$out .= ' /ColorSpace <<';
9350					foreach ($data['spot_colors'] as $name => $color) {
9351						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9352					}
9353					$out .= ' >>';
9354				}
9355				// fonts
9356				if (!empty($data['fonts'])) {
9357					$out .= ' /Font <<';
9358					foreach ($data['fonts'] as $fontkey => $fontid) {
9359						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9360					}
9361					$out .= ' >>';
9362				}
9363				// images or nested xobjects
9364				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9365					$out .= ' /XObject <<';
9366					foreach ($data['images'] as $imgid) {
9367						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9368					}
9369					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9370						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9371					}
9372					$out .= ' >>';
9373				}
9374				$out .= ' >>'; //end resources
9375				if (isset($data['group']) AND ($data['group'] !== false)) {
9376					// set transparency group
9377					$out .= ' /Group << /Type /Group /S /Transparency';
9378					if (is_array($data['group'])) {
9379						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9380							$out .= ' /CS /'.$data['group']['CS'];
9381						}
9382						if (isset($data['group']['I'])) {
9383							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9384						}
9385						if (isset($data['group']['K'])) {
9386							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9387						}
9388					}
9389					$out .= ' >>';
9390				}
9391				$stream = $this->_getrawstream($stream, $data['n']);
9392				$out .= ' /Length '.strlen($stream);
9393				$out .= ' >>';
9394				$out .= ' stream'."\n".$stream."\n".'endstream';
9395				$out .= "\n".'endobj';
9396				$this->_out($out);
9397			}
9398		}
9399	}
9400
9401	/**
9402	 * Output Spot Colors Resources.
9403	 * @protected
9404	 * @since 4.0.024 (2008-09-12)
9405	 */
9406	protected function _putspotcolors() {
9407		foreach ($this->spot_colors as $name => $color) {
9408			$this->_newobj();
9409			$this->spot_colors[$name]['n'] = $this->n;
9410			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9411			$out .= ' /DeviceCMYK <<';
9412			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9413			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9414			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9415			$out .= "\n".'endobj';
9416			$this->_out($out);
9417		}
9418	}
9419
9420	/**
9421	 * Return XObjects Dictionary.
9422	 * @return string XObjects dictionary
9423	 * @protected
9424	 * @since 5.8.014 (2010-08-23)
9425	 */
9426	protected function _getxobjectdict() {
9427		$out = '';
9428		foreach ($this->xobjects as $id => $objid) {
9429			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9430		}
9431		return $out;
9432	}
9433
9434	/**
9435	 * Output Resources Dictionary.
9436	 * @protected
9437	 */
9438	protected function _putresourcedict() {
9439		$out = $this->_getobj(2)."\n";
9440		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9441		$out .= ' /Font <<';
9442		foreach ($this->fontkeys as $fontkey) {
9443			$font = $this->getFontBuffer($fontkey);
9444			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9445		}
9446		$out .= ' >>';
9447		$out .= ' /XObject <<';
9448		$out .= $this->_getxobjectdict();
9449		$out .= ' >>';
9450		// layers
9451		if (!empty($this->pdflayers)) {
9452			$out .= ' /Properties <<';
9453			foreach ($this->pdflayers as $layer) {
9454				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9455			}
9456			$out .= ' >>';
9457		}
9458		if (!$this->pdfa_mode) {
9459			// transparency
9460			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9461				$out .= ' /ExtGState <<';
9462				foreach ($this->extgstates as $k => $extgstate) {
9463					if (isset($extgstate['name'])) {
9464						$out .= ' /'.$extgstate['name'];
9465					} else {
9466						$out .= ' /GS'.$k;
9467					}
9468					$out .= ' '.$extgstate['n'].' 0 R';
9469				}
9470				$out .= ' >>';
9471			}
9472			if (isset($this->gradients) AND !empty($this->gradients)) {
9473				$gp = '';
9474				$gs = '';
9475				foreach ($this->gradients as $id => $grad) {
9476					// gradient patterns
9477					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9478					// gradient shadings
9479					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9480				}
9481				$out .= ' /Pattern <<'.$gp.' >>';
9482				$out .= ' /Shading <<'.$gs.' >>';
9483			}
9484		}
9485		// spot colors
9486		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9487			$out .= ' /ColorSpace <<';
9488			foreach ($this->spot_colors as $color) {
9489				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9490			}
9491			$out .= ' >>';
9492		}
9493		$out .= ' >>';
9494		$out .= "\n".'endobj';
9495		$this->_out($out);
9496	}
9497
9498	/**
9499	 * Output Resources.
9500	 * @protected
9501	 */
9502	protected function _putresources() {
9503		$this->_putextgstates();
9504		$this->_putocg();
9505		$this->_putfonts();
9506		$this->_putimages();
9507		$this->_putspotcolors();
9508		$this->_putshaders();
9509		$this->_putxobjects();
9510		$this->_putresourcedict();
9511		$this->_putdests();
9512		$this->_putEmbeddedFiles();
9513		$this->_putannotsobjs();
9514		$this->_putjavascript();
9515		$this->_putbookmarks();
9516		$this->_putencryption();
9517	}
9518
9519	/**
9520	 * Adds some Metadata information (Document Information Dictionary)
9521	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9522	 * @return int object id
9523	 * @protected
9524	 */
9525	protected function _putinfo() {
9526		$oid = $this->_newobj();
9527		$out = '<<';
9528		// store current isunicode value
9529		$prev_isunicode = $this->isunicode;
9530		if ($this->docinfounicode) {
9531			$this->isunicode = true;
9532		}
9533		if (!TCPDF_STATIC::empty_string($this->title)) {
9534			// The document's title.
9535			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9536		}
9537		if (!TCPDF_STATIC::empty_string($this->author)) {
9538			// The name of the person who created the document.
9539			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9540		}
9541		if (!TCPDF_STATIC::empty_string($this->subject)) {
9542			// The subject of the document.
9543			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9544		}
9545		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9546			// Keywords associated with the document.
9547			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9548		}
9549		if (!TCPDF_STATIC::empty_string($this->creator)) {
9550			// 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.
9551			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9552		}
9553		// restore previous isunicode value
9554		$this->isunicode = $prev_isunicode;
9555		// default producer
9556		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9557		// The date and time the document was created, in human-readable form
9558		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9559		// The date and time the document was most recently modified, in human-readable form
9560		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9561		// A name object indicating whether the document has been modified to include trapping information
9562		$out .= ' /Trapped /False';
9563		$out .= ' >>';
9564		$out .= "\n".'endobj';
9565		$this->_out($out);
9566		return $oid;
9567	}
9568
9569	/**
9570	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9571	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9572	 * @param $xmp (string) Custom XMP data.
9573	 * @since 5.9.128 (2011-10-06)
9574	 * @public
9575	 */
9576	public function setExtraXMP($xmp) {
9577		$this->custom_xmp = $xmp;
9578	}
9579
9580	/**
9581	 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9582	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9583	 * @param $xmp (string) Custom XMP RDF data.
9584	 * @since 6.3.0 (2019-09-19)
9585	 * @public
9586	 */
9587	public function setExtraXMPRDF($xmp) {
9588		$this->custom_xmp_rdf = $xmp;
9589	}
9590
9591	/**
9592	 * Put XMP data object and return ID.
9593	 * @return (int) The object ID.
9594	 * @since 5.9.121 (2011-09-28)
9595	 * @protected
9596	 */
9597	protected function _putXMP() {
9598		$oid = $this->_newobj();
9599		// store current isunicode value
9600		$prev_isunicode = $this->isunicode;
9601		$this->isunicode = true;
9602		$prev_encrypted = $this->encrypted;
9603		$this->encrypted = false;
9604		// set XMP data
9605		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9606		$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";
9607		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9608		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9609		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9610		$xmp .= "\t\t\t".'<dc:title>'."\n";
9611		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9612		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9613		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9614		$xmp .= "\t\t\t".'</dc:title>'."\n";
9615		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9616		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9617		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9618		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9619		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9620		$xmp .= "\t\t\t".'<dc:description>'."\n";
9621		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9622		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9623		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9624		$xmp .= "\t\t\t".'</dc:description>'."\n";
9625		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9626		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9627		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9628		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9629		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9630		$xmp .= "\t\t".'</rdf:Description>'."\n";
9631		// convert doc creation date format
9632		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9633		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9634		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9635		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9636		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9637		// convert doc modification date format
9638		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9639		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9640		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9641		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9642		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9643		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9644		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9645		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9646		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9647		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9648		$xmp .= "\t\t".'</rdf:Description>'."\n";
9649		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9650		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9651		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9652		$xmp .= "\t\t".'</rdf:Description>'."\n";
9653		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9654		$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);
9655		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9656		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9657		$xmp .= "\t\t".'</rdf:Description>'."\n";
9658		if ($this->pdfa_mode) {
9659			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9660			$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9661			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9662			$xmp .= "\t\t".'</rdf:Description>'."\n";
9663		}
9664		// XMP extension schemas
9665		$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";
9666		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9667		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9668		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9669		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9670		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9671		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9672		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9673		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9674		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9675		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9676		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9677		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9678		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9679		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9680		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9681		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9682		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9683		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9684		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9685		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9686		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9687		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9688		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9689		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9690		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9691		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9692		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9693		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9694		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9695		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9696		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9697		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9698		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9699		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9700		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9701		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9702		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9703		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9704		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9705		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9706		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9707		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9708		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9709		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9710		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9711		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9712		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9713		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9714		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9715		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9716		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9717		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9718		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9719		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9720		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9721		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9722		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9723		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9724		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9725		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9726		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9727		$xmp .= "\t\t".'</rdf:Description>'."\n";
9728		$xmp .= $this->custom_xmp_rdf;
9729		$xmp .= "\t".'</rdf:RDF>'."\n";
9730		$xmp .= $this->custom_xmp;
9731		$xmp .= '</x:xmpmeta>'."\n";
9732		$xmp .= '<?xpacket end="w"?>';
9733		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9734		// restore previous isunicode value
9735		$this->isunicode = $prev_isunicode;
9736		$this->encrypted = $prev_encrypted;
9737		$this->_out($out);
9738		return $oid;
9739	}
9740
9741	/**
9742	 * Output Catalog.
9743	 * @return int object id
9744	 * @protected
9745	 */
9746	protected function _putcatalog() {
9747		// put XMP
9748		$xmpobj = $this->_putXMP();
9749		// if required, add standard sRGB ICC colour profile
9750		if ($this->pdfa_mode OR $this->force_srgb) {
9751			$iccobj = $this->_newobj();
9752			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9753			$filter = '';
9754			if ($this->compress) {
9755				$filter = ' /Filter /FlateDecode';
9756				$icc = gzcompress($icc);
9757			}
9758			$icc = $this->_getrawstream($icc);
9759			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9760		}
9761		// start catalog
9762		$oid = $this->_newobj();
9763		$out = '<< /Type /Catalog';
9764		$out .= ' /Version /'.$this->PDFVersion;
9765		//$out .= ' /Extensions <<>>';
9766		$out .= ' /Pages 1 0 R';
9767		//$out .= ' /PageLabels ' //...;
9768		$out .= ' /Names <<';
9769		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9770			$out .= ' /JavaScript '.$this->n_js;
9771		}
9772		if (!empty($this->efnames)) {
9773			$out .= ' /EmbeddedFiles <</Names [';
9774			foreach ($this->efnames AS $fn => $fref) {
9775				$out .= ' '.$this->_datastring($fn).' '.$fref;
9776			}
9777			$out .= ' ]>>';
9778		}
9779		$out .= ' >>';
9780		if (!empty($this->dests)) {
9781			$out .= ' /Dests '.($this->n_dests).' 0 R';
9782		}
9783		$out .= $this->_putviewerpreferences();
9784		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9785			$out .= ' /PageLayout /'.$this->LayoutMode;
9786		}
9787		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9788			$out .= ' /PageMode /'.$this->PageMode;
9789		}
9790		if (count($this->outlines) > 0) {
9791			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9792			$out .= ' /PageMode /UseOutlines';
9793		}
9794		//$out .= ' /Threads []';
9795		if ($this->ZoomMode == 'fullpage') {
9796			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9797		} elseif ($this->ZoomMode == 'fullwidth') {
9798			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9799		} elseif ($this->ZoomMode == 'real') {
9800			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9801		} elseif (!is_string($this->ZoomMode)) {
9802			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9803		}
9804		//$out .= ' /AA <<>>';
9805		//$out .= ' /URI <<>>';
9806		$out .= ' /Metadata '.$xmpobj.' 0 R';
9807		//$out .= ' /StructTreeRoot <<>>';
9808		//$out .= ' /MarkInfo <<>>';
9809		if (isset($this->l['a_meta_language'])) {
9810			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9811		}
9812		//$out .= ' /SpiderInfo <<>>';
9813		// set OutputIntent to sRGB IEC61966-2.1 if required
9814		if ($this->pdfa_mode OR $this->force_srgb) {
9815			$out .= ' /OutputIntents [<<';
9816			$out .= ' /Type /OutputIntent';
9817			$out .= ' /S /GTS_PDFA1';
9818			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9819			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9820			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9821			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9822			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9823			$out .= ' >>]';
9824		}
9825		//$out .= ' /PieceInfo <<>>';
9826		if (!empty($this->pdflayers)) {
9827			$lyrobjs = '';
9828			$lyrobjs_off = '';
9829			$lyrobjs_lock = '';
9830			foreach ($this->pdflayers as $layer) {
9831				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9832				$lyrobjs .= $layer_obj_ref;
9833				if ($layer['view'] === false) {
9834					$lyrobjs_off .= $layer_obj_ref;
9835				}
9836				if ($layer['lock']) {
9837					$lyrobjs_lock .= $layer_obj_ref;
9838				}
9839			}
9840			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9841			$out .= ' /D <<';
9842			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9843			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9844			$out .= ' /BaseState /ON';
9845			$out .= ' /OFF ['.$lyrobjs_off.']';
9846			$out .= ' /Locked ['.$lyrobjs_lock.']';
9847			$out .= ' /Intent /View';
9848			$out .= ' /AS [';
9849			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9850			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9851			$out .= ' ]';
9852			$out .= ' /Order ['.$lyrobjs.']';
9853			$out .= ' /ListMode /AllPages';
9854			//$out .= ' /RBGroups ['..']';
9855			//$out .= ' /Locked ['..']';
9856			$out .= ' >>';
9857			$out .= ' >>';
9858		}
9859		// AcroForm
9860		if (!empty($this->form_obj_id)
9861			OR ($this->sign AND isset($this->signature_data['cert_type']))
9862			OR !empty($this->empty_signature_appearance)) {
9863			$out .= ' /AcroForm <<';
9864			$objrefs = '';
9865			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9866				// set reference for signature object
9867				$objrefs .= $this->sig_obj_id.' 0 R';
9868			}
9869			if (!empty($this->empty_signature_appearance)) {
9870				foreach ($this->empty_signature_appearance as $esa) {
9871					// set reference for empty signature objects
9872					$objrefs .= ' '.$esa['objid'].' 0 R';
9873				}
9874			}
9875			if (!empty($this->form_obj_id)) {
9876				foreach($this->form_obj_id as $objid) {
9877					$objrefs .= ' '.$objid.' 0 R';
9878				}
9879			}
9880			$out .= ' /Fields ['.$objrefs.']';
9881			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9882			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9883				$out .= ' /NeedAppearances false';
9884			}
9885			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9886				if ($this->signature_data['cert_type'] > 0) {
9887					$out .= ' /SigFlags 3';
9888				} else {
9889					$out .= ' /SigFlags 1';
9890				}
9891			}
9892			//$out .= ' /CO ';
9893			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9894				$out .= ' /DR <<';
9895				$out .= ' /Font <<';
9896				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9897					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9898				}
9899				$out .= ' >> >>';
9900			}
9901			$font = $this->getFontBuffer('helvetica');
9902			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9903			$out .= ' /Q '.(($this->rtl)?'2':'0');
9904			//$out .= ' /XFA ';
9905			$out .= ' >>';
9906			// signatures
9907			if ($this->sign AND isset($this->signature_data['cert_type'])
9908				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9909				if ($this->signature_data['cert_type'] > 0) {
9910					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9911				} else {
9912					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9913				}
9914			}
9915		}
9916		//$out .= ' /Legal <<>>';
9917		//$out .= ' /Requirements []';
9918		//$out .= ' /Collection <<>>';
9919		//$out .= ' /NeedsRendering true';
9920		$out .= ' >>';
9921		$out .= "\n".'endobj';
9922		$this->_out($out);
9923		return $oid;
9924	}
9925
9926	/**
9927	 * Output viewer preferences.
9928	 * @return string for viewer preferences
9929	 * @author Nicola asuni
9930	 * @since 3.1.000 (2008-06-09)
9931	 * @protected
9932	 */
9933	protected function _putviewerpreferences() {
9934		$vp = $this->viewer_preferences;
9935		$out = ' /ViewerPreferences <<';
9936		if ($this->rtl) {
9937			$out .= ' /Direction /R2L';
9938		} else {
9939			$out .= ' /Direction /L2R';
9940		}
9941		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9942			$out .= ' /HideToolbar true';
9943		}
9944		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9945			$out .= ' /HideMenubar true';
9946		}
9947		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9948			$out .= ' /HideWindowUI true';
9949		}
9950		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9951			$out .= ' /FitWindow true';
9952		}
9953		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9954			$out .= ' /CenterWindow true';
9955		}
9956		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9957			$out .= ' /DisplayDocTitle true';
9958		}
9959		if (isset($vp['NonFullScreenPageMode'])) {
9960			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9961		}
9962		if (isset($vp['ViewArea'])) {
9963			$out .= ' /ViewArea /'.$vp['ViewArea'];
9964		}
9965		if (isset($vp['ViewClip'])) {
9966			$out .= ' /ViewClip /'.$vp['ViewClip'];
9967		}
9968		if (isset($vp['PrintArea'])) {
9969			$out .= ' /PrintArea /'.$vp['PrintArea'];
9970		}
9971		if (isset($vp['PrintClip'])) {
9972			$out .= ' /PrintClip /'.$vp['PrintClip'];
9973		}
9974		if (isset($vp['PrintScaling'])) {
9975			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
9976		}
9977		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9978			$out .= ' /Duplex /'.$vp['Duplex'];
9979		}
9980		if (isset($vp['PickTrayByPDFSize'])) {
9981			if ($vp['PickTrayByPDFSize']) {
9982				$out .= ' /PickTrayByPDFSize true';
9983			} else {
9984				$out .= ' /PickTrayByPDFSize false';
9985			}
9986		}
9987		if (isset($vp['PrintPageRange'])) {
9988			$PrintPageRangeNum = '';
9989			foreach ($vp['PrintPageRange'] as $k => $v) {
9990				$PrintPageRangeNum .= ' '.($v - 1).'';
9991			}
9992			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9993		}
9994		if (isset($vp['NumCopies'])) {
9995			$out .= ' /NumCopies '.intval($vp['NumCopies']);
9996		}
9997		$out .= ' >>';
9998		return $out;
9999	}
10000
10001	/**
10002	 * Output PDF File Header (7.5.2).
10003	 * @protected
10004	 */
10005	protected function _putheader() {
10006		$this->_out('%PDF-'.$this->PDFVersion);
10007		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10008	}
10009
10010	/**
10011	 * Output end of document (EOF).
10012	 * @protected
10013	 */
10014	protected function _enddoc() {
10015		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10016			// save subset chars of the previous font
10017			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10018		}
10019		$this->state = 1;
10020		$this->_putheader();
10021		$this->_putpages();
10022		$this->_putresources();
10023		// empty signature fields
10024		if (!empty($this->empty_signature_appearance)) {
10025			foreach ($this->empty_signature_appearance as $key => $esa) {
10026				// widget annotation for empty signature
10027				$out = $this->_getobj($esa['objid'])."\n";
10028				$out .= '<< /Type /Annot';
10029				$out .= ' /Subtype /Widget';
10030				$out .= ' /Rect ['.$esa['rect'].']';
10031				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10032				$out .= ' /F 4';
10033				$out .= ' /FT /Sig';
10034				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10035				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10036				$out .= ' /Ff 0';
10037				$out .= ' >>';
10038				$out .= "\n".'endobj';
10039				$this->_out($out);
10040			}
10041		}
10042		// Signature
10043		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10044			// widget annotation for signature
10045			$out = $this->_getobj($this->sig_obj_id)."\n";
10046			$out .= '<< /Type /Annot';
10047			$out .= ' /Subtype /Widget';
10048			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10049			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10050			$out .= ' /F 4';
10051			$out .= ' /FT /Sig';
10052			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10053			$out .= ' /Ff 0';
10054			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10055			$out .= ' >>';
10056			$out .= "\n".'endobj';
10057			$this->_out($out);
10058			// signature
10059			$this->_putsignature();
10060		}
10061		// Info
10062		$objid_info = $this->_putinfo();
10063		// Catalog
10064		$objid_catalog = $this->_putcatalog();
10065		// Cross-ref
10066		$o = $this->bufferlen;
10067		// XREF section
10068		$this->_out('xref');
10069		$this->_out('0 '.($this->n + 1));
10070		$this->_out('0000000000 65535 f ');
10071		$freegen = ($this->n + 2);
10072		for ($i=1; $i <= $this->n; ++$i) {
10073			if (!isset($this->offsets[$i]) AND ($i > 1)) {
10074				$this->_out(sprintf('0000000000 %05d f ', $freegen));
10075				++$freegen;
10076			} else {
10077				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10078			}
10079		}
10080		// TRAILER
10081		$out = 'trailer'."\n";
10082		$out .= '<<';
10083		$out .= ' /Size '.($this->n + 1);
10084		$out .= ' /Root '.$objid_catalog.' 0 R';
10085		$out .= ' /Info '.$objid_info.' 0 R';
10086		if ($this->encrypted) {
10087			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10088		}
10089		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10090		$out .= ' >>';
10091		$this->_out($out);
10092		$this->_out('startxref');
10093		$this->_out($o);
10094		$this->_out('%%EOF');
10095		$this->state = 3; // end-of-doc
10096	}
10097
10098	/**
10099	 * Initialize a new page.
10100	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10101	 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10102	 * @protected
10103	 * @see getPageSizeFromFormat(), setPageFormat()
10104	 */
10105	protected function _beginpage($orientation='', $format='') {
10106		++$this->page;
10107		$this->pageobjects[$this->page] = array();
10108		$this->setPageBuffer($this->page, '');
10109		// initialize array for graphics tranformation positions inside a page buffer
10110		$this->transfmrk[$this->page] = array();
10111		$this->state = 2;
10112		if (TCPDF_STATIC::empty_string($orientation)) {
10113			if (isset($this->CurOrientation)) {
10114				$orientation = $this->CurOrientation;
10115			} elseif ($this->fwPt > $this->fhPt) {
10116				// landscape
10117				$orientation = 'L';
10118			} else {
10119				// portrait
10120				$orientation = 'P';
10121			}
10122		}
10123		if (TCPDF_STATIC::empty_string($format)) {
10124			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10125			$this->setPageOrientation($orientation);
10126		} else {
10127			$this->setPageFormat($format, $orientation);
10128		}
10129		if ($this->rtl) {
10130			$this->x = $this->w - $this->rMargin;
10131		} else {
10132			$this->x = $this->lMargin;
10133		}
10134		$this->y = $this->tMargin;
10135		if (isset($this->newpagegroup[$this->page])) {
10136			// start a new group
10137			$this->currpagegroup = $this->newpagegroup[$this->page];
10138			$this->pagegroups[$this->currpagegroup] = 1;
10139		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10140			++$this->pagegroups[$this->currpagegroup];
10141		}
10142	}
10143
10144	/**
10145	 * Mark end of page.
10146	 * @protected
10147	 */
10148	protected function _endpage() {
10149		$this->setVisibility('all');
10150		$this->state = 1;
10151	}
10152
10153	/**
10154	 * Begin a new object and return the object number.
10155	 * @return int object number
10156	 * @protected
10157	 */
10158	protected function _newobj() {
10159		$this->_out($this->_getobj());
10160		return $this->n;
10161	}
10162
10163	/**
10164	 * Return the starting object string for the selected object ID.
10165	 * @param $objid (int) Object ID (leave empty to get a new ID).
10166	 * @return string the starting object string
10167	 * @protected
10168	 * @since 5.8.009 (2010-08-20)
10169	 */
10170	protected function _getobj($objid='') {
10171		if ($objid === '') {
10172			++$this->n;
10173			$objid = $this->n;
10174		}
10175		$this->offsets[$objid] = $this->bufferlen;
10176		$this->pageobjects[$this->page][] = $objid;
10177		return $objid.' 0 obj';
10178	}
10179
10180	/**
10181	 * Underline text.
10182	 * @param $x (int) X coordinate
10183	 * @param $y (int) Y coordinate
10184	 * @param $txt (string) text to underline
10185	 * @protected
10186	 */
10187	protected function _dounderline($x, $y, $txt) {
10188		$w = $this->GetStringWidth($txt);
10189		return $this->_dounderlinew($x, $y, $w);
10190	}
10191
10192	/**
10193	 * Underline for rectangular text area.
10194	 * @param $x (int) X coordinate
10195	 * @param $y (int) Y coordinate
10196	 * @param $w (int) width to underline
10197	 * @protected
10198	 * @since 4.8.008 (2009-09-29)
10199	 */
10200	protected function _dounderlinew($x, $y, $w) {
10201		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10202		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10203	}
10204
10205	/**
10206	 * Line through text.
10207	 * @param $x (int) X coordinate
10208	 * @param $y (int) Y coordinate
10209	 * @param $txt (string) text to linethrough
10210	 * @protected
10211	 */
10212	protected function _dolinethrough($x, $y, $txt) {
10213		$w = $this->GetStringWidth($txt);
10214		return $this->_dolinethroughw($x, $y, $w);
10215	}
10216
10217	/**
10218	 * Line through for rectangular text area.
10219	 * @param $x (int) X coordinate
10220	 * @param $y (int) Y coordinate
10221	 * @param $w (int) line length (width)
10222	 * @protected
10223	 * @since 4.9.008 (2009-09-29)
10224	 */
10225	protected function _dolinethroughw($x, $y, $w) {
10226		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10227		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10228	}
10229
10230	/**
10231	 * Overline text.
10232	 * @param $x (int) X coordinate
10233	 * @param $y (int) Y coordinate
10234	 * @param $txt (string) text to overline
10235	 * @protected
10236	 * @since 4.9.015 (2010-04-19)
10237	 */
10238	protected function _dooverline($x, $y, $txt) {
10239		$w = $this->GetStringWidth($txt);
10240		return $this->_dooverlinew($x, $y, $w);
10241	}
10242
10243	/**
10244	 * Overline for rectangular text area.
10245	 * @param $x (int) X coordinate
10246	 * @param $y (int) Y coordinate
10247	 * @param $w (int) width to overline
10248	 * @protected
10249	 * @since 4.9.015 (2010-04-19)
10250	 */
10251	protected function _dooverlinew($x, $y, $w) {
10252		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10253		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10254
10255	}
10256
10257	/**
10258	 * Format a data string for meta information
10259	 * @param $s (string) data string to escape.
10260	 * @param $n (int) object ID
10261	 * @return string escaped string.
10262	 * @protected
10263	 */
10264	protected function _datastring($s, $n=0) {
10265		if ($n == 0) {
10266			$n = $this->n;
10267		}
10268		$s = $this->_encrypt_data($n, $s);
10269		return '('. TCPDF_STATIC::_escape($s).')';
10270	}
10271
10272	/**
10273	 * Set the document creation timestamp
10274	 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10275	 * @public
10276	 * @since 5.9.152 (2012-03-23)
10277	 */
10278	public function setDocCreationTimestamp($time) {
10279		if (is_string($time)) {
10280			$time = TCPDF_STATIC::getTimestamp($time);
10281		}
10282		$this->doc_creation_timestamp = intval($time);
10283	}
10284
10285	/**
10286	 * Set the document modification timestamp
10287	 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10288	 * @public
10289	 * @since 5.9.152 (2012-03-23)
10290	 */
10291	public function setDocModificationTimestamp($time) {
10292		if (is_string($time)) {
10293			$time = TCPDF_STATIC::getTimestamp($time);
10294		}
10295		$this->doc_modification_timestamp = intval($time);
10296	}
10297
10298	/**
10299	 * Returns document creation timestamp in seconds.
10300	 * @return (int) Creation timestamp in seconds.
10301	 * @public
10302	 * @since 5.9.152 (2012-03-23)
10303	 */
10304	public function getDocCreationTimestamp() {
10305		return $this->doc_creation_timestamp;
10306	}
10307
10308	/**
10309	 * Returns document modification timestamp in seconds.
10310	 * @return (int) Modfication timestamp in seconds.
10311	 * @public
10312	 * @since 5.9.152 (2012-03-23)
10313	 */
10314	public function getDocModificationTimestamp() {
10315		return $this->doc_modification_timestamp;
10316	}
10317
10318	/**
10319	 * Returns a formatted date for meta information
10320	 * @param $n (int) Object ID.
10321	 * @param $timestamp (int) Timestamp to convert.
10322	 * @return string escaped date string.
10323	 * @protected
10324	 * @since 4.6.028 (2009-08-25)
10325	 */
10326	protected function _datestring($n=0, $timestamp=0) {
10327		if ((empty($timestamp)) OR ($timestamp < 0)) {
10328			$timestamp = $this->doc_creation_timestamp;
10329		}
10330		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10331	}
10332
10333	/**
10334	 * Format a text string for meta information
10335	 * @param $s (string) string to escape.
10336	 * @param $n (int) object ID
10337	 * @return string escaped string.
10338	 * @protected
10339	 */
10340	protected function _textstring($s, $n=0) {
10341		if ($this->isunicode) {
10342			//Convert string to UTF-16BE
10343			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10344		}
10345		return $this->_datastring($s, $n);
10346	}
10347
10348	/**
10349	 * get raw output stream.
10350	 * @param $s (string) string to output.
10351	 * @param $n (int) object reference for encryption mode
10352	 * @protected
10353	 * @author Nicola Asuni
10354	 * @since 5.5.000 (2010-06-22)
10355	 */
10356	protected function _getrawstream($s, $n=0) {
10357		if ($n <= 0) {
10358			// default to current object
10359			$n = $this->n;
10360		}
10361		return $this->_encrypt_data($n, $s);
10362	}
10363
10364	/**
10365	 * Output a string to the document.
10366	 * @param $s (string) string to output.
10367	 * @protected
10368	 */
10369	protected function _out($s) {
10370		if ($this->state == 2) {
10371			if ($this->inxobj) {
10372				// we are inside an XObject template
10373				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10374			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10375				// puts data before page footer
10376				$pagebuff = $this->getPageBuffer($this->page);
10377				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10378				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10379				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10380				// update footer position
10381				$this->footerpos[$this->page] += strlen($s."\n");
10382			} else {
10383				// set page data
10384				$this->setPageBuffer($this->page, $s."\n", true);
10385			}
10386		} elseif ($this->state > 0) {
10387			// set general data
10388			$this->setBuffer($s."\n");
10389		}
10390	}
10391
10392	/**
10393	 * Set header font.
10394	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10395	 * @public
10396	 * @since 1.1
10397	 */
10398	public function setHeaderFont($font) {
10399		$this->header_font = $font;
10400	}
10401
10402	/**
10403	 * Get header font.
10404	 * @return array() Array describing the basic font parameters: (family, style, size).
10405	 * @public
10406	 * @since 4.0.012 (2008-07-24)
10407	 */
10408	public function getHeaderFont() {
10409		return $this->header_font;
10410	}
10411
10412	/**
10413	 * Set footer font.
10414	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10415	 * @public
10416	 * @since 1.1
10417	 */
10418	public function setFooterFont($font) {
10419		$this->footer_font = $font;
10420	}
10421
10422	/**
10423	 * Get Footer font.
10424	 * @return array() Array describing the basic font parameters: (family, style, size).
10425	 * @public
10426	 * @since 4.0.012 (2008-07-24)
10427	 */
10428	public function getFooterFont() {
10429		return $this->footer_font;
10430	}
10431
10432	/**
10433	 * Set language array.
10434	 * @param $language (array)
10435	 * @public
10436	 * @since 1.1
10437	 */
10438	public function setLanguageArray($language) {
10439		$this->l = $language;
10440		if (isset($this->l['a_meta_dir'])) {
10441			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10442		} else {
10443			$this->rtl = false;
10444		}
10445	}
10446
10447	/**
10448	 * Returns the PDF data.
10449	 * @public
10450	 */
10451	public function getPDFData() {
10452		if ($this->state < 3) {
10453			$this->Close();
10454		}
10455		return $this->buffer;
10456	}
10457
10458	/**
10459	 * Output anchor link.
10460	 * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10461	 * @param $name (string) link name
10462	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10463	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10464	 * @param $color (array) array of RGB text color
10465	 * @param $style (string) font style (U, D, B, I)
10466	 * @param $firstblock (boolean) if true the string is the starting of a line.
10467	 * @return the number of cells used or the remaining text if $firstline = true;
10468	 * @public
10469	 */
10470	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10471		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10472			// convert url to internal link
10473			$lnkdata = explode(',', $url);
10474			if (isset($lnkdata[0]) ) {
10475				$page = substr($lnkdata[0], 1);
10476				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10477					$lnky = floatval($lnkdata[1]);
10478				} else {
10479					$lnky = 0;
10480				}
10481				$url = $this->AddLink();
10482				$this->SetLink($url, $lnky, $page);
10483			}
10484		}
10485		// store current settings
10486		$prevcolor = $this->fgcolor;
10487		$prevstyle = $this->FontStyle;
10488		if (empty($color)) {
10489			$this->SetTextColorArray($this->htmlLinkColorArray);
10490		} else {
10491			$this->SetTextColorArray($color);
10492		}
10493		if ($style == -1) {
10494			$this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10495		} else {
10496			$this->SetFont('', $this->FontStyle.$style);
10497		}
10498		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10499		// restore settings
10500		$this->SetFont('', $prevstyle);
10501		$this->SetTextColorArray($prevcolor);
10502		return $ret;
10503	}
10504
10505	/**
10506	 * Converts pixels to User's Units.
10507	 * @param $px (int) pixels
10508	 * @return float value in user's unit
10509	 * @public
10510	 * @see setImageScale(), getImageScale()
10511	 */
10512	public function pixelsToUnits($px) {
10513		return ($px / ($this->imgscale * $this->k));
10514	}
10515
10516	/**
10517	 * Reverse function for htmlentities.
10518	 * Convert entities in UTF-8.
10519	 * @param $text_to_convert (string) Text to convert.
10520	 * @return string converted text string
10521	 * @public
10522	 */
10523	public function unhtmlentities($text_to_convert) {
10524		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10525	}
10526
10527	// ENCRYPTION METHODS ----------------------------------
10528
10529	/**
10530	 * Compute encryption key depending on object number where the encrypted data is stored.
10531	 * This is used for all strings and streams without crypt filter specifier.
10532	 * @param $n (int) object number
10533	 * @return int object key
10534	 * @protected
10535	 * @author Nicola Asuni
10536	 * @since 2.0.000 (2008-01-02)
10537	 */
10538	protected function _objectkey($n) {
10539		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10540		if ($this->encryptdata['mode'] == 2) { // AES-128
10541			// AES padding
10542			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10543		}
10544		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10545		$objkey = substr($objkey, 0, 16);
10546		return $objkey;
10547	}
10548
10549	/**
10550	 * Encrypt the input string.
10551	 * @param $n (int) object number
10552	 * @param $s (string) data string to encrypt
10553	 * @return encrypted string
10554	 * @protected
10555	 * @author Nicola Asuni
10556	 * @since 5.0.005 (2010-05-11)
10557	 */
10558	protected function _encrypt_data($n, $s) {
10559		if (!$this->encrypted) {
10560			return $s;
10561		}
10562		switch ($this->encryptdata['mode']) {
10563			case 0:   // RC4-40
10564			case 1: { // RC4-128
10565				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10566				break;
10567			}
10568			case 2: { // AES-128
10569				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10570				break;
10571			}
10572			case 3: { // AES-256
10573				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10574				break;
10575			}
10576		}
10577		return $s;
10578	}
10579
10580	/**
10581	 * Put encryption on PDF document.
10582	 * @protected
10583	 * @author Nicola Asuni
10584	 * @since 2.0.000 (2008-01-02)
10585	 */
10586	protected function _putencryption() {
10587		if (!$this->encrypted) {
10588			return;
10589		}
10590		$this->encryptdata['objid'] = $this->_newobj();
10591		$out = '<<';
10592		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10593			$this->encryptdata['Filter'] = 'Standard';
10594		}
10595		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10596		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10597			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10598		}
10599		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10600			$this->encryptdata['V'] = 1;
10601		}
10602		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10603		$out .= ' /V '.$this->encryptdata['V'];
10604		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10605			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10606			$out .= ' /Length '.$this->encryptdata['Length'];
10607		} else {
10608			$out .= ' /Length 40';
10609		}
10610		if ($this->encryptdata['V'] >= 4) {
10611			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10612				$this->encryptdata['StmF'] = 'Identity';
10613			}
10614			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10615				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10616				$this->encryptdata['StrF'] = 'Identity';
10617			}
10618			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10619			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10620				$out .= ' /CF <<';
10621				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10622				$out .= ' /Type /CryptFilter';
10623				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10624					// The method used
10625					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10626					if ($this->encryptdata['pubkey']) {
10627						$out .= ' /Recipients [';
10628						foreach ($this->encryptdata['Recipients'] as $rec) {
10629							$out .= ' <'.$rec.'>';
10630						}
10631						$out .= ' ]';
10632						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10633							$out .= ' /EncryptMetadata false';
10634						} else {
10635							$out .= ' /EncryptMetadata true';
10636						}
10637					}
10638				} else {
10639					$out .= ' /CFM /None';
10640				}
10641				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10642					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10643					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10644				} else {
10645					$out .= ' /AuthEvent /DocOpen';
10646				}
10647				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10648					// The bit length of the encryption key.
10649					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10650				}
10651				$out .= ' >> >>';
10652			}
10653			// The name of the crypt filter that shall be used by default when decrypting streams.
10654			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10655			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10656			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10657			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10658				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10659				$out .= ' /EFF /'.$this->encryptdata[''];
10660			}
10661		}
10662		// Additional encryption dictionary entries for the standard security handler
10663		if ($this->encryptdata['pubkey']) {
10664			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10665				$out .= ' /Recipients [';
10666				foreach ($this->encryptdata['Recipients'] as $rec) {
10667					$out .= ' <'.$rec.'>';
10668				}
10669				$out .= ' ]';
10670			}
10671		} else {
10672			$out .= ' /R';
10673			if ($this->encryptdata['V'] == 5) { // AES-256
10674				$out .= ' 5';
10675				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10676				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10677				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10678			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10679				$out .= ' 4';
10680			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10681				$out .= ' 2';
10682			} else { // RC-128
10683				$out .= ' 3';
10684			}
10685			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10686			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10687			$out .= ' /P '.$this->encryptdata['P'];
10688			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10689				$out .= ' /EncryptMetadata false';
10690			} else {
10691				$out .= ' /EncryptMetadata true';
10692			}
10693		}
10694		$out .= ' >>';
10695		$out .= "\n".'endobj';
10696		$this->_out($out);
10697	}
10698
10699	/**
10700	 * Compute U value (used for encryption)
10701	 * @return string U value
10702	 * @protected
10703	 * @since 2.0.000 (2008-01-02)
10704	 * @author Nicola Asuni
10705	 */
10706	protected function _Uvalue() {
10707		if ($this->encryptdata['mode'] == 0) { // RC4-40
10708			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10709		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10710			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10711			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10712			$len = strlen($tmp);
10713			for ($i = 1; $i <= 19; ++$i) {
10714				$ek = '';
10715				for ($j = 0; $j < $len; ++$j) {
10716					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10717				}
10718				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10719			}
10720			$enc .= str_repeat("\x00", 16);
10721			return substr($enc, 0, 32);
10722		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10723			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10724			// User Validation Salt
10725			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10726			// User Key Salt
10727			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10728			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10729		}
10730	}
10731
10732	/**
10733	 * Compute UE value (used for encryption)
10734	 * @return string UE value
10735	 * @protected
10736	 * @since 5.9.006 (2010-10-19)
10737	 * @author Nicola Asuni
10738	 */
10739	protected function _UEvalue() {
10740		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10741		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10742	}
10743
10744	/**
10745	 * Compute O value (used for encryption)
10746	 * @return string O value
10747	 * @protected
10748	 * @since 2.0.000 (2008-01-02)
10749	 * @author Nicola Asuni
10750	 */
10751	protected function _Ovalue() {
10752		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10753			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10754			if ($this->encryptdata['mode'] > 0) {
10755				for ($i = 0; $i < 50; ++$i) {
10756					$tmp = TCPDF_STATIC::_md5_16($tmp);
10757				}
10758			}
10759			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10760			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10761			if ($this->encryptdata['mode'] > 0) {
10762				$len = strlen($owner_key);
10763				for ($i = 1; $i <= 19; ++$i) {
10764					$ek = '';
10765					for ($j = 0; $j < $len; ++$j) {
10766						$ek .= chr(ord($owner_key[$j]) ^ $i);
10767					}
10768					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10769				}
10770			}
10771			return $enc;
10772		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10773			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10774			// Owner Validation Salt
10775			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10776			// Owner Key Salt
10777			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10778			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10779		}
10780	}
10781
10782	/**
10783	 * Compute OE value (used for encryption)
10784	 * @return string OE value
10785	 * @protected
10786	 * @since 5.9.006 (2010-10-19)
10787	 * @author Nicola Asuni
10788	 */
10789	protected function _OEvalue() {
10790		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10791		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10792	}
10793
10794	/**
10795	 * Convert password for AES-256 encryption mode
10796	 * @param $password (string) password
10797	 * @return string password
10798	 * @protected
10799	 * @since 5.9.006 (2010-10-19)
10800	 * @author Nicola Asuni
10801	 */
10802	protected function _fixAES256Password($password) {
10803		$psw = ''; // password to be returned
10804		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10805		foreach ($psw_array as $c) {
10806			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10807		}
10808		return substr($psw, 0, 127);
10809	}
10810
10811	/**
10812	 * Compute encryption key
10813	 * @protected
10814	 * @since 2.0.000 (2008-01-02)
10815	 * @author Nicola Asuni
10816	 */
10817	protected function _generateencryptionkey() {
10818		$keybytelen = ($this->encryptdata['Length'] / 8);
10819		if (!$this->encryptdata['pubkey']) { // standard mode
10820			if ($this->encryptdata['mode'] == 3) { // AES-256
10821				// generate 256 bit random key
10822				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10823				// truncate passwords
10824				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10825				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10826				// Compute U value
10827				$this->encryptdata['U'] = $this->_Uvalue();
10828				// Compute UE value
10829				$this->encryptdata['UE'] = $this->_UEvalue();
10830				// Compute O value
10831				$this->encryptdata['O'] = $this->_Ovalue();
10832				// Compute OE value
10833				$this->encryptdata['OE'] = $this->_OEvalue();
10834				// Compute P value
10835				$this->encryptdata['P'] = $this->encryptdata['protection'];
10836				// Computing the encryption dictionary's Perms (permissions) value
10837				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10838				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10839				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10840					$perms .= 'F';
10841				} else {
10842					$perms .= 'T';
10843				}
10844				$perms .= 'adb'; // bytes 9-11
10845				$perms .= 'nick'; // bytes 12-15
10846				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10847			} else { // RC4-40, RC4-128, AES-128
10848				// Pad passwords
10849				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10850				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10851				// Compute O value
10852				$this->encryptdata['O'] = $this->_Ovalue();
10853				// get default permissions (reverse byte order)
10854				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10855				// Compute encryption key
10856				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10857				if ($this->encryptdata['mode'] > 0) {
10858					for ($i = 0; $i < 50; ++$i) {
10859						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10860					}
10861				}
10862				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10863				// Compute U value
10864				$this->encryptdata['U'] = $this->_Uvalue();
10865				// Compute P value
10866				$this->encryptdata['P'] = $this->encryptdata['protection'];
10867			}
10868		} else { // Public-Key mode
10869			// random 20-byte seed
10870			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10871			$recipient_bytes = '';
10872			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10873				// for each public certificate
10874				if (isset($pubkey['p'])) {
10875					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10876				} else {
10877					$pkprotection = $this->encryptdata['protection'];
10878				}
10879				// get default permissions (reverse byte order)
10880				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10881				// envelope data
10882				$envelope = $seed.$pkpermissions;
10883				// write the envelope data to a temporary file
10884				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10885				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10886				if (!$f) {
10887					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10888				}
10889				$envelope_length = strlen($envelope);
10890				fwrite($f, $envelope, $envelope_length);
10891				fclose($f);
10892				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10893				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10894					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10895				}
10896				// read encryption signature
10897				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10898				// extract signature
10899				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10900				$tmparr = explode("\n\n", $signature);
10901				$signature = trim($tmparr[1]);
10902				unset($tmparr);
10903				// decode signature
10904				$signature = base64_decode($signature);
10905				// convert signature to hex
10906				$hexsignature = current(unpack('H*', $signature));
10907				// store signature on recipients array
10908				$this->encryptdata['Recipients'][] = $hexsignature;
10909				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10910				$recipient_bytes .= $signature;
10911			}
10912			// calculate encryption key
10913			if ($this->encryptdata['mode'] == 3) { // AES-256
10914				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10915			} else { // RC4-40, RC4-128, AES-128
10916				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10917			}
10918		}
10919	}
10920
10921	/**
10922	 * Set document protection
10923	 * Remark: the protection against modification is for people who have the full Acrobat product.
10924	 * 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.
10925	 * 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.
10926	 * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
10927	 * @param $user_pass (String) user password. Empty by default.
10928	 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10929	 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10930	 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10931	 * @public
10932	 * @since 2.0.000 (2008-01-02)
10933	 * @author Nicola Asuni
10934	 */
10935	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) {
10936		if ($this->pdfa_mode) {
10937			// encryption is not allowed in PDF/A mode
10938			return;
10939		}
10940		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10941		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10942			// public-key mode
10943			$this->encryptdata['pubkeys'] = $pubkeys;
10944			if ($mode == 0) {
10945				// public-Key Security requires at least 128 bit
10946				$mode = 1;
10947			}
10948			if (!function_exists('openssl_pkcs7_encrypt')) {
10949				$this->Error('Public-Key Security requires openssl library.');
10950			}
10951			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10952			$this->encryptdata['pubkey'] = true;
10953			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10954			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
10955			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
10956		} else {
10957			// standard mode (password mode)
10958			$this->encryptdata['pubkey'] = false;
10959			$this->encryptdata['Filter'] = 'Standard';
10960			$this->encryptdata['StmF'] = 'StdCF';
10961			$this->encryptdata['StrF'] = 'StdCF';
10962		}
10963		if ($mode > 1) { // AES
10964			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10965				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10966			}
10967			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10968				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10969			}
10970			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10971				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10972			}
10973			if (($mode == 3) AND !function_exists('hash')) {
10974				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10975				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10976			}
10977		}
10978		if ($owner_pass === null) {
10979			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10980		}
10981		$this->encryptdata['user_password'] = $user_pass;
10982		$this->encryptdata['owner_password'] = $owner_pass;
10983		$this->encryptdata['mode'] = $mode;
10984		switch ($mode) {
10985			case 0: { // RC4 40 bit
10986				$this->encryptdata['V'] = 1;
10987				$this->encryptdata['Length'] = 40;
10988				$this->encryptdata['CF']['CFM'] = 'V2';
10989				break;
10990			}
10991			case 1: { // RC4 128 bit
10992				$this->encryptdata['V'] = 2;
10993				$this->encryptdata['Length'] = 128;
10994				$this->encryptdata['CF']['CFM'] = 'V2';
10995				if ($this->encryptdata['pubkey']) {
10996					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10997					$this->encryptdata['Recipients'] = array();
10998				}
10999				break;
11000			}
11001			case 2: { // AES 128 bit
11002				$this->encryptdata['V'] = 4;
11003				$this->encryptdata['Length'] = 128;
11004				$this->encryptdata['CF']['CFM'] = 'AESV2';
11005				$this->encryptdata['CF']['Length'] = 128;
11006				if ($this->encryptdata['pubkey']) {
11007					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11008					$this->encryptdata['Recipients'] = array();
11009				}
11010				break;
11011			}
11012			case 3: { // AES 256 bit
11013				$this->encryptdata['V'] = 5;
11014				$this->encryptdata['Length'] = 256;
11015				$this->encryptdata['CF']['CFM'] = 'AESV3';
11016				$this->encryptdata['CF']['Length'] = 256;
11017				if ($this->encryptdata['pubkey']) {
11018					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11019					$this->encryptdata['Recipients'] = array();
11020				}
11021				break;
11022			}
11023		}
11024		$this->encrypted = true;
11025		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11026		$this->_generateencryptionkey();
11027	}
11028
11029	// END OF ENCRYPTION FUNCTIONS -------------------------
11030
11031	// START TRANSFORMATIONS SECTION -----------------------
11032
11033	/**
11034	 * Starts a 2D tranformation saving current graphic state.
11035	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11036	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11037	 * @public
11038	 * @since 2.1.000 (2008-01-07)
11039	 * @see StartTransform(), StopTransform()
11040	 */
11041	public function StartTransform() {
11042		if ($this->state != 2) {
11043			return;
11044		}
11045		$this->_outSaveGraphicsState();
11046		if ($this->inxobj) {
11047			// we are inside an XObject template
11048			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11049		} else {
11050			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11051		}
11052		++$this->transfmatrix_key;
11053		$this->transfmatrix[$this->transfmatrix_key] = array();
11054	}
11055
11056	/**
11057	 * Stops a 2D tranformation restoring previous graphic state.
11058	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11059	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11060	 * @public
11061	 * @since 2.1.000 (2008-01-07)
11062	 * @see StartTransform(), StopTransform()
11063	 */
11064	public function StopTransform() {
11065		if ($this->state != 2) {
11066			return;
11067		}
11068		$this->_outRestoreGraphicsState();
11069		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11070			array_pop($this->transfmatrix[$this->transfmatrix_key]);
11071			--$this->transfmatrix_key;
11072		}
11073		if ($this->inxobj) {
11074			// we are inside an XObject template
11075			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11076		} else {
11077			array_pop($this->transfmrk[$this->page]);
11078		}
11079	}
11080	/**
11081	 * Horizontal Scaling.
11082	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11083	 * @param $x (int) abscissa of the scaling center. Default is current x position
11084	 * @param $y (int) ordinate of the scaling center. Default is current y position
11085	 * @public
11086	 * @since 2.1.000 (2008-01-07)
11087	 * @see StartTransform(), StopTransform()
11088	 */
11089	public function ScaleX($s_x, $x='', $y='') {
11090		$this->Scale($s_x, 100, $x, $y);
11091	}
11092
11093	/**
11094	 * Vertical Scaling.
11095	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11096	 * @param $x (int) abscissa of the scaling center. Default is current x position
11097	 * @param $y (int) ordinate of the scaling center. Default is current y position
11098	 * @public
11099	 * @since 2.1.000 (2008-01-07)
11100	 * @see StartTransform(), StopTransform()
11101	 */
11102	public function ScaleY($s_y, $x='', $y='') {
11103		$this->Scale(100, $s_y, $x, $y);
11104	}
11105
11106	/**
11107	 * Vertical and horizontal proportional Scaling.
11108	 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11109	 * @param $x (int) abscissa of the scaling center. Default is current x position
11110	 * @param $y (int) ordinate of the scaling center. Default is current y position
11111	 * @public
11112	 * @since 2.1.000 (2008-01-07)
11113	 * @see StartTransform(), StopTransform()
11114	 */
11115	public function ScaleXY($s, $x='', $y='') {
11116		$this->Scale($s, $s, $x, $y);
11117	}
11118
11119	/**
11120	 * Vertical and horizontal non-proportional Scaling.
11121	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11122	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11123	 * @param $x (int) abscissa of the scaling center. Default is current x position
11124	 * @param $y (int) ordinate of the scaling center. Default is current y position
11125	 * @public
11126	 * @since 2.1.000 (2008-01-07)
11127	 * @see StartTransform(), StopTransform()
11128	 */
11129	public function Scale($s_x, $s_y, $x='', $y='') {
11130		if ($x === '') {
11131			$x = $this->x;
11132		}
11133		if ($y === '') {
11134			$y = $this->y;
11135		}
11136		if (($s_x == 0) OR ($s_y == 0)) {
11137			$this->Error('Please do not use values equal to zero for scaling');
11138		}
11139		$y = ($this->h - $y) * $this->k;
11140		$x *= $this->k;
11141		//calculate elements of transformation matrix
11142		$s_x /= 100;
11143		$s_y /= 100;
11144		$tm = array();
11145		$tm[0] = $s_x;
11146		$tm[1] = 0;
11147		$tm[2] = 0;
11148		$tm[3] = $s_y;
11149		$tm[4] = $x * (1 - $s_x);
11150		$tm[5] = $y * (1 - $s_y);
11151		//scale the coordinate system
11152		$this->Transform($tm);
11153	}
11154
11155	/**
11156	 * Horizontal Mirroring.
11157	 * @param $x (int) abscissa of the point. Default is current x position
11158	 * @public
11159	 * @since 2.1.000 (2008-01-07)
11160	 * @see StartTransform(), StopTransform()
11161	 */
11162	public function MirrorH($x='') {
11163		$this->Scale(-100, 100, $x);
11164	}
11165
11166	/**
11167	 * Verical Mirroring.
11168	 * @param $y (int) ordinate of the point. Default is current y position
11169	 * @public
11170	 * @since 2.1.000 (2008-01-07)
11171	 * @see StartTransform(), StopTransform()
11172	 */
11173	public function MirrorV($y='') {
11174		$this->Scale(100, -100, '', $y);
11175	}
11176
11177	/**
11178	 * Point reflection mirroring.
11179	 * @param $x (int) abscissa of the point. Default is current x position
11180	 * @param $y (int) ordinate of the point. Default is current y position
11181	 * @public
11182	 * @since 2.1.000 (2008-01-07)
11183	 * @see StartTransform(), StopTransform()
11184	 */
11185	public function MirrorP($x='',$y='') {
11186		$this->Scale(-100, -100, $x, $y);
11187	}
11188
11189	/**
11190	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11191	 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11192	 * @param $x (int) abscissa of the point. Default is current x position
11193	 * @param $y (int) ordinate of the point. Default is current y position
11194	 * @public
11195	 * @since 2.1.000 (2008-01-07)
11196	 * @see StartTransform(), StopTransform()
11197	 */
11198	public function MirrorL($angle=0, $x='',$y='') {
11199		$this->Scale(-100, 100, $x, $y);
11200		$this->Rotate(-2*($angle-90), $x, $y);
11201	}
11202
11203	/**
11204	 * Translate graphic object horizontally.
11205	 * @param $t_x (int) movement to the right (or left for RTL)
11206	 * @public
11207	 * @since 2.1.000 (2008-01-07)
11208	 * @see StartTransform(), StopTransform()
11209	 */
11210	public function TranslateX($t_x) {
11211		$this->Translate($t_x, 0);
11212	}
11213
11214	/**
11215	 * Translate graphic object vertically.
11216	 * @param $t_y (int) movement to the bottom
11217	 * @public
11218	 * @since 2.1.000 (2008-01-07)
11219	 * @see StartTransform(), StopTransform()
11220	 */
11221	public function TranslateY($t_y) {
11222		$this->Translate(0, $t_y);
11223	}
11224
11225	/**
11226	 * Translate graphic object horizontally and vertically.
11227	 * @param $t_x (int) movement to the right
11228	 * @param $t_y (int) movement to the bottom
11229	 * @public
11230	 * @since 2.1.000 (2008-01-07)
11231	 * @see StartTransform(), StopTransform()
11232	 */
11233	public function Translate($t_x, $t_y) {
11234		//calculate elements of transformation matrix
11235		$tm = array();
11236		$tm[0] = 1;
11237		$tm[1] = 0;
11238		$tm[2] = 0;
11239		$tm[3] = 1;
11240		$tm[4] = $t_x * $this->k;
11241		$tm[5] = -$t_y * $this->k;
11242		//translate the coordinate system
11243		$this->Transform($tm);
11244	}
11245
11246	/**
11247	 * Rotate object.
11248	 * @param $angle (float) angle in degrees for counter-clockwise rotation
11249	 * @param $x (int) abscissa of the rotation center. Default is current x position
11250	 * @param $y (int) ordinate of the rotation center. Default is current y position
11251	 * @public
11252	 * @since 2.1.000 (2008-01-07)
11253	 * @see StartTransform(), StopTransform()
11254	 */
11255	public function Rotate($angle, $x='', $y='') {
11256		if ($x === '') {
11257			$x = $this->x;
11258		}
11259		if ($y === '') {
11260			$y = $this->y;
11261		}
11262		$y = ($this->h - $y) * $this->k;
11263		$x *= $this->k;
11264		//calculate elements of transformation matrix
11265		$tm = array();
11266		$tm[0] = cos(deg2rad($angle));
11267		$tm[1] = sin(deg2rad($angle));
11268		$tm[2] = -$tm[1];
11269		$tm[3] = $tm[0];
11270		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11271		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11272		//rotate the coordinate system around ($x,$y)
11273		$this->Transform($tm);
11274	}
11275
11276	/**
11277	 * Skew horizontally.
11278	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11279	 * @param $x (int) abscissa of the skewing center. default is current x position
11280	 * @param $y (int) ordinate of the skewing center. default is current y position
11281	 * @public
11282	 * @since 2.1.000 (2008-01-07)
11283	 * @see StartTransform(), StopTransform()
11284	 */
11285	public function SkewX($angle_x, $x='', $y='') {
11286		$this->Skew($angle_x, 0, $x, $y);
11287	}
11288
11289	/**
11290	 * Skew vertically.
11291	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11292	 * @param $x (int) abscissa of the skewing center. default is current x position
11293	 * @param $y (int) ordinate of the skewing center. default is current y position
11294	 * @public
11295	 * @since 2.1.000 (2008-01-07)
11296	 * @see StartTransform(), StopTransform()
11297	 */
11298	public function SkewY($angle_y, $x='', $y='') {
11299		$this->Skew(0, $angle_y, $x, $y);
11300	}
11301
11302	/**
11303	 * Skew.
11304	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11305	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11306	 * @param $x (int) abscissa of the skewing center. default is current x position
11307	 * @param $y (int) ordinate of the skewing center. default is current y position
11308	 * @public
11309	 * @since 2.1.000 (2008-01-07)
11310	 * @see StartTransform(), StopTransform()
11311	 */
11312	public function Skew($angle_x, $angle_y, $x='', $y='') {
11313		if ($x === '') {
11314			$x = $this->x;
11315		}
11316		if ($y === '') {
11317			$y = $this->y;
11318		}
11319		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11320			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11321		}
11322		$x *= $this->k;
11323		$y = ($this->h - $y) * $this->k;
11324		//calculate elements of transformation matrix
11325		$tm = array();
11326		$tm[0] = 1;
11327		$tm[1] = tan(deg2rad($angle_y));
11328		$tm[2] = tan(deg2rad($angle_x));
11329		$tm[3] = 1;
11330		$tm[4] = -$tm[2] * $y;
11331		$tm[5] = -$tm[1] * $x;
11332		//skew the coordinate system
11333		$this->Transform($tm);
11334	}
11335
11336	/**
11337	 * Apply graphic transformations.
11338	 * @param $tm (array) transformation matrix
11339	 * @protected
11340	 * @since 2.1.000 (2008-01-07)
11341	 * @see StartTransform(), StopTransform()
11342	 */
11343	protected function Transform($tm) {
11344		if ($this->state != 2) {
11345			return;
11346		}
11347		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11348		// add tranformation matrix
11349		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11350		// update transformation mark
11351		if ($this->inxobj) {
11352			// we are inside an XObject template
11353			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11354				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11355				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11356			}
11357		} elseif (end($this->transfmrk[$this->page]) !== false) {
11358			$key = key($this->transfmrk[$this->page]);
11359			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11360		}
11361	}
11362
11363	// END TRANSFORMATIONS SECTION -------------------------
11364
11365	// START GRAPHIC FUNCTIONS SECTION ---------------------
11366	// The following section is based on the code provided by David Hernandez Sanz
11367
11368	/**
11369	 * 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.
11370	 * @param $width (float) The width.
11371	 * @public
11372	 * @since 1.0
11373	 * @see Line(), Rect(), Cell(), MultiCell()
11374	 */
11375	public function SetLineWidth($width) {
11376		//Set line width
11377		$this->LineWidth = $width;
11378		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11379		if ($this->state == 2) {
11380			$this->_out($this->linestyleWidth);
11381		}
11382	}
11383
11384	/**
11385	 * Returns the current the line width.
11386	 * @return int Line width
11387	 * @public
11388	 * @since 2.1.000 (2008-01-07)
11389	 * @see Line(), SetLineWidth()
11390	 */
11391	public function GetLineWidth() {
11392		return $this->LineWidth;
11393	}
11394
11395	/**
11396	 * Set line style.
11397	 * @param $style (array) Line style. Array with keys among the following:
11398	 * <ul>
11399	 *	 <li>width (float): Width of the line in user units.</li>
11400	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11401	 * butt, round, square. The difference between "square" and "butt" is that
11402	 * "square" projects a flat end past the end of the line.</li>
11403	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11404	 * bevel.</li>
11405	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11406	 * series of length values, which are the lengths of the on and off dashes.
11407	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11408	 * 1 off, 2 on, 1 off, ...</li>
11409	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11410	 * the point at which the pattern starts.</li>
11411	 *	 <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>
11412	 * </ul>
11413	 * @param $ret (boolean) if true do not send the command.
11414	 * @return string the PDF command
11415	 * @public
11416	 * @since 2.1.000 (2008-01-08)
11417	 */
11418	public function SetLineStyle($style, $ret=false) {
11419		$s = ''; // string to be returned
11420		if (!is_array($style)) {
11421			return;
11422		}
11423		if (isset($style['width'])) {
11424			$this->LineWidth = $style['width'];
11425			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11426			$s .= $this->linestyleWidth.' ';
11427		}
11428		if (isset($style['cap'])) {
11429			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11430			if (isset($ca[$style['cap']])) {
11431				$this->linestyleCap = $ca[$style['cap']].' J';
11432				$s .= $this->linestyleCap.' ';
11433			}
11434		}
11435		if (isset($style['join'])) {
11436			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11437			if (isset($ja[$style['join']])) {
11438				$this->linestyleJoin = $ja[$style['join']].' j';
11439				$s .= $this->linestyleJoin.' ';
11440			}
11441		}
11442		if (isset($style['dash'])) {
11443			$dash_string = '';
11444			if ($style['dash']) {
11445				if (preg_match('/^.+,/', $style['dash']) > 0) {
11446					$tab = explode(',', $style['dash']);
11447				} else {
11448					$tab = array($style['dash']);
11449				}
11450				$dash_string = '';
11451				foreach ($tab as $i => $v) {
11452					if ($i) {
11453						$dash_string .= ' ';
11454					}
11455					$dash_string .= sprintf('%F', $v);
11456				}
11457			}
11458			if (!isset($style['phase']) OR !$style['dash']) {
11459				$style['phase'] = 0;
11460			}
11461			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11462			$s .= $this->linestyleDash.' ';
11463		}
11464		if (isset($style['color'])) {
11465			$s .= $this->SetDrawColorArray($style['color'], true).' ';
11466		}
11467		if (!$ret AND ($this->state == 2)) {
11468			$this->_out($s);
11469		}
11470		return $s;
11471	}
11472
11473	/**
11474	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11475	 * @param $x (float) Abscissa of point.
11476	 * @param $y (float) Ordinate of point.
11477	 * @protected
11478	 * @since 2.1.000 (2008-01-08)
11479	 */
11480	protected function _outPoint($x, $y) {
11481		if ($this->state == 2) {
11482			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11483		}
11484	}
11485
11486	/**
11487	 * Append a straight line segment from the current point to the point (x, y).
11488	 * The new current point shall be (x, y).
11489	 * @param $x (float) Abscissa of end point.
11490	 * @param $y (float) Ordinate of end point.
11491	 * @protected
11492	 * @since 2.1.000 (2008-01-08)
11493	 */
11494	protected function _outLine($x, $y) {
11495		if ($this->state == 2) {
11496			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11497		}
11498	}
11499
11500	/**
11501	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11502	 * @param $x (float) Abscissa of upper-left corner.
11503	 * @param $y (float) Ordinate of upper-left corner.
11504	 * @param $w (float) Width.
11505	 * @param $h (float) Height.
11506	 * @param $op (string) options
11507	 * @protected
11508	 * @since 2.1.000 (2008-01-08)
11509	 */
11510	protected function _outRect($x, $y, $w, $h, $op) {
11511		if ($this->state == 2) {
11512			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11513		}
11514	}
11515
11516	/**
11517	 * 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.
11518	 * The new current point shall be (x3, y3).
11519	 * @param $x1 (float) Abscissa of control point 1.
11520	 * @param $y1 (float) Ordinate of control point 1.
11521	 * @param $x2 (float) Abscissa of control point 2.
11522	 * @param $y2 (float) Ordinate of control point 2.
11523	 * @param $x3 (float) Abscissa of end point.
11524	 * @param $y3 (float) Ordinate of end point.
11525	 * @protected
11526	 * @since 2.1.000 (2008-01-08)
11527	 */
11528	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11529		if ($this->state == 2) {
11530			$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)));
11531		}
11532	}
11533
11534	/**
11535	 * 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.
11536	 * The new current point shall be (x3, y3).
11537	 * @param $x2 (float) Abscissa of control point 2.
11538	 * @param $y2 (float) Ordinate of control point 2.
11539	 * @param $x3 (float) Abscissa of end point.
11540	 * @param $y3 (float) Ordinate of end point.
11541	 * @protected
11542	 * @since 4.9.019 (2010-04-26)
11543	 */
11544	protected function _outCurveV($x2, $y2, $x3, $y3) {
11545		if ($this->state == 2) {
11546			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11547		}
11548	}
11549
11550	/**
11551	 * 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.
11552	 * The new current point shall be (x3, y3).
11553	 * @param $x1 (float) Abscissa of control point 1.
11554	 * @param $y1 (float) Ordinate of control point 1.
11555	 * @param $x3 (float) Abscissa of end point.
11556	 * @param $y3 (float) Ordinate of end point.
11557	 * @protected
11558	 * @since 2.1.000 (2008-01-08)
11559	 */
11560	protected function _outCurveY($x1, $y1, $x3, $y3) {
11561		if ($this->state == 2) {
11562			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11563		}
11564	}
11565
11566	/**
11567	 * Draws a line between two points.
11568	 * @param $x1 (float) Abscissa of first point.
11569	 * @param $y1 (float) Ordinate of first point.
11570	 * @param $x2 (float) Abscissa of second point.
11571	 * @param $y2 (float) Ordinate of second point.
11572	 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11573	 * @public
11574	 * @since 1.0
11575	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11576	 */
11577	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11578		if ($this->state != 2) {
11579			return;
11580		}
11581		if (is_array($style)) {
11582			$this->SetLineStyle($style);
11583		}
11584		$this->_outPoint($x1, $y1);
11585		$this->_outLine($x2, $y2);
11586		$this->_out('S');
11587	}
11588
11589	/**
11590	 * Draws a rectangle.
11591	 * @param $x (float) Abscissa of upper-left corner.
11592	 * @param $y (float) Ordinate of upper-left corner.
11593	 * @param $w (float) Width.
11594	 * @param $h (float) Height.
11595	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11596	 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11597	 * <ul>
11598	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11599	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11600	 * </ul>
11601	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11602	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11603	 * @public
11604	 * @since 1.0
11605	 * @see SetLineStyle()
11606	 */
11607	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11608		if ($this->state != 2) {
11609			return;
11610		}
11611		if (empty($style)) {
11612			$style = 'S';
11613		}
11614		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11615			// set background color
11616			$this->SetFillColorArray($fill_color);
11617		}
11618		if (!empty($border_style)) {
11619			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11620				//set global style for border
11621				$this->SetLineStyle($border_style['all']);
11622				$border_style = array();
11623			} else {
11624				// remove stroke operator from style
11625				$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*' );
11626				if (isset($opnostroke[$style])) {
11627					$style = $opnostroke[$style];
11628				}
11629			}
11630		}
11631		if (!empty($style)) {
11632			$op = TCPDF_STATIC::getPathPaintOperator($style);
11633			$this->_outRect($x, $y, $w, $h, $op);
11634		}
11635		if (!empty($border_style)) {
11636			$border_style2 = array();
11637			foreach ($border_style as $line => $value) {
11638				$length = strlen($line);
11639				for ($i = 0; $i < $length; ++$i) {
11640					$border_style2[$line[$i]] = $value;
11641				}
11642			}
11643			$border_style = $border_style2;
11644			if (isset($border_style['L']) AND $border_style['L']) {
11645				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11646			}
11647			if (isset($border_style['T']) AND $border_style['T']) {
11648				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11649			}
11650			if (isset($border_style['R']) AND $border_style['R']) {
11651				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11652			}
11653			if (isset($border_style['B']) AND $border_style['B']) {
11654				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11655			}
11656		}
11657	}
11658
11659	/**
11660	 * Draws a Bezier curve.
11661	 * The Bezier curve is a tangent to the line between the control points at
11662	 * either end of the curve.
11663	 * @param $x0 (float) Abscissa of start point.
11664	 * @param $y0 (float) Ordinate of start point.
11665	 * @param $x1 (float) Abscissa of control point 1.
11666	 * @param $y1 (float) Ordinate of control point 1.
11667	 * @param $x2 (float) Abscissa of control point 2.
11668	 * @param $y2 (float) Ordinate of control point 2.
11669	 * @param $x3 (float) Abscissa of end point.
11670	 * @param $y3 (float) Ordinate of end point.
11671	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11672	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11673	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11674	 * @public
11675	 * @see SetLineStyle()
11676	 * @since 2.1.000 (2008-01-08)
11677	 */
11678	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11679		if ($this->state != 2) {
11680			return;
11681		}
11682		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11683			$this->SetFillColorArray($fill_color);
11684		}
11685		$op = TCPDF_STATIC::getPathPaintOperator($style);
11686		if ($line_style) {
11687			$this->SetLineStyle($line_style);
11688		}
11689		$this->_outPoint($x0, $y0);
11690		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11691		$this->_out($op);
11692	}
11693
11694	/**
11695	 * Draws a poly-Bezier curve.
11696	 * Each Bezier curve segment is a tangent to the line between the control points at
11697	 * either end of the curve.
11698	 * @param $x0 (float) Abscissa of start point.
11699	 * @param $y0 (float) Ordinate of start point.
11700	 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11701	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11702	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11703	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11704	 * @public
11705	 * @see SetLineStyle()
11706	 * @since 3.0008 (2008-05-12)
11707	 */
11708	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11709		if ($this->state != 2) {
11710			return;
11711		}
11712		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11713			$this->SetFillColorArray($fill_color);
11714		}
11715		$op = TCPDF_STATIC::getPathPaintOperator($style);
11716		if ($op == 'f') {
11717			$line_style = array();
11718		}
11719		if ($line_style) {
11720			$this->SetLineStyle($line_style);
11721		}
11722		$this->_outPoint($x0, $y0);
11723		foreach ($segments as $segment) {
11724			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11725			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11726		}
11727		$this->_out($op);
11728	}
11729
11730	/**
11731	 * Draws an ellipse.
11732	 * An ellipse is formed from n Bezier curves.
11733	 * @param $x0 (float) Abscissa of center point.
11734	 * @param $y0 (float) Ordinate of center point.
11735	 * @param $rx (float) Horizontal radius.
11736	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11737	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11738	 * @param $astart: (float) Angle start of draw line. Default value: 0.
11739	 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11740	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11741	 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11742	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11743	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11744	 * @author Nicola Asuni
11745	 * @public
11746	 * @since 2.1.000 (2008-01-08)
11747	 */
11748	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11749		if ($this->state != 2) {
11750			return;
11751		}
11752		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11753			$ry = $rx;
11754		}
11755		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11756			$this->SetFillColorArray($fill_color);
11757		}
11758		$op = TCPDF_STATIC::getPathPaintOperator($style);
11759		if ($op == 'f') {
11760			$line_style = array();
11761		}
11762		if ($line_style) {
11763			$this->SetLineStyle($line_style);
11764		}
11765		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11766		$this->_out($op);
11767	}
11768
11769	/**
11770	 * Append an elliptical arc to the current path.
11771	 * An ellipse is formed from n Bezier curves.
11772	 * @param $xc (float) Abscissa of center point.
11773	 * @param $yc (float) Ordinate of center point.
11774	 * @param $rx (float) Horizontal radius.
11775	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11776	 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11777	 * @param $angs: (float) Angle start of draw line. Default value: 0.
11778	 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11779	 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11780	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11781	 * @param $startpoint (boolean) if true output a starting point.
11782	 * @param $ccw (boolean) if true draws in counter-clockwise.
11783	 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11784	 * @return array bounding box coordinates (x min, y min, x max, y max)
11785	 * @author Nicola Asuni
11786	 * @protected
11787	 * @since 4.9.019 (2010-04-26)
11788	 */
11789	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11790		if (($rx <= 0) OR ($ry < 0)) {
11791			return;
11792		}
11793		$k = $this->k;
11794		if ($nc < 2) {
11795			$nc = 2;
11796		}
11797		$xmin = 2147483647;
11798		$ymin = 2147483647;
11799		$xmax = 0;
11800		$ymax = 0;
11801		if ($pie) {
11802			// center of the arc
11803			$this->_outPoint($xc, $yc);
11804		}
11805		$xang = deg2rad((float) $xang);
11806		$angs = deg2rad((float) $angs);
11807		$angf = deg2rad((float) $angf);
11808		if ($svg) {
11809			$as = $angs;
11810			$af = $angf;
11811		} else {
11812			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11813			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11814		}
11815		if ($as < 0) {
11816			$as += (2 * M_PI);
11817		}
11818		if ($af < 0) {
11819			$af += (2 * M_PI);
11820		}
11821		if ($ccw AND ($as > $af)) {
11822			// reverse rotation
11823			$as -= (2 * M_PI);
11824		} elseif (!$ccw AND ($as < $af)) {
11825			// reverse rotation
11826			$af -= (2 * M_PI);
11827		}
11828		$total_angle = ($af - $as);
11829		if ($nc < 2) {
11830			$nc = 2;
11831		}
11832		// total arcs to draw
11833		$nc *= (2 * abs($total_angle) / M_PI);
11834		$nc = round($nc) + 1;
11835		// angle of each arc
11836		$arcang = ($total_angle / $nc);
11837		// center point in PDF coordinates
11838		$x0 = $xc;
11839		$y0 = ($this->h - $yc);
11840		// starting angle
11841		$ang = $as;
11842		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11843		$cos_xang = cos($xang);
11844		$sin_xang = sin($xang);
11845		$cos_ang = cos($ang);
11846		$sin_ang = sin($ang);
11847		// first arc point
11848		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11849		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11850		// first Bezier control point
11851		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11852		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11853		if ($pie) {
11854			// line from center to arc starting point
11855			$this->_outLine($px1, $this->h - $py1);
11856		} elseif ($startpoint) {
11857			// arc starting point
11858			$this->_outPoint($px1, $this->h - $py1);
11859		}
11860		// draw arcs
11861		for ($i = 1; $i <= $nc; ++$i) {
11862			// starting angle
11863			$ang = $as + ($i * $arcang);
11864			if ($i == $nc) {
11865				$ang = $af;
11866			}
11867			$cos_ang = cos($ang);
11868			$sin_ang = sin($ang);
11869			// second arc point
11870			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11871			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11872			// second Bezier control point
11873			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11874			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11875			// draw arc
11876			$cx1 = ($px1 + $qx1);
11877			$cy1 = ($this->h - ($py1 + $qy1));
11878			$cx2 = ($px2 - $qx2);
11879			$cy2 = ($this->h - ($py2 - $qy2));
11880			$cx3 = $px2;
11881			$cy3 = ($this->h - $py2);
11882			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11883			// get bounding box coordinates
11884			$xmin = min($xmin, $cx1, $cx2, $cx3);
11885			$ymin = min($ymin, $cy1, $cy2, $cy3);
11886			$xmax = max($xmax, $cx1, $cx2, $cx3);
11887			$ymax = max($ymax, $cy1, $cy2, $cy3);
11888			// move to next point
11889			$px1 = $px2;
11890			$py1 = $py2;
11891			$qx1 = $qx2;
11892			$qy1 = $qy2;
11893		}
11894		if ($pie) {
11895			$this->_outLine($xc, $yc);
11896			// get bounding box coordinates
11897			$xmin = min($xmin, $xc);
11898			$ymin = min($ymin, $yc);
11899			$xmax = max($xmax, $xc);
11900			$ymax = max($ymax, $yc);
11901		}
11902		return array($xmin, $ymin, $xmax, $ymax);
11903	}
11904
11905	/**
11906	 * Draws a circle.
11907	 * A circle is formed from n Bezier curves.
11908	 * @param $x0 (float) Abscissa of center point.
11909	 * @param $y0 (float) Ordinate of center point.
11910	 * @param $r (float) Radius.
11911	 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11912	 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11913	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11914	 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11915	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11916	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11917	 * @public
11918	 * @since 2.1.000 (2008-01-08)
11919	 */
11920	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11921		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11922	}
11923
11924	/**
11925	 * Draws a polygonal line
11926	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11927	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11928	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11929	 * <ul>
11930	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11931	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11932	 * </ul>
11933	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11934	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11935	 * @since 4.8.003 (2009-09-15)
11936	 * @public
11937	 */
11938	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11939		$this->Polygon($p, $style, $line_style, $fill_color, false);
11940	}
11941
11942	/**
11943	 * Draws a polygon.
11944	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11945	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11946	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11947	 * <ul>
11948	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11949	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11950	 * </ul>
11951	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11952	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11953	 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11954	 * @public
11955	 * @since 2.1.000 (2008-01-08)
11956	 */
11957	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11958		if ($this->state != 2) {
11959			return;
11960		}
11961		$nc = count($p); // number of coordinates
11962		$np = $nc / 2; // number of points
11963		if ($closed) {
11964			// close polygon by adding the first 2 points at the end (one line)
11965			for ($i = 0; $i < 4; ++$i) {
11966				$p[$nc + $i] = $p[$i];
11967			}
11968			// copy style for the last added line
11969			if (isset($line_style[0])) {
11970				$line_style[$np] = $line_style[0];
11971			}
11972			$nc += 4;
11973		}
11974		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11975			$this->SetFillColorArray($fill_color);
11976		}
11977		$op = TCPDF_STATIC::getPathPaintOperator($style);
11978		if ($op == 'f') {
11979			$line_style = array();
11980		}
11981		$draw = true;
11982		if ($line_style) {
11983			if (isset($line_style['all'])) {
11984				$this->SetLineStyle($line_style['all']);
11985			} else {
11986				$draw = false;
11987				if ($op == 'B') {
11988					// draw fill
11989					$op = 'f';
11990					$this->_outPoint($p[0], $p[1]);
11991					for ($i = 2; $i < $nc; $i = $i + 2) {
11992						$this->_outLine($p[$i], $p[$i + 1]);
11993					}
11994					$this->_out($op);
11995				}
11996				// draw outline
11997				$this->_outPoint($p[0], $p[1]);
11998				for ($i = 2; $i < $nc; $i = $i + 2) {
11999					$line_num = ($i / 2) - 1;
12000					if (isset($line_style[$line_num])) {
12001						if ($line_style[$line_num] != 0) {
12002							if (is_array($line_style[$line_num])) {
12003								$this->_out('S');
12004								$this->SetLineStyle($line_style[$line_num]);
12005								$this->_outPoint($p[$i - 2], $p[$i - 1]);
12006								$this->_outLine($p[$i], $p[$i + 1]);
12007								$this->_out('S');
12008								$this->_outPoint($p[$i], $p[$i + 1]);
12009							} else {
12010								$this->_outLine($p[$i], $p[$i + 1]);
12011							}
12012						}
12013					} else {
12014						$this->_outLine($p[$i], $p[$i + 1]);
12015					}
12016				}
12017				$this->_out($op);
12018			}
12019		}
12020		if ($draw) {
12021			$this->_outPoint($p[0], $p[1]);
12022			for ($i = 2; $i < $nc; $i = $i + 2) {
12023				$this->_outLine($p[$i], $p[$i + 1]);
12024			}
12025			$this->_out($op);
12026		}
12027	}
12028
12029	/**
12030	 * Draws a regular polygon.
12031	 * @param $x0 (float) Abscissa of center point.
12032	 * @param $y0 (float) Ordinate of center point.
12033	 * @param $r: (float) Radius of inscribed circle.
12034	 * @param $ns (integer) Number of sides.
12035	 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
12036	 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
12037	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12038	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12039	 * <ul>
12040	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12041	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12042	 * </ul>
12043	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12044	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12045	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12046	 * <ul>
12047	 *	 <li>D or empty string: Draw (default).</li>
12048	 *	 <li>F: Fill.</li>
12049	 *	 <li>DF or FD: Draw and fill.</li>
12050	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12051	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12052	 * </ul>
12053	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12054	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12055	 * @public
12056	 * @since 2.1.000 (2008-01-08)
12057	 */
12058	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()) {
12059		if (3 > $ns) {
12060			$ns = 3;
12061		}
12062		if ($draw_circle) {
12063			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12064		}
12065		$p = array();
12066		for ($i = 0; $i < $ns; ++$i) {
12067			$a = $angle + ($i * 360 / $ns);
12068			$a_rad = deg2rad((float) $a);
12069			$p[] = $x0 + ($r * sin($a_rad));
12070			$p[] = $y0 + ($r * cos($a_rad));
12071		}
12072		$this->Polygon($p, $style, $line_style, $fill_color);
12073	}
12074
12075	/**
12076	 * Draws a star polygon
12077	 * @param $x0 (float) Abscissa of center point.
12078	 * @param $y0 (float) Ordinate of center point.
12079	 * @param $r (float) Radius of inscribed circle.
12080	 * @param $nv (integer) Number of vertices.
12081	 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12082	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
12083	 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
12084	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12085	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12086	 * <ul>
12087	 *	 <li>all: Line style of all sides. Array like for
12088	 * SetLineStyle().</li>
12089	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12090	 * </ul>
12091	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12092	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12093	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12094	 * <ul>
12095	 *	 <li>D or empty string: Draw (default).</li>
12096	 *	 <li>F: Fill.</li>
12097	 *	 <li>DF or FD: Draw and fill.</li>
12098	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12099	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12100	 * </ul>
12101	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12102	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12103	 * @public
12104	 * @since 2.1.000 (2008-01-08)
12105	 */
12106	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()) {
12107		if ($nv < 2) {
12108			$nv = 2;
12109		}
12110		if ($draw_circle) {
12111			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12112		}
12113		$p2 = array();
12114		$visited = array();
12115		for ($i = 0; $i < $nv; ++$i) {
12116			$a = $angle + ($i * 360 / $nv);
12117			$a_rad = deg2rad((float) $a);
12118			$p2[] = $x0 + ($r * sin($a_rad));
12119			$p2[] = $y0 + ($r * cos($a_rad));
12120			$visited[] = false;
12121		}
12122		$p = array();
12123		$i = 0;
12124		do {
12125			$p[] = $p2[$i * 2];
12126			$p[] = $p2[($i * 2) + 1];
12127			$visited[$i] = true;
12128			$i += $ng;
12129			$i %= $nv;
12130		} while (!$visited[$i]);
12131		$this->Polygon($p, $style, $line_style, $fill_color);
12132	}
12133
12134	/**
12135	 * Draws a rounded rectangle.
12136	 * @param $x (float) Abscissa of upper-left corner.
12137	 * @param $y (float) Ordinate of upper-left corner.
12138	 * @param $w (float) Width.
12139	 * @param $h (float) Height.
12140	 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12141	 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12142	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12143	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12144	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12145	 * @public
12146	 * @since 2.1.000 (2008-01-08)
12147	 */
12148	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12149		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12150	}
12151
12152	/**
12153	 * Draws a rounded rectangle.
12154	 * @param $x (float) Abscissa of upper-left corner.
12155	 * @param $y (float) Ordinate of upper-left corner.
12156	 * @param $w (float) Width.
12157	 * @param $h (float) Height.
12158	 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12159	 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12160	 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12161	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12162	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12163	 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12164	 * @public
12165	 * @since 4.9.019 (2010-04-22)
12166	 */
12167	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12168		if ($this->state != 2) {
12169			return;
12170		}
12171		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12172			// Not rounded
12173			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12174			return;
12175		}
12176		// Rounded
12177		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12178			$this->SetFillColorArray($fill_color);
12179		}
12180		$op = TCPDF_STATIC::getPathPaintOperator($style);
12181		if ($op == 'f') {
12182			$border_style = array();
12183		}
12184		if ($border_style) {
12185			$this->SetLineStyle($border_style);
12186		}
12187		$MyArc = 4 / 3 * (sqrt(2) - 1);
12188		$this->_outPoint($x + $rx, $y);
12189		$xc = $x + $w - $rx;
12190		$yc = $y + $ry;
12191		$this->_outLine($xc, $y);
12192		if ($round_corner[0]) {
12193			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12194		} else {
12195			$this->_outLine($x + $w, $y);
12196		}
12197		$xc = $x + $w - $rx;
12198		$yc = $y + $h - $ry;
12199		$this->_outLine($x + $w, $yc);
12200		if ($round_corner[1]) {
12201			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12202		} else {
12203			$this->_outLine($x + $w, $y + $h);
12204		}
12205		$xc = $x + $rx;
12206		$yc = $y + $h - $ry;
12207		$this->_outLine($xc, $y + $h);
12208		if ($round_corner[2]) {
12209			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12210		} else {
12211			$this->_outLine($x, $y + $h);
12212		}
12213		$xc = $x + $rx;
12214		$yc = $y + $ry;
12215		$this->_outLine($x, $yc);
12216		if ($round_corner[3]) {
12217			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12218		} else {
12219			$this->_outLine($x, $y);
12220			$this->_outLine($x + $rx, $y);
12221		}
12222		$this->_out($op);
12223	}
12224
12225	/**
12226	 * Draws a grahic arrow.
12227	 * @param $x0 (float) Abscissa of first point.
12228	 * @param $y0 (float) Ordinate of first point.
12229	 * @param $x1 (float) Abscissa of second point.
12230	 * @param $y1 (float) Ordinate of second point.
12231	 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12232	 * @param $arm_size (float) length of arrowhead arms
12233	 * @param $arm_angle (int) angle between an arm and the shaft
12234	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12235	 * @since 4.6.018 (2009-07-10)
12236	 */
12237	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12238		// getting arrow direction angle
12239		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12240		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12241		if ($dir_angle < 0) {
12242			$dir_angle += (2 * M_PI);
12243		}
12244		$arm_angle = deg2rad($arm_angle);
12245		$sx1 = $x1;
12246		$sy1 = $y1;
12247		if ($head_style > 0) {
12248			// calculate the stopping point for the arrow shaft
12249			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12250			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12251		}
12252		// main arrow line / shaft
12253		$this->Line($x0, $y0, $sx1, $sy1);
12254		// left arrowhead arm tip
12255		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12256		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12257		// right arrowhead arm tip
12258		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12259		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12260		$mode = 'D';
12261		$style = array();
12262		switch ($head_style) {
12263			case 0: {
12264				// draw only arrowhead arms
12265				$mode = 'D';
12266				$style = array(1, 1, 0);
12267				break;
12268			}
12269			case 1: {
12270				// draw closed arrowhead, but no fill
12271				$mode = 'D';
12272				break;
12273			}
12274			case 2: {
12275				// closed and filled arrowhead
12276				$mode = 'DF';
12277				break;
12278			}
12279			case 3: {
12280				// filled arrowhead
12281				$mode = 'F';
12282				break;
12283			}
12284		}
12285		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12286	}
12287
12288	// END GRAPHIC FUNCTIONS SECTION -----------------------
12289
12290	/**
12291	 * Add a Named Destination.
12292	 * NOTE: destination names are unique, so only last entry will be saved.
12293	 * @param $name (string) Destination name.
12294	 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12295	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12296	 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12297	 * @return (string) Stripped named destination identifier or false in case of error.
12298	 * @public
12299	 * @author Christian Deligant, Nicola Asuni
12300	 * @since 5.9.097 (2011-06-23)
12301	 */
12302	public function setDestination($name, $y=-1, $page='', $x=-1) {
12303		// remove unsupported characters
12304		$name = TCPDF_STATIC::encodeNameObject($name);
12305		if (TCPDF_STATIC::empty_string($name)) {
12306			return false;
12307		}
12308		if ($y == -1) {
12309			$y = $this->GetY();
12310		} elseif ($y < 0) {
12311			$y = 0;
12312		} elseif ($y > $this->h) {
12313			$y = $this->h;
12314		}
12315		if ($x == -1) {
12316			$x = $this->GetX();
12317		} elseif ($x < 0) {
12318			$x = 0;
12319		} elseif ($x > $this->w) {
12320			$x = $this->w;
12321		}
12322		$fixed = false;
12323		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12324			$page = intval(substr($page, 1));
12325			// this page number will not be changed when moving/add/deleting pages
12326			$fixed = true;
12327		}
12328		if (empty($page)) {
12329			$page = $this->PageNo();
12330			if (empty($page)) {
12331				return;
12332			}
12333		}
12334		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12335		return $name;
12336	}
12337
12338	/**
12339	 * Return the Named Destination array.
12340	 * @return (array) Named Destination array.
12341	 * @public
12342	 * @author Nicola Asuni
12343	 * @since 5.9.097 (2011-06-23)
12344	 */
12345	public function getDestination() {
12346		return $this->dests;
12347	}
12348
12349	/**
12350	 * Insert Named Destinations.
12351	 * @protected
12352	 * @author Johannes G\FCntert, Nicola Asuni
12353	 * @since 5.9.098 (2011-06-23)
12354	 */
12355	protected function _putdests() {
12356		if (empty($this->dests)) {
12357			return;
12358		}
12359		$this->n_dests = $this->_newobj();
12360		$out = ' <<';
12361		foreach($this->dests as $name => $o) {
12362			$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)));
12363		}
12364		$out .= ' >>';
12365		$out .= "\n".'endobj';
12366		$this->_out($out);
12367	}
12368
12369	/**
12370	 * Adds a bookmark - alias for Bookmark().
12371	 * @param $txt (string) Bookmark description.
12372	 * @param $level (int) Bookmark level (minimum value is 0).
12373	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12374	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12375	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12376	 * @param $color (array) RGB color array (values from 0 to 255).
12377	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12378	 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12379	 * @public
12380	 */
12381	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12382		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12383	}
12384
12385	/**
12386	 * Adds a bookmark.
12387	 * @param $txt (string) Bookmark description.
12388	 * @param $level (int) Bookmark level (minimum value is 0).
12389	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12390	 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12391	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12392	 * @param $color (array) RGB color array (values from 0 to 255).
12393	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12394	 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12395	 * @public
12396	 * @since 2.1.002 (2008-02-12)
12397	 */
12398	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12399		if ($level < 0) {
12400			$level = 0;
12401		}
12402		if (isset($this->outlines[0])) {
12403			$lastoutline = end($this->outlines);
12404			$maxlevel = $lastoutline['l'] + 1;
12405		} else {
12406			$maxlevel = 0;
12407		}
12408		if ($level > $maxlevel) {
12409			$level = $maxlevel;
12410		}
12411		if ($y == -1) {
12412			$y = $this->GetY();
12413		} elseif ($y < 0) {
12414			$y = 0;
12415		} elseif ($y > $this->h) {
12416			$y = $this->h;
12417		}
12418		if ($x == -1) {
12419			$x = $this->GetX();
12420		} elseif ($x < 0) {
12421			$x = 0;
12422		} elseif ($x > $this->w) {
12423			$x = $this->w;
12424		}
12425		$fixed = false;
12426		$pageAsString = (string) $page;
12427		if ($pageAsString && $pageAsString[0] == '*') {
12428			$page = intval(substr($page, 1));
12429			// this page number will not be changed when moving/add/deleting pages
12430			$fixed = true;
12431		}
12432		if (empty($page)) {
12433			$page = $this->PageNo();
12434			if (empty($page)) {
12435				return;
12436			}
12437		}
12438		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12439	}
12440
12441	/**
12442	 * Sort bookmarks for page and key.
12443	 * @protected
12444	 * @since 5.9.119 (2011-09-19)
12445	 */
12446	protected function sortBookmarks() {
12447		// get sorting columns
12448		$outline_p = array();
12449		$outline_y = array();
12450		foreach ($this->outlines as $key => $row) {
12451			$outline_p[$key] = $row['p'];
12452			$outline_k[$key] = $key;
12453		}
12454		// sort outlines by page and original position
12455		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12456	}
12457
12458	/**
12459	 * Create a bookmark PDF string.
12460	 * @protected
12461	 * @author Olivier Plathey, Nicola Asuni
12462	 * @since 2.1.002 (2008-02-12)
12463	 */
12464	protected function _putbookmarks() {
12465		$nb = count($this->outlines);
12466		if ($nb == 0) {
12467			return;
12468		}
12469		// sort bookmarks
12470		$this->sortBookmarks();
12471		$lru = array();
12472		$level = 0;
12473		foreach ($this->outlines as $i => $o) {
12474			if ($o['l'] > 0) {
12475				$parent = $lru[($o['l'] - 1)];
12476				//Set parent and last pointers
12477				$this->outlines[$i]['parent'] = $parent;
12478				$this->outlines[$parent]['last'] = $i;
12479				if ($o['l'] > $level) {
12480					//Level increasing: set first pointer
12481					$this->outlines[$parent]['first'] = $i;
12482				}
12483			} else {
12484				$this->outlines[$i]['parent'] = $nb;
12485			}
12486			if (($o['l'] <= $level) AND ($i > 0)) {
12487				//Set prev and next pointers
12488				$prev = $lru[$o['l']];
12489				$this->outlines[$prev]['next'] = $i;
12490				$this->outlines[$i]['prev'] = $prev;
12491			}
12492			$lru[$o['l']] = $i;
12493			$level = $o['l'];
12494		}
12495		//Outline items
12496		$n = $this->n + 1;
12497		$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';
12498		foreach ($this->outlines as $i => $o) {
12499			$oid = $this->_newobj();
12500			// covert HTML title to string
12501			$title = preg_replace($nltags, "\n", $o['t']);
12502			$title = preg_replace("/[\r]+/si", '', $title);
12503			$title = preg_replace("/[\n]+/si", "\n", $title);
12504			$title = strip_tags($title);
12505			$title = $this->stringTrim($title);
12506			$out = '<</Title '.$this->_textstring($title, $oid);
12507			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12508			if (isset($o['prev'])) {
12509				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12510			}
12511			if (isset($o['next'])) {
12512				$out .= ' /Next '.($n + $o['next']).' 0 R';
12513			}
12514			if (isset($o['first'])) {
12515				$out .= ' /First '.($n + $o['first']).' 0 R';
12516			}
12517			if (isset($o['last'])) {
12518				$out .= ' /Last '.($n + $o['last']).' 0 R';
12519			}
12520			if (isset($o['u']) AND !empty($o['u'])) {
12521				// link
12522				if (is_string($o['u'])) {
12523					if ($o['u'][0] == '#') {
12524						// internal destination
12525						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12526					} elseif ($o['u'][0] == '%') {
12527						// embedded PDF file
12528						$filename = basename(substr($o['u'], 1));
12529						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12530					} elseif ($o['u'][0] == '*') {
12531						// embedded generic file
12532						$filename = basename(substr($o['u'], 1));
12533						$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});';
12534						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12535					} else {
12536						// external URI link
12537						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12538					}
12539				} elseif (isset($this->links[$o['u']])) {
12540					// internal link ID
12541					$l = $this->links[$o['u']];
12542					if (isset($this->page_obj_id[($l['p'])])) {
12543						$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)));
12544					}
12545				}
12546			} elseif (isset($this->page_obj_id[($o['p'])])) {
12547				// link to a page
12548				$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)));
12549			}
12550			// set font style
12551			$style = 0;
12552			if (!empty($o['s'])) {
12553				// bold
12554				if (strpos($o['s'], 'B') !== false) {
12555					$style |= 2;
12556				}
12557				// oblique
12558				if (strpos($o['s'], 'I') !== false) {
12559					$style |= 1;
12560				}
12561			}
12562			$out .= sprintf(' /F %d', $style);
12563			// set bookmark color
12564			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12565				$color = array_values($o['c']);
12566				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12567			} else {
12568				// black
12569				$out .= ' /C [0.0 0.0 0.0]';
12570			}
12571			$out .= ' /Count 0'; // normally closed item
12572			$out .= ' >>';
12573			$out .= "\n".'endobj';
12574			$this->_out($out);
12575		}
12576		//Outline root
12577		$this->OutlineRoot = $this->_newobj();
12578		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12579	}
12580
12581	// --- JAVASCRIPT ------------------------------------------------------
12582
12583	/**
12584	 * Adds a javascript
12585	 * @param $script (string) Javascript code
12586	 * @public
12587	 * @author Johannes G\FCntert, Nicola Asuni
12588	 * @since 2.1.002 (2008-02-12)
12589	 */
12590	public function IncludeJS($script) {
12591		$this->javascript .= $script;
12592	}
12593
12594	/**
12595	 * Adds a javascript object and return object ID
12596	 * @param $script (string) Javascript code
12597	 * @param $onload (boolean) if true executes this object when opening the document
12598	 * @return int internal object ID
12599	 * @public
12600	 * @author Nicola Asuni
12601	 * @since 4.8.000 (2009-09-07)
12602	 */
12603	public function addJavascriptObject($script, $onload=false) {
12604		if ($this->pdfa_mode) {
12605			// javascript is not allowed in PDF/A mode
12606			return false;
12607		}
12608		++$this->n;
12609		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12610		return $this->n;
12611	}
12612
12613	/**
12614	 * Create a javascript PDF string.
12615	 * @protected
12616	 * @author Johannes G\FCntert, Nicola Asuni
12617	 * @since 2.1.002 (2008-02-12)
12618	 */
12619	protected function _putjavascript() {
12620		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12621			return;
12622		}
12623		if (strpos($this->javascript, 'this.addField') > 0) {
12624			if (!$this->ur['enabled']) {
12625				//$this->setUserRights();
12626			}
12627			// the following two lines are used to avoid form fields duplication after saving
12628			// The addField method only works when releasing user rights (UR3)
12629			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12630			$jsb = "getField('tcpdfdocsaved').value='saved';";
12631			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12632		}
12633		// name tree for javascript
12634		$this->n_js = '<< /Names [';
12635		if (!empty($this->javascript)) {
12636			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12637		}
12638		if (!empty($this->js_objects)) {
12639			foreach ($this->js_objects as $key => $val) {
12640				if ($val['onload']) {
12641					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12642				}
12643			}
12644		}
12645		$this->n_js .= ' ] >>';
12646		// default Javascript object
12647		if (!empty($this->javascript)) {
12648			$obj_id = $this->_newobj();
12649			$out = '<< /S /JavaScript';
12650			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12651			$out .= ' >>';
12652			$out .= "\n".'endobj';
12653			$this->_out($out);
12654		}
12655		// additional Javascript objects
12656		if (!empty($this->js_objects)) {
12657			foreach ($this->js_objects as $key => $val) {
12658				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12659				$this->_out($out);
12660			}
12661		}
12662	}
12663
12664	/**
12665	 * Adds a javascript form field.
12666	 * @param $type (string) field type
12667	 * @param $name (string) field name
12668	 * @param $x (int) horizontal position
12669	 * @param $y (int) vertical position
12670	 * @param $w (int) width
12671	 * @param $h (int) height
12672	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12673	 * @protected
12674	 * @author Denis Van Nuffelen, Nicola Asuni
12675	 * @since 2.1.002 (2008-02-12)
12676	 */
12677	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12678		if ($this->rtl) {
12679			$x = $x - $w;
12680		}
12681		// the followind avoid fields duplication after saving the document
12682		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12683		$k = $this->k;
12684		$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";
12685		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12686		foreach($prop as $key => $val) {
12687			if (strcmp(substr($key, -5), 'Color') == 0) {
12688				$val = TCPDF_COLORS::_JScolor($val);
12689			} else {
12690				$val = "'".$val."'";
12691			}
12692			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12693		}
12694		if ($this->rtl) {
12695			$this->x -= $w;
12696		} else {
12697			$this->x += $w;
12698		}
12699		$this->javascript .= '}';
12700	}
12701
12702	// --- FORM FIELDS -----------------------------------------------------
12703
12704
12705
12706	/**
12707	 * Set default properties for form fields.
12708	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12709	 * @public
12710	 * @author Nicola Asuni
12711	 * @since 4.8.000 (2009-09-06)
12712	 */
12713	public function setFormDefaultProp($prop=array()) {
12714		$this->default_form_prop = $prop;
12715	}
12716
12717	/**
12718	 * Return the default properties for form fields.
12719	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12720	 * @public
12721	 * @author Nicola Asuni
12722	 * @since 4.8.000 (2009-09-06)
12723	 */
12724	public function getFormDefaultProp() {
12725		return $this->default_form_prop;
12726	}
12727
12728	/**
12729	 * Creates a text field
12730	 * @param $name (string) field name
12731	 * @param $w (float) Width of the rectangle
12732	 * @param $h (float) Height of the rectangle
12733	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12734	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12735	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12736	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12737	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12738	 * @public
12739	 * @author Nicola Asuni
12740	 * @since 4.8.000 (2009-09-07)
12741	 */
12742	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12743		if ($x === '') {
12744			$x = $this->x;
12745		}
12746		if ($y === '') {
12747			$y = $this->y;
12748		}
12749		// check page for no-write regions and adapt page margins if necessary
12750		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12751		if ($js) {
12752			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12753			return;
12754		}
12755		// get default style
12756		$prop = array_merge($this->getFormDefaultProp(), $prop);
12757		// get annotation data
12758		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12759		// set default appearance stream
12760		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12761		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12762		$popt['da'] = $fontstyle;
12763		// build appearance stream
12764		$popt['ap'] = array();
12765		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12766		$text = '';
12767		if (isset($prop['value']) AND !empty($prop['value'])) {
12768			$text = $prop['value'];
12769		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12770			$text = $opt['v'];
12771		}
12772		$tmpid = $this->startTemplate($w, $h, false);
12773		$align = '';
12774		if (isset($popt['q'])) {
12775			switch ($popt['q']) {
12776				case 0: {
12777					$align = 'L';
12778					break;
12779				}
12780				case 1: {
12781					$align = 'C';
12782					break;
12783				}
12784				case 2: {
12785					$align = 'R';
12786					break;
12787				}
12788				default: {
12789					$align = '';
12790					break;
12791				}
12792			}
12793		}
12794		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12795		$this->endTemplate();
12796		--$this->n;
12797		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12798		unset($this->xobjects[$tmpid]);
12799		$popt['ap']['n'] .= 'Q EMC';
12800		// merge options
12801		$opt = array_merge($popt, $opt);
12802		// remove some conflicting options
12803		unset($opt['bs']);
12804		// set remaining annotation data
12805		$opt['Subtype'] = 'Widget';
12806		$opt['ft'] = 'Tx';
12807		$opt['t'] = $name;
12808		// Additional annotation's parameters (check _putannotsobj() method):
12809		//$opt['f']
12810		//$opt['as']
12811		//$opt['bs']
12812		//$opt['be']
12813		//$opt['c']
12814		//$opt['border']
12815		//$opt['h']
12816		//$opt['mk'];
12817		//$opt['mk']['r']
12818		//$opt['mk']['bc'];
12819		//$opt['mk']['bg'];
12820		unset($opt['mk']['ca']);
12821		unset($opt['mk']['rc']);
12822		unset($opt['mk']['ac']);
12823		unset($opt['mk']['i']);
12824		unset($opt['mk']['ri']);
12825		unset($opt['mk']['ix']);
12826		unset($opt['mk']['if']);
12827		//$opt['mk']['if']['sw'];
12828		//$opt['mk']['if']['s'];
12829		//$opt['mk']['if']['a'];
12830		//$opt['mk']['if']['fb'];
12831		unset($opt['mk']['tp']);
12832		//$opt['tu']
12833		//$opt['tm']
12834		//$opt['ff']
12835		//$opt['v']
12836		//$opt['dv']
12837		//$opt['a']
12838		//$opt['aa']
12839		//$opt['q']
12840		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12841		if ($this->rtl) {
12842			$this->x -= $w;
12843		} else {
12844			$this->x += $w;
12845		}
12846	}
12847
12848	/**
12849	 * Creates a RadioButton field.
12850	 * @param $name (string) Field name.
12851	 * @param $w (int) Width of the radio button.
12852	 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12853	 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12854	 * @param $onvalue (string) Value to be returned if selected.
12855	 * @param $checked (boolean) Define the initial state.
12856	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12857	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12858	 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12859	 * @public
12860	 * @author Nicola Asuni
12861	 * @since 4.8.000 (2009-09-07)
12862	 */
12863	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12864		if ($x === '') {
12865			$x = $this->x;
12866		}
12867		if ($y === '') {
12868			$y = $this->y;
12869		}
12870		// check page for no-write regions and adapt page margins if necessary
12871		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12872		if ($js) {
12873			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12874			return;
12875		}
12876		if (TCPDF_STATIC::empty_string($onvalue)) {
12877			$onvalue = 'On';
12878		}
12879		if ($checked) {
12880			$defval = $onvalue;
12881		} else {
12882			$defval = 'Off';
12883		}
12884		// set font
12885		$font = 'zapfdingbats';
12886		if ($this->pdfa_mode) {
12887			// all fonts must be embedded
12888			$font = 'pdfa'.$font;
12889		}
12890		$this->AddFont($font);
12891		$tmpfont = $this->getFontBuffer($font);
12892		// set data for parent group
12893		if (!isset($this->radiobutton_groups[$this->page])) {
12894			$this->radiobutton_groups[$this->page] = array();
12895		}
12896		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12897			$this->radiobutton_groups[$this->page][$name] = array();
12898			++$this->n;
12899			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12900			$this->radio_groups[] = $this->n;
12901		}
12902		$kid = ($this->n + 1);
12903		// save object ID to be added on Kids entry on parent object
12904		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12905		// get default style
12906		$prop = array_merge($this->getFormDefaultProp(), $prop);
12907		$prop['NoToggleToOff'] = 'true';
12908		$prop['Radio'] = 'true';
12909		$prop['borderStyle'] = 'inset';
12910		// get annotation data
12911		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12912		// set additional default options
12913		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12914		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12915		$popt['da'] = $fontstyle;
12916		// build appearance stream
12917		$popt['ap'] = array();
12918		$popt['ap']['n'] = array();
12919		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12920		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12921		$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);
12922		$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);
12923		if (!isset($popt['mk'])) {
12924			$popt['mk'] = array();
12925		}
12926		$popt['mk']['ca'] = '(l)';
12927		// merge options
12928		$opt = array_merge($popt, $opt);
12929		// set remaining annotation data
12930		$opt['Subtype'] = 'Widget';
12931		$opt['ft'] = 'Btn';
12932		if ($checked) {
12933			$opt['v'] = array('/'.$onvalue);
12934			$opt['as'] = $onvalue;
12935		} else {
12936			$opt['as'] = 'Off';
12937		}
12938		// store readonly flag
12939		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12940			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12941		}
12942		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12943		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12944		if ($this->rtl) {
12945			$this->x -= $w;
12946		} else {
12947			$this->x += $w;
12948		}
12949	}
12950
12951	/**
12952	 * Creates a List-box field
12953	 * @param $name (string) field name
12954	 * @param $w (int) width
12955	 * @param $h (int) height
12956	 * @param $values (array) array containing the list of values.
12957	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12958	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12959	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12960	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12961	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12962	 * @public
12963	 * @author Nicola Asuni
12964	 * @since 4.8.000 (2009-09-07)
12965	 */
12966	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12967		if ($x === '') {
12968			$x = $this->x;
12969		}
12970		if ($y === '') {
12971			$y = $this->y;
12972		}
12973		// check page for no-write regions and adapt page margins if necessary
12974		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12975		if ($js) {
12976			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12977			$s = '';
12978			foreach ($values as $value) {
12979				if (is_array($value)) {
12980					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12981				} else {
12982					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12983				}
12984			}
12985			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12986			return;
12987		}
12988		// get default style
12989		$prop = array_merge($this->getFormDefaultProp(), $prop);
12990		// get annotation data
12991		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12992		// set additional default values
12993		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12994		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12995		$popt['da'] = $fontstyle;
12996		// build appearance stream
12997		$popt['ap'] = array();
12998		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12999		$text = '';
13000		foreach($values as $item) {
13001			if (is_array($item)) {
13002				$text .= $item[1]."\n";
13003			} else {
13004				$text .= $item."\n";
13005			}
13006		}
13007		$tmpid = $this->startTemplate($w, $h, false);
13008		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13009		$this->endTemplate();
13010		--$this->n;
13011		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13012		unset($this->xobjects[$tmpid]);
13013		$popt['ap']['n'] .= 'Q EMC';
13014		// merge options
13015		$opt = array_merge($popt, $opt);
13016		// set remaining annotation data
13017		$opt['Subtype'] = 'Widget';
13018		$opt['ft'] = 'Ch';
13019		$opt['t'] = $name;
13020		$opt['opt'] = $values;
13021		unset($opt['mk']['ca']);
13022		unset($opt['mk']['rc']);
13023		unset($opt['mk']['ac']);
13024		unset($opt['mk']['i']);
13025		unset($opt['mk']['ri']);
13026		unset($opt['mk']['ix']);
13027		unset($opt['mk']['if']);
13028		unset($opt['mk']['tp']);
13029		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13030		if ($this->rtl) {
13031			$this->x -= $w;
13032		} else {
13033			$this->x += $w;
13034		}
13035	}
13036
13037	/**
13038	 * Creates a Combo-box field
13039	 * @param $name (string) field name
13040	 * @param $w (int) width
13041	 * @param $h (int) height
13042	 * @param $values (array) array containing the list of values.
13043	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13044	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13045	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13046	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13047	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13048	 * @public
13049	 * @author Nicola Asuni
13050	 * @since 4.8.000 (2009-09-07)
13051	 */
13052	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13053		if ($x === '') {
13054			$x = $this->x;
13055		}
13056		if ($y === '') {
13057			$y = $this->y;
13058		}
13059		// check page for no-write regions and adapt page margins if necessary
13060		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13061		if ($js) {
13062			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13063			$s = '';
13064			foreach ($values as $value) {
13065				if (is_array($value)) {
13066					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13067				} else {
13068					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13069				}
13070			}
13071			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13072			return;
13073		}
13074		// get default style
13075		$prop = array_merge($this->getFormDefaultProp(), $prop);
13076		$prop['Combo'] = true;
13077		// get annotation data
13078		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13079		// set additional default options
13080		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13081		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13082		$popt['da'] = $fontstyle;
13083		// build appearance stream
13084		$popt['ap'] = array();
13085		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13086		$text = '';
13087		foreach($values as $item) {
13088			if (is_array($item)) {
13089				$text .= $item[1]."\n";
13090			} else {
13091				$text .= $item."\n";
13092			}
13093		}
13094		$tmpid = $this->startTemplate($w, $h, false);
13095		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13096		$this->endTemplate();
13097		--$this->n;
13098		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13099		unset($this->xobjects[$tmpid]);
13100		$popt['ap']['n'] .= 'Q EMC';
13101		// merge options
13102		$opt = array_merge($popt, $opt);
13103		// set remaining annotation data
13104		$opt['Subtype'] = 'Widget';
13105		$opt['ft'] = 'Ch';
13106		$opt['t'] = $name;
13107		$opt['opt'] = $values;
13108		unset($opt['mk']['ca']);
13109		unset($opt['mk']['rc']);
13110		unset($opt['mk']['ac']);
13111		unset($opt['mk']['i']);
13112		unset($opt['mk']['ri']);
13113		unset($opt['mk']['ix']);
13114		unset($opt['mk']['if']);
13115		unset($opt['mk']['tp']);
13116		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13117		if ($this->rtl) {
13118			$this->x -= $w;
13119		} else {
13120			$this->x += $w;
13121		}
13122	}
13123
13124	/**
13125	 * Creates a CheckBox field
13126	 * @param $name (string) field name
13127	 * @param $w (int) width
13128	 * @param $checked (boolean) define the initial state.
13129	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13130	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13131	 * @param $onvalue (string) value to be returned if selected.
13132	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13133	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13134	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13135	 * @public
13136	 * @author Nicola Asuni
13137	 * @since 4.8.000 (2009-09-07)
13138	 */
13139	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13140		if ($x === '') {
13141			$x = $this->x;
13142		}
13143		if ($y === '') {
13144			$y = $this->y;
13145		}
13146		// check page for no-write regions and adapt page margins if necessary
13147		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13148		if ($js) {
13149			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13150			return;
13151		}
13152		if (!isset($prop['value'])) {
13153			$prop['value'] = array('Yes');
13154		}
13155		// get default style
13156		$prop = array_merge($this->getFormDefaultProp(), $prop);
13157		$prop['borderStyle'] = 'inset';
13158		// get annotation data
13159		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13160		// set additional default options
13161		$font = 'zapfdingbats';
13162		if ($this->pdfa_mode) {
13163			// all fonts must be embedded
13164			$font = 'pdfa'.$font;
13165		}
13166		$this->AddFont($font);
13167		$tmpfont = $this->getFontBuffer($font);
13168		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13169		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13170		$popt['da'] = $fontstyle;
13171		// build appearance stream
13172		$popt['ap'] = array();
13173		$popt['ap']['n'] = array();
13174		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13175		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13176		$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);
13177		$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);
13178		// merge options
13179		$opt = array_merge($popt, $opt);
13180		// set remaining annotation data
13181		$opt['Subtype'] = 'Widget';
13182		$opt['ft'] = 'Btn';
13183		$opt['t'] = $name;
13184		if (TCPDF_STATIC::empty_string($onvalue)) {
13185			$onvalue = 'Yes';
13186		}
13187		$opt['opt'] = array($onvalue);
13188		if ($checked) {
13189			$opt['v'] = array('/Yes');
13190			$opt['as'] = 'Yes';
13191		} else {
13192			$opt['v'] = array('/Off');
13193			$opt['as'] = 'Off';
13194		}
13195		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13196		if ($this->rtl) {
13197			$this->x -= $w;
13198		} else {
13199			$this->x += $w;
13200		}
13201	}
13202
13203	/**
13204	 * Creates a button field
13205	 * @param $name (string) field name
13206	 * @param $w (int) width
13207	 * @param $h (int) height
13208	 * @param $caption (string) caption.
13209	 * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13210	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13211	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13212	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13213	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13214	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13215	 * @public
13216	 * @author Nicola Asuni
13217	 * @since 4.8.000 (2009-09-07)
13218	 */
13219	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13220		if ($x === '') {
13221			$x = $this->x;
13222		}
13223		if ($y === '') {
13224			$y = $this->y;
13225		}
13226		// check page for no-write regions and adapt page margins if necessary
13227		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13228		if ($js) {
13229			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13230			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13231			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13232			$this->javascript .= 'f'.$name.".highlight='push';\n";
13233			$this->javascript .= 'f'.$name.".print=false;\n";
13234			return;
13235		}
13236		// get default style
13237		$prop = array_merge($this->getFormDefaultProp(), $prop);
13238		$prop['Pushbutton'] = 'true';
13239		$prop['highlight'] = 'push';
13240		$prop['display'] = 'display.noPrint';
13241		// get annotation data
13242		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13243		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13244		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13245		$popt['da'] = $fontstyle;
13246		// build appearance stream
13247		$popt['ap'] = array();
13248		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13249		$tmpid = $this->startTemplate($w, $h, false);
13250		$bw = (2 / $this->k); // border width
13251		$border = array(
13252			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13253			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13254			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13255			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13256		$this->SetFillColor(204);
13257		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13258		$this->endTemplate();
13259		--$this->n;
13260		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13261		unset($this->xobjects[$tmpid]);
13262		$popt['ap']['n'] .= 'Q EMC';
13263		// set additional default options
13264		if (!isset($popt['mk'])) {
13265			$popt['mk'] = array();
13266		}
13267		$ann_obj_id = ($this->n + 1);
13268		if (!empty($action) AND !is_array($action)) {
13269			$ann_obj_id = ($this->n + 2);
13270		}
13271		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13272		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13273		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13274		// merge options
13275		$opt = array_merge($popt, $opt);
13276		// set remaining annotation data
13277		$opt['Subtype'] = 'Widget';
13278		$opt['ft'] = 'Btn';
13279		$opt['t'] = $caption;
13280		$opt['v'] = $name;
13281		if (!empty($action)) {
13282			if (is_array($action)) {
13283				// form action options as on section 12.7.5 of PDF32000_2008.
13284				$opt['aa'] = '/D <<';
13285				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13286				foreach ($action AS $key => $val) {
13287					if (($key == 'S') AND in_array($val, $bmode)) {
13288						$opt['aa'] .= ' /S /'.$val;
13289					} elseif (($key == 'F') AND (!empty($val))) {
13290						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13291					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13292						$opt['aa'] .= ' /Fields [';
13293						foreach ($val AS $field) {
13294							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13295						}
13296						$opt['aa'] .= ']';
13297					} elseif (($key == 'Flags')) {
13298						$ff = 0;
13299						if (is_array($val)) {
13300							foreach ($val AS $flag) {
13301								switch ($flag) {
13302									case 'Include/Exclude': {
13303										$ff += 1 << 0;
13304										break;
13305									}
13306									case 'IncludeNoValueFields': {
13307										$ff += 1 << 1;
13308										break;
13309									}
13310									case 'ExportFormat': {
13311										$ff += 1 << 2;
13312										break;
13313									}
13314									case 'GetMethod': {
13315										$ff += 1 << 3;
13316										break;
13317									}
13318									case 'SubmitCoordinates': {
13319										$ff += 1 << 4;
13320										break;
13321									}
13322									case 'XFDF': {
13323										$ff += 1 << 5;
13324										break;
13325									}
13326									case 'IncludeAppendSaves': {
13327										$ff += 1 << 6;
13328										break;
13329									}
13330									case 'IncludeAnnotations': {
13331										$ff += 1 << 7;
13332										break;
13333									}
13334									case 'SubmitPDF': {
13335										$ff += 1 << 8;
13336										break;
13337									}
13338									case 'CanonicalFormat': {
13339										$ff += 1 << 9;
13340										break;
13341									}
13342									case 'ExclNonUserAnnots': {
13343										$ff += 1 << 10;
13344										break;
13345									}
13346									case 'ExclFKey': {
13347										$ff += 1 << 11;
13348										break;
13349									}
13350									case 'EmbedForm': {
13351										$ff += 1 << 13;
13352										break;
13353									}
13354								}
13355							}
13356						} else {
13357							$ff = intval($val);
13358						}
13359						$opt['aa'] .= ' /Flags '.$ff;
13360					}
13361				}
13362				$opt['aa'] .= ' >>';
13363			} else {
13364				// Javascript action or raw action command
13365				$js_obj_id = $this->addJavascriptObject($action);
13366				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13367			}
13368		}
13369		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13370		if ($this->rtl) {
13371			$this->x -= $w;
13372		} else {
13373			$this->x += $w;
13374		}
13375	}
13376
13377	// --- END FORMS FIELDS ------------------------------------------------
13378
13379	/**
13380	 * Add certification signature (DocMDP or UR3)
13381	 * You can set only one signature type
13382	 * @protected
13383	 * @author Nicola Asuni
13384	 * @since 4.6.008 (2009-05-07)
13385	 */
13386	protected function _putsignature() {
13387		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13388			return;
13389		}
13390		$sigobjid = ($this->sig_obj_id + 1);
13391		$out = $this->_getobj($sigobjid)."\n";
13392		$out .= '<< /Type /Sig';
13393		$out .= ' /Filter /Adobe.PPKLite';
13394		$out .= ' /SubFilter /adbe.pkcs7.detached';
13395		$out .= ' '.TCPDF_STATIC::$byterange_string;
13396		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13397		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13398			$out .= ' /Reference ['; // array of signature reference dictionaries
13399			$out .= ' << /Type /SigRef';
13400			if ($this->signature_data['cert_type'] > 0) {
13401				$out .= ' /TransformMethod /DocMDP';
13402				$out .= ' /TransformParams <<';
13403				$out .= ' /Type /TransformParams';
13404				$out .= ' /P '.$this->signature_data['cert_type'];
13405				$out .= ' /V /1.2';
13406			} else {
13407				$out .= ' /TransformMethod /UR3';
13408				$out .= ' /TransformParams <<';
13409				$out .= ' /Type /TransformParams';
13410				$out .= ' /V /2.2';
13411				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13412					$out .= ' /Document['.$this->ur['document'].']';
13413				}
13414				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13415					$out .= ' /Form['.$this->ur['form'].']';
13416				}
13417				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13418					$out .= ' /Signature['.$this->ur['signature'].']';
13419				}
13420				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13421					$out .= ' /Annots['.$this->ur['annots'].']';
13422				}
13423				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13424					$out .= ' /EF['.$this->ur['ef'].']';
13425				}
13426				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13427					$out .= ' /FormEX['.$this->ur['formex'].']';
13428				}
13429			}
13430			$out .= ' >>'; // close TransformParams
13431			// optional digest data (values must be calculated and replaced later)
13432			//$out .= ' /Data ********** 0 R';
13433			//$out .= ' /DigestMethod/MD5';
13434			//$out .= ' /DigestLocation[********** 34]';
13435			//$out .= ' /DigestValue<********************************>';
13436			$out .= ' >>';
13437			$out .= ' ]'; // end of reference
13438		}
13439		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13440			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13441		}
13442		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13443			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13444		}
13445		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13446			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13447		}
13448		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13449			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13450		}
13451		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13452		$out .= ' >>';
13453		$out .= "\n".'endobj';
13454		$this->_out($out);
13455	}
13456
13457	/**
13458	 * Set User's Rights for PDF Reader
13459	 * WARNING: This is experimental and currently do not work.
13460	 * Check the PDF Reference 8.7.1 Transform Methods,
13461	 * Table 8.105 Entries in the UR transform parameters dictionary
13462	 * @param $enable (boolean) if true enable user's rights on PDF reader
13463	 * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13464	 * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13465	 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13466	 * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13467	 * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13468	 Names specifying additional embedded-files-related usage rights for the document.
13469	 * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13470	 * @public
13471	 * @author Nicola Asuni
13472	 * @since 2.9.000 (2008-03-26)
13473	 */
13474	public function setUserRights(
13475			$enable=true,
13476			$document='/FullSave',
13477			$annots='/Create/Delete/Modify/Copy/Import/Export',
13478			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13479			$signature='/Modify',
13480			$ef='/Create/Delete/Modify/Import',
13481			$formex='') {
13482		$this->ur['enabled'] = $enable;
13483		$this->ur['document'] = $document;
13484		$this->ur['annots'] = $annots;
13485		$this->ur['form'] = $form;
13486		$this->ur['signature'] = $signature;
13487		$this->ur['ef'] = $ef;
13488		$this->ur['formex'] = $formex;
13489		if (!$this->sign) {
13490			$this->setSignature('', '', '', '', 0, array());
13491		}
13492	}
13493
13494	/**
13495	 * Enable document signature (requires the OpenSSL Library).
13496	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13497	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13498	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13499	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13500	 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13501	 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13502	 * @param $private_key_password (string) password
13503	 * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13504	 * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13505	 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13506	 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13507	 * @public
13508	 * @author Nicola Asuni
13509	 * @since 4.6.005 (2009-04-24)
13510	 */
13511	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13512		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13513		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13514		// to convert pfx certificate to pem: openssl
13515		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13516		$this->sign = true;
13517		++$this->n;
13518		$this->sig_obj_id = $this->n; // signature widget
13519		++$this->n; // signature object ($this->sig_obj_id + 1)
13520		$this->signature_data = array();
13521		if (strlen($signing_cert) == 0) {
13522			$this->Error('Please provide a certificate file and password!');
13523		}
13524		if (strlen($private_key) == 0) {
13525			$private_key = $signing_cert;
13526		}
13527		$this->signature_data['signcert'] = $signing_cert;
13528		$this->signature_data['privkey'] = $private_key;
13529		$this->signature_data['password'] = $private_key_password;
13530		$this->signature_data['extracerts'] = $extracerts;
13531		$this->signature_data['cert_type'] = $cert_type;
13532		$this->signature_data['info'] = $info;
13533		$this->signature_data['approval'] = $approval;
13534	}
13535
13536	/**
13537	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13538	 * @param $x (float) Abscissa of the upper-left corner.
13539	 * @param $y (float) Ordinate of the upper-left corner.
13540	 * @param $w (float) Width of the signature area.
13541	 * @param $h (float) Height of the signature area.
13542	 * @param $page (int) option page number (if < 0 the current page is used).
13543	 * @param $name (string) Name of the signature.
13544	 * @public
13545	 * @author Nicola Asuni
13546	 * @since 5.3.011 (2010-06-17)
13547	 */
13548	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13549		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13550	}
13551
13552	/**
13553	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13554	 * @param $x (float) Abscissa of the upper-left corner.
13555	 * @param $y (float) Ordinate of the upper-left corner.
13556	 * @param $w (float) Width of the signature area.
13557	 * @param $h (float) Height of the signature area.
13558	 * @param $page (int) option page number (if < 0 the current page is used).
13559	 * @param $name (string) Name of the signature.
13560	 * @public
13561	 * @author Nicola Asuni
13562	 * @since 5.9.101 (2011-07-06)
13563	 */
13564	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13565		++$this->n;
13566		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13567	}
13568
13569	/**
13570	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13571	 * @param $x (float) Abscissa of the upper-left corner.
13572	 * @param $y (float) Ordinate of the upper-left corner.
13573	 * @param $w (float) Width of the signature area.
13574	 * @param $h (float) Height of the signature area.
13575	 * @param $page (int) option page number (if < 0 the current page is used).
13576	 * @param $name (string) Name of the signature.
13577	 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13578	 * @protected
13579	 * @author Nicola Asuni
13580	 * @since 5.9.101 (2011-07-06)
13581	 */
13582	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13583		$sigapp = array();
13584		if (($page < 1) OR ($page > $this->numpages)) {
13585			$sigapp['page'] = $this->page;
13586		} else {
13587			$sigapp['page'] = intval($page);
13588		}
13589		if (empty($name)) {
13590			$sigapp['name'] = 'Signature';
13591		} else {
13592			$sigapp['name'] = $name;
13593		}
13594		$a = $x * $this->k;
13595		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13596		$c = $w * $this->k;
13597		$d = $h * $this->k;
13598		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13599		return $sigapp;
13600	}
13601
13602	/**
13603	 * Enable document timestamping (requires the OpenSSL Library).
13604	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13605	 * Use with digital signature only!
13606	 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13607	 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13608	 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13609	 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13610	 * @public
13611	 * @author Richard Stockinger
13612	 * @since 6.0.090 (2014-06-16)
13613	 */
13614	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13615		$this->tsa_data = array();
13616		if (!function_exists('curl_init')) {
13617			$this->Error('Please enable cURL PHP extension!');
13618		}
13619		if (strlen($tsa_host) == 0) {
13620			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13621		}
13622		$this->tsa_data['tsa_host'] = $tsa_host;
13623		if (is_file($tsa_username)) {
13624			$this->tsa_data['tsa_auth'] = $tsa_username;
13625		} else {
13626			$this->tsa_data['tsa_username'] = $tsa_username;
13627		}
13628		$this->tsa_data['tsa_password'] = $tsa_password;
13629		$this->tsa_data['tsa_cert'] = $tsa_cert;
13630		$this->tsa_timestamp = true;
13631	}
13632
13633	/**
13634	 * NOT YET IMPLEMENTED
13635	 * Request TSA for a timestamp
13636	 * @param $signature (string) Digital signature as binary string
13637	 * @return (string) Timestamped digital signature
13638	 * @protected
13639	 * @author Richard Stockinger
13640	 * @since 6.0.090 (2014-06-16)
13641	 */
13642	protected function applyTSA($signature) {
13643		if (!$this->tsa_timestamp) {
13644			return $signature;
13645		}
13646		//@TODO: implement this feature
13647		return $signature;
13648	}
13649
13650	/**
13651	 * Create a new page group.
13652	 * NOTE: call this function before calling AddPage()
13653	 * @param $page (int) starting group page (leave empty for next page).
13654	 * @public
13655	 * @since 3.0.000 (2008-03-27)
13656	 */
13657	public function startPageGroup($page='') {
13658		if (empty($page)) {
13659			$page = $this->page + 1;
13660		}
13661		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13662	}
13663
13664	/**
13665	 * Set the starting page number.
13666	 * @param $num (int) Starting page number.
13667	 * @since 5.9.093 (2011-06-16)
13668	 * @public
13669	 */
13670	public function setStartingPageNumber($num=1) {
13671		$this->starting_page_number = max(0, intval($num));
13672	}
13673
13674	/**
13675	 * Returns the string alias used right align page numbers.
13676	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13677	 * @return string
13678	 * @since 5.9.099 (2011-06-27)
13679	 * @public
13680	 */
13681	public function getAliasRightShift() {
13682		// calculate aproximatively the ratio between widths of aliases and replacements.
13683		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13684		$rep = str_repeat(' ', $this->GetNumChars($ref));
13685		$wrep = $this->GetStringWidth($rep);
13686		if ($wrep > 0) {
13687			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13688		} else {
13689			$wdiff = 1;
13690		}
13691		$sdiff = sprintf('%F', $wdiff);
13692		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13693		if ($this->isUnicodeFont()) {
13694			$alias = '{'.$alias;
13695		}
13696		return $alias;
13697	}
13698
13699	/**
13700	 * Returns the string alias used for the total number of pages.
13701	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13702	 * This alias will be replaced by the total number of pages in the document.
13703	 * @return string
13704	 * @since 4.0.018 (2008-08-08)
13705	 * @public
13706	 */
13707	public function getAliasNbPages() {
13708		if ($this->isUnicodeFont()) {
13709			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13710		}
13711		return TCPDF_STATIC::$alias_tot_pages;
13712	}
13713
13714	/**
13715	 * Returns the string alias used for the page number.
13716	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13717	 * This alias will be replaced by the page number.
13718	 * @return string
13719	 * @since 4.5.000 (2009-01-02)
13720	 * @public
13721	 */
13722	public function getAliasNumPage() {
13723		if ($this->isUnicodeFont()) {
13724			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13725		}
13726		return TCPDF_STATIC::$alias_num_page;
13727	}
13728
13729	/**
13730	 * Return the alias for the total number of pages in the current page group.
13731	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13732	 * This alias will be replaced by the total number of pages in this group.
13733	 * @return alias of the current page group
13734	 * @public
13735	 * @since 3.0.000 (2008-03-27)
13736	 */
13737	public function getPageGroupAlias() {
13738		if ($this->isUnicodeFont()) {
13739			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13740		}
13741		return TCPDF_STATIC::$alias_group_tot_pages;
13742	}
13743
13744	/**
13745	 * Return the alias for the page number on the current page group.
13746	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13747	 * This alias will be replaced by the page number (relative to the belonging group).
13748	 * @return alias of the current page group
13749	 * @public
13750	 * @since 4.5.000 (2009-01-02)
13751	 */
13752	public function getPageNumGroupAlias() {
13753		if ($this->isUnicodeFont()) {
13754			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13755		}
13756		return TCPDF_STATIC::$alias_group_num_page;
13757	}
13758
13759	/**
13760	 * Return the current page in the group.
13761	 * @return current page in the group
13762	 * @public
13763	 * @since 3.0.000 (2008-03-27)
13764	 */
13765	public function getGroupPageNo() {
13766		return $this->pagegroups[$this->currpagegroup];
13767	}
13768
13769	/**
13770	 * Returns the current group page number formatted as a string.
13771	 * @public
13772	 * @since 4.3.003 (2008-11-18)
13773	 * @see PaneNo(), formatPageNumber()
13774	 */
13775	public function getGroupPageNoFormatted() {
13776		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13777	}
13778
13779	/**
13780	 * Returns the current page number formatted as a string.
13781	 * @public
13782	 * @since 4.2.005 (2008-11-06)
13783	 * @see PaneNo(), formatPageNumber()
13784	 */
13785	public function PageNoFormatted() {
13786		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13787	}
13788
13789	/**
13790	 * Put pdf layers.
13791	 * @protected
13792	 * @since 3.0.000 (2008-03-27)
13793	 */
13794	protected function _putocg() {
13795		if (empty($this->pdflayers)) {
13796			return;
13797		}
13798		foreach ($this->pdflayers as $key => $layer) {
13799			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13800			 $out = '<< /Type /OCG';
13801			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13802			 $out .= ' /Usage <<';
13803			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13804				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13805			 }
13806			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13807			 $out .= ' >> >>';
13808			 $out .= "\n".'endobj';
13809			 $this->_out($out);
13810		}
13811	}
13812
13813	/**
13814	 * Start a new pdf layer.
13815	 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13816	 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13817	 * @param $view (boolean) Set to true to view this layer.
13818	 * @param $lock (boolean) If true lock the layer
13819	 * @public
13820	 * @since 5.9.102 (2011-07-13)
13821	 */
13822	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13823		if ($this->state != 2) {
13824			return;
13825		}
13826		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13827		if (empty($name)) {
13828			$name = $layer;
13829		} else {
13830			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13831		}
13832		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13833		$this->openMarkedContent = true;
13834		$this->_out('/OC /'.$layer.' BDC');
13835	}
13836
13837	/**
13838	 * End the current PDF layer.
13839	 * @public
13840	 * @since 5.9.102 (2011-07-13)
13841	 */
13842	public function endLayer() {
13843		if ($this->state != 2) {
13844			return;
13845		}
13846		if ($this->openMarkedContent) {
13847			// close existing open marked-content layer
13848			$this->_out('EMC');
13849			$this->openMarkedContent = false;
13850		}
13851	}
13852
13853	/**
13854	 * Set the visibility of the successive elements.
13855	 * This can be useful, for instance, to put a background
13856	 * image or color that will show on screen but won't print.
13857	 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13858	 * @public
13859	 * @since 3.0.000 (2008-03-27)
13860	 */
13861	public function setVisibility($v) {
13862		if ($this->state != 2) {
13863			return;
13864		}
13865		$this->endLayer();
13866		switch($v) {
13867			case 'print': {
13868				$this->startLayer('Print', true, false);
13869				break;
13870			}
13871			case 'view':
13872			case 'screen': {
13873				$this->startLayer('View', false, true);
13874				break;
13875			}
13876			case 'all': {
13877				$this->_out('');
13878				break;
13879			}
13880			default: {
13881				$this->Error('Incorrect visibility: '.$v);
13882				break;
13883			}
13884		}
13885	}
13886
13887	/**
13888	 * Add transparency parameters to the current extgstate
13889	 * @param $parms (array) parameters
13890	 * @return the number of extgstates
13891	 * @protected
13892	 * @since 3.0.000 (2008-03-27)
13893	 */
13894	protected function addExtGState($parms) {
13895		if ($this->pdfa_mode) {
13896			// transparencies are not allowed in PDF/A mode
13897			return;
13898		}
13899		// check if this ExtGState already exist
13900		foreach ($this->extgstates as $i => $ext) {
13901			if ($ext['parms'] == $parms) {
13902				if ($this->inxobj) {
13903					// we are inside an XObject template
13904					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13905				}
13906				// return reference to existing ExtGState
13907				return $i;
13908			}
13909		}
13910		$n = (count($this->extgstates) + 1);
13911		$this->extgstates[$n] = array('parms' => $parms);
13912		if ($this->inxobj) {
13913			// we are inside an XObject template
13914			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13915		}
13916		return $n;
13917	}
13918
13919	/**
13920	 * Add an extgstate
13921	 * @param $gs (array) extgstate
13922	 * @protected
13923	 * @since 3.0.000 (2008-03-27)
13924	 */
13925	protected function setExtGState($gs) {
13926		if ($this->pdfa_mode OR ($this->state != 2)) {
13927			// transparency is not allowed in PDF/A mode
13928			return;
13929		}
13930		$this->_out(sprintf('/GS%d gs', $gs));
13931	}
13932
13933	/**
13934	 * Put extgstates for object transparency
13935	 * @protected
13936	 * @since 3.0.000 (2008-03-27)
13937	 */
13938	protected function _putextgstates() {
13939		foreach ($this->extgstates as $i => $ext) {
13940			$this->extgstates[$i]['n'] = $this->_newobj();
13941			$out = '<< /Type /ExtGState';
13942			foreach ($ext['parms'] as $k => $v) {
13943				if (is_float($v)) {
13944					$v = sprintf('%F', $v);
13945				} elseif ($v === true) {
13946					$v = 'true';
13947				} elseif ($v === false) {
13948					$v = 'false';
13949				}
13950				$out .= ' /'.$k.' '.$v;
13951			}
13952			$out .= ' >>';
13953			$out .= "\n".'endobj';
13954			$this->_out($out);
13955		}
13956	}
13957
13958	/**
13959	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13960	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13961	 * @param $stroking (boolean) If true apply overprint for stroking operations.
13962	 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13963	 * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
13964	 * @public
13965	 * @since 5.9.152 (2012-03-23)
13966	 */
13967	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13968		if ($this->state != 2) {
13969			return;
13970		}
13971		$stroking = $stroking ? true : false;
13972		if (TCPDF_STATIC::empty_string($nonstroking)) {
13973			// default value if not set
13974			$nonstroking = $stroking;
13975		} else {
13976			$nonstroking = $nonstroking ? true : false;
13977		}
13978		if (($mode != 0) AND ($mode != 1)) {
13979			$mode = 0;
13980		}
13981		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13982		$gs = $this->addExtGState($this->overprint);
13983		$this->setExtGState($gs);
13984	}
13985
13986	/**
13987	 * Get the overprint mode array (OP, op, OPM).
13988	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13989	 * @return array.
13990	 * @public
13991	 * @since 5.9.152 (2012-03-23)
13992	 */
13993	public function getOverprint() {
13994		return $this->overprint;
13995	}
13996
13997	/**
13998	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13999	 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14000	 * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
14001	 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14002	 * @param $ais (boolean)
14003	 * @public
14004	 * @since 3.0.000 (2008-03-27)
14005	 */
14006	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
14007		if ($this->pdfa_mode) {
14008			// transparency is not allowed in PDF/A mode
14009			return;
14010		}
14011		$stroking = floatval($stroking);
14012		if (TCPDF_STATIC::empty_string($nonstroking)) {
14013			// default value if not set
14014			$nonstroking = $stroking;
14015		} else {
14016			$nonstroking = floatval($nonstroking);
14017		}
14018		if ($bm[0] == '/') {
14019			// remove trailing slash
14020			$bm = substr($bm, 1);
14021		}
14022		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14023			$bm = 'Normal';
14024		}
14025		$ais = $ais ? true : false;
14026		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14027		$gs = $this->addExtGState($this->alpha);
14028		$this->setExtGState($gs);
14029	}
14030
14031	/**
14032	 * Get the alpha mode array (CA, ca, BM, AIS).
14033	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14034	 * @return array.
14035	 * @public
14036	 * @since 5.9.152 (2012-03-23)
14037	 */
14038	public function getAlpha() {
14039		return $this->alpha;
14040	}
14041
14042	/**
14043	 * Set the default JPEG compression quality (1-100)
14044	 * @param $quality (int) JPEG quality, integer between 1 and 100
14045	 * @public
14046	 * @since 3.0.000 (2008-03-27)
14047	 */
14048	public function setJPEGQuality($quality) {
14049		if (($quality < 1) OR ($quality > 100)) {
14050			$quality = 75;
14051		}
14052		$this->jpeg_quality = intval($quality);
14053	}
14054
14055	/**
14056	 * Set the default number of columns in a row for HTML tables.
14057	 * @param $cols (int) number of columns
14058	 * @public
14059	 * @since 3.0.014 (2008-06-04)
14060	 */
14061	public function setDefaultTableColumns($cols=4) {
14062		$this->default_table_columns = intval($cols);
14063	}
14064
14065	/**
14066	 * Set the height of the cell (line height) respect the font height.
14067	 * @param $h (int) cell proportion respect font height (typical value = 1.25).
14068	 * @public
14069	 * @since 3.0.014 (2008-06-04)
14070	 */
14071	public function setCellHeightRatio($h) {
14072		$this->cell_height_ratio = $h;
14073	}
14074
14075	/**
14076	 * return the height of cell repect font height.
14077	 * @public
14078	 * @since 4.0.012 (2008-07-24)
14079	 */
14080	public function getCellHeightRatio() {
14081		return $this->cell_height_ratio;
14082	}
14083
14084	/**
14085	 * Set the PDF version (check PDF reference for valid values).
14086	 * @param $version (string) PDF document version.
14087	 * @public
14088	 * @since 3.1.000 (2008-06-09)
14089	 */
14090	public function setPDFVersion($version='1.7') {
14091		if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14092			// PDF/A mode
14093			$this->PDFVersion = '1.4';
14094		} else {
14095			$this->PDFVersion = $version;
14096		}
14097	}
14098
14099	/**
14100	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14101	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14102	 * <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>
14103	 * @param $preferences (array) array of options.
14104	 * @author Nicola Asuni
14105	 * @public
14106	 * @since 3.1.000 (2008-06-09)
14107	 */
14108	public function setViewerPreferences($preferences) {
14109		$this->viewer_preferences = $preferences;
14110	}
14111
14112	/**
14113	 * Paints color transition registration bars
14114	 * @param $x (float) abscissa of the top left corner of the rectangle.
14115	 * @param $y (float) ordinate of the top left corner of the rectangle.
14116	 * @param $w (float) width of the rectangle.
14117	 * @param $h (float) height of the rectangle.
14118	 * @param $transition (boolean) if true prints tcolor transitions to white.
14119	 * @param $vertical (boolean) if true prints bar vertically.
14120	 * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14121	 * @author Nicola Asuni
14122	 * @since 4.9.000 (2010-03-26)
14123	 * @public
14124	 */
14125	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14126		if (strpos($colors, 'ALLSPOT') !== false) {
14127			// expand spot colors
14128			$spot_colors = '';
14129			foreach ($this->spot_colors as $spot_color_name => $v) {
14130				$spot_colors .= ','.$spot_color_name;
14131			}
14132			if (!empty($spot_colors)) {
14133				$spot_colors = substr($spot_colors, 1);
14134				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14135			} else {
14136				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14137			}
14138		}
14139		$bars = explode(',', $colors);
14140		$numbars = count($bars); // number of bars to print
14141		if ($numbars <= 0) {
14142			return;
14143		}
14144		// set bar measures
14145		if ($vertical) {
14146			$coords = array(0, 0, 0, 1);
14147			$wb = $w / $numbars; // bar width
14148			$hb = $h; // bar height
14149			$xd = $wb; // delta x
14150			$yd = 0; // delta y
14151		} else {
14152			$coords = array(1, 0, 0, 0);
14153			$wb = $w; // bar width
14154			$hb = $h / $numbars; // bar height
14155			$xd = 0; // delta x
14156			$yd = $hb; // delta y
14157		}
14158		$xb = $x;
14159		$yb = $y;
14160		foreach ($bars as $col) {
14161			switch ($col) {
14162				// set transition colors
14163				case 'A': { // BLACK (GRAYSCALE)
14164					$col_a = array(255);
14165					$col_b = array(0);
14166					break;
14167				}
14168				case 'W': { // WHITE (GRAYSCALE)
14169					$col_a = array(0);
14170					$col_b = array(255);
14171					break;
14172				}
14173				case 'R': { // RED (RGB)
14174					$col_a = array(255,255,255);
14175					$col_b = array(255,0,0);
14176					break;
14177				}
14178				case 'G': { // GREEN (RGB)
14179					$col_a = array(255,255,255);
14180					$col_b = array(0,255,0);
14181					break;
14182				}
14183				case 'B': { // BLUE (RGB)
14184					$col_a = array(255,255,255);
14185					$col_b = array(0,0,255);
14186					break;
14187				}
14188				case 'C': { // CYAN (CMYK)
14189					$col_a = array(0,0,0,0);
14190					$col_b = array(100,0,0,0);
14191					break;
14192				}
14193				case 'M': { // MAGENTA (CMYK)
14194					$col_a = array(0,0,0,0);
14195					$col_b = array(0,100,0,0);
14196					break;
14197				}
14198				case 'Y': { // YELLOW (CMYK)
14199					$col_a = array(0,0,0,0);
14200					$col_b = array(0,0,100,0);
14201					break;
14202				}
14203				case 'K': { // KEY - BLACK (CMYK)
14204					$col_a = array(0,0,0,0);
14205					$col_b = array(0,0,0,100);
14206					break;
14207				}
14208				case 'RGB': { // BLACK REGISTRATION (RGB)
14209					$col_a = array(255,255,255);
14210					$col_b = array(0,0,0);
14211					break;
14212				}
14213				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14214					$col_a = array(0,0,0,0);
14215					$col_b = array(100,100,100,100);
14216					break;
14217				}
14218				case 'ALL': { // SPOT COLOR REGISTRATION
14219					$col_a = array(0,0,0,0,'None');
14220					$col_b = array(100,100,100,100,'All');
14221					break;
14222				}
14223				case 'NONE': { // SKIP THIS COLOR
14224					$col_a = array(0,0,0,0,'None');
14225					$col_b = array(0,0,0,0,'None');
14226					break;
14227				}
14228				default: { // SPECIFIC SPOT COLOR NAME
14229					$col_a = array(0,0,0,0,'None');
14230					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14231					if ($col_b === false) {
14232						// in case of error defaults to the registration color
14233						$col_b = array(100,100,100,100,'All');
14234					}
14235					break;
14236				}
14237			}
14238			if ($col != 'NONE') {
14239				if ($transition) {
14240					// color gradient
14241					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14242				} else {
14243					$this->SetFillColorArray($col_b);
14244					// colored rectangle
14245					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14246				}
14247				$xb += $xd;
14248				$yb += $yd;
14249			}
14250		}
14251	}
14252
14253	/**
14254	 * Paints crop marks.
14255	 * @param $x (float) abscissa of the crop mark center.
14256	 * @param $y (float) ordinate of the crop mark center.
14257	 * @param $w (float) width of the crop mark.
14258	 * @param $h (float) height of the crop mark.
14259	 * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14260	 * @param $color (array) crop mark color (default spot registration color).
14261	 * @author Nicola Asuni
14262	 * @since 4.9.000 (2010-03-26)
14263	 * @public
14264	 */
14265	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14266		$this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14267		$type = strtoupper($type);
14268		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14269		// split type in single components
14270		$type = str_replace('-', ',', $type);
14271		$type = str_replace('TL', 'T,L', $type);
14272		$type = str_replace('TR', 'T,R', $type);
14273		$type = str_replace('BL', 'F,L', $type);
14274		$type = str_replace('BR', 'F,R', $type);
14275		$type = str_replace('A', 'T,L', $type);
14276		$type = str_replace('B', 'T,R', $type);
14277		$type = str_replace('T,RO', 'BO', $type);
14278		$type = str_replace('C', 'F,L', $type);
14279		$type = str_replace('D', 'F,R', $type);
14280		$crops = explode(',', strtoupper($type));
14281		// remove duplicates
14282		$crops = array_unique($crops);
14283		$dw = ($w / 4); // horizontal space to leave before the intersection point
14284		$dh = ($h / 4); // vertical space to leave before the intersection point
14285		foreach ($crops as $crop) {
14286			switch ($crop) {
14287				case 'T':
14288				case 'TOP': {
14289					$x1 = $x;
14290					$y1 = ($y - $h);
14291					$x2 = $x;
14292					$y2 = ($y - $dh);
14293					break;
14294				}
14295				case 'F':
14296				case 'BOTTOM': {
14297					$x1 = $x;
14298					$y1 = ($y + $dh);
14299					$x2 = $x;
14300					$y2 = ($y + $h);
14301					break;
14302				}
14303				case 'L':
14304				case 'LEFT': {
14305					$x1 = ($x - $w);
14306					$y1 = $y;
14307					$x2 = ($x - $dw);
14308					$y2 = $y;
14309					break;
14310				}
14311				case 'R':
14312				case 'RIGHT': {
14313					$x1 = ($x + $dw);
14314					$y1 = $y;
14315					$x2 = ($x + $w);
14316					$y2 = $y;
14317					break;
14318				}
14319			}
14320			$this->Line($x1, $y1, $x2, $y2);
14321		}
14322	}
14323
14324	/**
14325	 * Paints a registration mark
14326	 * @param $x (float) abscissa of the registration mark center.
14327	 * @param $y (float) ordinate of the registration mark center.
14328	 * @param $r (float) radius of the crop mark.
14329	 * @param $double (boolean) if true print two concentric crop marks.
14330	 * @param $cola (array) crop mark color (default spot registration color 'All').
14331	 * @param $colb (array) second crop mark color (default spot registration color 'None').
14332	 * @author Nicola Asuni
14333	 * @since 4.9.000 (2010-03-26)
14334	 * @public
14335	 */
14336	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14337		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14338		$this->SetFillColorArray($cola);
14339		$this->PieSector($x, $y, $r, 90, 180, 'F');
14340		$this->PieSector($x, $y, $r, 270, 360, 'F');
14341		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14342		if ($double) {
14343			$ri = $r * 0.5;
14344			$this->SetFillColorArray($colb);
14345			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14346			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14347			$this->SetFillColorArray($cola);
14348			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14349			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14350			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14351		}
14352	}
14353
14354	/**
14355	 * Paints a CMYK registration mark
14356	 * @param $x (float) abscissa of the registration mark center.
14357	 * @param $y (float) ordinate of the registration mark center.
14358	 * @param $r (float) radius of the crop mark.
14359	 * @author Nicola Asuni
14360	 * @since 6.0.038 (2013-09-30)
14361	 * @public
14362	 */
14363	public function registrationMarkCMYK($x, $y, $r) {
14364		// line width
14365		$lw = max((0.5 / $this->k),($r / 8));
14366		// internal radius
14367		$ri = ($r * 0.6);
14368		// external radius
14369		$re = ($r * 1.3);
14370		// Cyan
14371		$this->SetFillColorArray(array(100,0,0,0));
14372		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14373		// Magenta
14374		$this->SetFillColorArray(array(0,100,0,0));
14375		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14376		// Yellow
14377		$this->SetFillColorArray(array(0,0,100,0));
14378		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14379		// Key - black
14380		$this->SetFillColorArray(array(0,0,0,100));
14381		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14382		// registration color
14383		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14384		$this->SetFillColorArray(array(100,100,100,100,'All'));
14385		// external circle
14386		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14387		// cross lines
14388		$this->Line($x, ($y - $re), $x, ($y - $ri));
14389		$this->Line($x, ($y + $ri), $x, ($y + $re));
14390		$this->Line(($x - $re), $y, ($x - $ri), $y);
14391		$this->Line(($x + $ri), $y, ($x + $re), $y);
14392	}
14393
14394	/**
14395	 * Paints a linear colour gradient.
14396	 * @param $x (float) abscissa of the top left corner of the rectangle.
14397	 * @param $y (float) ordinate of the top left corner of the rectangle.
14398	 * @param $w (float) width of the rectangle.
14399	 * @param $h (float) height of the rectangle.
14400	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14401	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14402	 * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14403	 * @author Andreas W\FCrmser, Nicola Asuni
14404	 * @since 3.1.000 (2008-06-09)
14405	 * @public
14406	 */
14407	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14408		$this->Clip($x, $y, $w, $h);
14409		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14410	}
14411
14412	/**
14413	 * Paints a radial colour gradient.
14414	 * @param $x (float) abscissa of the top left corner of the rectangle.
14415	 * @param $y (float) ordinate of the top left corner of the rectangle.
14416	 * @param $w (float) width of the rectangle.
14417	 * @param $h (float) height of the rectangle.
14418	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14419	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14420	 * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14421	 * @author Andreas W\FCrmser, Nicola Asuni
14422	 * @since 3.1.000 (2008-06-09)
14423	 * @public
14424	 */
14425	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14426		$this->Clip($x, $y, $w, $h);
14427		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14428	}
14429
14430	/**
14431	 * Paints a coons patch mesh.
14432	 * @param $x (float) abscissa of the top left corner of the rectangle.
14433	 * @param $y (float) ordinate of the top left corner of the rectangle.
14434	 * @param $w (float) width of the rectangle.
14435	 * @param $h (float) height of the rectangle.
14436	 * @param $col1 (array) first color (lower left corner) (RGB components).
14437	 * @param $col2 (array) second color (lower right corner) (RGB components).
14438	 * @param $col3 (array) third color (upper right corner) (RGB components).
14439	 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14440	 * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14441	 * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14442	 * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14443	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14444	 * @author Andreas W\FCrmser, Nicola Asuni
14445	 * @since 3.1.000 (2008-06-09)
14446	 * @public
14447	 */
14448	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) {
14449		if ($this->pdfa_mode OR ($this->state != 2)) {
14450			return;
14451		}
14452		$this->Clip($x, $y, $w, $h);
14453		$n = count($this->gradients) + 1;
14454		$this->gradients[$n] = array();
14455		$this->gradients[$n]['type'] = 6; //coons patch mesh
14456		$this->gradients[$n]['coords'] = array();
14457		$this->gradients[$n]['antialias'] = $antialias;
14458		$this->gradients[$n]['colors'] = array();
14459		$this->gradients[$n]['transparency'] = false;
14460		//check the coords array if it is the simple array or the multi patch array
14461		if (!isset($coords[0]['f'])) {
14462			//simple array -> convert to multi patch array
14463			if (!isset($col1[1])) {
14464				$col1[1] = $col1[2] = $col1[0];
14465			}
14466			if (!isset($col2[1])) {
14467				$col2[1] = $col2[2] = $col2[0];
14468			}
14469			if (!isset($col3[1])) {
14470				$col3[1] = $col3[2] = $col3[0];
14471			}
14472			if (!isset($col4[1])) {
14473				$col4[1] = $col4[2] = $col4[0];
14474			}
14475			$patch_array[0]['f'] = 0;
14476			$patch_array[0]['points'] = $coords;
14477			$patch_array[0]['colors'][0]['r'] = $col1[0];
14478			$patch_array[0]['colors'][0]['g'] = $col1[1];
14479			$patch_array[0]['colors'][0]['b'] = $col1[2];
14480			$patch_array[0]['colors'][1]['r'] = $col2[0];
14481			$patch_array[0]['colors'][1]['g'] = $col2[1];
14482			$patch_array[0]['colors'][1]['b'] = $col2[2];
14483			$patch_array[0]['colors'][2]['r'] = $col3[0];
14484			$patch_array[0]['colors'][2]['g'] = $col3[1];
14485			$patch_array[0]['colors'][2]['b'] = $col3[2];
14486			$patch_array[0]['colors'][3]['r'] = $col4[0];
14487			$patch_array[0]['colors'][3]['g'] = $col4[1];
14488			$patch_array[0]['colors'][3]['b'] = $col4[2];
14489		} else {
14490			//multi patch array
14491			$patch_array = $coords;
14492		}
14493		$bpcd = 65535; //16 bits per coordinate
14494		//build the data stream
14495		$this->gradients[$n]['stream'] = '';
14496		$count_patch = count($patch_array);
14497		for ($i=0; $i < $count_patch; ++$i) {
14498			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14499			$count_points = count($patch_array[$i]['points']);
14500			for ($j=0; $j < $count_points; ++$j) {
14501				//each point as 16 bit
14502				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14503				if ($patch_array[$i]['points'][$j] < 0) {
14504					$patch_array[$i]['points'][$j] = 0;
14505				}
14506				if ($patch_array[$i]['points'][$j] > $bpcd) {
14507					$patch_array[$i]['points'][$j] = $bpcd;
14508				}
14509				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14510				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14511			}
14512			$count_cols = count($patch_array[$i]['colors']);
14513			for ($j=0; $j < $count_cols; ++$j) {
14514				//each color component as 8 bit
14515				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14516				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14517				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14518			}
14519		}
14520		//paint the gradient
14521		$this->_out('/Sh'.$n.' sh');
14522		//restore previous Graphic State
14523		$this->_outRestoreGraphicsState();
14524		if ($this->inxobj) {
14525			// we are inside an XObject template
14526			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14527		}
14528	}
14529
14530	/**
14531	 * Set a rectangular clipping area.
14532	 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14533	 * @param $y (float) ordinate of the top left corner of the rectangle.
14534	 * @param $w (float) width of the rectangle.
14535	 * @param $h (float) height of the rectangle.
14536	 * @author Andreas W\FCrmser, Nicola Asuni
14537	 * @since 3.1.000 (2008-06-09)
14538	 * @protected
14539	 */
14540	protected function Clip($x, $y, $w, $h) {
14541		if ($this->state != 2) {
14542			 return;
14543		}
14544		if ($this->rtl) {
14545			$x = $this->w - $x - $w;
14546		}
14547		//save current Graphic State
14548		$s = 'q';
14549		//set clipping area
14550		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14551		//set up transformation matrix for gradient
14552		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14553		$this->_out($s);
14554	}
14555
14556	/**
14557	 * Output gradient.
14558	 * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14559	 * @param $coords (array) array of coordinates.
14560	 * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14561	 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14562	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14563	 * @author Nicola Asuni
14564	 * @since 3.1.000 (2008-06-09)
14565	 * @public
14566	 */
14567	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14568		if ($this->pdfa_mode OR ($this->state != 2)) {
14569			return;
14570		}
14571		$n = count($this->gradients) + 1;
14572		$this->gradients[$n] = array();
14573		$this->gradients[$n]['type'] = $type;
14574		$this->gradients[$n]['coords'] = $coords;
14575		$this->gradients[$n]['antialias'] = $antialias;
14576		$this->gradients[$n]['colors'] = array();
14577		$this->gradients[$n]['transparency'] = false;
14578		// color space
14579		$numcolspace = count($stops[0]['color']);
14580		$bcolor = array_values($background);
14581		switch($numcolspace) {
14582			case 5:   // SPOT
14583			case 4: { // CMYK
14584				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14585				if (!empty($background)) {
14586					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14587				}
14588				break;
14589			}
14590			case 3: { // RGB
14591				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14592				if (!empty($background)) {
14593					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14594				}
14595				break;
14596			}
14597			case 1: { // GRAY SCALE
14598				$this->gradients[$n]['colspace'] = 'DeviceGray';
14599				if (!empty($background)) {
14600					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14601				}
14602				break;
14603			}
14604		}
14605		$num_stops = count($stops);
14606		$last_stop_id = $num_stops - 1;
14607		foreach ($stops as $key => $stop) {
14608			$this->gradients[$n]['colors'][$key] = array();
14609			// offset represents a location along the gradient vector
14610			if (isset($stop['offset'])) {
14611				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14612			} else {
14613				if ($key == 0) {
14614					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14615				} elseif ($key == $last_stop_id) {
14616					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14617				} else {
14618					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14619					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14620				}
14621			}
14622			if (isset($stop['opacity'])) {
14623				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14624				if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14625					$this->gradients[$n]['transparency'] = true;
14626				}
14627			} else {
14628				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14629			}
14630			// exponent for the exponential interpolation function
14631			if (isset($stop['exponent'])) {
14632				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14633			} else {
14634				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14635			}
14636			// set colors
14637			$color = array_values($stop['color']);
14638			switch($numcolspace) {
14639				case 5:   // SPOT
14640				case 4: { // CMYK
14641					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14642					break;
14643				}
14644				case 3: { // RGB
14645					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14646					break;
14647				}
14648				case 1: { // GRAY SCALE
14649					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14650					break;
14651				}
14652			}
14653		}
14654		if ($this->gradients[$n]['transparency']) {
14655			// paint luminosity gradient
14656			$this->_out('/TGS'.$n.' gs');
14657		}
14658		//paint the gradient
14659		$this->_out('/Sh'.$n.' sh');
14660		//restore previous Graphic State
14661		$this->_outRestoreGraphicsState();
14662		if ($this->inxobj) {
14663			// we are inside an XObject template
14664			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14665		}
14666	}
14667
14668	/**
14669	 * Output gradient shaders.
14670	 * @author Nicola Asuni
14671	 * @since 3.1.000 (2008-06-09)
14672	 * @protected
14673	 */
14674	function _putshaders() {
14675		if ($this->pdfa_mode) {
14676			return;
14677		}
14678		$idt = count($this->gradients); //index for transparency gradients
14679		foreach ($this->gradients as $id => $grad) {
14680			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14681				$fc = $this->_newobj();
14682				$out = '<<';
14683				$out .= ' /FunctionType 3';
14684				$out .= ' /Domain [0 1]';
14685				$functions = '';
14686				$bounds = '';
14687				$encode = '';
14688				$i = 1;
14689				$num_cols = count($grad['colors']);
14690				$lastcols = $num_cols - 1;
14691				for ($i = 1; $i < $num_cols; ++$i) {
14692					$functions .= ($fc + $i).' 0 R ';
14693					if ($i < $lastcols) {
14694						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14695					}
14696					$encode .= '0 1 ';
14697				}
14698				$out .= ' /Functions ['.trim($functions).']';
14699				$out .= ' /Bounds ['.trim($bounds).']';
14700				$out .= ' /Encode ['.trim($encode).']';
14701				$out .= ' >>';
14702				$out .= "\n".'endobj';
14703				$this->_out($out);
14704				for ($i = 1; $i < $num_cols; ++$i) {
14705					$this->_newobj();
14706					$out = '<<';
14707					$out .= ' /FunctionType 2';
14708					$out .= ' /Domain [0 1]';
14709					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14710					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14711					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14712					$out .= ' >>';
14713					$out .= "\n".'endobj';
14714					$this->_out($out);
14715				}
14716				// set transparency functions
14717				if ($grad['transparency']) {
14718					$ft = $this->_newobj();
14719					$out = '<<';
14720					$out .= ' /FunctionType 3';
14721					$out .= ' /Domain [0 1]';
14722					$functions = '';
14723					$i = 1;
14724					$num_cols = count($grad['colors']);
14725					for ($i = 1; $i < $num_cols; ++$i) {
14726						$functions .= ($ft + $i).' 0 R ';
14727					}
14728					$out .= ' /Functions ['.trim($functions).']';
14729					$out .= ' /Bounds ['.trim($bounds).']';
14730					$out .= ' /Encode ['.trim($encode).']';
14731					$out .= ' >>';
14732					$out .= "\n".'endobj';
14733					$this->_out($out);
14734					for ($i = 1; $i < $num_cols; ++$i) {
14735						$this->_newobj();
14736						$out = '<<';
14737						$out .= ' /FunctionType 2';
14738						$out .= ' /Domain [0 1]';
14739						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14740						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14741						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14742						$out .= ' >>';
14743						$out .= "\n".'endobj';
14744						$this->_out($out);
14745					}
14746				}
14747			}
14748			// set shading object
14749			$this->_newobj();
14750			$out = '<< /ShadingType '.$grad['type'];
14751			if (isset($grad['colspace'])) {
14752				$out .= ' /ColorSpace /'.$grad['colspace'];
14753			} else {
14754				$out .= ' /ColorSpace /DeviceRGB';
14755			}
14756			if (isset($grad['background']) AND !empty($grad['background'])) {
14757				$out .= ' /Background ['.$grad['background'].']';
14758			}
14759			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14760				$out .= ' /AntiAlias true';
14761			}
14762			if ($grad['type'] == 2) {
14763				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14764				$out .= ' /Domain [0 1]';
14765				$out .= ' /Function '.$fc.' 0 R';
14766				$out .= ' /Extend [true true]';
14767				$out .= ' >>';
14768			} elseif ($grad['type'] == 3) {
14769				//x0, y0, r0, x1, y1, r1
14770				//at this this time radius of inner circle is 0
14771				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14772				$out .= ' /Domain [0 1]';
14773				$out .= ' /Function '.$fc.' 0 R';
14774				$out .= ' /Extend [true true]';
14775				$out .= ' >>';
14776			} elseif ($grad['type'] == 6) {
14777				$out .= ' /BitsPerCoordinate 16';
14778				$out .= ' /BitsPerComponent 8';
14779				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14780				$out .= ' /BitsPerFlag 8';
14781				$stream = $this->_getrawstream($grad['stream']);
14782				$out .= ' /Length '.strlen($stream);
14783				$out .= ' >>';
14784				$out .= ' stream'."\n".$stream."\n".'endstream';
14785			}
14786			$out .= "\n".'endobj';
14787			$this->_out($out);
14788			if ($grad['transparency']) {
14789				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14790				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14791			}
14792			$this->gradients[$id]['id'] = $this->n;
14793			// set pattern object
14794			$this->_newobj();
14795			$out = '<< /Type /Pattern /PatternType 2';
14796			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14797			$out .= ' >>';
14798			$out .= "\n".'endobj';
14799			$this->_out($out);
14800			$this->gradients[$id]['pattern'] = $this->n;
14801			// set shading and pattern for transparency mask
14802			if ($grad['transparency']) {
14803				// luminosity pattern
14804				$idgs = $id + $idt;
14805				$this->_newobj();
14806				$this->_out($shading_transparency);
14807				$this->gradients[$idgs]['id'] = $this->n;
14808				$this->_newobj();
14809				$out = '<< /Type /Pattern /PatternType 2';
14810				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14811				$out .= ' >>';
14812				$out .= "\n".'endobj';
14813				$this->_out($out);
14814				$this->gradients[$idgs]['pattern'] = $this->n;
14815				// luminosity XObject
14816				$oid = $this->_newobj();
14817				$this->xobjects['LX'.$oid] = array('n' => $oid);
14818				$filter = '';
14819				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14820				if ($this->compress) {
14821					$filter = ' /Filter /FlateDecode';
14822					$stream = gzcompress($stream);
14823				}
14824				$stream = $this->_getrawstream($stream);
14825				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14826				$out .= ' /Length '.strlen($stream);
14827				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14828				$out .= ' /BBox [0 0 '.$rect.']';
14829				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14830				$out .= ' /Resources <<';
14831				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14832				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14833				$out .= ' >>';
14834				$out .= ' >> ';
14835				$out .= ' stream'."\n".$stream."\n".'endstream';
14836				$out .= "\n".'endobj';
14837				$this->_out($out);
14838				// SMask
14839				$this->_newobj();
14840				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14841				$this->_out($out);
14842				// ExtGState
14843				$this->_newobj();
14844				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14845				$this->_out($out);
14846				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14847			}
14848		}
14849	}
14850
14851	/**
14852	 * Draw the sector of a circle.
14853	 * It can be used for instance to render pie charts.
14854	 * @param $xc (float) abscissa of the center.
14855	 * @param $yc (float) ordinate of the center.
14856	 * @param $r (float) radius.
14857	 * @param $a (float) start angle (in degrees).
14858	 * @param $b (float) end angle (in degrees).
14859	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14860	 * @param $cw: (float) indicates whether to go clockwise (default: true).
14861	 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14862	 * @author Maxime Delorme, Nicola Asuni
14863	 * @since 3.1.000 (2008-06-09)
14864	 * @public
14865	 */
14866	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14867		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14868	}
14869
14870	/**
14871	 * Draw the sector of an ellipse.
14872	 * It can be used for instance to render pie charts.
14873	 * @param $xc (float) abscissa of the center.
14874	 * @param $yc (float) ordinate of the center.
14875	 * @param $rx (float) the x-axis radius.
14876	 * @param $ry (float) the y-axis radius.
14877	 * @param $a (float) start angle (in degrees).
14878	 * @param $b (float) end angle (in degrees).
14879	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14880	 * @param $cw: (float) indicates whether to go clockwise.
14881	 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14882	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14883	 * @author Maxime Delorme, Nicola Asuni
14884	 * @since 3.1.000 (2008-06-09)
14885	 * @public
14886	 */
14887	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14888		if ($this->state != 2) {
14889			 return;
14890		}
14891		if ($this->rtl) {
14892			$xc = ($this->w - $xc);
14893		}
14894		$op = TCPDF_STATIC::getPathPaintOperator($style);
14895		if ($op == 'f') {
14896			$line_style = array();
14897		}
14898		if ($cw) {
14899			$d = $b;
14900			$b = (360 - $a + $o);
14901			$a = (360 - $d + $o);
14902		} else {
14903			$b += $o;
14904			$a += $o;
14905		}
14906		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14907		$this->_out($op);
14908	}
14909
14910	/**
14911	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14912	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14913	 * Only vector drawing is supported, not text or bitmap.
14914	 * 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).
14915	 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14916	 * @param $x (float) Abscissa of the upper-left corner.
14917	 * @param $y (float) Ordinate of the upper-left corner.
14918	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14919	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14920	 * @param $link (mixed) URL or identifier returned by AddLink().
14921	 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14922	 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
14923	 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
14924	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
14925	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14926	 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14927	 * @author Valentin Schmidt, Nicola Asuni
14928	 * @since 3.1.000 (2008-06-09)
14929	 * @public
14930	 */
14931	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14932		if ($this->state != 2) {
14933			 return;
14934		}
14935		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14936			// convert EPS to raster image using GD or ImageMagick libraries
14937			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14938		}
14939		if ($x === '') {
14940			$x = $this->x;
14941		}
14942		if ($y === '') {
14943			$y = $this->y;
14944		}
14945		// check page for no-write regions and adapt page margins if necessary
14946		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14947		$k = $this->k;
14948		if ($file[0] === '@') { // image from string
14949			$data = substr($file, 1);
14950		} else { // EPS/AI file
14951            $data = $this->getCachedFileContents($file);
14952		}
14953		if ($data === FALSE) {
14954			$this->Error('EPS file not found: '.$file);
14955		}
14956		$regs = array();
14957		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14958		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14959		if (count($regs) > 1) {
14960			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14961			if (strpos($version_str, 'Adobe Illustrator') !== false) {
14962				$versexp = explode(' ', $version_str);
14963				$version = (float)array_pop($versexp);
14964				if ($version >= 9) {
14965					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14966				}
14967			}
14968		}
14969		// strip binary bytes in front of PS-header
14970		$start = strpos($data, '%!PS-Adobe');
14971		if ($start > 0) {
14972			$data = substr($data, $start);
14973		}
14974		// find BoundingBox params
14975		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14976		if (count($regs) > 1) {
14977			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14978		} else {
14979			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
14980		}
14981		$start = strpos($data, '%%EndSetup');
14982		if ($start === false) {
14983			$start = strpos($data, '%%EndProlog');
14984		}
14985		if ($start === false) {
14986			$start = strpos($data, '%%BoundingBox');
14987		}
14988		$data = substr($data, $start);
14989		$end = strpos($data, '%%PageTrailer');
14990		if ($end===false) {
14991			$end = strpos($data, 'showpage');
14992		}
14993		if ($end) {
14994			$data = substr($data, 0, $end);
14995		}
14996		// calculate image width and height on document
14997		if (($w <= 0) AND ($h <= 0)) {
14998			$w = ($x2 - $x1) / $k;
14999			$h = ($y2 - $y1) / $k;
15000		} elseif ($w <= 0) {
15001			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15002		} elseif ($h <= 0) {
15003			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15004		}
15005		// fit the image on available space
15006		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15007		if ($this->rasterize_vector_images) {
15008			// convert EPS to raster image using GD or ImageMagick libraries
15009			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15010		}
15011		// set scaling factors
15012		$scale_x = $w / (($x2 - $x1) / $k);
15013		$scale_y = $h / (($y2 - $y1) / $k);
15014		// set alignment
15015		$this->img_rb_y = $y + $h;
15016		// set alignment
15017		if ($this->rtl) {
15018			if ($palign == 'L') {
15019				$ximg = $this->lMargin;
15020			} elseif ($palign == 'C') {
15021				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15022			} elseif ($palign == 'R') {
15023				$ximg = $this->w - $this->rMargin - $w;
15024			} else {
15025				$ximg = $x - $w;
15026			}
15027			$this->img_rb_x = $ximg;
15028		} else {
15029			if ($palign == 'L') {
15030				$ximg = $this->lMargin;
15031			} elseif ($palign == 'C') {
15032				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15033			} elseif ($palign == 'R') {
15034				$ximg = $this->w - $this->rMargin - $w;
15035			} else {
15036				$ximg = $x;
15037			}
15038			$this->img_rb_x = $ximg + $w;
15039		}
15040		if ($useBoundingBox) {
15041			$dx = $ximg * $k - $x1;
15042			$dy = $y * $k - $y1;
15043		} else {
15044			$dx = $ximg * $k;
15045			$dy = $y * $k;
15046		}
15047		// save the current graphic state
15048		$this->_out('q'.$this->epsmarker);
15049		// translate
15050		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15051		// scale
15052		if (isset($scale_x)) {
15053			$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15054		}
15055		// handle pc/unix/mac line endings
15056		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15057		$u=0;
15058		$cnt = count($lines);
15059		for ($i=0; $i < $cnt; ++$i) {
15060			$line = $lines[$i];
15061			if (($line == '') OR ($line[0] == '%')) {
15062				continue;
15063			}
15064			$len = strlen($line);
15065			// check for spot color names
15066			$color_name = '';
15067			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15068				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15069					// extract spot color name
15070					$color_name = $matches[0];
15071					// remove color name from string
15072					$line = str_replace(' '.$color_name, '', $line);
15073					// remove pharentesis from color name
15074					$color_name = substr($color_name, 1, -1);
15075				}
15076			}
15077			$chunks = explode(' ', $line);
15078			$cmd = trim(array_pop($chunks));
15079			// RGB
15080			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15081				$b = array_pop($chunks);
15082				$g = array_pop($chunks);
15083				$r = array_pop($chunks);
15084				$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!
15085				continue;
15086			}
15087			$skip = false;
15088			if ($fixoutvals) {
15089				// check for values outside the bounding box
15090				switch ($cmd) {
15091					case 'm':
15092					case 'l':
15093					case 'L': {
15094						// skip values outside bounding box
15095						foreach ($chunks as $key => $val) {
15096							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15097								$skip = true;
15098							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15099								$skip = true;
15100							}
15101						}
15102					}
15103				}
15104			}
15105			switch ($cmd) {
15106				case 'm':
15107				case 'l':
15108				case 'v':
15109				case 'y':
15110				case 'c':
15111				case 'k':
15112				case 'K':
15113				case 'g':
15114				case 'G':
15115				case 's':
15116				case 'S':
15117				case 'J':
15118				case 'j':
15119				case 'w':
15120				case 'M':
15121				case 'd':
15122				case 'n': {
15123					if ($skip) {
15124						break;
15125					}
15126					$this->_out($line);
15127					break;
15128				}
15129				case 'x': {// custom fill color
15130					if (empty($color_name)) {
15131						// CMYK color
15132						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15133						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15134					} else {
15135						// Spot Color (CMYK + tint)
15136						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15137						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15138						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15139						$this->_out($color_cmd);
15140					}
15141					break;
15142				}
15143				case 'X': { // custom stroke color
15144					if (empty($color_name)) {
15145						// CMYK color
15146						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15147						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15148					} else {
15149						// Spot Color (CMYK + tint)
15150						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15151						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15152						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15153						$this->_out($color_cmd);
15154					}
15155					break;
15156				}
15157				case 'Y':
15158				case 'N':
15159				case 'V':
15160				case 'L':
15161				case 'C': {
15162					if ($skip) {
15163						break;
15164					}
15165					$line[($len - 1)] = strtolower($cmd);
15166					$this->_out($line);
15167					break;
15168				}
15169				case 'b':
15170				case 'B': {
15171					$this->_out($cmd . '*');
15172					break;
15173				}
15174				case 'f':
15175				case 'F': {
15176					if ($u > 0) {
15177						$isU = false;
15178						$max = min(($i + 5), $cnt);
15179						for ($j = ($i + 1); $j < $max; ++$j) {
15180							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15181						}
15182						if ($isU) {
15183							$this->_out('f*');
15184						}
15185					} else {
15186						$this->_out('f*');
15187					}
15188					break;
15189				}
15190				case '*u': {
15191					++$u;
15192					break;
15193				}
15194				case '*U': {
15195					--$u;
15196					break;
15197				}
15198			}
15199		}
15200		// restore previous graphic state
15201		$this->_out($this->epsmarker.'Q');
15202		if (!empty($border)) {
15203			$bx = $this->x;
15204			$by = $this->y;
15205			$this->x = $ximg;
15206			if ($this->rtl) {
15207				$this->x += $w;
15208			}
15209			$this->y = $y;
15210			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15211			$this->x = $bx;
15212			$this->y = $by;
15213		}
15214		if ($link) {
15215			$this->Link($ximg, $y, $w, $h, $link, 0);
15216		}
15217		// set pointer to align the next text/objects
15218		switch($align) {
15219			case 'T':{
15220				$this->y = $y;
15221				$this->x = $this->img_rb_x;
15222				break;
15223			}
15224			case 'M':{
15225				$this->y = $y + round($h/2);
15226				$this->x = $this->img_rb_x;
15227				break;
15228			}
15229			case 'B':{
15230				$this->y = $this->img_rb_y;
15231				$this->x = $this->img_rb_x;
15232				break;
15233			}
15234			case 'N':{
15235				$this->SetY($this->img_rb_y);
15236				break;
15237			}
15238			default:{
15239				break;
15240			}
15241		}
15242		$this->endlinex = $this->img_rb_x;
15243	}
15244
15245	/**
15246	 * Set document barcode.
15247	 * @param $bc (string) barcode
15248	 * @public
15249	 */
15250	public function setBarcode($bc='') {
15251		$this->barcode = $bc;
15252	}
15253
15254	/**
15255	 * Get current barcode.
15256	 * @return string
15257	 * @public
15258	 * @since 4.0.012 (2008-07-24)
15259	 */
15260	public function getBarcode() {
15261		return $this->barcode;
15262	}
15263
15264	/**
15265	 * Print a Linear Barcode.
15266	 * @param $code (string) code to print
15267	 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15268	 * @param $x (int) x position in user units (empty string = current x position)
15269	 * @param $y (int) y position in user units (empty string = current y position)
15270	 * @param $w (int) width in user units (empty string = remaining page width)
15271	 * @param $h (int) height in user units (empty string = remaining page height)
15272	 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15273	 * @param $style (array) array of options:<ul>
15274	 * <li>boolean $style['border'] if true prints a border</li>
15275	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15276	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15277	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15278	 * <li>array $style['fgcolor'] color array for bars and text</li>
15279	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15280	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15281	 * <li>string $style['label'] override default label</li>
15282	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15283	 * <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>
15284	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15285	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15286	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15287	 * <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>
15288	 * <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>
15289	 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15290	 * @author Nicola Asuni
15291	 * @since 3.1.000 (2008-06-09)
15292	 * @public
15293	 */
15294	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') {
15295		if (TCPDF_STATIC::empty_string(trim($code))) {
15296			return;
15297		}
15298		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15299		// save current graphic settings
15300		$gvars = $this->getGraphicVars();
15301		// create new barcode object
15302		$barcodeobj = new TCPDFBarcode($code, $type);
15303		$arrcode = $barcodeobj->getBarcodeArray();
15304		if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15305			$this->Error('Error in 1D barcode string');
15306		}
15307		if ($arrcode['maxh'] <= 0) {
15308			$arrcode['maxh'] = 1;
15309		}
15310		// set default values
15311		if (!isset($style['position'])) {
15312			$style['position'] = '';
15313		} elseif ($style['position'] == 'S') {
15314			// keep this for backward compatibility
15315			$style['position'] = '';
15316			$style['stretch'] = true;
15317		}
15318		if (!isset($style['fitwidth'])) {
15319			if (!isset($style['stretch'])) {
15320				$style['fitwidth'] = true;
15321			} else {
15322				$style['fitwidth'] = false;
15323			}
15324		}
15325		if ($style['fitwidth']) {
15326			// disable stretch
15327			$style['stretch'] = false;
15328		}
15329		if (!isset($style['stretch'])) {
15330			if (($w === '') OR ($w <= 0)) {
15331				$style['stretch'] = false;
15332			} else {
15333				$style['stretch'] = true;
15334			}
15335		}
15336		if (!isset($style['fgcolor'])) {
15337			$style['fgcolor'] = array(0,0,0); // default black
15338		}
15339		if (!isset($style['bgcolor'])) {
15340			$style['bgcolor'] = false; // default transparent
15341		}
15342		if (!isset($style['border'])) {
15343			$style['border'] = false;
15344		}
15345		$fontsize = 0;
15346		if (!isset($style['text'])) {
15347			$style['text'] = false;
15348		}
15349		if ($style['text'] AND isset($style['font'])) {
15350			if (isset($style['fontsize'])) {
15351				$fontsize = $style['fontsize'];
15352			}
15353			$this->SetFont($style['font'], '', $fontsize);
15354		}
15355		if (!isset($style['stretchtext'])) {
15356			$style['stretchtext'] = 4;
15357		}
15358		if ($x === '') {
15359			$x = $this->x;
15360		}
15361		if ($y === '') {
15362			$y = $this->y;
15363		}
15364		// check page for no-write regions and adapt page margins if necessary
15365		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15366		if (($w === '') OR ($w <= 0)) {
15367			if ($this->rtl) {
15368				$w = $x - $this->lMargin;
15369			} else {
15370				$w = $this->w - $this->rMargin - $x;
15371			}
15372		}
15373		// padding
15374		if (!isset($style['padding'])) {
15375			$padding = 0;
15376		} elseif ($style['padding'] === 'auto') {
15377			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15378		} else {
15379			$padding = floatval($style['padding']);
15380		}
15381		// horizontal padding
15382		if (!isset($style['hpadding'])) {
15383			$hpadding = $padding;
15384		} elseif ($style['hpadding'] === 'auto') {
15385			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15386		} else {
15387			$hpadding = floatval($style['hpadding']);
15388		}
15389		// vertical padding
15390		if (!isset($style['vpadding'])) {
15391			$vpadding = $padding;
15392		} elseif ($style['vpadding'] === 'auto') {
15393			$vpadding = ($hpadding / 2);
15394		} else {
15395			$vpadding = floatval($style['vpadding']);
15396		}
15397		// calculate xres (single bar width)
15398		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15399		if ($style['stretch']) {
15400			$xres = $max_xres;
15401		} else {
15402			if (TCPDF_STATIC::empty_string($xres)) {
15403				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15404			}
15405			if ($xres > $max_xres) {
15406				// correct xres to fit on $w
15407				$xres = $max_xres;
15408			}
15409			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15410				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15411				$hpadding = 10 * $xres;
15412				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15413					$vpadding = ($hpadding / 2);
15414				}
15415			}
15416		}
15417		if ($style['fitwidth']) {
15418			$wold = $w;
15419			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15420			if (isset($style['cellfitalign'])) {
15421				switch ($style['cellfitalign']) {
15422					case 'L': {
15423						if ($this->rtl) {
15424							$x -= ($wold - $w);
15425						}
15426						break;
15427					}
15428					case 'R': {
15429						if (!$this->rtl) {
15430							$x += ($wold - $w);
15431						}
15432						break;
15433					}
15434					case 'C': {
15435						if ($this->rtl) {
15436							$x -= (($wold - $w) / 2);
15437						} else {
15438							$x += (($wold - $w) / 2);
15439						}
15440						break;
15441					}
15442					default : {
15443						break;
15444					}
15445				}
15446			}
15447		}
15448		$text_height = $this->getCellHeight($fontsize / $this->k);
15449		// height
15450		if (($h === '') OR ($h <= 0)) {
15451			// set default height
15452			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15453		}
15454		$barh = $h - $text_height - (2 * $vpadding);
15455		if ($barh <=0) {
15456			// try to reduce font or padding to fit barcode on available height
15457			if ($text_height > $h) {
15458				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15459				$text_height = $this->getCellHeight($fontsize / $this->k);
15460				$this->SetFont($style['font'], '', $fontsize);
15461			}
15462			if ($vpadding > 0) {
15463				$vpadding = (($h - $text_height) / 4);
15464			}
15465			$barh = $h - $text_height - (2 * $vpadding);
15466		}
15467		// fit the barcode on available space
15468		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15469		// set alignment
15470		$this->img_rb_y = $y + $h;
15471		// set alignment
15472		if ($this->rtl) {
15473			if ($style['position'] == 'L') {
15474				$xpos = $this->lMargin;
15475			} elseif ($style['position'] == 'C') {
15476				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15477			} elseif ($style['position'] == 'R') {
15478				$xpos = $this->w - $this->rMargin - $w;
15479			} else {
15480				$xpos = $x - $w;
15481			}
15482			$this->img_rb_x = $xpos;
15483		} else {
15484			if ($style['position'] == 'L') {
15485				$xpos = $this->lMargin;
15486			} elseif ($style['position'] == 'C') {
15487				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15488			} elseif ($style['position'] == 'R') {
15489				$xpos = $this->w - $this->rMargin - $w;
15490			} else {
15491				$xpos = $x;
15492			}
15493			$this->img_rb_x = $xpos + $w;
15494		}
15495		$xpos_rect = $xpos;
15496		if (!isset($style['align'])) {
15497			$style['align'] = 'C';
15498		}
15499		switch ($style['align']) {
15500			case 'L': {
15501				$xpos = $xpos_rect + $hpadding;
15502				break;
15503			}
15504			case 'R': {
15505				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15506				break;
15507			}
15508			case 'C':
15509			default : {
15510				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15511				break;
15512			}
15513		}
15514		$xpos_text = $xpos;
15515		// barcode is always printed in LTR direction
15516		$tempRTL = $this->rtl;
15517		$this->rtl = false;
15518		// print background color
15519		if ($style['bgcolor']) {
15520			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15521		} elseif ($style['border']) {
15522			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15523		}
15524		// set foreground color
15525		$this->SetDrawColorArray($style['fgcolor']);
15526		$this->SetTextColorArray($style['fgcolor']);
15527		// print bars
15528		foreach ($arrcode['bcode'] as $k => $v) {
15529			$bw = ($v['w'] * $xres);
15530			if ($v['t']) {
15531				// draw a vertical bar
15532				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15533				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15534			}
15535			$xpos += $bw;
15536		}
15537		// print text
15538		if ($style['text']) {
15539			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15540				$label = $style['label'];
15541			} else {
15542				$label = $code;
15543			}
15544			$txtwidth = ($arrcode['maxw'] * $xres);
15545			if ($this->GetStringWidth($label) > $txtwidth) {
15546				$style['stretchtext'] = 2;
15547			}
15548			// print text
15549			$this->x = $xpos_text;
15550			$this->y = $y + $vpadding + $barh;
15551			$cellpadding = $this->cell_padding;
15552			$this->SetCellPadding(0);
15553			$this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15554			$this->cell_padding = $cellpadding;
15555		}
15556		// restore original direction
15557		$this->rtl = $tempRTL;
15558		// restore previous settings
15559		$this->setGraphicVars($gvars);
15560		// set pointer to align the next text/objects
15561		switch($align) {
15562			case 'T':{
15563				$this->y = $y;
15564				$this->x = $this->img_rb_x;
15565				break;
15566			}
15567			case 'M':{
15568				$this->y = $y + round($h / 2);
15569				$this->x = $this->img_rb_x;
15570				break;
15571			}
15572			case 'B':{
15573				$this->y = $this->img_rb_y;
15574				$this->x = $this->img_rb_x;
15575				break;
15576			}
15577			case 'N':{
15578				$this->SetY($this->img_rb_y);
15579				break;
15580			}
15581			default:{
15582				break;
15583			}
15584		}
15585		$this->endlinex = $this->img_rb_x;
15586	}
15587
15588	/**
15589	 * Print 2D Barcode.
15590	 * @param $code (string) code to print
15591	 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15592	 * @param $x (int) x position in user units
15593	 * @param $y (int) y position in user units
15594	 * @param $w (int) width in user units
15595	 * @param $h (int) height in user units
15596	 * @param $style (array) array of options:<ul>
15597	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15598	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15599	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15600	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15601	 * <li>int $style['module_width'] width of a single module in points</li>
15602	 * <li>int $style['module_height'] height of a single module in points</li>
15603	 * <li>array $style['fgcolor'] color array for bars and text</li>
15604	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15605	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15606	 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15607	 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15608	 * @author Nicola Asuni
15609	 * @since 4.5.037 (2009-04-07)
15610	 * @public
15611	 */
15612	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) {
15613		if (TCPDF_STATIC::empty_string(trim($code))) {
15614			return;
15615		}
15616		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15617		// save current graphic settings
15618		$gvars = $this->getGraphicVars();
15619		// create new barcode object
15620		$barcodeobj = new TCPDF2DBarcode($code, $type);
15621		$arrcode = $barcodeobj->getBarcodeArray();
15622		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)) {
15623			$this->Error('Error in 2D barcode string');
15624		}
15625		// set default values
15626		if (!isset($style['position'])) {
15627			$style['position'] = '';
15628		}
15629		if (!isset($style['fgcolor'])) {
15630			$style['fgcolor'] = array(0,0,0); // default black
15631		}
15632		if (!isset($style['bgcolor'])) {
15633			$style['bgcolor'] = false; // default transparent
15634		}
15635		if (!isset($style['border'])) {
15636			$style['border'] = false;
15637		}
15638		// padding
15639		if (!isset($style['padding'])) {
15640			$style['padding'] = 0;
15641		} elseif ($style['padding'] === 'auto') {
15642			$style['padding'] = 4;
15643		}
15644		if (!isset($style['hpadding'])) {
15645			$style['hpadding'] = $style['padding'];
15646		} elseif ($style['hpadding'] === 'auto') {
15647			$style['hpadding'] = 4;
15648		}
15649		if (!isset($style['vpadding'])) {
15650			$style['vpadding'] = $style['padding'];
15651		} elseif ($style['vpadding'] === 'auto') {
15652			$style['vpadding'] = 4;
15653		}
15654		$hpad = (2 * $style['hpadding']);
15655		$vpad = (2 * $style['vpadding']);
15656		// cell (module) dimension
15657		if (!isset($style['module_width'])) {
15658			$style['module_width'] = 1; // width of a single module in points
15659		}
15660		if (!isset($style['module_height'])) {
15661			$style['module_height'] = 1; // height of a single module in points
15662		}
15663		if ($x === '') {
15664			$x = $this->x;
15665		}
15666		if ($y === '') {
15667			$y = $this->y;
15668		}
15669		// check page for no-write regions and adapt page margins if necessary
15670		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15671		// number of barcode columns and rows
15672		$rows = $arrcode['num_rows'];
15673		$cols = $arrcode['num_cols'];
15674		if (($rows <= 0) || ($cols <= 0)){
15675			$this->Error('Error in 2D barcode string');
15676		}
15677		// module width and height
15678		$mw = $style['module_width'];
15679		$mh = $style['module_height'];
15680		if (($mw <= 0) OR ($mh <= 0)) {
15681			$this->Error('Error in 2D barcode string');
15682		}
15683		// get max dimensions
15684		if ($this->rtl) {
15685			$maxw = $x - $this->lMargin;
15686		} else {
15687			$maxw = $this->w - $this->rMargin - $x;
15688		}
15689		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15690		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15691		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15692		if (!$distort) {
15693			if (($maxw * $ratioHW) > $maxh) {
15694				$maxw = $maxh * $ratioWH;
15695			}
15696			if (($maxh * $ratioWH) > $maxw) {
15697				$maxh = $maxw * $ratioHW;
15698			}
15699		}
15700		// set maximum dimensions
15701		if ($w > $maxw) {
15702			$w = $maxw;
15703		}
15704		if ($h > $maxh) {
15705			$h = $maxh;
15706		}
15707		// set dimensions
15708		if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15709			$w = ($cols + $hpad) * ($mw / $this->k);
15710			$h = ($rows + $vpad) * ($mh / $this->k);
15711		} elseif (($w === '') OR ($w <= 0)) {
15712			$w = $h * $ratioWH;
15713		} elseif (($h === '') OR ($h <= 0)) {
15714			$h = $w * $ratioHW;
15715		}
15716		// barcode size (excluding padding)
15717		$bw = ($w * $cols) / ($cols + $hpad);
15718		$bh = ($h * $rows) / ($rows + $vpad);
15719		// dimension of single barcode cell unit
15720		$cw = $bw / $cols;
15721		$ch = $bh / $rows;
15722		if (!$distort) {
15723			if (($cw / $ch) > ($mw / $mh)) {
15724				// correct horizontal distortion
15725				$cw = $ch * $mw / $mh;
15726				$bw = $cw * $cols;
15727				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15728			} else {
15729				// correct vertical distortion
15730				$ch = $cw * $mh / $mw;
15731				$bh = $ch * $rows;
15732				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15733			}
15734		}
15735		// fit the barcode on available space
15736		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15737		// set alignment
15738		$this->img_rb_y = $y + $h;
15739		// set alignment
15740		if ($this->rtl) {
15741			if ($style['position'] == 'L') {
15742				$xpos = $this->lMargin;
15743			} elseif ($style['position'] == 'C') {
15744				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15745			} elseif ($style['position'] == 'R') {
15746				$xpos = $this->w - $this->rMargin - $w;
15747			} else {
15748				$xpos = $x - $w;
15749			}
15750			$this->img_rb_x = $xpos;
15751		} else {
15752			if ($style['position'] == 'L') {
15753				$xpos = $this->lMargin;
15754			} elseif ($style['position'] == 'C') {
15755				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15756			} elseif ($style['position'] == 'R') {
15757				$xpos = $this->w - $this->rMargin - $w;
15758			} else {
15759				$xpos = $x;
15760			}
15761			$this->img_rb_x = $xpos + $w;
15762		}
15763		$xstart = $xpos + ($style['hpadding'] * $cw);
15764		$ystart = $y + ($style['vpadding'] * $ch);
15765		// barcode is always printed in LTR direction
15766		$tempRTL = $this->rtl;
15767		$this->rtl = false;
15768		// print background color
15769		if ($style['bgcolor']) {
15770			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15771		} elseif ($style['border']) {
15772			$this->Rect($xpos, $y, $w, $h, 'D');
15773		}
15774		// set foreground color
15775		$this->SetDrawColorArray($style['fgcolor']);
15776		// print barcode cells
15777		// for each row
15778		for ($r = 0; $r < $rows; ++$r) {
15779			$xr = $xstart;
15780			// for each column
15781			for ($c = 0; $c < $cols; ++$c) {
15782				if ($arrcode['bcode'][$r][$c] == 1) {
15783					// draw a single barcode cell
15784					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15785				}
15786				$xr += $cw;
15787			}
15788			$ystart += $ch;
15789		}
15790		// restore original direction
15791		$this->rtl = $tempRTL;
15792		// restore previous settings
15793		$this->setGraphicVars($gvars);
15794		// set pointer to align the next text/objects
15795		switch($align) {
15796			case 'T':{
15797				$this->y = $y;
15798				$this->x = $this->img_rb_x;
15799				break;
15800			}
15801			case 'M':{
15802				$this->y = $y + round($h/2);
15803				$this->x = $this->img_rb_x;
15804				break;
15805			}
15806			case 'B':{
15807				$this->y = $this->img_rb_y;
15808				$this->x = $this->img_rb_x;
15809				break;
15810			}
15811			case 'N':{
15812				$this->SetY($this->img_rb_y);
15813				break;
15814			}
15815			default:{
15816				break;
15817			}
15818		}
15819		$this->endlinex = $this->img_rb_x;
15820	}
15821
15822	/**
15823	 * Returns an array containing current margins:
15824	 * <ul>
15825			<li>$ret['left'] = left margin</li>
15826			<li>$ret['right'] = right margin</li>
15827			<li>$ret['top'] = top margin</li>
15828			<li>$ret['bottom'] = bottom margin</li>
15829			<li>$ret['header'] = header margin</li>
15830			<li>$ret['footer'] = footer margin</li>
15831			<li>$ret['cell'] = cell padding array</li>
15832			<li>$ret['padding_left'] = cell left padding</li>
15833			<li>$ret['padding_top'] = cell top padding</li>
15834			<li>$ret['padding_right'] = cell right padding</li>
15835			<li>$ret['padding_bottom'] = cell bottom padding</li>
15836	 * </ul>
15837	 * @return array containing all margins measures
15838	 * @public
15839	 * @since 3.2.000 (2008-06-23)
15840	 */
15841	public function getMargins() {
15842		$ret = array(
15843			'left' => $this->lMargin,
15844			'right' => $this->rMargin,
15845			'top' => $this->tMargin,
15846			'bottom' => $this->bMargin,
15847			'header' => $this->header_margin,
15848			'footer' => $this->footer_margin,
15849			'cell' => $this->cell_padding,
15850			'padding_left' => $this->cell_padding['L'],
15851			'padding_top' => $this->cell_padding['T'],
15852			'padding_right' => $this->cell_padding['R'],
15853			'padding_bottom' => $this->cell_padding['B']
15854		);
15855		return $ret;
15856	}
15857
15858	/**
15859	 * Returns an array containing original margins:
15860	 * <ul>
15861			<li>$ret['left'] = left margin</li>
15862			<li>$ret['right'] = right margin</li>
15863	 * </ul>
15864	 * @return array containing all margins measures
15865	 * @public
15866	 * @since 4.0.012 (2008-07-24)
15867	 */
15868	public function getOriginalMargins() {
15869		$ret = array(
15870			'left' => $this->original_lMargin,
15871			'right' => $this->original_rMargin
15872		);
15873		return $ret;
15874	}
15875
15876	/**
15877	 * Returns the current font size.
15878	 * @return current font size
15879	 * @public
15880	 * @since 3.2.000 (2008-06-23)
15881	 */
15882	public function getFontSize() {
15883		return $this->FontSize;
15884	}
15885
15886	/**
15887	 * Returns the current font size in points unit.
15888	 * @return current font size in points unit
15889	 * @public
15890	 * @since 3.2.000 (2008-06-23)
15891	 */
15892	public function getFontSizePt() {
15893		return $this->FontSizePt;
15894	}
15895
15896	/**
15897	 * Returns the current font family name.
15898	 * @return string current font family name
15899	 * @public
15900	 * @since 4.3.008 (2008-12-05)
15901	 */
15902	public function getFontFamily() {
15903		return $this->FontFamily;
15904	}
15905
15906	/**
15907	 * Returns the current font style.
15908	 * @return string current font style
15909	 * @public
15910	 * @since 4.3.008 (2008-12-05)
15911	 */
15912	public function getFontStyle() {
15913		return $this->FontStyle;
15914	}
15915
15916	/**
15917	 * Cleanup HTML code (requires HTML Tidy library).
15918	 * @param $html (string) htmlcode to fix
15919	 * @param $default_css (string) CSS commands to add
15920	 * @param $tagvs (array) parameters for setHtmlVSpace method
15921	 * @param $tidy_options (array) options for tidy_parse_string function
15922	 * @return string XHTML code cleaned up
15923	 * @author Nicola Asuni
15924	 * @public
15925	 * @since 5.9.017 (2010-11-16)
15926	 * @see setHtmlVSpace()
15927	 */
15928	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15929		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15930	}
15931
15932	/**
15933	 * Returns the border width from CSS property
15934	 * @param $width (string) border width
15935	 * @return int with in user units
15936	 * @protected
15937	 * @since 5.7.000 (2010-08-02)
15938	 */
15939	protected function getCSSBorderWidth($width) {
15940		if ($width == 'thin') {
15941			$width = (2 / $this->k);
15942		} elseif ($width == 'medium') {
15943			$width = (4 / $this->k);
15944		} elseif ($width == 'thick') {
15945			$width = (6 / $this->k);
15946		} else {
15947			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15948		}
15949		return $width;
15950	}
15951
15952	/**
15953	 * Returns the border dash style from CSS property
15954	 * @param $style (string) border style to convert
15955	 * @return int sash style (return -1 in case of none or hidden border)
15956	 * @protected
15957	 * @since 5.7.000 (2010-08-02)
15958	 */
15959	protected function getCSSBorderDashStyle($style) {
15960		switch (strtolower($style)) {
15961			case 'none':
15962			case 'hidden': {
15963				$dash = -1;
15964				break;
15965			}
15966			case 'dotted': {
15967				$dash = 1;
15968				break;
15969			}
15970			case 'dashed': {
15971				$dash = 3;
15972				break;
15973			}
15974			case 'double':
15975			case 'groove':
15976			case 'ridge':
15977			case 'inset':
15978			case 'outset':
15979			case 'solid':
15980			default: {
15981				$dash = 0;
15982				break;
15983			}
15984		}
15985		return $dash;
15986	}
15987
15988	/**
15989	 * Returns the border style array from CSS border properties
15990	 * @param $cssborder (string) border properties
15991	 * @return array containing border properties
15992	 * @protected
15993	 * @since 5.7.000 (2010-08-02)
15994	 */
15995	protected function getCSSBorderStyle($cssborder) {
15996		$bprop = preg_split('/[\s]+/', trim($cssborder));
15997		$border = array(); // value to be returned
15998		switch (count($bprop)) {
15999			case 3: {
16000				$width = $bprop[0];
16001				$style = $bprop[1];
16002				$color = $bprop[2];
16003				break;
16004			}
16005			case 2: {
16006				$width = 'medium';
16007				$style = $bprop[0];
16008				$color = $bprop[1];
16009				break;
16010			}
16011			case 1: {
16012				$width = 'medium';
16013				$style = $bprop[0];
16014				$color = 'black';
16015				break;
16016			}
16017			default: {
16018				$width = 'medium';
16019				$style = 'solid';
16020				$color = 'black';
16021				break;
16022			}
16023		}
16024		if ($style == 'none') {
16025			return array();
16026		}
16027		$border['cap'] = 'square';
16028		$border['join'] = 'miter';
16029		$border['dash'] = $this->getCSSBorderDashStyle($style);
16030		if ($border['dash'] < 0) {
16031			return array();
16032		}
16033		$border['width'] = $this->getCSSBorderWidth($width);
16034		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16035		return $border;
16036	}
16037
16038	/**
16039	 * Get the internal Cell padding from CSS attribute.
16040	 * @param $csspadding (string) padding properties
16041	 * @param $width (float) width of the containing element
16042	 * @return array of cell paddings
16043	 * @public
16044	 * @since 5.9.000 (2010-10-04)
16045	 */
16046	public function getCSSPadding($csspadding, $width=0) {
16047		$padding = preg_split('/[\s]+/', trim($csspadding));
16048		$cell_padding = array(); // value to be returned
16049		switch (count($padding)) {
16050			case 4: {
16051				$cell_padding['T'] = $padding[0];
16052				$cell_padding['R'] = $padding[1];
16053				$cell_padding['B'] = $padding[2];
16054				$cell_padding['L'] = $padding[3];
16055				break;
16056			}
16057			case 3: {
16058				$cell_padding['T'] = $padding[0];
16059				$cell_padding['R'] = $padding[1];
16060				$cell_padding['B'] = $padding[2];
16061				$cell_padding['L'] = $padding[1];
16062				break;
16063			}
16064			case 2: {
16065				$cell_padding['T'] = $padding[0];
16066				$cell_padding['R'] = $padding[1];
16067				$cell_padding['B'] = $padding[0];
16068				$cell_padding['L'] = $padding[1];
16069				break;
16070			}
16071			case 1: {
16072				$cell_padding['T'] = $padding[0];
16073				$cell_padding['R'] = $padding[0];
16074				$cell_padding['B'] = $padding[0];
16075				$cell_padding['L'] = $padding[0];
16076				break;
16077			}
16078			default: {
16079				return $this->cell_padding;
16080			}
16081		}
16082		if ($width == 0) {
16083			$width = $this->w - $this->lMargin - $this->rMargin;
16084		}
16085		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16086		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16087		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16088		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16089		return $cell_padding;
16090	}
16091
16092	/**
16093	 * Get the internal Cell margin from CSS attribute.
16094	 * @param $cssmargin (string) margin properties
16095	 * @param $width (float) width of the containing element
16096	 * @return array of cell margins
16097	 * @public
16098	 * @since 5.9.000 (2010-10-04)
16099	 */
16100	public function getCSSMargin($cssmargin, $width=0) {
16101		$margin = preg_split('/[\s]+/', trim($cssmargin));
16102		$cell_margin = array(); // value to be returned
16103		switch (count($margin)) {
16104			case 4: {
16105				$cell_margin['T'] = $margin[0];
16106				$cell_margin['R'] = $margin[1];
16107				$cell_margin['B'] = $margin[2];
16108				$cell_margin['L'] = $margin[3];
16109				break;
16110			}
16111			case 3: {
16112				$cell_margin['T'] = $margin[0];
16113				$cell_margin['R'] = $margin[1];
16114				$cell_margin['B'] = $margin[2];
16115				$cell_margin['L'] = $margin[1];
16116				break;
16117			}
16118			case 2: {
16119				$cell_margin['T'] = $margin[0];
16120				$cell_margin['R'] = $margin[1];
16121				$cell_margin['B'] = $margin[0];
16122				$cell_margin['L'] = $margin[1];
16123				break;
16124			}
16125			case 1: {
16126				$cell_margin['T'] = $margin[0];
16127				$cell_margin['R'] = $margin[0];
16128				$cell_margin['B'] = $margin[0];
16129				$cell_margin['L'] = $margin[0];
16130				break;
16131			}
16132			default: {
16133				return $this->cell_margin;
16134			}
16135		}
16136		if ($width == 0) {
16137			$width = $this->w - $this->lMargin - $this->rMargin;
16138		}
16139		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16140		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16141		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16142		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16143		return $cell_margin;
16144	}
16145
16146	/**
16147	 * Get the border-spacing from CSS attribute.
16148	 * @param $cssbspace (string) border-spacing CSS properties
16149	 * @param $width (float) width of the containing element
16150	 * @return array of border spacings
16151	 * @public
16152	 * @since 5.9.010 (2010-10-27)
16153	 */
16154	public function getCSSBorderMargin($cssbspace, $width=0) {
16155		$space = preg_split('/[\s]+/', trim($cssbspace));
16156		$border_spacing = array(); // value to be returned
16157		switch (count($space)) {
16158			case 2: {
16159				$border_spacing['H'] = $space[0];
16160				$border_spacing['V'] = $space[1];
16161				break;
16162			}
16163			case 1: {
16164				$border_spacing['H'] = $space[0];
16165				$border_spacing['V'] = $space[0];
16166				break;
16167			}
16168			default: {
16169				return array('H' => 0, 'V' => 0);
16170			}
16171		}
16172		if ($width == 0) {
16173			$width = $this->w - $this->lMargin - $this->rMargin;
16174		}
16175		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16176		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16177		return $border_spacing;
16178	}
16179
16180	/**
16181	 * Returns the letter-spacing value from CSS value
16182	 * @param $spacing (string) letter-spacing value
16183	 * @param $parent (float) font spacing (tracking) value of the parent element
16184	 * @return float quantity to increases or decreases the space between characters in a text.
16185	 * @protected
16186	 * @since 5.9.000 (2010-10-02)
16187	 */
16188	protected function getCSSFontSpacing($spacing, $parent=0) {
16189		$val = 0; // value to be returned
16190		$spacing = trim($spacing);
16191		switch ($spacing) {
16192			case 'normal': {
16193				$val = 0;
16194				break;
16195			}
16196			case 'inherit': {
16197				if ($parent == 'normal') {
16198					$val = 0;
16199				} else {
16200					$val = $parent;
16201				}
16202				break;
16203			}
16204			default: {
16205				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16206			}
16207		}
16208		return $val;
16209	}
16210
16211	/**
16212	 * Returns the percentage of font stretching from CSS value
16213	 * @param $stretch (string) stretch mode
16214	 * @param $parent (float) stretch value of the parent element
16215	 * @return float font stretching percentage
16216	 * @protected
16217	 * @since 5.9.000 (2010-10-02)
16218	 */
16219	protected function getCSSFontStretching($stretch, $parent=100) {
16220		$val = 100; // value to be returned
16221		$stretch = trim($stretch);
16222		switch ($stretch) {
16223			case 'ultra-condensed': {
16224				$val = 40;
16225				break;
16226			}
16227			case 'extra-condensed': {
16228				$val = 55;
16229				break;
16230			}
16231			case 'condensed': {
16232				$val = 70;
16233				break;
16234			}
16235			case 'semi-condensed': {
16236				$val = 85;
16237				break;
16238			}
16239			case 'normal': {
16240				$val = 100;
16241				break;
16242			}
16243			case 'semi-expanded': {
16244				$val = 115;
16245				break;
16246			}
16247			case 'expanded': {
16248				$val = 130;
16249				break;
16250			}
16251			case 'extra-expanded': {
16252				$val = 145;
16253				break;
16254			}
16255			case 'ultra-expanded': {
16256				$val = 160;
16257				break;
16258			}
16259			case 'wider': {
16260				$val = ($parent + 10);
16261				break;
16262			}
16263			case 'narrower': {
16264				$val = ($parent - 10);
16265				break;
16266			}
16267			case 'inherit': {
16268				if ($parent == 'normal') {
16269					$val = 100;
16270				} else {
16271					$val = $parent;
16272				}
16273				break;
16274			}
16275			default: {
16276				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16277			}
16278		}
16279		return $val;
16280	}
16281
16282	/**
16283	 * Convert HTML string containing font size value to points
16284	 * @param $val (string) String containing font size value and unit.
16285	 * @param $refsize (float) Reference font size in points.
16286	 * @param $parent_size (float) Parent font size in points.
16287	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16288	 * @return float value in points
16289	 * @public
16290	 */
16291	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16292		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16293		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16294		switch ($val) {
16295			case 'xx-small': {
16296				$size = ($refsize - 4);
16297				break;
16298			}
16299			case 'x-small': {
16300				$size = ($refsize - 3);
16301				break;
16302			}
16303			case 'small': {
16304				$size = ($refsize - 2);
16305				break;
16306			}
16307			case 'medium': {
16308				$size = $refsize;
16309				break;
16310			}
16311			case 'large': {
16312				$size = ($refsize + 2);
16313				break;
16314			}
16315			case 'x-large': {
16316				$size = ($refsize + 4);
16317				break;
16318			}
16319			case 'xx-large': {
16320				$size = ($refsize + 6);
16321				break;
16322			}
16323			case 'smaller': {
16324				$size = ($parent_size - 3);
16325				break;
16326			}
16327			case 'larger': {
16328				$size = ($parent_size + 3);
16329				break;
16330			}
16331			default: {
16332				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16333			}
16334		}
16335		return $size;
16336	}
16337
16338	/**
16339	 * Returns the HTML DOM array.
16340	 * @param $html (string) html code
16341	 * @return array
16342	 * @protected
16343	 * @since 3.2.000 (2008-06-20)
16344	 */
16345	protected function getHtmlDomArray($html) {
16346		// array of CSS styles ( selector => properties).
16347		$css = array();
16348		// get CSS array defined at previous call
16349		$matches = array();
16350		if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16351			if (isset($matches[1][0])) {
16352				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16353			}
16354			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16355		}
16356		// extract external CSS files
16357		$matches = array();
16358		if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16359			foreach ($matches[1] as $key => $link) {
16360				$type = array();
16361				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16362					$type = array();
16363					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16364					// get 'all' and 'print' media, other media types are discarded
16365					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16366					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16367						$type = array();
16368						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16369							// read CSS data file
16370                            $cssdata = $this->getCachedFileContents(trim($type[1]));
16371							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16372								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16373							}
16374						}
16375					}
16376				}
16377			}
16378		}
16379		// extract style tags
16380		$matches = array();
16381		if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16382			foreach ($matches[1] as $key => $media) {
16383				$type = array();
16384				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16385				// get 'all' and 'print' media, other media types are discarded
16386				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16387				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16388					$cssdata = $matches[2][$key];
16389					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16390				}
16391			}
16392		}
16393		// create a special tag to contain the CSS array (used for table content)
16394		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16395		// remove head and style blocks
16396		$html = preg_replace('/<head([^\>]*)>(.*)?<\/head>/siU', '', $html);
16397		$html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16398		// define block tags
16399		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16400		// define self-closing tags
16401		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16402		// remove all unsupported tags (the line below lists all supported tags)
16403		$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>');
16404		//replace some blank characters
16405		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16406		$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);
16407		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16408		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16409		$html = strtr($html, $repTable);
16410		$offset = 0;
16411		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16412			$html_a = substr($html, 0, $offset);
16413			$html_b = substr($html, $offset, ($pos - $offset + 6));
16414			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16415				// preserve newlines on <pre> tag
16416				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16417			}
16418			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16419				// preserve spaces on <pre> tag
16420				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16421			}
16422			$html = $html_a.$html_b.substr($html, $pos + 6);
16423			$offset = strlen($html_a.$html_b);
16424		}
16425		$offset = 0;
16426		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16427			$html_a = substr($html, 0, $offset);
16428			$html_b = substr($html, $offset, ($pos - $offset + 11));
16429			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16430				// preserve newlines on <textarea> tag
16431				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16432				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16433			}
16434			$html = $html_a.$html_b.substr($html, $pos + 11);
16435			$offset = strlen($html_a.$html_b);
16436		}
16437		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16438		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16439		$offset = 0;
16440		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16441			$html_a = substr($html, 0, $offset);
16442			$html_b = substr($html, $offset, ($pos - $offset + 9));
16443			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16444				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16445				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16446			}
16447			$html = $html_a.$html_b.substr($html, $pos + 9);
16448			$offset = strlen($html_a.$html_b);
16449		}
16450		if (preg_match("'</select'si", $html)) {
16451			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16452			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16453		}
16454		$html = str_replace("\n", ' ', $html);
16455		// restore textarea newlines
16456		$html = str_replace('<TBR>', "\n", $html);
16457		// remove extra spaces from code
16458		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16459		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16460		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16461		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16462		$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);
16463		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16464		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16465		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16466		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16467		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16468		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16469		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16470		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16471		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16472		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16473		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16474		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16475		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16476		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16477		// trim string
16478		$html = $this->stringTrim($html);
16479		// fix br tag after li
16480		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16481		// fix first image tag alignment
16482		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16483		// pattern for generic tag
16484		$tagpattern = '/(<[^>]+>)/';
16485		// explodes the string
16486		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16487		// count elements
16488		$maxel = count($a);
16489		$elkey = 0;
16490		$key = 0;
16491		// create an array of elements
16492		$dom = array();
16493		$dom[$key] = array();
16494		// set inheritable properties fot the first void element
16495		// 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
16496		$dom[$key]['tag'] = false;
16497		$dom[$key]['block'] = false;
16498		$dom[$key]['value'] = '';
16499		$dom[$key]['parent'] = 0;
16500		$dom[$key]['hide'] = false;
16501		$dom[$key]['fontname'] = $this->FontFamily;
16502		$dom[$key]['fontstyle'] = $this->FontStyle;
16503		$dom[$key]['fontsize'] = $this->FontSizePt;
16504		$dom[$key]['font-stretch'] = $this->font_stretching;
16505		$dom[$key]['letter-spacing'] = $this->font_spacing;
16506		$dom[$key]['stroke'] = $this->textstrokewidth;
16507		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16508		$dom[$key]['clip'] = ($this->textrendermode > 3);
16509		$dom[$key]['line-height'] = $this->cell_height_ratio;
16510		$dom[$key]['bgcolor'] = false;
16511		$dom[$key]['fgcolor'] = $this->fgcolor; // color
16512		$dom[$key]['strokecolor'] = $this->strokecolor;
16513		$dom[$key]['align'] = '';
16514		$dom[$key]['listtype'] = '';
16515		$dom[$key]['text-indent'] = 0;
16516		$dom[$key]['text-transform'] = '';
16517		$dom[$key]['border'] = array();
16518		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16519		$thead = false; // true when we are inside the THEAD tag
16520		++$key;
16521		$level = array();
16522		array_push($level, 0); // root
16523		while ($elkey < $maxel) {
16524			$dom[$key] = array();
16525			$element = $a[$elkey];
16526			$dom[$key]['elkey'] = $elkey;
16527			if (preg_match($tagpattern, $element)) {
16528				// html tag
16529				$element = substr($element, 1, -1);
16530				// get tag name
16531				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16532				$tagname = strtolower($tag[1]);
16533				// check if we are inside a table header
16534				if ($tagname == 'thead') {
16535					if ($element[0] == '/') {
16536						$thead = false;
16537					} else {
16538						$thead = true;
16539					}
16540					++$elkey;
16541					continue;
16542				}
16543				$dom[$key]['tag'] = true;
16544				$dom[$key]['value'] = $tagname;
16545				if (in_array($dom[$key]['value'], $blocktags)) {
16546					$dom[$key]['block'] = true;
16547				} else {
16548					$dom[$key]['block'] = false;
16549				}
16550				if ($element[0] == '/') {
16551					// *** closing html tag
16552					$dom[$key]['opening'] = false;
16553					$dom[$key]['parent'] = end($level);
16554					array_pop($level);
16555					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16556					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16557					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16558					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16559					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16560					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16561					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16562					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16563					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16564					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16565					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16566					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16567					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16568					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16569					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16570					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16571					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16572						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16573					}
16574					// set the number of columns in table tag
16575					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16576						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16577					}
16578					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16579						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16580						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16581							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16582						}
16583						$key = $i;
16584						// mark nested tables
16585						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16586						// remove thead sections from nested tables
16587						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16588						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16589					}
16590					// store header rows on a new table
16591					if (
16592						($dom[$key]['value'] === 'tr')
16593						&& !empty($dom[($dom[$key]['parent'])]['thead'])
16594						&& ($dom[($dom[$key]['parent'])]['thead'] === true)
16595					) {
16596						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16597							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16598						}
16599						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16600							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16601						}
16602						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16603							$dom[($dom[$key]['parent'])]['attribute'] = array();
16604						}
16605						// header elements must be always contained in a single page
16606						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16607					}
16608					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16609						// remove the nobr attributes from the table header
16610						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16611						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16612					}
16613				} else {
16614					// *** opening or self-closing html tag
16615					$dom[$key]['opening'] = true;
16616					$dom[$key]['parent'] = end($level);
16617					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16618						// self-closing tag
16619						$dom[$key]['self'] = true;
16620					} else {
16621						// opening tag
16622						array_push($level, $key);
16623						$dom[$key]['self'] = false;
16624					}
16625					// copy some values from parent
16626					$parentkey = 0;
16627					if ($key > 0) {
16628						$parentkey = $dom[$key]['parent'];
16629						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16630						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16631						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16632						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16633						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16634						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16635						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16636						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16637						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16638						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16639						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16640						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16641						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16642						$dom[$key]['align'] = $dom[$parentkey]['align'];
16643						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16644						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16645						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16646						$dom[$key]['border'] = array();
16647						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16648					}
16649					// get attributes
16650					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16651					$dom[$key]['attribute'] = array(); // reset attribute array
16652                    foreach($attr_array[1] as $id => $name) {
16653                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16654                    }
16655					if (!empty($css)) {
16656						// merge CSS style to current style
16657						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16658						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16659					}
16660					// split style attributes
16661					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16662						// get style attributes
16663						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16664						$dom[$key]['style'] = array(); // reset style attribute array
16665                        foreach($style_array[1] as $id => $name) {
16666                            // in case of duplicate attribute the last replace the previous
16667                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16668                        }
16669						// --- get some style attributes ---
16670						// text direction
16671						if (isset($dom[$key]['style']['direction'])) {
16672							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16673						}
16674						// display
16675						if (isset($dom[$key]['style']['display'])) {
16676							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16677						}
16678						// font family
16679						if (isset($dom[$key]['style']['font-family'])) {
16680							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16681						}
16682						// list-style-type
16683						if (isset($dom[$key]['style']['list-style-type'])) {
16684							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16685							if ($dom[$key]['listtype'] == 'inherit') {
16686								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16687							}
16688						}
16689						// text-indent
16690						if (isset($dom[$key]['style']['text-indent'])) {
16691							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16692							if ($dom[$key]['text-indent'] == 'inherit') {
16693								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16694							}
16695						}
16696						// text-transform
16697						if (isset($dom[$key]['style']['text-transform'])) {
16698							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16699						}
16700						// font size
16701						if (isset($dom[$key]['style']['font-size'])) {
16702							$fsize = trim($dom[$key]['style']['font-size']);
16703							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16704						}
16705						// font-stretch
16706						if (isset($dom[$key]['style']['font-stretch'])) {
16707							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16708						}
16709						// letter-spacing
16710						if (isset($dom[$key]['style']['letter-spacing'])) {
16711							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16712						}
16713						// line-height (internally is the cell height ratio)
16714						if (isset($dom[$key]['style']['line-height'])) {
16715							$lineheight = trim($dom[$key]['style']['line-height']);
16716							switch ($lineheight) {
16717								// A normal line height. This is default
16718								case 'normal': {
16719									$dom[$key]['line-height'] = $dom[0]['line-height'];
16720									break;
16721								}
16722								case 'inherit': {
16723									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16724								}
16725								default: {
16726									if (is_numeric($lineheight)) {
16727										// convert to percentage of font height
16728										$lineheight = ($lineheight * 100).'%';
16729									}
16730									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16731									if (substr($lineheight, -1) !== '%') {
16732										if ($dom[$key]['fontsize'] <= 0) {
16733											$dom[$key]['line-height'] = 1;
16734										} else {
16735											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16736										}
16737									}
16738								}
16739							}
16740						}
16741						// font style
16742						if (isset($dom[$key]['style']['font-weight'])) {
16743							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16744								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16745									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16746								}
16747							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16748								$dom[$key]['fontstyle'] .= 'B';
16749							}
16750						}
16751						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16752							$dom[$key]['fontstyle'] .= 'I';
16753						}
16754						// font color
16755						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16756							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16757						} elseif ($dom[$key]['value'] == 'a') {
16758							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16759						}
16760						// background color
16761						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16762							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16763						}
16764						// text-decoration
16765						if (isset($dom[$key]['style']['text-decoration'])) {
16766							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16767							foreach ($decors as $dec) {
16768								$dec = trim($dec);
16769								if (!TCPDF_STATIC::empty_string($dec)) {
16770									if ($dec[0] == 'u') {
16771										// underline
16772										$dom[$key]['fontstyle'] .= 'U';
16773									} elseif ($dec[0] == 'l') {
16774										// line-through
16775										$dom[$key]['fontstyle'] .= 'D';
16776									} elseif ($dec[0] == 'o') {
16777										// overline
16778										$dom[$key]['fontstyle'] .= 'O';
16779									}
16780								}
16781							}
16782						} elseif ($dom[$key]['value'] == 'a') {
16783							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16784						}
16785						// check for width attribute
16786						if (isset($dom[$key]['style']['width'])) {
16787							$dom[$key]['width'] = $dom[$key]['style']['width'];
16788						}
16789						// check for height attribute
16790						if (isset($dom[$key]['style']['height'])) {
16791							$dom[$key]['height'] = $dom[$key]['style']['height'];
16792						}
16793						// check for text alignment
16794						if (isset($dom[$key]['style']['text-align'])) {
16795							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16796						}
16797						// check for CSS border properties
16798						if (isset($dom[$key]['style']['border'])) {
16799							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16800							if (!empty($borderstyle)) {
16801								$dom[$key]['border']['LTRB'] = $borderstyle;
16802							}
16803						}
16804						if (isset($dom[$key]['style']['border-color'])) {
16805							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16806							if (isset($brd_colors[3])) {
16807								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16808							}
16809							if (isset($brd_colors[1])) {
16810								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16811							}
16812							if (isset($brd_colors[0])) {
16813								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16814							}
16815							if (isset($brd_colors[2])) {
16816								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16817							}
16818						}
16819						if (isset($dom[$key]['style']['border-width'])) {
16820							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16821							if (isset($brd_widths[3])) {
16822								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16823							}
16824							if (isset($brd_widths[1])) {
16825								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16826							}
16827							if (isset($brd_widths[0])) {
16828								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16829							}
16830							if (isset($brd_widths[2])) {
16831								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16832							}
16833						}
16834						if (isset($dom[$key]['style']['border-style'])) {
16835							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16836							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16837								$dom[$key]['border']['L']['cap'] = 'square';
16838								$dom[$key]['border']['L']['join'] = 'miter';
16839								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16840								if ($dom[$key]['border']['L']['dash'] < 0) {
16841									$dom[$key]['border']['L'] = array();
16842								}
16843							}
16844							if (isset($brd_styles[1])) {
16845								$dom[$key]['border']['R']['cap'] = 'square';
16846								$dom[$key]['border']['R']['join'] = 'miter';
16847								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16848								if ($dom[$key]['border']['R']['dash'] < 0) {
16849									$dom[$key]['border']['R'] = array();
16850								}
16851							}
16852							if (isset($brd_styles[0])) {
16853								$dom[$key]['border']['T']['cap'] = 'square';
16854								$dom[$key]['border']['T']['join'] = 'miter';
16855								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16856								if ($dom[$key]['border']['T']['dash'] < 0) {
16857									$dom[$key]['border']['T'] = array();
16858								}
16859							}
16860							if (isset($brd_styles[2])) {
16861								$dom[$key]['border']['B']['cap'] = 'square';
16862								$dom[$key]['border']['B']['join'] = 'miter';
16863								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16864								if ($dom[$key]['border']['B']['dash'] < 0) {
16865									$dom[$key]['border']['B'] = array();
16866								}
16867							}
16868						}
16869						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16870						foreach ($cellside as $bsk => $bsv) {
16871							if (isset($dom[$key]['style']['border-'.$bsv])) {
16872								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16873								if (!empty($borderstyle)) {
16874									$dom[$key]['border'][$bsk] = $borderstyle;
16875								}
16876							}
16877							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16878								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16879							}
16880							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16881								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16882							}
16883							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16884								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16885								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16886									$dom[$key]['border'][$bsk] = array();
16887								}
16888							}
16889						}
16890						// check for CSS padding properties
16891						if (isset($dom[$key]['style']['padding'])) {
16892							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16893						} else {
16894							$dom[$key]['padding'] = $this->cell_padding;
16895						}
16896						foreach ($cellside as $psk => $psv) {
16897							if (isset($dom[$key]['style']['padding-'.$psv])) {
16898								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16899							}
16900						}
16901						// check for CSS margin properties
16902						if (isset($dom[$key]['style']['margin'])) {
16903							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16904						} else {
16905							$dom[$key]['margin'] = $this->cell_margin;
16906						}
16907						foreach ($cellside as $psk => $psv) {
16908							if (isset($dom[$key]['style']['margin-'.$psv])) {
16909								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16910							}
16911						}
16912						// check for CSS border-spacing properties
16913						if (isset($dom[$key]['style']['border-spacing'])) {
16914							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16915						}
16916						// page-break-inside
16917						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16918							$dom[$key]['attribute']['nobr'] = 'true';
16919						}
16920						// page-break-before
16921						if (isset($dom[$key]['style']['page-break-before'])) {
16922							if ($dom[$key]['style']['page-break-before'] == 'always') {
16923								$dom[$key]['attribute']['pagebreak'] = 'true';
16924							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16925								$dom[$key]['attribute']['pagebreak'] = 'left';
16926							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16927								$dom[$key]['attribute']['pagebreak'] = 'right';
16928							}
16929						}
16930						// page-break-after
16931						if (isset($dom[$key]['style']['page-break-after'])) {
16932							if ($dom[$key]['style']['page-break-after'] == 'always') {
16933								$dom[$key]['attribute']['pagebreakafter'] = 'true';
16934							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16935								$dom[$key]['attribute']['pagebreakafter'] = 'left';
16936							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16937								$dom[$key]['attribute']['pagebreakafter'] = 'right';
16938							}
16939						}
16940					}
16941					if (isset($dom[$key]['attribute']['display'])) {
16942						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16943					}
16944					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16945						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16946						if (!empty($borderstyle)) {
16947							$dom[$key]['border']['LTRB'] = $borderstyle;
16948						}
16949					}
16950					// check for font tag
16951					if ($dom[$key]['value'] == 'font') {
16952						// font family
16953						if (isset($dom[$key]['attribute']['face'])) {
16954							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16955						}
16956						// font size
16957						if (isset($dom[$key]['attribute']['size'])) {
16958							if ($key > 0) {
16959								if ($dom[$key]['attribute']['size'][0] == '+') {
16960									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16961								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
16962									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16963								} else {
16964									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16965								}
16966							} else {
16967								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16968							}
16969						}
16970					}
16971					// force natural alignment for lists
16972					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16973						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16974						if ($this->rtl) {
16975							$dom[$key]['align'] = 'R';
16976						} else {
16977							$dom[$key]['align'] = 'L';
16978						}
16979					}
16980					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16981						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16982							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16983						}
16984					}
16985					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16986						$dom[$key]['fontstyle'] .= 'B';
16987					}
16988					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16989						$dom[$key]['fontstyle'] .= 'I';
16990					}
16991					if ($dom[$key]['value'] == 'u') {
16992						$dom[$key]['fontstyle'] .= 'U';
16993					}
16994					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16995						$dom[$key]['fontstyle'] .= 'D';
16996					}
16997					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16998						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16999					}
17000					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17001						$dom[$key]['fontname'] = $this->default_monospaced_font;
17002					}
17003					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)) {
17004						// headings h1, h2, h3, h4, h5, h6
17005						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17006							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17007							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17008						}
17009						if (!isset($dom[$key]['style']['font-weight'])) {
17010							$dom[$key]['fontstyle'] .= 'B';
17011						}
17012					}
17013					if (($dom[$key]['value'] == 'table')) {
17014						$dom[$key]['rows'] = 0; // number of rows
17015						$dom[$key]['trids'] = array(); // IDs of TR elements
17016						$dom[$key]['thead'] = ''; // table header rows
17017					}
17018					if (($dom[$key]['value'] == 'tr')) {
17019						$dom[$key]['cols'] = 0;
17020						if ($thead) {
17021							$dom[$key]['thead'] = true;
17022							// rows on thead block are printed as a separate table
17023						} else {
17024							$dom[$key]['thead'] = false;
17025							$parent = $dom[$key]['parent'];
17026
17027							if (!isset($dom[$parent]['rows'])) {
17028								$dom[$parent]['rows'] = 0;
17029							}
17030							// store the number of rows on table element
17031							++$dom[$parent]['rows'];
17032
17033							if (!isset($dom[$parent]['trids'])) {
17034								$dom[$parent]['trids'] = array();
17035							}
17036
17037							// store the TR elements IDs on table element
17038							array_push($dom[$parent]['trids'], $key);
17039						}
17040					}
17041					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17042						if (isset($dom[$key]['attribute']['colspan'])) {
17043							$colspan = intval($dom[$key]['attribute']['colspan']);
17044						} else {
17045							$colspan = 1;
17046						}
17047						$dom[$key]['attribute']['colspan'] = $colspan;
17048						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
17049					}
17050					// text direction
17051					if (isset($dom[$key]['attribute']['dir'])) {
17052						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17053					}
17054					// set foreground color attribute
17055					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17056						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17057					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17058						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17059					}
17060					// set background color attribute
17061					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17062						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17063					}
17064					// set stroke color attribute
17065					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17066						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17067					}
17068					// check for width attribute
17069					if (isset($dom[$key]['attribute']['width'])) {
17070						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
17071					}
17072					// check for height attribute
17073					if (isset($dom[$key]['attribute']['height'])) {
17074						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
17075					}
17076					// check for text alignment
17077					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17078						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17079					}
17080					// check for text rendering mode (the following attributes do not exist in HTML)
17081					if (isset($dom[$key]['attribute']['stroke'])) {
17082						// font stroke width
17083						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17084					}
17085					if (isset($dom[$key]['attribute']['fill'])) {
17086						// font fill
17087						if ($dom[$key]['attribute']['fill'] == 'true') {
17088							$dom[$key]['fill'] = true;
17089						} else {
17090							$dom[$key]['fill'] = false;
17091						}
17092					}
17093					if (isset($dom[$key]['attribute']['clip'])) {
17094						// clipping mode
17095						if ($dom[$key]['attribute']['clip'] == 'true') {
17096							$dom[$key]['clip'] = true;
17097						} else {
17098							$dom[$key]['clip'] = false;
17099						}
17100					}
17101				} // end opening tag
17102			} else {
17103				// text
17104				$dom[$key]['tag'] = false;
17105				$dom[$key]['block'] = false;
17106				$dom[$key]['parent'] = end($level);
17107				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17108				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17109					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17110					if (function_exists('mb_convert_case')) {
17111						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17112						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17113							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17114						}
17115					} elseif (!$this->isunicode) {
17116						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17117							case 'capitalize': {
17118								$element = ucwords(strtolower($element));
17119								break;
17120							}
17121							case 'uppercase': {
17122								$element = strtoupper($element);
17123								break;
17124							}
17125							case 'lowercase': {
17126								$element = strtolower($element);
17127								break;
17128							}
17129						}
17130					}
17131				}
17132				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17133			}
17134			++$elkey;
17135			++$key;
17136		}
17137		return $dom;
17138	}
17139
17140	/**
17141	 * Returns the string used to find spaces
17142	 * @return string
17143	 * @protected
17144	 * @author Nicola Asuni
17145	 * @since 4.8.024 (2010-01-15)
17146	 */
17147	protected function getSpaceString() {
17148		$spacestr = chr(32);
17149		if ($this->isUnicodeFont()) {
17150			$spacestr = chr(0).chr(32);
17151		}
17152		return $spacestr;
17153	}
17154
17155	/**
17156	 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17157	 * @param $data (string) serialized data
17158	 * @return string
17159	 * @public static
17160	 */
17161	protected function getHashForTCPDFtagParams($data) {
17162		return md5(strlen($data).$this->file_id.$data);
17163	}
17164
17165	/**
17166	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17167	 * @param $data (array) parameters array
17168	 * @return string containing serialized data
17169	 * @public static
17170	 */
17171	public function serializeTCPDFtagParameters($data) {
17172		$encoded = urlencode(json_encode($data));
17173		return $this->getHashForTCPDFtagParams($encoded).$encoded;
17174	}
17175
17176	/**
17177	 * Unserialize parameters to be used with TCPDF tag in HTML code.
17178	 * @param $data (string) serialized data
17179	 * @return array containing unserialized data
17180	 * @protected static
17181	 */
17182	protected function unserializeTCPDFtagParameters($data) {
17183		$hash = substr($data, 0, 32);
17184		$encoded = substr($data, 32);
17185		if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17186			$this->Error('Invalid parameters');
17187		}
17188		return json_decode(urldecode($encoded), true);
17189	}
17190
17191	/**
17192	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17193	 * 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 />
17194	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17195	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17196	 * 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
17197	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17198	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17199	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17200	 * @param $x (float) upper-left corner X coordinate
17201	 * @param $y (float) upper-left corner Y coordinate
17202	 * @param $html (string) html text to print. Default value: empty string.
17203	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17204	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
17205Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17206	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17207	 * @param $reseth (boolean) if true reset the last cell height (default true).
17208	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17209	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17210	 * @see Multicell(), writeHTML()
17211	 * @public
17212	 */
17213	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17214		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17215	}
17216
17217	/**
17218	 * Allows to preserve some HTML formatting (limited support).<br />
17219	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17220	 * 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
17221	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17222	 * @param $html (string) text to display
17223	 * @param $ln (boolean) if true add a new line after text (default = true)
17224	 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17225	 * @param $reseth (boolean) if true reset the last cell height (default false).
17226	 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17227	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17228	 * @public
17229	 */
17230	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17231		$gvars = $this->getGraphicVars();
17232		// store current values
17233		$prev_cell_margin = $this->cell_margin;
17234		$prev_cell_padding = $this->cell_padding;
17235		$prevPage = $this->page;
17236		$prevlMargin = $this->lMargin;
17237		$prevrMargin = $this->rMargin;
17238		$curfontname = $this->FontFamily;
17239		$curfontstyle = $this->FontStyle;
17240		$curfontsize = $this->FontSizePt;
17241		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17242		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17243		$curfontstretcing = $this->font_stretching;
17244		$curfonttracking = $this->font_spacing;
17245		$this->newline = true;
17246		$newline = true;
17247		$startlinepage = $this->page;
17248		$minstartliney = $this->y;
17249		$maxbottomliney = 0;
17250		$startlinex = $this->x;
17251		$startliney = $this->y;
17252		$yshift = 0;
17253		$loop = 0;
17254		$curpos = 0;
17255		$this_method_vars = array();
17256		$undo = false;
17257		$fontaligned = false;
17258		$reverse_dir = false; // true when the text direction is reversed
17259		$this->premode = false;
17260		if ($this->inxobj) {
17261			// we are inside an XObject template
17262			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17263		} elseif (isset($this->PageAnnots[$this->page])) {
17264			$pask = count($this->PageAnnots[$this->page]);
17265		} else {
17266			$pask = 0;
17267		}
17268		if ($this->inxobj) {
17269			// we are inside an XObject template
17270			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17271		} elseif (!$this->InFooter) {
17272			if (isset($this->footerlen[$this->page])) {
17273				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17274			} else {
17275				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17276			}
17277			$startlinepos = $this->footerpos[$this->page];
17278		} else {
17279			// we are inside the footer
17280			$startlinepos = $this->pagelen[$this->page];
17281		}
17282		$lalign = $align;
17283		$plalign = $align;
17284		if ($this->rtl) {
17285			$w = $this->x - $this->lMargin;
17286		} else {
17287			$w = $this->w - $this->rMargin - $this->x;
17288		}
17289		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17290		if ($cell) {
17291			if ($this->rtl) {
17292				$this->x -= $this->cell_padding['R'];
17293				$this->lMargin += $this->cell_padding['L'];
17294			} else {
17295				$this->x += $this->cell_padding['L'];
17296				$this->rMargin += $this->cell_padding['R'];
17297			}
17298		}
17299		if ($this->customlistindent >= 0) {
17300			$this->listindent = $this->customlistindent;
17301		} else {
17302			$this->listindent = $this->GetStringWidth('000000');
17303		}
17304		$this->listindentlevel = 0;
17305		// save previous states
17306		$prev_cell_height_ratio = $this->cell_height_ratio;
17307		$prev_listnum = $this->listnum;
17308		$prev_listordered = $this->listordered;
17309		$prev_listcount = $this->listcount;
17310		$prev_lispacer = $this->lispacer;
17311		$this->listnum = 0;
17312		$this->listordered = array();
17313		$this->listcount = array();
17314		$this->lispacer = '';
17315		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17316			// reset row height
17317			$this->resetLastH();
17318		}
17319		$dom = $this->getHtmlDomArray($html);
17320		$maxel = count($dom);
17321		$key = 0;
17322		while ($key < $maxel) {
17323			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17324				// store the node key
17325				$hidden_node_key = $key;
17326				if ($dom[$key]['self']) {
17327					// skip just this self-closing tag
17328					++$key;
17329				} else {
17330					// skip this and all children tags
17331					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17332						// skip hidden objects
17333						++$key;
17334					}
17335					++$key;
17336				}
17337			}
17338			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17339				// check for pagebreak
17340				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17341					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17342					$this->checkPageBreak($this->PageBreakTrigger + 1);
17343					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17344				}
17345				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17346					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17347					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17348					$this->checkPageBreak($this->PageBreakTrigger + 1);
17349					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17350				}
17351			}
17352			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17353				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17354					$dom[$key]['attribute']['nobr'] = false;
17355				} else {
17356					// store current object
17357					$this->startTransaction();
17358					// save this method vars
17359					$this_method_vars['html'] = $html;
17360					$this_method_vars['ln'] = $ln;
17361					$this_method_vars['fill'] = $fill;
17362					$this_method_vars['reseth'] = $reseth;
17363					$this_method_vars['cell'] = $cell;
17364					$this_method_vars['align'] = $align;
17365					$this_method_vars['gvars'] = $gvars;
17366					$this_method_vars['prevPage'] = $prevPage;
17367					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17368					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17369					$this_method_vars['prevlMargin'] = $prevlMargin;
17370					$this_method_vars['prevrMargin'] = $prevrMargin;
17371					$this_method_vars['curfontname'] = $curfontname;
17372					$this_method_vars['curfontstyle'] = $curfontstyle;
17373					$this_method_vars['curfontsize'] = $curfontsize;
17374					$this_method_vars['curfontascent'] = $curfontascent;
17375					$this_method_vars['curfontdescent'] = $curfontdescent;
17376					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17377					$this_method_vars['curfonttracking'] = $curfonttracking;
17378					$this_method_vars['minstartliney'] = $minstartliney;
17379					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17380					$this_method_vars['yshift'] = $yshift;
17381					$this_method_vars['startlinepage'] = $startlinepage;
17382					$this_method_vars['startlinepos'] = $startlinepos;
17383					$this_method_vars['startlinex'] = $startlinex;
17384					$this_method_vars['startliney'] = $startliney;
17385					$this_method_vars['newline'] = $newline;
17386					$this_method_vars['loop'] = $loop;
17387					$this_method_vars['curpos'] = $curpos;
17388					$this_method_vars['pask'] = $pask;
17389					$this_method_vars['lalign'] = $lalign;
17390					$this_method_vars['plalign'] = $plalign;
17391					$this_method_vars['w'] = $w;
17392					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17393					$this_method_vars['prev_listnum'] = $prev_listnum;
17394					$this_method_vars['prev_listordered'] = $prev_listordered;
17395					$this_method_vars['prev_listcount'] = $prev_listcount;
17396					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17397					$this_method_vars['fontaligned'] = $fontaligned;
17398					$this_method_vars['key'] = $key;
17399					$this_method_vars['dom'] = $dom;
17400				}
17401			}
17402			// print THEAD block
17403			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17404				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17405					$this->inthead = true;
17406					// print table header (thead)
17407					$this->writeHTML($this->thead, false, false, false, false, '');
17408					// check if we are on a new page or on a new column
17409					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17410						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17411						// restore previous object
17412						$this->rollbackTransaction(true);
17413						// restore previous values
17414						foreach ($this_method_vars as $vkey => $vval) {
17415							$$vkey = $vval;
17416						}
17417						// disable table header
17418						$tmp_thead = $this->thead;
17419						$this->thead = '';
17420						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17421						$pre_y = $this->y;
17422						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17423							// fix for multicolumn mode
17424							$startliney = $this->y;
17425						}
17426						$this->start_transaction_page = $this->page;
17427						$this->start_transaction_y = $this->y;
17428						// restore table header
17429						$this->thead = $tmp_thead;
17430						// fix table border properties
17431						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17432							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17433						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17434							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17435						} else {
17436							$tmp_cellspacing = 0;
17437						}
17438						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17439						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17440						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17441						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17442						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17443						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17444						// print table header (thead)
17445						$this->writeHTML($this->thead, false, false, false, false, '');
17446					}
17447				}
17448				// move $key index forward to skip THEAD block
17449				while ( ($key < $maxel) AND (!(
17450					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17451					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17452					++$key;
17453				}
17454			}
17455			if ($dom[$key]['tag'] OR ($key == 0)) {
17456				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17457					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17458				}
17459				// vertically align image in line
17460				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17461					// get image height
17462					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17463					$autolinebreak = false;
17464					if (!empty($dom[$key]['width'])) {
17465						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17466						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17467							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17468							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17469							// add automatic line break
17470							$autolinebreak = true;
17471							$this->Ln('', $cell);
17472							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17473								// go back to evaluate this line break
17474								--$key;
17475							}
17476						}
17477					}
17478					if (!$autolinebreak) {
17479						if ($this->inPageBody()) {
17480							$pre_y = $this->y;
17481							// check for page break
17482							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17483								// fix for multicolumn mode
17484								$startliney = $this->y;
17485							}
17486						}
17487						if ($this->page > $startlinepage) {
17488							// fix line splitted over two pages
17489							if (isset($this->footerlen[$startlinepage])) {
17490								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17491							}
17492							// line to be moved one page forward
17493							$pagebuff = $this->getPageBuffer($startlinepage);
17494							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17495							$tstart = substr($pagebuff, 0, $startlinepos);
17496							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17497							// remove line from previous page
17498							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17499							$pagebuff = $this->getPageBuffer($this->page);
17500							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17501							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17502							// add line start to current page
17503							$yshift = ($minstartliney - $this->y);
17504							if ($fontaligned) {
17505								$yshift += ($curfontsize / $this->k);
17506							}
17507							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17508							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17509							// shift the annotations and links
17510							if (isset($this->PageAnnots[$this->page])) {
17511								$next_pask = count($this->PageAnnots[$this->page]);
17512							} else {
17513								$next_pask = 0;
17514							}
17515							if (isset($this->PageAnnots[$startlinepage])) {
17516								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17517									if ($pak >= $pask) {
17518										$this->PageAnnots[$this->page][] = $pac;
17519										unset($this->PageAnnots[$startlinepage][$pak]);
17520										$npak = count($this->PageAnnots[$this->page]) - 1;
17521										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17522									}
17523								}
17524							}
17525							$pask = $next_pask;
17526							$startlinepos = $this->cntmrk[$this->page];
17527							$startlinepage = $this->page;
17528							$startliney = $this->y;
17529							$this->newline = false;
17530						}
17531						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17532						$minstartliney = min($this->y, $minstartliney);
17533						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17534					}
17535				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17536					// account for different font size
17537					$pfontname = $curfontname;
17538					$pfontstyle = $curfontstyle;
17539					$pfontsize = $curfontsize;
17540					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17541					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17542					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17543					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17544					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17545					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17546						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17547						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17548						if (($key < ($maxel - 1)) AND (
17549								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17550								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17551								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17552								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17553								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17554							)) {
17555							if ($this->page > $startlinepage) {
17556								// fix lines splitted over two pages
17557								if (isset($this->footerlen[$startlinepage])) {
17558									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17559								}
17560								// line to be moved one page forward
17561								$pagebuff = $this->getPageBuffer($startlinepage);
17562								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17563								$tstart = substr($pagebuff, 0, $startlinepos);
17564								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17565								// remove line start from previous page
17566								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17567								$pagebuff = $this->getPageBuffer($this->page);
17568								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17569								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17570								// add line start to current page
17571								$yshift = ($minstartliney - $this->y);
17572								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17573								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17574								// shift the annotations and links
17575								if (isset($this->PageAnnots[$this->page])) {
17576									$next_pask = count($this->PageAnnots[$this->page]);
17577								} else {
17578									$next_pask = 0;
17579								}
17580								if (isset($this->PageAnnots[$startlinepage])) {
17581									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17582										if ($pak >= $pask) {
17583											$this->PageAnnots[$this->page][] = $pac;
17584											unset($this->PageAnnots[$startlinepage][$pak]);
17585											$npak = count($this->PageAnnots[$this->page]) - 1;
17586											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17587										}
17588									}
17589								}
17590								$pask = $next_pask;
17591								$startlinepos = $this->cntmrk[$this->page];
17592								$startlinepage = $this->page;
17593								$startliney = $this->y;
17594							}
17595							if (!isset($dom[$key]['line-height'])) {
17596								$dom[$key]['line-height'] = $this->cell_height_ratio;
17597							}
17598							if (!$dom[$key]['block']) {
17599								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']))) {
17600									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17601								}
17602								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17603									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17604									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)))) {
17605										$minstartliney = min($this->y, $line_align_data[1]);
17606										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17607									} else {
17608										$minstartliney = min($this->y, $minstartliney);
17609										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17610									}
17611									$line_align_data = $current_line_align_data;
17612								}
17613							}
17614							$this->cell_height_ratio = $dom[$key]['line-height'];
17615							$fontaligned = true;
17616						}
17617						$this->SetFont($fontname, $fontstyle, $fontsize);
17618						// reset row height
17619						$this->resetLastH();
17620						$curfontname = $fontname;
17621						$curfontstyle = $fontstyle;
17622						$curfontsize = $fontsize;
17623						$curfontascent = $fontascent;
17624						$curfontdescent = $fontdescent;
17625					}
17626				}
17627				// set text rendering mode
17628				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17629				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17630				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17631				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17632				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17633					$this->setFontStretching($dom[$key]['font-stretch']);
17634				}
17635				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17636					$this->setFontSpacing($dom[$key]['letter-spacing']);
17637				}
17638				if (($plalign == 'J') AND $dom[$key]['block']) {
17639					$plalign = '';
17640				}
17641				// get current position on page buffer
17642				$curpos = $this->pagelen[$startlinepage];
17643				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17644					$this->SetFillColorArray($dom[$key]['bgcolor']);
17645					$wfill = true;
17646				} else {
17647					$wfill = $fill | false;
17648				}
17649				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17650					$this->SetTextColorArray($dom[$key]['fgcolor']);
17651				}
17652				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17653					$this->SetDrawColorArray($dom[$key]['strokecolor']);
17654				}
17655				if (isset($dom[$key]['align'])) {
17656					$lalign = $dom[$key]['align'];
17657				}
17658				if (TCPDF_STATIC::empty_string($lalign)) {
17659					$lalign = $align;
17660				}
17661			}
17662			// align lines
17663			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17664				$newline = true;
17665				$fontaligned = false;
17666				// we are at the beginning of a new line
17667				if (isset($startlinex)) {
17668					$yshift = ($minstartliney - $startliney);
17669					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17670						$yshift = 0;
17671					}
17672					$t_x = 0;
17673					// the last line must be shifted to be aligned as requested
17674					$linew = abs($this->endlinex - $startlinex);
17675					if ($this->inxobj) {
17676						// we are inside an XObject template
17677						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17678						if (isset($opentagpos)) {
17679							$midpos = $opentagpos;
17680						} else {
17681							$midpos = 0;
17682						}
17683						if ($midpos > 0) {
17684							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17685							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17686						} else {
17687							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17688							$pend = '';
17689						}
17690					} else {
17691						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17692						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17693							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17694							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17695						} elseif (isset($opentagpos)) {
17696							$midpos = $opentagpos;
17697						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17698							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17699							$midpos = $this->footerpos[$startlinepage];
17700						} else {
17701							$midpos = 0;
17702						}
17703						if ($midpos > 0) {
17704							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17705							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17706						} else {
17707							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17708							$pend = '';
17709						}
17710					}
17711					if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17712						// calculate shifting amount
17713						$tw = $w;
17714						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17715							$tw += $this->cell_padding['R'];
17716						}
17717						if ($this->lMargin != $prevlMargin) {
17718							$tw += ($prevlMargin - $this->lMargin);
17719						}
17720						if ($this->rMargin != $prevrMargin) {
17721							$tw += ($prevrMargin - $this->rMargin);
17722						}
17723						$one_space_width = $this->GetStringWidth(chr(32));
17724						$no = 0; // number of spaces on a line contained on a single block
17725						if ($this->isRTLTextDir()) { // RTL
17726							// remove left space if exist
17727							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17728							if ($pos1 > 0) {
17729								$pos1 = intval($pos1);
17730								if ($this->isUnicodeFont()) {
17731									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17732									$spacelen = 2;
17733								} else {
17734									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17735									$spacelen = 1;
17736								}
17737								if ($pos1 == $pos2) {
17738									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17739									if (substr($pmid, $pos1, 4) == '[()]') {
17740										$linew -= $one_space_width;
17741									} elseif ($pos1 == strpos($pmid, '[(')) {
17742										$no = 1;
17743									}
17744								}
17745							}
17746						} else { // LTR
17747							// remove right space if exist
17748							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17749							if ($pos1 > 0) {
17750								$pos1 = intval($pos1);
17751								if ($this->isUnicodeFont()) {
17752									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17753									$spacelen = 2;
17754								} else {
17755									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17756									$spacelen = 1;
17757								}
17758								if ($pos1 == $pos2) {
17759									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17760									$linew -= $one_space_width;
17761								}
17762							}
17763						}
17764						$mdiff = ($tw - $linew);
17765						if ($plalign == 'C') {
17766							if ($this->rtl) {
17767								$t_x = -($mdiff / 2);
17768							} else {
17769								$t_x = ($mdiff / 2);
17770							}
17771						} elseif ($plalign == 'R') {
17772							// right alignment on LTR document
17773							$t_x = $mdiff;
17774						} elseif ($plalign == 'L') {
17775							// left alignment on RTL document
17776							$t_x = -$mdiff;
17777						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17778							// Justification
17779							if ($this->isRTLTextDir()) {
17780								// align text on the left
17781								$t_x = -$mdiff;
17782							}
17783							$ns = 0; // number of spaces
17784							$pmidtemp = $pmid;
17785							// escape special characters
17786							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17787							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17788							// search spaces
17789							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17790								$spacestr = $this->getSpaceString();
17791								$maxkk = count($lnstring[1]) - 1;
17792								for ($kk=0; $kk <= $maxkk; ++$kk) {
17793									// restore special characters
17794									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17795									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17796									// store number of spaces on the strings
17797									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17798									// count total spaces on line
17799									$ns += $lnstring[2][$kk];
17800									$lnstring[3][$kk] = $ns;
17801								}
17802								if ($ns == 0) {
17803									$ns = 1;
17804								}
17805								// calculate additional space to add to each existing space
17806								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17807								if ($this->FontSize <= 0) {
17808									$this->FontSize = 1;
17809								}
17810								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17811								if ($this->font_spacing != 0) {
17812									// fixed spacing mode
17813									$osw = -1000 * $this->font_spacing / $this->FontSize;
17814									$spacewidthu += $osw;
17815								}
17816								$nsmax = $ns;
17817								$ns = 0;
17818								reset($lnstring);
17819								$offset = 0;
17820								$strcount = 0;
17821								$prev_epsposbeg = 0;
17822								$textpos = 0;
17823								if ($this->isRTLTextDir()) {
17824									$textpos = $this->wPt;
17825								}
17826								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17827									// check if we are inside a string section '[( ... )]'
17828									$stroffset = strpos($pmid, '[(', $offset);
17829									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17830										// set offset to the end of string section
17831										$offset = strpos($pmid, ')]', $stroffset);
17832										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17833											$offset = strpos($pmid, ')]', ($offset + 1));
17834										}
17835										if ($offset === false) {
17836											$this->Error('HTML Justification: malformed PDF code.');
17837										}
17838										continue;
17839									}
17840									if ($this->isRTLTextDir()) {
17841										$spacew = ($spacewidth * ($nsmax - $ns));
17842									} else {
17843										$spacew = ($spacewidth * $ns);
17844									}
17845									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17846									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17847									if ($epsposend !== null) {
17848										$epsposend += strlen($this->epsmarker.'Q');
17849										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17850										if ($epsposbeg === null) {
17851											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17852											$prev_epsposbeg = $epsposbeg;
17853										}
17854										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17855											// shift EPS images
17856											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17857											$pmid_b = substr($pmid, 0, $epsposbeg);
17858											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17859											$pmid_e = substr($pmid, $epsposend);
17860											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17861											$offset = $epsposend;
17862											continue;
17863										}
17864									}
17865									$currentxpos = 0;
17866									// shift blocks of code
17867									switch ($strpiece[2][0]) {
17868										case 'Td':
17869										case 'cm':
17870										case 'm':
17871										case 'l': {
17872											// get current X position
17873											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17874											if (!isset($xmatches[1])) {
17875												break;
17876											}
17877											$currentxpos = $xmatches[1];
17878											$textpos = $currentxpos;
17879											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17880												$ns = $lnstring[3][$strcount];
17881												if ($this->isRTLTextDir()) {
17882													$spacew = ($spacewidth * ($nsmax - $ns));
17883												}
17884												++$strcount;
17885											}
17886											// justify block
17887											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17888												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17889												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17890												unset($pmatch, $newpmid);
17891											}
17892											break;
17893										}
17894										case 're': {
17895											// justify block
17896											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17897												$this->lispacer = '';
17898												break;
17899											}
17900											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17901											if (!isset($xmatches[1])) {
17902												break;
17903											}
17904											$currentxpos = $xmatches[1];
17905											$x_diff = 0;
17906											$w_diff = 0;
17907											if ($this->isRTLTextDir()) { // RTL
17908												if ($currentxpos < $textpos) {
17909													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17910													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17911												} else {
17912													if ($strcount > 0) {
17913														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17914														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17915													}
17916												}
17917											} else { // LTR
17918												if ($currentxpos > $textpos) {
17919													if ($strcount > 0) {
17920														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17921													}
17922													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17923												} else {
17924													if ($strcount > 1) {
17925														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17926													}
17927													if ($strcount > 0) {
17928														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17929													}
17930												}
17931											}
17932											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17933												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17934												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17935												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17936												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17937												unset($pmatch, $newpmid, $newx, $neww);
17938											}
17939											break;
17940										}
17941										case 'c': {
17942											// get current X position
17943											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);
17944											if (!isset($xmatches[1])) {
17945												break;
17946											}
17947											$currentxpos = $xmatches[1];
17948											// justify block
17949											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) {
17950												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17951												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17952												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17953												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17954												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17955												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17956											}
17957											break;
17958										}
17959									}
17960									// shift the annotations and links
17961									$cxpos = ($currentxpos / $this->k);
17962									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17963									if ($this->inxobj) {
17964										// we are inside an XObject template
17965										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17966											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17967												if ($cxpos > $lmpos) {
17968													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17969													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17970												} else {
17971													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17972												}
17973												break;
17974											}
17975										}
17976									} elseif (isset($this->PageAnnots[$this->page])) {
17977										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17978											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17979												if ($cxpos > $lmpos) {
17980													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17981													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17982												} else {
17983													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17984												}
17985												break;
17986											}
17987										}
17988									}
17989								} // end of while
17990								// remove markers
17991								$pmid = str_replace('x*#!#*x', '', $pmid);
17992								if ($this->isUnicodeFont()) {
17993									// multibyte characters
17994									$spacew = $spacewidthu;
17995									if ($this->font_stretching != 100) {
17996										// word spacing is affected by stretching
17997										$spacew /= ($this->font_stretching / 100);
17998									}
17999									// escape special characters
18000									$pos = 0;
18001									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18002									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18003									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18004										foreach($pamatch[0] as $pk => $pmatch) {
18005											$replace = $pamatch[1][$pk];
18006											$replace = str_replace('#!#OP#!#', '(', $replace);
18007											$replace = str_replace('#!#CP#!#', ')', $replace);
18008											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18009											$pos = strpos($pmid, $pmatch, $pos);
18010											if ($pos !== FALSE) {
18011												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18012											}
18013											++$pos;
18014										}
18015										unset($pamatch);
18016									}
18017									if ($this->inxobj) {
18018										// we are inside an XObject template
18019										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18020									} else {
18021										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18022									}
18023									$endlinepos = strlen($pstart."\n".$pmid."\n");
18024								} else {
18025									// non-unicode (single-byte characters)
18026									if ($this->font_stretching != 100) {
18027										// word spacing (Tw) is affected by stretching
18028										$spacewidth /= ($this->font_stretching / 100);
18029									}
18030									$rs = sprintf('%F Tw', $spacewidth);
18031									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18032									if ($this->inxobj) {
18033										// we are inside an XObject template
18034										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18035									} else {
18036										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18037									}
18038									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18039								}
18040							}
18041						} // end of J
18042					} // end if $startlinex
18043					if (($t_x != 0) OR ($yshift < 0)) {
18044						// shift the line
18045						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18046						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18047						$endlinepos = strlen($pstart);
18048						if ($this->inxobj) {
18049							// we are inside an XObject template
18050							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18051							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18052								if ($pak >= $pask) {
18053									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18054									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18055								}
18056							}
18057						} else {
18058							$this->setPageBuffer($startlinepage, $pstart.$pend);
18059							// shift the annotations and links
18060							if (isset($this->PageAnnots[$this->page])) {
18061								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18062									if ($pak >= $pask) {
18063										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18064										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18065									}
18066								}
18067							}
18068						}
18069						$this->y -= $yshift;
18070					}
18071				}
18072				$pbrk = $this->checkPageBreak($this->lasth);
18073				$this->newline = false;
18074				$startlinex = $this->x;
18075				$startliney = $this->y;
18076				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18077					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18078				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18079					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
18080				} else {
18081					$minstartliney = $startliney;
18082					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18083				}
18084				$startlinepage = $this->page;
18085				if (isset($endlinepos) AND (!$pbrk)) {
18086					$startlinepos = $endlinepos;
18087				} else {
18088					if ($this->inxobj) {
18089						// we are inside an XObject template
18090						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18091					} elseif (!$this->InFooter) {
18092						if (isset($this->footerlen[$this->page])) {
18093							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18094						} else {
18095							$this->footerpos[$this->page] = $this->pagelen[$this->page];
18096						}
18097						$startlinepos = $this->footerpos[$this->page];
18098					} else {
18099						$startlinepos = $this->pagelen[$this->page];
18100					}
18101				}
18102				unset($endlinepos);
18103				$plalign = $lalign;
18104				if (isset($this->PageAnnots[$this->page])) {
18105					$pask = count($this->PageAnnots[$this->page]);
18106				} else {
18107					$pask = 0;
18108				}
18109				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18110					AND (isset($this->emptypagemrk[$this->page]))
18111					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18112					$this->SetFont($fontname, $fontstyle, $fontsize);
18113					if ($wfill) {
18114						$this->SetFillColorArray($this->bgcolor);
18115					}
18116				}
18117			} // end newline
18118			if (isset($opentagpos)) {
18119				unset($opentagpos);
18120			}
18121			if ($dom[$key]['tag']) {
18122				if ($dom[$key]['opening']) {
18123					// get text indentation (if any)
18124					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18125						$this->textindent = $dom[$key]['text-indent'];
18126						$this->newline = true;
18127					}
18128					// table
18129					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18130						// available page width
18131						if ($this->rtl) {
18132							$wtmp = $this->x - $this->lMargin;
18133						} else {
18134							$wtmp = $this->w - $this->rMargin - $this->x;
18135						}
18136						// get cell spacing
18137						if (isset($dom[$key]['attribute']['cellspacing'])) {
18138							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18139							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18140						} elseif (isset($dom[$key]['border-spacing'])) {
18141							$cellspacing = $dom[$key]['border-spacing'];
18142						} else {
18143							$cellspacing = array('H' => 0, 'V' => 0);
18144						}
18145						// table width
18146						if (isset($dom[$key]['width'])) {
18147							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18148						} else {
18149							$table_width = $wtmp;
18150						}
18151						$table_width -= (2 * $cellspacing['H']);
18152						if (!$this->inthead) {
18153							$this->y += $cellspacing['V'];
18154						}
18155						if ($this->rtl) {
18156							$cellspacingx = -$cellspacing['H'];
18157						} else {
18158							$cellspacingx = $cellspacing['H'];
18159						}
18160						// total table width without cellspaces
18161						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18162						// minimum column width
18163						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18164						// array of custom column widths
18165						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18166					}
18167					// table row
18168					if ($dom[$key]['value'] == 'tr') {
18169						// reset column counter
18170						$colid = 0;
18171					}
18172					// table cell
18173					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18174						$trid = $dom[$key]['parent'];
18175						$table_el = $dom[$trid]['parent'];
18176						if (!isset($dom[$table_el]['cols'])) {
18177							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18178						}
18179						// store border info
18180						$tdborder = 0;
18181						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18182							$tdborder = $dom[$key]['border'];
18183						}
18184						$colspan = intval($dom[$key]['attribute']['colspan']);
18185						if ($colspan <= 0) {
18186							$colspan = 1;
18187						}
18188						$old_cell_padding = $this->cell_padding;
18189						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18190							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18191							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18192						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18193							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18194						} else {
18195							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18196						}
18197						$this->cell_padding = $current_cell_padding;
18198						if (isset($dom[$key]['height'])) {
18199							// minimum cell height
18200							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18201						} else {
18202							$cellh = 0;
18203						}
18204						if (isset($dom[$key]['content'])) {
18205							$cell_content = $dom[$key]['content'];
18206						} else {
18207							$cell_content = '&nbsp;';
18208						}
18209						$tagtype = $dom[$key]['value'];
18210						$parentid = $key;
18211						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18212							// move $key index forward
18213							++$key;
18214						}
18215						if (!isset($dom[$trid]['startpage'])) {
18216							$dom[$trid]['startpage'] = $this->page;
18217						} else {
18218							$this->setPage($dom[$trid]['startpage']);
18219						}
18220						if (!isset($dom[$trid]['startcolumn'])) {
18221							$dom[$trid]['startcolumn'] = $this->current_column;
18222						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18223							$tmpx = $this->x;
18224							$this->selectColumn($dom[$trid]['startcolumn']);
18225							$this->x = $tmpx;
18226						}
18227						if (!isset($dom[$trid]['starty'])) {
18228							$dom[$trid]['starty'] = $this->y;
18229						} else {
18230							$this->y = $dom[$trid]['starty'];
18231						}
18232						if (!isset($dom[$trid]['startx'])) {
18233							$dom[$trid]['startx'] = $this->x;
18234							$this->x += $cellspacingx;
18235						} else {
18236							$this->x += ($cellspacingx / 2);
18237						}
18238						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18239							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18240						} else {
18241							$rowspan = 1;
18242						}
18243						// skip row-spanned cells started on the previous rows
18244						if (isset($dom[$table_el]['rowspans'])) {
18245							$rsk = 0;
18246							$rskmax = count($dom[$table_el]['rowspans']);
18247							while ($rsk < $rskmax) {
18248								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18249								$rsstartx = $trwsp['startx'];
18250								$rsendx = $trwsp['endx'];
18251								// account for margin changes
18252								if ($trwsp['startpage'] < $this->page) {
18253									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18254										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18255										$rsstartx -= $dl;
18256										$rsendx -= $dl;
18257									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18258										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18259										$rsstartx += $dl;
18260										$rsendx += $dl;
18261									}
18262								}
18263								if (($trwsp['rowspan'] > 0)
18264									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18265									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18266									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18267									// set the starting X position of the current cell
18268									$this->x = $rsendx + $cellspacingx;
18269									// increment column indicator
18270									$colid += $trwsp['colspan'];
18271									if (($trwsp['rowspan'] == 1)
18272										AND (isset($dom[$trid]['endy']))
18273										AND (isset($dom[$trid]['endpage']))
18274										AND (isset($dom[$trid]['endcolumn']))
18275										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18276										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18277										// set ending Y position for row
18278										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18279										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18280									}
18281									$rsk = 0;
18282								} else {
18283									++$rsk;
18284								}
18285							}
18286						}
18287						if (isset($dom[$parentid]['width'])) {
18288							// user specified width
18289							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18290							$tmpcw = ($cellw / $colspan);
18291							for ($i = 0; $i < $colspan; ++$i) {
18292								$table_colwidths[($colid + $i)] = $tmpcw;
18293							}
18294						} else {
18295							// inherit column width
18296							$cellw = 0;
18297							for ($i = 0; $i < $colspan; ++$i) {
18298								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18299							}
18300						}
18301						$cellw += (($colspan - 1) * $cellspacing['H']);
18302						// increment column indicator
18303						$colid += $colspan;
18304						// add rowspan information to table element
18305						if ($rowspan > 1) {
18306							$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));
18307						}
18308						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18309						if ($rowspan > 1) {
18310							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18311						}
18312						// push background colors
18313						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18314							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18315						}
18316						// store border info
18317						if (isset($tdborder) AND !empty($tdborder)) {
18318							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18319						}
18320						$prevLastH = $this->lasth;
18321						// store some info for multicolumn mode
18322						if ($this->rtl) {
18323							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18324						} else {
18325							$this->colxshift['x'] = $this->x - $this->lMargin;
18326						}
18327						$this->colxshift['s'] = $cellspacing;
18328						$this->colxshift['p'] = $current_cell_padding;
18329						// ****** write the cell content ******
18330						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18331						// restore some values
18332						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18333						$this->lasth = $prevLastH;
18334						$this->cell_padding = $old_cell_padding;
18335						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18336						// update the end of row position
18337						if ($rowspan <= 1) {
18338							if (isset($dom[$trid]['endy'])) {
18339								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18340									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18341								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18342									$dom[$trid]['endy'] = $this->y;
18343								}
18344							} else {
18345								$dom[$trid]['endy'] = $this->y;
18346							}
18347							if (isset($dom[$trid]['endpage'])) {
18348								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18349							} else {
18350								$dom[$trid]['endpage'] = $this->page;
18351							}
18352							if (isset($dom[$trid]['endcolumn'])) {
18353								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18354							} else {
18355								$dom[$trid]['endcolumn'] = $this->current_column;
18356							}
18357						} else {
18358							// account for row-spanned cells
18359							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18360							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18361							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18362							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18363						}
18364						if (isset($dom[$table_el]['rowspans'])) {
18365							// update endy and endpage on rowspanned cells
18366							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18367								if ($trwsp['rowspan'] > 0) {
18368									if (isset($dom[$trid]['endpage'])) {
18369										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18370											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18371										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18372											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18373											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18374											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18375										} else {
18376											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18377										}
18378									}
18379								}
18380							}
18381						}
18382						$this->x += ($cellspacingx / 2);
18383					} else {
18384						// opening tag (or self-closing tag)
18385						if (!isset($opentagpos)) {
18386							if ($this->inxobj) {
18387								// we are inside an XObject template
18388								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18389							} elseif (!$this->InFooter) {
18390								if (isset($this->footerlen[$this->page])) {
18391									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18392								} else {
18393									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18394								}
18395								$opentagpos = $this->footerpos[$this->page];
18396							}
18397						}
18398						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18399					}
18400				} else { // closing tag
18401					$prev_numpages = $this->numpages;
18402					$old_bordermrk = $this->bordermrk[$this->page];
18403					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18404					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18405						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18406					}
18407					if ($prev_numpages > $this->numpages) {
18408						$startlinepage = $this->page;
18409					}
18410				}
18411			} elseif (strlen($dom[$key]['value']) > 0) {
18412				// print list-item
18413				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18414					$this->SetFont($pfontname, $pfontstyle, $pfontsize);
18415					$this->resetLastH();
18416					$minstartliney = $this->y;
18417					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18418					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18419						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18420					}
18421					$this->SetFont($curfontname, $curfontstyle, $curfontsize);
18422					$this->resetLastH();
18423					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18424						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18425						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18426						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18427						$minstartliney = min($this->y, $minstartliney);
18428						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18429					}
18430				}
18431				// text
18432				$this->htmlvspace = 0;
18433				$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']);
18434				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18435					// reverse spaces order
18436					$lsp = ''; // left spaces
18437					$rsp = ''; // right spaces
18438					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18439						$lsp = $matches[1];
18440					}
18441					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18442						$rsp = $matches[1];
18443					}
18444					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18445				}
18446				if ($newline) {
18447					if (!$this->premode) {
18448						$prelen = strlen($dom[$key]['value']);
18449						if ($this->isRTLTextDir() AND !$isRTLString) {
18450							// right trim except non-breaking space
18451							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18452						} else {
18453							// left trim except non-breaking space
18454							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18455						}
18456						$postlen = strlen($dom[$key]['value']);
18457						if (($postlen == 0) AND ($prelen > 0)) {
18458							$dom[$key]['trimmed_space'] = true;
18459						}
18460					}
18461					$newline = false;
18462					$firstblock = true;
18463				} else {
18464					$firstblock = false;
18465					// replace empty multiple spaces string with a single space
18466					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18467				}
18468				$strrest = '';
18469				if ($this->rtl) {
18470					$this->x -= $this->textindent;
18471				} else {
18472					$this->x += $this->textindent;
18473				}
18474				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18475					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18476					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18477						// HTML <a> Link
18478						$hrefcolor = '';
18479						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18480							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18481						}
18482						$hrefstyle = -1;
18483						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18484							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18485						}
18486						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18487					} else {
18488						$wadj = 0; // space to leave for block continuity
18489						if ($this->rtl) {
18490							$cwa = ($this->x - $this->lMargin);
18491						} else {
18492							$cwa = ($this->w - $this->rMargin - $this->x);
18493						}
18494						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18495							// check the next text blocks for continuity
18496							$nkey = ($key + 1);
18497							$write_block = true;
18498							$same_textdir = true;
18499							$tmp_fontname = $this->FontFamily;
18500							$tmp_fontstyle = $this->FontStyle;
18501							$tmp_fontsize = $this->FontSizePt;
18502							while ($write_block AND isset($dom[$nkey])) {
18503								if ($dom[$nkey]['tag']) {
18504									if ($dom[$nkey]['block']) {
18505										// end of block
18506										$write_block = false;
18507									}
18508									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18509									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18510									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18511									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18512								} else {
18513									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18514									if (isset($nextstr[0]) AND $same_textdir) {
18515										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18516										if (isset($nextstr[1])) {
18517											$write_block = false;
18518										}
18519									}
18520								}
18521								++$nkey;
18522							}
18523						}
18524						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18525							$wadj = 0;
18526							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18527							$numblks = count($nextstr);
18528							if ($numblks > 1) {
18529								// try to split on blank spaces
18530								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18531							} else {
18532								// set the entire block on new line
18533								$wadj = $this->GetStringWidth($nextstr[0]);
18534							}
18535						}
18536						// check for reversed text direction
18537						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18538							// LTR text on RTL direction or RTL text on LTR direction
18539							$reverse_dir = true;
18540							$this->rtl = !$this->rtl;
18541							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18542							if ($this->rtl) {
18543								$this->x += $revshift;
18544							} else {
18545								$this->x -= $revshift;
18546							}
18547							$xws = $this->x;
18548						}
18549						// ****** write only until the end of the line and get the rest ******
18550						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18551						// restore default direction
18552						if ($reverse_dir AND ($wadj == 0)) {
18553							$this->x = $xws;
18554							$this->rtl = !$this->rtl;
18555							$reverse_dir = false;
18556						}
18557					}
18558				}
18559				$this->textindent = 0;
18560				if (strlen($strrest) > 0) {
18561					// store the remaining string on the previous $key position
18562					$this->newline = true;
18563					if ($strrest == $dom[$key]['value']) {
18564						// used to avoid infinite loop
18565						++$loop;
18566					} else {
18567						$loop = 0;
18568					}
18569					$dom[$key]['value'] = $strrest;
18570					if ($cell) {
18571						if ($this->rtl) {
18572							$this->x -= $this->cell_padding['R'];
18573						} else {
18574							$this->x += $this->cell_padding['L'];
18575						}
18576					}
18577					if ($loop < 3) {
18578						--$key;
18579					}
18580				} else {
18581					$loop = 0;
18582					// add the positive font spacing of the last character (if any)
18583					 if ($this->font_spacing > 0) {
18584					 	if ($this->rtl) {
18585							$this->x -= $this->font_spacing;
18586						} else {
18587							$this->x += $this->font_spacing;
18588						}
18589					}
18590				}
18591			}
18592			++$key;
18593			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')) {
18594				// check if we are on a new page or on a new column
18595				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18596					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18597					// restore previous object
18598					$this->rollbackTransaction(true);
18599					// restore previous values
18600					foreach ($this_method_vars as $vkey => $vval) {
18601						$$vkey = $vval;
18602					}
18603					if (!empty($dom[$key]['thead'])) {
18604						$this->inthead = true;
18605					}
18606					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18607					$pre_y = $this->y;
18608					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18609						$startliney = $this->y;
18610					}
18611					$undo = true; // avoid infinite loop
18612				} else {
18613					$undo = false;
18614				}
18615			}
18616		} // end for each $key
18617		// align the last line
18618		if (isset($startlinex)) {
18619			$yshift = ($minstartliney - $startliney);
18620			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18621				$yshift = 0;
18622			}
18623			$t_x = 0;
18624			// the last line must be shifted to be aligned as requested
18625			$linew = abs($this->endlinex - $startlinex);
18626			if ($this->inxobj) {
18627				// we are inside an XObject template
18628				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18629				if (isset($opentagpos)) {
18630					$midpos = $opentagpos;
18631				} else {
18632					$midpos = 0;
18633				}
18634				if ($midpos > 0) {
18635					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18636					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18637				} else {
18638					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18639					$pend = '';
18640				}
18641			} else {
18642				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18643				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18644					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18645					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18646				} elseif (isset($opentagpos)) {
18647					$midpos = $opentagpos;
18648				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18649					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18650					$midpos = $this->footerpos[$startlinepage];
18651				} else {
18652					$midpos = 0;
18653				}
18654				if ($midpos > 0) {
18655					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18656					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18657				} else {
18658					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18659					$pend = '';
18660				}
18661			}
18662			if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18663				// calculate shifting amount
18664				$tw = $w;
18665				if ($this->lMargin != $prevlMargin) {
18666					$tw += ($prevlMargin - $this->lMargin);
18667				}
18668				if ($this->rMargin != $prevrMargin) {
18669					$tw += ($prevrMargin - $this->rMargin);
18670				}
18671				$one_space_width = $this->GetStringWidth(chr(32));
18672				$no = 0; // number of spaces on a line contained on a single block
18673				if ($this->isRTLTextDir()) { // RTL
18674					// remove left space if exist
18675					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18676					if ($pos1 > 0) {
18677						$pos1 = intval($pos1);
18678						if ($this->isUnicodeFont()) {
18679							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18680							$spacelen = 2;
18681						} else {
18682							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18683							$spacelen = 1;
18684						}
18685						if ($pos1 == $pos2) {
18686							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18687							if (substr($pmid, $pos1, 4) == '[()]') {
18688								$linew -= $one_space_width;
18689							} elseif ($pos1 == strpos($pmid, '[(')) {
18690								$no = 1;
18691							}
18692						}
18693					}
18694				} else { // LTR
18695					// remove right space if exist
18696					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18697					if ($pos1 > 0) {
18698						$pos1 = intval($pos1);
18699						if ($this->isUnicodeFont()) {
18700							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18701							$spacelen = 2;
18702						} else {
18703							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18704							$spacelen = 1;
18705						}
18706						if ($pos1 == $pos2) {
18707							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18708							$linew -= $one_space_width;
18709						}
18710					}
18711				}
18712				$mdiff = ($tw - $linew);
18713				if ($plalign == 'C') {
18714					if ($this->rtl) {
18715						$t_x = -($mdiff / 2);
18716					} else {
18717						$t_x = ($mdiff / 2);
18718					}
18719				} elseif ($plalign == 'R') {
18720					// right alignment on LTR document
18721					$t_x = $mdiff;
18722				} elseif ($plalign == 'L') {
18723					// left alignment on RTL document
18724					$t_x = -$mdiff;
18725				}
18726			} // end if startlinex
18727			if (($t_x != 0) OR ($yshift < 0)) {
18728				// shift the line
18729				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18730				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18731				$endlinepos = strlen($pstart);
18732				if ($this->inxobj) {
18733					// we are inside an XObject template
18734					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18735					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18736						if ($pak >= $pask) {
18737							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18738							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18739						}
18740					}
18741				} else {
18742					$this->setPageBuffer($startlinepage, $pstart.$pend);
18743					// shift the annotations and links
18744					if (isset($this->PageAnnots[$this->page])) {
18745						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18746							if ($pak >= $pask) {
18747								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18748								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18749							}
18750						}
18751					}
18752				}
18753				$this->y -= $yshift;
18754				$yshift = 0;
18755			}
18756		}
18757		// restore previous values
18758		$this->setGraphicVars($gvars);
18759		if ($this->num_columns > 1) {
18760			$this->selectColumn();
18761		} elseif ($this->page > $prevPage) {
18762			$this->lMargin = $this->pagedim[$this->page]['olm'];
18763			$this->rMargin = $this->pagedim[$this->page]['orm'];
18764		}
18765		// restore previous list state
18766		$this->cell_height_ratio = $prev_cell_height_ratio;
18767		$this->listnum = $prev_listnum;
18768		$this->listordered = $prev_listordered;
18769		$this->listcount = $prev_listcount;
18770		$this->lispacer = $prev_lispacer;
18771		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18772			$this->Ln($this->lasth);
18773			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18774				$this->y = $maxbottomliney;
18775			}
18776		}
18777		unset($dom);
18778	}
18779
18780	/**
18781	 * Process opening tags.
18782	 * @param $dom (array) html dom array
18783	 * @param $key (int) current element id
18784	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18785	 * @return $dom array
18786	 * @protected
18787	 */
18788	protected function openHTMLTagHandler($dom, $key, $cell) {
18789		$tag = $dom[$key];
18790		$parent = $dom[($dom[$key]['parent'])];
18791		$firsttag = ($key == 1);
18792		// check for text direction attribute
18793		if (isset($tag['dir'])) {
18794			$this->setTempRTL($tag['dir']);
18795		} else {
18796			$this->tmprtl = false;
18797		}
18798		if ($tag['block']) {
18799			$hbz = 0; // distance from y to line bottom
18800			$hb = 0; // vertical space between block tags
18801			// calculate vertical space for block tags
18802			if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18803				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18804			} elseif (isset($tag['fontsize'])) {
18805				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18806			} else {
18807				$cur_h = $this->getCellHeight($this->FontSize);
18808			}
18809			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18810				$on = $this->tagvspaces[$tag['value']][0]['n'];
18811			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18812				$on = 0.6;
18813			} else {
18814				$on = 1;
18815			}
18816			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18817				$hb = 0;
18818			} else {
18819				$hb = ($on * $cur_h);
18820			}
18821			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18822				if (isset($parent['fontsize'])) {
18823					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18824				} else {
18825					$hbz = $this->getCellHeight($this->FontSize);
18826				}
18827			}
18828			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18829				// fix vertical space after table
18830				$hbz = 0;
18831			}
18832			// closing vertical space
18833			$hbc = 0;
18834			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18835				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18836			} elseif (isset($parent['fontsize'])) {
18837				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18838			} else {
18839				$pre_h = $this->getCellHeight($this->FontSize);
18840			}
18841			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18842				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18843			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18844				$cn = 0.6;
18845			} else {
18846				$cn = 1;
18847			}
18848			if (isset($this->tagvspaces[$tag['value']][1])) {
18849				$hbc = ($cn * $pre_h);
18850			}
18851		}
18852		// Opening tag
18853		switch($tag['value']) {
18854			case 'table': {
18855				$cp = 0;
18856				$cs = 0;
18857				$dom[$key]['rowspans'] = array();
18858				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18859					$this->htmlvspace = 0;
18860					// set table header
18861					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18862						// set table header
18863						$this->thead = $dom[$key]['thead'];
18864						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18865							$this->theadMargins = array();
18866							$this->theadMargins['cell_padding'] = $this->cell_padding;
18867							$this->theadMargins['lmargin'] = $this->lMargin;
18868							$this->theadMargins['rmargin'] = $this->rMargin;
18869							$this->theadMargins['page'] = $this->page;
18870							$this->theadMargins['cell'] = $cell;
18871							$this->theadMargins['gvars'] = $this->getGraphicVars();
18872						}
18873					}
18874				}
18875				// store current margins and page
18876				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18877				if (isset($tag['attribute']['cellpadding'])) {
18878					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18879					$this->SetCellPadding($pad);
18880				} elseif (isset($tag['padding'])) {
18881					$this->cell_padding = $tag['padding'];
18882				}
18883				if (isset($tag['attribute']['cellspacing'])) {
18884					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18885				} elseif (isset($tag['border-spacing'])) {
18886					$cs = $tag['border-spacing']['V'];
18887				}
18888				$prev_y = $this->y;
18889				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18890					$this->inthead = true;
18891					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18892					$this->checkPageBreak($this->PageBreakTrigger + 1);
18893				}
18894				break;
18895			}
18896			case 'tr': {
18897				// array of columns positions
18898				$dom[$key]['cellpos'] = array();
18899				break;
18900			}
18901			case 'hr': {
18902				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18903					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18904				} else {
18905					$hrHeight = $this->GetLineWidth();
18906				}
18907				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18908				$x = $this->GetX();
18909				$y = $this->GetY();
18910				$wtmp = $this->w - $this->lMargin - $this->rMargin;
18911				if ($cell) {
18912					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18913				}
18914				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18915					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18916				} else {
18917					$hrWidth = $wtmp;
18918				}
18919				$prevlinewidth = $this->GetLineWidth();
18920				$this->SetLineWidth($hrHeight);
18921
18922				$lineStyle = array(
18923					'color' => $tag['fgcolor'],
18924					'cap'   => $tag['style']['cap'],
18925					'join'  => $tag['style']['join'],
18926					'dash'  => $tag['style']['dash'],
18927					'phase' => $tag['style']['phase'],
18928				);
18929
18930				$lineStyle = array_filter($lineStyle);
18931
18932				$this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
18933				$this->SetLineWidth($prevlinewidth);
18934				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18935				break;
18936			}
18937			case 'a': {
18938				if (array_key_exists('href', $tag['attribute'])) {
18939					$this->HREF['url'] = $tag['attribute']['href'];
18940				}
18941				break;
18942			}
18943			case 'img': {
18944				if (empty($tag['attribute']['src'])) {
18945					break;
18946				}
18947				$imgsrc = $tag['attribute']['src'];
18948				if ($imgsrc[0] === '@') {
18949					// data stream
18950					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
18951					$type = '';
18952				} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
18953                    // get image type from a local file path
18954                    $imgsrc = substr($imgsrc, 7);
18955                    $type = TCPDF_IMAGES::getImageFileType($imgsrc);
18956                } else {
18957					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
18958						// fix image path
18959						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
18960						if (($findroot === false) OR ($findroot > 1)) {
18961							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
18962								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
18963							} else {
18964								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
18965							}
18966						}
18967						$imgsrc = urldecode($imgsrc);
18968						$testscrtype = @parse_url($imgsrc);
18969						if (empty($testscrtype['query'])) {
18970							// convert URL to server path
18971							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
18972						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
18973							// convert URL to server path
18974							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
18975						}
18976					}
18977					// get image type
18978					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
18979				}
18980				if (!isset($tag['width'])) {
18981					$tag['width'] = 0;
18982				}
18983				if (!isset($tag['height'])) {
18984					$tag['height'] = 0;
18985				}
18986				//if (!isset($tag['attribute']['align'])) {
18987					// the only alignment supported is "bottom"
18988					// further development is required for other modes.
18989					$tag['attribute']['align'] = 'bottom';
18990				//}
18991				switch($tag['attribute']['align']) {
18992					case 'top': {
18993						$align = 'T';
18994						break;
18995					}
18996					case 'middle': {
18997						$align = 'M';
18998						break;
18999					}
19000					case 'bottom': {
19001						$align = 'B';
19002						break;
19003					}
19004					default: {
19005						$align = 'B';
19006						break;
19007					}
19008				}
19009				$prevy = $this->y;
19010				$xpos = $this->x;
19011				$imglink = '';
19012				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19013					$imglink = $this->HREF['url'];
19014					if ($imglink[0] == '#') {
19015						// convert url to internal link
19016						$lnkdata = explode(',', $imglink);
19017						if (isset($lnkdata[0])) {
19018							$page = intval(substr($lnkdata[0], 1));
19019							if (empty($page) OR ($page <= 0)) {
19020								$page = $this->page;
19021							}
19022							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19023								$lnky = floatval($lnkdata[1]);
19024							} else {
19025								$lnky = 0;
19026							}
19027							$imglink = $this->AddLink();
19028							$this->SetLink($imglink, $lnky, $page);
19029						}
19030					}
19031				}
19032				$border = 0;
19033				if (isset($tag['border']) AND !empty($tag['border'])) {
19034					// currently only support 1 (frame) or a combination of 'LTRB'
19035					$border = $tag['border'];
19036				}
19037				$iw = '';
19038				if (isset($tag['width'])) {
19039					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19040				}
19041				$ih = '';
19042				if (isset($tag['height'])) {
19043					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19044				}
19045				if (($type == 'eps') OR ($type == 'ai')) {
19046					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19047				} elseif ($type == 'svg') {
19048					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19049				} else {
19050					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19051				}
19052				switch($align) {
19053					case 'T': {
19054						$this->y = $prevy;
19055						break;
19056					}
19057					case 'M': {
19058						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19059						break;
19060					}
19061					case 'B': {
19062						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19063						break;
19064					}
19065				}
19066				break;
19067			}
19068			case 'dl': {
19069				++$this->listnum;
19070				if ($this->listnum == 1) {
19071					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19072				} else {
19073					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19074				}
19075				break;
19076			}
19077			case 'dt': {
19078				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19079				break;
19080			}
19081			case 'dd': {
19082				if ($this->rtl) {
19083					$this->rMargin += $this->listindent;
19084				} else {
19085					$this->lMargin += $this->listindent;
19086				}
19087				++$this->listindentlevel;
19088				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19089				break;
19090			}
19091			case 'ul':
19092			case 'ol': {
19093				++$this->listnum;
19094				if ($tag['value'] == 'ol') {
19095					$this->listordered[$this->listnum] = true;
19096				} else {
19097					$this->listordered[$this->listnum] = false;
19098				}
19099				if (isset($tag['attribute']['start'])) {
19100					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19101				} else {
19102					$this->listcount[$this->listnum] = 0;
19103				}
19104				if ($this->rtl) {
19105					$this->rMargin += $this->listindent;
19106					$this->x -= $this->listindent;
19107				} else {
19108					$this->lMargin += $this->listindent;
19109					$this->x += $this->listindent;
19110				}
19111				++$this->listindentlevel;
19112				if ($this->listnum == 1) {
19113					if ($key > 1) {
19114						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19115					}
19116				} else {
19117					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19118				}
19119				break;
19120			}
19121			case 'li': {
19122				if ($key > 2) {
19123					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19124				}
19125				if ($this->listordered[$this->listnum]) {
19126					// ordered item
19127					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19128						$this->lispacer = $parent['attribute']['type'];
19129					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19130						$this->lispacer = $parent['listtype'];
19131					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19132						$this->lispacer = $this->lisymbol;
19133					} else {
19134						$this->lispacer = '#';
19135					}
19136					++$this->listcount[$this->listnum];
19137					if (isset($tag['attribute']['value'])) {
19138						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19139					}
19140				} else {
19141					// unordered item
19142					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19143						$this->lispacer = $parent['attribute']['type'];
19144					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19145						$this->lispacer = $parent['listtype'];
19146					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19147						$this->lispacer = $this->lisymbol;
19148					} else {
19149						$this->lispacer = '!';
19150					}
19151				}
19152				break;
19153			}
19154			case 'blockquote': {
19155				if ($this->rtl) {
19156					$this->rMargin += $this->listindent;
19157				} else {
19158					$this->lMargin += $this->listindent;
19159				}
19160				++$this->listindentlevel;
19161				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19162				break;
19163			}
19164			case 'br': {
19165				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19166				break;
19167			}
19168			case 'div': {
19169				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19170				break;
19171			}
19172			case 'p': {
19173				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19174				break;
19175			}
19176			case 'pre': {
19177				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19178				$this->premode = true;
19179				break;
19180			}
19181			case 'sup': {
19182				$this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19183				break;
19184			}
19185			case 'sub': {
19186				$this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19187				break;
19188			}
19189			case 'h1':
19190			case 'h2':
19191			case 'h3':
19192			case 'h4':
19193			case 'h5':
19194			case 'h6': {
19195				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19196				break;
19197			}
19198			// Form fields (since 4.8.000 - 2009-09-07)
19199			case 'form': {
19200				if (isset($tag['attribute']['action'])) {
19201					$this->form_action = $tag['attribute']['action'];
19202				} else {
19203					$this->Error('Please explicitly set action attribute path!');
19204				}
19205				if (isset($tag['attribute']['enctype'])) {
19206					$this->form_enctype = $tag['attribute']['enctype'];
19207				} else {
19208					$this->form_enctype = 'application/x-www-form-urlencoded';
19209				}
19210				if (isset($tag['attribute']['method'])) {
19211					$this->form_mode = $tag['attribute']['method'];
19212				} else {
19213					$this->form_mode = 'post';
19214				}
19215				break;
19216			}
19217			case 'input': {
19218				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19219					$name = $tag['attribute']['name'];
19220				} else {
19221					break;
19222				}
19223				$prop = array();
19224				$opt = array();
19225				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19226					$prop['readonly'] = true;
19227				}
19228				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19229					$value = $tag['attribute']['value'];
19230				}
19231				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19232					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19233				}
19234				$h = $this->getCellHeight($this->FontSize);
19235				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19236					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19237				} else {
19238					$w = $h;
19239				}
19240				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19241					$checked = true;
19242				} else {
19243					$checked = false;
19244				}
19245				if (isset($tag['align'])) {
19246					switch ($tag['align']) {
19247						case 'C': {
19248							$opt['q'] = 1;
19249							break;
19250						}
19251						case 'R': {
19252							$opt['q'] = 2;
19253							break;
19254						}
19255						case 'L':
19256						default: {
19257							break;
19258						}
19259					}
19260				}
19261				switch ($tag['attribute']['type']) {
19262					case 'text': {
19263						if (isset($value)) {
19264							$opt['v'] = $value;
19265						}
19266						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19267						break;
19268					}
19269					case 'password': {
19270						if (isset($value)) {
19271							$opt['v'] = $value;
19272						}
19273						$prop['password'] = 'true';
19274						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19275						break;
19276					}
19277					case 'checkbox': {
19278						if (!isset($value)) {
19279							break;
19280						}
19281						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19282						break;
19283					}
19284					case 'radio': {
19285						if (!isset($value)) {
19286							break;
19287						}
19288						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19289						break;
19290					}
19291					case 'submit': {
19292						if (!isset($value)) {
19293							$value = 'submit';
19294						}
19295						$w = $this->GetStringWidth($value) * 1.5;
19296						$h *= 1.6;
19297						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19298						$action = array();
19299						$action['S'] = 'SubmitForm';
19300						$action['F'] = $this->form_action;
19301						if ($this->form_enctype != 'FDF') {
19302							$action['Flags'] = array('ExportFormat');
19303						}
19304						if ($this->form_mode == 'get') {
19305							$action['Flags'] = array('GetMethod');
19306						}
19307						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19308						break;
19309					}
19310					case 'reset': {
19311						if (!isset($value)) {
19312							$value = 'reset';
19313						}
19314						$w = $this->GetStringWidth($value) * 1.5;
19315						$h *= 1.6;
19316						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19317						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19318						break;
19319					}
19320					case 'file': {
19321						$prop['fileSelect'] = 'true';
19322						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19323						if (!isset($value)) {
19324							$value = '*';
19325						}
19326						$w = $this->GetStringWidth($value) * 2;
19327						$h *= 1.2;
19328						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19329						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19330						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19331						break;
19332					}
19333					case 'hidden': {
19334						if (isset($value)) {
19335							$opt['v'] = $value;
19336						}
19337						$opt['f'] = array('invisible', 'hidden');
19338						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19339						break;
19340					}
19341					case 'image': {
19342						// THIS TYPE MUST BE FIXED
19343						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19344							$img = $tag['attribute']['src'];
19345						} else {
19346							break;
19347						}
19348						$value = 'img';
19349						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19350						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19351							$jsaction = $tag['attribute']['onclick'];
19352						} else {
19353							$jsaction = '';
19354						}
19355						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19356						break;
19357					}
19358					case 'button': {
19359						if (!isset($value)) {
19360							$value = ' ';
19361						}
19362						$w = $this->GetStringWidth($value) * 1.5;
19363						$h *= 1.6;
19364						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19365						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19366							$jsaction = $tag['attribute']['onclick'];
19367						} else {
19368							$jsaction = '';
19369						}
19370						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19371						break;
19372					}
19373				}
19374				break;
19375			}
19376			case 'textarea': {
19377				$prop = array();
19378				$opt = array();
19379				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19380					$prop['readonly'] = true;
19381				}
19382				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19383					$name = $tag['attribute']['name'];
19384				} else {
19385					break;
19386				}
19387				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19388					$opt['v'] = $tag['attribute']['value'];
19389				}
19390				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19391					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19392				} else {
19393					$w = 40;
19394				}
19395				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19396					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19397				} else {
19398					$h = 10;
19399				}
19400				$prop['multiline'] = 'true';
19401				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19402				break;
19403			}
19404			case 'select': {
19405				$h = $this->getCellHeight($this->FontSize);
19406				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19407					$h *= ($tag['attribute']['size'] + 1);
19408				}
19409				$prop = array();
19410				$opt = array();
19411				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19412					$name = $tag['attribute']['name'];
19413				} else {
19414					break;
19415				}
19416				$w = 0;
19417				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19418					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19419					$values = array();
19420					foreach ($options as $val) {
19421						if (strpos($val, '#!TaB!#') !== false) {
19422							$opts = explode('#!TaB!#', $val);
19423							$values[] = $opts;
19424							$w = max($w, $this->GetStringWidth($opts[1]));
19425						} else {
19426							$values[] = $val;
19427							$w = max($w, $this->GetStringWidth($val));
19428						}
19429					}
19430				} else {
19431					break;
19432				}
19433				$w *= 2;
19434				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19435					$prop['multipleSelection'] = 'true';
19436					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19437				} else {
19438					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19439				}
19440				break;
19441			}
19442			case 'tcpdf': {
19443				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19444					// Special tag used to call TCPDF methods
19445					if (isset($tag['attribute']['method'])) {
19446						$tcpdf_method = $tag['attribute']['method'];
19447						if (method_exists($this, $tcpdf_method)) {
19448							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19449								$params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19450								call_user_func_array(array($this, $tcpdf_method), $params);
19451							} else {
19452								$this->$tcpdf_method();
19453							}
19454							$this->newline = true;
19455						}
19456					}
19457				}
19458				break;
19459			}
19460			default: {
19461				break;
19462			}
19463		}
19464		// define tags that support borders and background colors
19465		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19466		if (in_array($tag['value'], $bordertags)) {
19467			// set border
19468			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19469		}
19470		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19471			$pba = $dom[$key]['attribute']['pagebreakafter'];
19472			// check for pagebreak
19473			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19474				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19475				$this->checkPageBreak($this->PageBreakTrigger + 1);
19476			}
19477			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19478				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19479				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19480				$this->checkPageBreak($this->PageBreakTrigger + 1);
19481			}
19482		}
19483		return $dom;
19484	}
19485
19486	/**
19487	 * Process closing tags.
19488	 * @param $dom (array) html dom array
19489	 * @param $key (int) current element id
19490	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19491	 * @param $maxbottomliney (int) maximum y value of current line
19492	 * @return $dom array
19493	 * @protected
19494	 */
19495	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19496		$tag = $dom[$key];
19497		$parent = $dom[($dom[$key]['parent'])];
19498		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19499		$in_table_head = false;
19500		// maximum x position (used to draw borders)
19501		if ($this->rtl) {
19502			$xmax = $this->w;
19503		} else {
19504			$xmax = 0;
19505		}
19506		if ($tag['block']) {
19507			$hbz = 0; // distance from y to line bottom
19508			$hb = 0; // vertical space between block tags
19509			// calculate vertical space for block tags
19510			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19511				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19512			} elseif (isset($parent['fontsize'])) {
19513				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19514			} else {
19515				$pre_h = $this->getCellHeight($this->FontSize);
19516			}
19517			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19518				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19519			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19520				$cn = 0.6;
19521			} else {
19522				$cn = 1;
19523			}
19524			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19525				$hb = 0;
19526			} else {
19527				$hb = ($cn * $pre_h);
19528			}
19529			if ($maxbottomliney > $this->PageBreakTrigger) {
19530				$hbz = $this->getCellHeight($this->FontSize);
19531			} elseif ($this->y < $maxbottomliney) {
19532				$hbz = ($maxbottomliney - $this->y);
19533			}
19534		}
19535		// Closing tag
19536		switch($tag['value']) {
19537			case 'tr': {
19538				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19539				if (!isset($parent['endy'])) {
19540					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19541					$parent['endy'] = $this->y;
19542				}
19543				if (!isset($parent['endpage'])) {
19544					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19545					$parent['endpage'] = $this->page;
19546				}
19547				if (!isset($parent['endcolumn'])) {
19548					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19549					$parent['endcolumn'] = $this->current_column;
19550				}
19551				// update row-spanned cells
19552				if (isset($dom[$table_el]['rowspans'])) {
19553					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19554						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19555						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19556							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19557								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19558							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19559								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19560								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19561								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19562							}
19563						}
19564					}
19565					// report new endy and endpage to the rowspanned cells
19566					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19567						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19568							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19569							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19570							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19571							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19572							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19573							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19574						}
19575					}
19576					// update remaining rowspanned cells
19577					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19578						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19579							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19580							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19581							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19582						}
19583					}
19584				}
19585				$prev_page = $this->page;
19586				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19587				if ($this->num_columns > 1) {
19588					if (($prev_page < $this->page)
19589						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19590							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19591						// page jump
19592						$this->selectColumn(0);
19593						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19594						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19595					} else {
19596						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19597						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19598					}
19599				} else {
19600					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19601				}
19602				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19603					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19604				} elseif (isset($dom[$table_el]['border-spacing'])) {
19605					$this->y += $dom[$table_el]['border-spacing']['V'];
19606				}
19607				$this->Ln(0, $cell);
19608				if ($this->current_column == $parent['startcolumn']) {
19609					$this->x = $parent['startx'];
19610				}
19611				// account for booklet mode
19612				if ($this->page > $parent['startpage']) {
19613					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19614						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19615					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19616						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19617					}
19618				}
19619				break;
19620			}
19621			case 'tablehead':
19622				// closing tag used for the thead part
19623				$in_table_head = true;
19624				$this->inthead = false;
19625			case 'table': {
19626				$table_el = $parent;
19627				// set default border
19628				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19629					// set default border
19630					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19631				} else {
19632					$border = 0;
19633				}
19634				$default_border = $border;
19635				// fix bottom line alignment of last line before page break
19636				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19637					// update row-spanned cells
19638					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19639						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19640							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19641								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19642							}
19643							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19644								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19645							}
19646						}
19647					}
19648					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19649						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19650						$dom[$prevtrkey]['endy'] = $pgendy;
19651						// update row-spanned cells
19652						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19653							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19654								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19655									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19656									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19657								}
19658							}
19659						}
19660					}
19661					$prevtrkey = $trkey;
19662					$table_el = $dom[($dom[$key]['parent'])];
19663				}
19664				// for each row
19665				if (count($table_el['trids']) > 0) {
19666					unset($xmax);
19667				}
19668				foreach ($table_el['trids'] as $j => $trkey) {
19669					$parent = $dom[$trkey];
19670					if (!isset($xmax)) {
19671						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19672					}
19673					// for each cell on the row
19674					foreach ($parent['cellpos'] as $k => $cellpos) {
19675						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19676							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19677							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19678							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19679							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19680							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19681							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19682							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19683						} else {
19684							$endy = $parent['endy'];
19685							$startpage = $parent['startpage'];
19686							$endpage = $parent['endpage'];
19687							$startcolumn = $parent['startcolumn'];
19688							$endcolumn = $parent['endcolumn'];
19689						}
19690						if ($this->num_columns == 0) {
19691							$this->num_columns = 1;
19692						}
19693						if (isset($cellpos['border'])) {
19694							$border = $cellpos['border'];
19695						}
19696						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19697							$this->SetFillColorArray($cellpos['bgcolor']);
19698							$fill = true;
19699						} else {
19700							$fill = false;
19701						}
19702						$x = $cellpos['startx'];
19703						$y = $parent['starty'];
19704						$starty = $y;
19705						$w = abs($cellpos['endx'] - $cellpos['startx']);
19706						// get border modes
19707						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19708						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19709						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19710						// design borders around HTML cells.
19711						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19712							$ccode = '';
19713							$this->setPage($page);
19714							if ($this->num_columns < 2) {
19715								// single-column mode
19716								$this->x = $x;
19717								$this->y = $this->tMargin;
19718							}
19719							// account for margin changes
19720							if ($page > $startpage) {
19721								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19722									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19723								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19724									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19725								}
19726							}
19727							if ($startpage == $endpage) { // single page
19728								$deltacol = 0;
19729								$deltath = 0;
19730								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19731									$this->selectColumn($column);
19732									if ($startcolumn == $endcolumn) { // single column
19733										$cborder = $border;
19734										$h = $endy - $parent['starty'];
19735										$this->y = $y;
19736										$this->x = $x;
19737									} elseif ($column == $startcolumn) { // first column
19738										$cborder = $border_start;
19739										$this->y = $starty;
19740										$this->x = $x;
19741										$h = $this->h - $this->y - $this->bMargin;
19742										if ($this->rtl) {
19743											$deltacol = $this->x + $this->rMargin - $this->w;
19744										} else {
19745											$deltacol = $this->x - $this->lMargin;
19746										}
19747									} elseif ($column == $endcolumn) { // end column
19748										$cborder = $border_end;
19749										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19750											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19751										}
19752										$this->x += $deltacol;
19753										$h = $endy - $this->y;
19754									} else { // middle column
19755										$cborder = $border_middle;
19756										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19757											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19758										}
19759										$this->x += $deltacol;
19760										$h = $this->h - $this->y - $this->bMargin;
19761									}
19762									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19763								} // end for each column
19764							} elseif ($page == $startpage) { // first page
19765								$deltacol = 0;
19766								$deltath = 0;
19767								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19768									$this->selectColumn($column);
19769									if ($column == $startcolumn) { // first column
19770										$cborder = $border_start;
19771										$this->y = $starty;
19772										$this->x = $x;
19773										$h = $this->h - $this->y - $this->bMargin;
19774										if ($this->rtl) {
19775											$deltacol = $this->x + $this->rMargin - $this->w;
19776										} else {
19777											$deltacol = $this->x - $this->lMargin;
19778										}
19779									} else { // middle column
19780										$cborder = $border_middle;
19781										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19782											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19783										}
19784										$this->x += $deltacol;
19785										$h = $this->h - $this->y - $this->bMargin;
19786									}
19787									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19788								} // end for each column
19789							} elseif ($page == $endpage) { // last page
19790								$deltacol = 0;
19791								$deltath = 0;
19792								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19793									$this->selectColumn($column);
19794									if ($column == $endcolumn) { // end column
19795										$cborder = $border_end;
19796										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19797											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19798										}
19799										$this->x += $deltacol;
19800										$h = $endy - $this->y;
19801									} else { // middle column
19802										$cborder = $border_middle;
19803										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19804											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19805										}
19806										$this->x += $deltacol;
19807										$h = $this->h - $this->y - $this->bMargin;
19808									}
19809									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19810								} // end for each column
19811							} else { // middle page
19812								$deltacol = 0;
19813								$deltath = 0;
19814								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19815									$this->selectColumn($column);
19816									$cborder = $border_middle;
19817									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19818										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19819									}
19820									$this->x += $deltacol;
19821									$h = $this->h - $this->y - $this->bMargin;
19822									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19823								} // end for each column
19824							}
19825							if (!empty($cborder) OR !empty($fill)) {
19826								$offsetlen = strlen($ccode);
19827								// draw border and fill
19828								if ($this->inxobj) {
19829									// we are inside an XObject template
19830									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19831										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19832										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19833										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19834									} else {
19835										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19836										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19837									}
19838									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19839									$pstart = substr($pagebuff, 0, $pagemark);
19840									$pend = substr($pagebuff, $pagemark);
19841									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19842								} else {
19843									// draw border and fill
19844									if (end($this->transfmrk[$this->page]) !== false) {
19845										$pagemarkkey = key($this->transfmrk[$this->page]);
19846										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19847									} elseif ($this->InFooter) {
19848										$pagemark = $this->footerpos[$this->page];
19849									} else {
19850										$pagemark = $this->intmrk[$this->page];
19851									}
19852									$pagebuff = $this->getPageBuffer($this->page);
19853									$pstart = substr($pagebuff, 0, $pagemark);
19854									$pend = substr($pagebuff, $pagemark);
19855									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19856								}
19857							}
19858						} // end for each page
19859						// restore default border
19860						$border = $default_border;
19861					} // end for each cell on the row
19862					if (isset($table_el['attribute']['cellspacing'])) {
19863						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19864					} elseif (isset($table_el['border-spacing'])) {
19865						$this->y += $table_el['border-spacing']['V'];
19866					}
19867					$this->Ln(0, $cell);
19868					$this->x = $parent['startx'];
19869					if ($endpage > $startpage) {
19870						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19871							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19872						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19873							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19874						}
19875					}
19876				}
19877				if (!$in_table_head) { // we are not inside a thead section
19878					$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null;
19879					// reset row height
19880					$this->resetLastH();
19881					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19882						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19883						if (($plendiff > 0) AND ($plendiff < 60)) {
19884							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19885							if (substr($pagediff, 0, 5) == 'BT /F') {
19886								// the difference is only a font setting
19887								$plendiff = 0;
19888							}
19889						}
19890						if ($plendiff == 0) {
19891							// remove last blank page
19892							$this->deletePage($this->numpages);
19893						}
19894					}
19895					if (isset($this->theadMargins['top'])) {
19896						// restore top margin
19897						$this->tMargin = $this->theadMargins['top'];
19898					}
19899					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19900						// reset main table header
19901						$this->thead = '';
19902						$this->theadMargins = array();
19903						$this->pagedim[$this->page]['tm'] = $this->tMargin;
19904					}
19905				}
19906				$parent = $table_el;
19907				break;
19908			}
19909			case 'a': {
19910				$this->HREF = array();
19911				break;
19912			}
19913			case 'sup': {
19914				$this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19915				break;
19916			}
19917			case 'sub': {
19918				$this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19919				break;
19920			}
19921			case 'div': {
19922				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19923				break;
19924			}
19925			case 'blockquote': {
19926				if ($this->rtl) {
19927					$this->rMargin -= $this->listindent;
19928				} else {
19929					$this->lMargin -= $this->listindent;
19930				}
19931				--$this->listindentlevel;
19932				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19933				break;
19934			}
19935			case 'p': {
19936				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19937				break;
19938			}
19939			case 'pre': {
19940				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19941				$this->premode = false;
19942				break;
19943			}
19944			case 'dl': {
19945				--$this->listnum;
19946				if ($this->listnum <= 0) {
19947					$this->listnum = 0;
19948					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19949				} else {
19950					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19951				}
19952				$this->resetLastH();
19953				break;
19954			}
19955			case 'dt': {
19956				$this->lispacer = '';
19957				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19958				break;
19959			}
19960			case 'dd': {
19961				$this->lispacer = '';
19962				if ($this->rtl) {
19963					$this->rMargin -= $this->listindent;
19964				} else {
19965					$this->lMargin -= $this->listindent;
19966				}
19967				--$this->listindentlevel;
19968				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19969				break;
19970			}
19971			case 'ul':
19972			case 'ol': {
19973				--$this->listnum;
19974				$this->lispacer = '';
19975				if ($this->rtl) {
19976					$this->rMargin -= $this->listindent;
19977				} else {
19978					$this->lMargin -= $this->listindent;
19979				}
19980				--$this->listindentlevel;
19981				if ($this->listnum <= 0) {
19982					$this->listnum = 0;
19983					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19984				} else {
19985					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19986				}
19987				$this->resetLastH();
19988				break;
19989			}
19990			case 'li': {
19991				$this->lispacer = '';
19992				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19993				break;
19994			}
19995			case 'h1':
19996			case 'h2':
19997			case 'h3':
19998			case 'h4':
19999			case 'h5':
20000			case 'h6': {
20001				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20002				break;
20003			}
20004			// Form fields (since 4.8.000 - 2009-09-07)
20005			case 'form': {
20006				$this->form_action = '';
20007				$this->form_enctype = 'application/x-www-form-urlencoded';
20008				break;
20009			}
20010			default : {
20011				break;
20012			}
20013		}
20014		// draw border and background (if any)
20015		$this->drawHTMLTagBorder($parent, $xmax);
20016		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20017			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20018			// check for pagebreak
20019			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20020				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20021				$this->checkPageBreak($this->PageBreakTrigger + 1);
20022			}
20023			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20024				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20025				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20026				$this->checkPageBreak($this->PageBreakTrigger + 1);
20027			}
20028		}
20029		$this->tmprtl = false;
20030		return $dom;
20031	}
20032
20033	/**
20034	 * Add vertical spaces if needed.
20035	 * @param $hbz (string) Distance between current y and line bottom.
20036	 * @param $hb (string) The height of the break.
20037	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
20038	 * @param $firsttag (boolean) set to true when the tag is the first.
20039	 * @param $lasttag (boolean) set to true when the tag is the last.
20040	 * @protected
20041	 */
20042	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20043		if ($firsttag) {
20044			$this->Ln(0, $cell);
20045			$this->htmlvspace = 0;
20046			return;
20047		}
20048		if ($lasttag) {
20049			$this->Ln($hbz, $cell);
20050			$this->htmlvspace = 0;
20051			return;
20052		}
20053		if ($hb < $this->htmlvspace) {
20054			$hd = 0;
20055		} else {
20056			$hd = $hb - $this->htmlvspace;
20057			$this->htmlvspace = $hb;
20058		}
20059		$this->Ln(($hbz + $hd), $cell);
20060	}
20061
20062	/**
20063	 * Return the starting coordinates to draw an html border
20064	 * @return array containing top-left border coordinates
20065	 * @protected
20066	 * @since 5.7.000 (2010-08-03)
20067	 */
20068	protected function getBorderStartPosition() {
20069		if ($this->rtl) {
20070			$xmax = $this->lMargin;
20071		} else {
20072			$xmax = $this->w - $this->rMargin;
20073		}
20074		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20075	}
20076
20077	/**
20078	 * Draw an HTML block border and fill
20079	 * @param $tag (array) array of tag properties.
20080	 * @param $xmax (int) end X coordinate for border.
20081	 * @protected
20082	 * @since 5.7.000 (2010-08-03)
20083	 */
20084	protected function drawHTMLTagBorder($tag, $xmax) {
20085		if (!isset($tag['borderposition'])) {
20086			// nothing to draw
20087			return;
20088		}
20089		$prev_x = $this->x;
20090		$prev_y = $this->y;
20091		$prev_lasth = $this->lasth;
20092		$border = 0;
20093		$fill = false;
20094		$this->lasth = 0;
20095		if (isset($tag['border']) AND !empty($tag['border'])) {
20096			// get border style
20097			$border = $tag['border'];
20098			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20099				// border for table header
20100				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20101			}
20102		}
20103		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20104			// get background color
20105			$old_bgcolor = $this->bgcolor;
20106			$this->SetFillColorArray($tag['bgcolor']);
20107			$fill = true;
20108		}
20109		if (!$border AND !$fill) {
20110			// nothing to draw
20111			return;
20112		}
20113		if (isset($tag['attribute']['cellspacing'])) {
20114			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20115			$cellspacing = array('H' => $clsp, 'V' => $clsp);
20116		} elseif (isset($tag['border-spacing'])) {
20117			$cellspacing = $tag['border-spacing'];
20118		} else {
20119			$cellspacing = array('H' => 0, 'V' => 0);
20120		}
20121		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20122			// draw the border externally respect the sqare edge.
20123			$border['mode'] = 'ext';
20124		}
20125		if ($this->rtl) {
20126			if ($xmax >= $tag['borderposition']['x']) {
20127				$xmax = $tag['borderposition']['xmax'];
20128			}
20129			$w = ($tag['borderposition']['x'] - $xmax);
20130		} else {
20131			if ($xmax <= $tag['borderposition']['x']) {
20132				$xmax = $tag['borderposition']['xmax'];
20133			}
20134			$w = ($xmax - $tag['borderposition']['x']);
20135		}
20136		if ($w <= 0) {
20137			return;
20138		}
20139		$w += $cellspacing['H'];
20140		$startpage = $tag['borderposition']['page'];
20141		$startcolumn = $tag['borderposition']['column'];
20142		$x = $tag['borderposition']['x'];
20143		$y = $tag['borderposition']['y'];
20144		$endpage = $this->page;
20145		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20146		$currentY = $this->y;
20147		$this->x = $x;
20148		// get latest column
20149		$endcolumn = $this->current_column;
20150		if ($this->num_columns == 0) {
20151			$this->num_columns = 1;
20152		}
20153		// get border modes
20154		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20155		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20156		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20157		// temporary disable page regions
20158		$temp_page_regions = $this->page_regions;
20159		$this->page_regions = array();
20160		// design borders around HTML cells.
20161		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20162			$ccode = '';
20163			$this->setPage($page);
20164			if ($this->num_columns < 2) {
20165				// single-column mode
20166				$this->x = $x;
20167				$this->y = $this->tMargin;
20168			}
20169			// account for margin changes
20170			if ($page > $startpage) {
20171				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20172					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20173				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20174					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20175				}
20176			}
20177			if ($startpage == $endpage) {
20178				// single page
20179				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20180					$this->selectColumn($column);
20181					if ($startcolumn == $endcolumn) { // single column
20182						$cborder = $border;
20183						$h = ($currentY - $y) + $cellspacing['V'];
20184						$this->y = $starty;
20185					} elseif ($column == $startcolumn) { // first column
20186						$cborder = $border_start;
20187						$this->y = $starty;
20188						$h = $this->h - $this->y - $this->bMargin;
20189					} elseif ($column == $endcolumn) { // end column
20190						$cborder = $border_end;
20191						$h = $currentY - $this->y;
20192					} else { // middle column
20193						$cborder = $border_middle;
20194						$h = $this->h - $this->y - $this->bMargin;
20195					}
20196					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20197				} // end for each column
20198			} elseif ($page == $startpage) { // first page
20199				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20200					$this->selectColumn($column);
20201					if ($column == $startcolumn) { // first column
20202						$cborder = $border_start;
20203						$this->y = $starty;
20204						$h = $this->h - $this->y - $this->bMargin;
20205					} else { // middle column
20206						$cborder = $border_middle;
20207						$h = $this->h - $this->y - $this->bMargin;
20208					}
20209					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20210				} // end for each column
20211			} elseif ($page == $endpage) { // last page
20212				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20213					$this->selectColumn($column);
20214					if ($column == $endcolumn) {
20215						// end column
20216						$cborder = $border_end;
20217						$h = $currentY - $this->y;
20218					} else {
20219						// middle column
20220						$cborder = $border_middle;
20221						$h = $this->h - $this->y - $this->bMargin;
20222					}
20223					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20224				} // end for each column
20225			} else { // middle page
20226				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20227					$this->selectColumn($column);
20228					$cborder = $border_middle;
20229					$h = $this->h - $this->y - $this->bMargin;
20230					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20231				} // end for each column
20232			}
20233			if ($cborder OR $fill) {
20234				$offsetlen = strlen($ccode);
20235				// draw border and fill
20236				if ($this->inxobj) {
20237					// we are inside an XObject template
20238					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20239						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20240						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20241						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20242					} else {
20243						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20244						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20245					}
20246					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20247					$pstart = substr($pagebuff, 0, $pagemark);
20248					$pend = substr($pagebuff, $pagemark);
20249					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20250				} else {
20251					if (end($this->transfmrk[$this->page]) !== false) {
20252						$pagemarkkey = key($this->transfmrk[$this->page]);
20253						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20254					} elseif ($this->InFooter) {
20255						$pagemark = $this->footerpos[$this->page];
20256					} else {
20257						$pagemark = $this->intmrk[$this->page];
20258					}
20259					$pagebuff = $this->getPageBuffer($this->page);
20260					$pstart = substr($pagebuff, 0, $pagemark);
20261					$pend = substr($pagebuff, $pagemark);
20262					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20263					$this->bordermrk[$this->page] += $offsetlen;
20264					$this->cntmrk[$this->page] += $offsetlen;
20265				}
20266			}
20267		} // end for each page
20268		// restore page regions
20269		$this->page_regions = $temp_page_regions;
20270		if (isset($old_bgcolor)) {
20271			// restore background color
20272			$this->SetFillColorArray($old_bgcolor);
20273		}
20274		// restore pointer position
20275		$this->x = $prev_x;
20276		$this->y = $prev_y;
20277		$this->lasth = $prev_lasth;
20278	}
20279
20280	/**
20281	 * Set the default bullet to be used as LI bullet symbol
20282	 * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20283	 * @public
20284	 * @since 4.0.028 (2008-09-26)
20285	 */
20286	public function setLIsymbol($symbol='!') {
20287		// check for custom image symbol
20288		if (substr($symbol, 0, 4) == 'img|') {
20289			$this->lisymbol = $symbol;
20290			return;
20291		}
20292		$symbol = strtolower($symbol);
20293		$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');
20294		if (in_array($symbol, $valid_symbols)) {
20295			$this->lisymbol = $symbol;
20296		} else {
20297			$this->lisymbol = '';
20298		}
20299	}
20300
20301	/**
20302	 * Set the booklet mode for double-sided pages.
20303	 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20304	 * @param $inner (float) Inner page margin.
20305	 * @param $outer (float) Outer page margin.
20306	 * @public
20307	 * @since 4.2.000 (2008-10-29)
20308	 */
20309	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20310		$this->booklet = $booklet;
20311		if ($inner >= 0) {
20312			$this->lMargin = $inner;
20313		}
20314		if ($outer >= 0) {
20315			$this->rMargin = $outer;
20316		}
20317	}
20318
20319	/**
20320	 * Swap the left and right margins.
20321	 * @param $reverse (boolean) if true swap left and right margins.
20322	 * @protected
20323	 * @since 4.2.000 (2008-10-29)
20324	 */
20325	protected function swapMargins($reverse=true) {
20326		if ($reverse) {
20327			// swap left and right margins
20328			$mtemp = $this->original_lMargin;
20329			$this->original_lMargin = $this->original_rMargin;
20330			$this->original_rMargin = $mtemp;
20331			$deltam = $this->original_lMargin - $this->original_rMargin;
20332			$this->lMargin += $deltam;
20333			$this->rMargin -= $deltam;
20334		}
20335	}
20336
20337	/**
20338	 * Set the vertical spaces for HTML tags.
20339	 * The array must have the following structure (example):
20340	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20341	 * The first array level contains the tag names,
20342	 * the second level contains 0 for opening tags or 1 for closing tags,
20343	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20344	 * If the h parameter is not specified, default values are used.
20345	 * @param $tagvs (array) array of tags and relative vertical spaces.
20346	 * @public
20347	 * @since 4.2.001 (2008-10-30)
20348	 */
20349	public function setHtmlVSpace($tagvs) {
20350		$this->tagvspaces = $tagvs;
20351	}
20352
20353	/**
20354	 * Set custom width for list indentation.
20355	 * @param $width (float) width of the indentation. Use negative value to disable it.
20356	 * @public
20357	 * @since 4.2.007 (2008-11-12)
20358	 */
20359	public function setListIndentWidth($width) {
20360		return $this->customlistindent = floatval($width);
20361	}
20362
20363	/**
20364	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20365	 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20366	 * @public
20367	 * @since 4.2.010 (2008-11-14)
20368	 */
20369	public function setOpenCell($isopen) {
20370		$this->opencell = $isopen;
20371	}
20372
20373	/**
20374	 * Set the color and font style for HTML links.
20375	 * @param $color (array) RGB array of colors
20376	 * @param $fontstyle (string) additional font styles to add
20377	 * @public
20378	 * @since 4.4.003 (2008-12-09)
20379	 */
20380	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20381		$this->htmlLinkColorArray = $color;
20382		$this->htmlLinkFontStyle = $fontstyle;
20383	}
20384
20385	/**
20386	 * Convert HTML string containing value and unit of measure to user's units or points.
20387	 * @param $htmlval (string) String containing values and unit.
20388	 * @param $refsize (string) Reference value in points.
20389	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20390	 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20391	 * @return float value in user's unit or point if $points=true
20392	 * @public
20393	 * @since 4.4.004 (2008-12-10)
20394	 */
20395	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20396		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20397		$retval = 0;
20398		$value = 0;
20399		$unit = 'px';
20400		if ($points) {
20401			$k = 1;
20402		} else {
20403			$k = $this->k;
20404		}
20405		if (in_array($defaultunit, $supportedunits)) {
20406			$unit = $defaultunit;
20407		}
20408		if (is_numeric($htmlval)) {
20409			$value = floatval($htmlval);
20410		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20411			$value = floatval($mnum[1]);
20412			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20413				if (in_array($munit[1], $supportedunits)) {
20414					$unit = $munit[1];
20415				}
20416			}
20417		}
20418		switch ($unit) {
20419			// percentage
20420			case '%': {
20421				$retval = (($value * $refsize) / 100);
20422				break;
20423			}
20424			// relative-size
20425			case 'em': {
20426				$retval = ($value * $refsize);
20427				break;
20428			}
20429			// height of lower case 'x' (about half the font-size)
20430			case 'ex': {
20431				$retval = ($value * ($refsize / 2));
20432				break;
20433			}
20434			// absolute-size
20435			case 'in': {
20436				$retval = (($value * $this->dpi) / $k);
20437				break;
20438			}
20439			// centimeters
20440			case 'cm': {
20441				$retval = (($value / 2.54 * $this->dpi) / $k);
20442				break;
20443			}
20444			// millimeters
20445			case 'mm': {
20446				$retval = (($value / 25.4 * $this->dpi) / $k);
20447				break;
20448			}
20449			// one pica is 12 points
20450			case 'pc': {
20451				$retval = (($value * 12) / $k);
20452				break;
20453			}
20454			// points
20455			case 'pt': {
20456				$retval = ($value / $k);
20457				break;
20458			}
20459			// pixels
20460			case 'px': {
20461				$retval = $this->pixelsToUnits($value);
20462				if ($points) {
20463					$retval *= $this->k;
20464				}
20465				break;
20466			}
20467		}
20468		return $retval;
20469	}
20470
20471	/**
20472	 * Output an HTML list bullet or ordered item symbol
20473	 * @param $listdepth (int) list nesting level
20474	 * @param $listtype (string) type of list
20475	 * @param $size (float) current font size
20476	 * @protected
20477	 * @since 4.4.004 (2008-12-10)
20478	 */
20479	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20480		if ($this->state != 2) {
20481			return;
20482		}
20483		$size /= $this->k;
20484		$fill = '';
20485		$bgcolor = $this->bgcolor;
20486		$color = $this->fgcolor;
20487		$strokecolor = $this->strokecolor;
20488		$width = 0;
20489		$textitem = '';
20490		$tmpx = $this->x;
20491		$lspace = $this->GetStringWidth('  ');
20492		if ($listtype == '^') {
20493			// special symbol used for avoid justification of rect bullet
20494			$this->lispacer = '';
20495			return;
20496		} elseif ($listtype == '!') {
20497			// set default list type for unordered list
20498			$deftypes = array('disc', 'circle', 'square');
20499			$listtype = $deftypes[($listdepth - 1) % 3];
20500		} elseif ($listtype == '#') {
20501			// set default list type for ordered list
20502			$listtype = 'decimal';
20503		} elseif (substr($listtype, 0, 4) == 'img|') {
20504			// custom image type ('img|type|width|height|image.ext')
20505			$img = explode('|', $listtype);
20506			$listtype = 'img';
20507		}
20508		switch ($listtype) {
20509			// unordered types
20510			case 'none': {
20511				break;
20512			}
20513			case 'disc': {
20514				$r = $size / 6;
20515				$lspace += (2 * $r);
20516				if ($this->rtl) {
20517					$this->x += $lspace;
20518				} else {
20519					$this->x -= $lspace;
20520				}
20521				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20522				break;
20523			}
20524			case 'circle': {
20525				$r = $size / 6;
20526				$lspace += (2 * $r);
20527				if ($this->rtl) {
20528					$this->x += $lspace;
20529				} else {
20530					$this->x -= $lspace;
20531				}
20532				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20533				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20534				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20535				$this->_out($prev_line_style); // restore line settings
20536				break;
20537			}
20538			case 'square': {
20539				$l = $size / 3;
20540				$lspace += $l;
20541				if ($this->rtl) {;
20542					$this->x += $lspace;
20543				} else {
20544					$this->x -= $lspace;
20545				}
20546				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20547				break;
20548			}
20549			case 'img': {
20550				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20551				$lspace += $img[2];
20552				if ($this->rtl) {;
20553					$this->x += $lspace;
20554				} else {
20555					$this->x -= $lspace;
20556				}
20557				$imgtype = strtolower($img[1]);
20558				$prev_y = $this->y;
20559				switch ($imgtype) {
20560					case 'svg': {
20561						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20562						break;
20563					}
20564					case 'ai':
20565					case 'eps': {
20566						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20567						break;
20568					}
20569					default: {
20570						$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);
20571						break;
20572					}
20573				}
20574				$this->y = $prev_y;
20575				break;
20576			}
20577			// ordered types
20578			// $this->listcount[$this->listnum];
20579			// $textitem
20580			case '1':
20581			case 'decimal': {
20582				$textitem = $this->listcount[$this->listnum];
20583				break;
20584			}
20585			case 'decimal-leading-zero': {
20586				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20587				break;
20588			}
20589			case 'i':
20590			case 'lower-roman': {
20591				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20592				break;
20593			}
20594			case 'I':
20595			case 'upper-roman': {
20596				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20597				break;
20598			}
20599			case 'a':
20600			case 'lower-alpha':
20601			case 'lower-latin': {
20602				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20603				break;
20604			}
20605			case 'A':
20606			case 'upper-alpha':
20607			case 'upper-latin': {
20608				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20609				break;
20610			}
20611			case 'lower-greek': {
20612				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20613				break;
20614			}
20615			/*
20616			// Types to be implemented (special handling)
20617			case 'hebrew': {
20618				break;
20619			}
20620			case 'armenian': {
20621				break;
20622			}
20623			case 'georgian': {
20624				break;
20625			}
20626			case 'cjk-ideographic': {
20627				break;
20628			}
20629			case 'hiragana': {
20630				break;
20631			}
20632			case 'katakana': {
20633				break;
20634			}
20635			case 'hiragana-iroha': {
20636				break;
20637			}
20638			case 'katakana-iroha': {
20639				break;
20640			}
20641			*/
20642			default: {
20643				$textitem = $this->listcount[$this->listnum];
20644			}
20645		}
20646		if (!TCPDF_STATIC::empty_string($textitem)) {
20647			// Check whether we need a new page or new column
20648			$prev_y = $this->y;
20649			$h = $this->getCellHeight($this->FontSize);
20650			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20651				$tmpx = $this->x;
20652			}
20653			// print ordered item
20654			if ($this->rtl) {
20655				$textitem = '.'.$textitem;
20656			} else {
20657				$textitem = $textitem.'.';
20658			}
20659			$lspace += $this->GetStringWidth($textitem);
20660			if ($this->rtl) {
20661				$this->x += $lspace;
20662			} else {
20663				$this->x -= $lspace;
20664			}
20665			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20666		}
20667		$this->x = $tmpx;
20668		$this->lispacer = '^';
20669		// restore colors
20670		$this->SetFillColorArray($bgcolor);
20671		$this->SetDrawColorArray($strokecolor);
20672		$this->SettextColorArray($color);
20673	}
20674
20675	/**
20676	 * Returns current graphic variables as array.
20677	 * @return array of graphic variables
20678	 * @protected
20679	 * @since 4.2.010 (2008-11-14)
20680	 */
20681	protected function getGraphicVars() {
20682		$grapvars = array(
20683			'FontFamily' => $this->FontFamily,
20684			'FontStyle' => $this->FontStyle,
20685			'FontSizePt' => $this->FontSizePt,
20686			'rMargin' => $this->rMargin,
20687			'lMargin' => $this->lMargin,
20688			'cell_padding' => $this->cell_padding,
20689			'cell_margin' => $this->cell_margin,
20690			'LineWidth' => $this->LineWidth,
20691			'linestyleWidth' => $this->linestyleWidth,
20692			'linestyleCap' => $this->linestyleCap,
20693			'linestyleJoin' => $this->linestyleJoin,
20694			'linestyleDash' => $this->linestyleDash,
20695			'textrendermode' => $this->textrendermode,
20696			'textstrokewidth' => $this->textstrokewidth,
20697			'DrawColor' => $this->DrawColor,
20698			'FillColor' => $this->FillColor,
20699			'TextColor' => $this->TextColor,
20700			'ColorFlag' => $this->ColorFlag,
20701			'bgcolor' => $this->bgcolor,
20702			'fgcolor' => $this->fgcolor,
20703			'htmlvspace' => $this->htmlvspace,
20704			'listindent' => $this->listindent,
20705			'listindentlevel' => $this->listindentlevel,
20706			'listnum' => $this->listnum,
20707			'listordered' => $this->listordered,
20708			'listcount' => $this->listcount,
20709			'lispacer' => $this->lispacer,
20710			'cell_height_ratio' => $this->cell_height_ratio,
20711			'font_stretching' => $this->font_stretching,
20712			'font_spacing' => $this->font_spacing,
20713			'alpha' => $this->alpha,
20714			// extended
20715			'lasth' => $this->lasth,
20716			'tMargin' => $this->tMargin,
20717			'bMargin' => $this->bMargin,
20718			'AutoPageBreak' => $this->AutoPageBreak,
20719			'PageBreakTrigger' => $this->PageBreakTrigger,
20720			'x' => $this->x,
20721			'y' => $this->y,
20722			'w' => $this->w,
20723			'h' => $this->h,
20724			'wPt' => $this->wPt,
20725			'hPt' => $this->hPt,
20726			'fwPt' => $this->fwPt,
20727			'fhPt' => $this->fhPt,
20728			'page' => $this->page,
20729			'current_column' => $this->current_column,
20730			'num_columns' => $this->num_columns
20731			);
20732		return $grapvars;
20733	}
20734
20735	/**
20736	 * Set graphic variables.
20737	 * @param $gvars (array) array of graphic variablesto restore
20738	 * @param $extended (boolean) if true restore extended graphic variables
20739	 * @protected
20740	 * @since 4.2.010 (2008-11-14)
20741	 */
20742	protected function setGraphicVars($gvars, $extended=false) {
20743		if ($this->state != 2) {
20744			 return;
20745		}
20746		$this->FontFamily = $gvars['FontFamily'];
20747		$this->FontStyle = $gvars['FontStyle'];
20748		$this->FontSizePt = $gvars['FontSizePt'];
20749		$this->rMargin = $gvars['rMargin'];
20750		$this->lMargin = $gvars['lMargin'];
20751		$this->cell_padding = $gvars['cell_padding'];
20752		$this->cell_margin = $gvars['cell_margin'];
20753		$this->LineWidth = $gvars['LineWidth'];
20754		$this->linestyleWidth = $gvars['linestyleWidth'];
20755		$this->linestyleCap = $gvars['linestyleCap'];
20756		$this->linestyleJoin = $gvars['linestyleJoin'];
20757		$this->linestyleDash = $gvars['linestyleDash'];
20758		$this->textrendermode = $gvars['textrendermode'];
20759		$this->textstrokewidth = $gvars['textstrokewidth'];
20760		$this->DrawColor = $gvars['DrawColor'];
20761		$this->FillColor = $gvars['FillColor'];
20762		$this->TextColor = $gvars['TextColor'];
20763		$this->ColorFlag = $gvars['ColorFlag'];
20764		$this->bgcolor = $gvars['bgcolor'];
20765		$this->fgcolor = $gvars['fgcolor'];
20766		$this->htmlvspace = $gvars['htmlvspace'];
20767		$this->listindent = $gvars['listindent'];
20768		$this->listindentlevel = $gvars['listindentlevel'];
20769		$this->listnum = $gvars['listnum'];
20770		$this->listordered = $gvars['listordered'];
20771		$this->listcount = $gvars['listcount'];
20772		$this->lispacer = $gvars['lispacer'];
20773		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20774		$this->font_stretching = $gvars['font_stretching'];
20775		$this->font_spacing = $gvars['font_spacing'];
20776		$this->alpha = $gvars['alpha'];
20777		if ($extended) {
20778			// restore extended values
20779			$this->lasth = $gvars['lasth'];
20780			$this->tMargin = $gvars['tMargin'];
20781			$this->bMargin = $gvars['bMargin'];
20782			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20783			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20784			$this->x = $gvars['x'];
20785			$this->y = $gvars['y'];
20786			$this->w = $gvars['w'];
20787			$this->h = $gvars['h'];
20788			$this->wPt = $gvars['wPt'];
20789			$this->hPt = $gvars['hPt'];
20790			$this->fwPt = $gvars['fwPt'];
20791			$this->fhPt = $gvars['fhPt'];
20792			$this->page = $gvars['page'];
20793			$this->current_column = $gvars['current_column'];
20794			$this->num_columns = $gvars['num_columns'];
20795		}
20796		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20797		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20798			$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20799		}
20800	}
20801
20802	/**
20803	 * Outputs the "save graphics state" operator 'q'
20804	 * @protected
20805	 */
20806	protected function _outSaveGraphicsState() {
20807		$this->_out('q');
20808	}
20809
20810	/**
20811	 * Outputs the "restore graphics state" operator 'Q'
20812	 * @protected
20813	 */
20814	protected function _outRestoreGraphicsState() {
20815		$this->_out('Q');
20816	}
20817
20818	/**
20819	 * Set buffer content (always append data).
20820	 * @param $data (string) data
20821	 * @protected
20822	 * @since 4.5.000 (2009-01-02)
20823	 */
20824	protected function setBuffer($data) {
20825		$this->bufferlen += strlen($data);
20826		$this->buffer .= $data;
20827	}
20828
20829	/**
20830	 * Replace the buffer content
20831	 * @param $data (string) data
20832	 * @protected
20833	 * @since 5.5.000 (2010-06-22)
20834	 */
20835	protected function replaceBuffer($data) {
20836		$this->bufferlen = strlen($data);
20837		$this->buffer = $data;
20838	}
20839
20840	/**
20841	 * Get buffer content.
20842	 * @return string buffer content
20843	 * @protected
20844	 * @since 4.5.000 (2009-01-02)
20845	 */
20846	protected function getBuffer() {
20847		return $this->buffer;
20848	}
20849
20850	/**
20851	 * Set page buffer content.
20852	 * @param $page (int) page number
20853	 * @param $data (string) page data
20854	 * @param $append (boolean) if true append data, false replace.
20855	 * @protected
20856	 * @since 4.5.000 (2008-12-31)
20857	 */
20858	protected function setPageBuffer($page, $data, $append=false) {
20859		if ($append) {
20860			$this->pages[$page] .= $data;
20861		} else {
20862			$this->pages[$page] = $data;
20863		}
20864		if ($append AND isset($this->pagelen[$page])) {
20865			$this->pagelen[$page] += strlen($data);
20866		} else {
20867			$this->pagelen[$page] = strlen($data);
20868		}
20869	}
20870
20871	/**
20872	 * Get page buffer content.
20873	 * @param $page (int) page number
20874	 * @return string page buffer content or false in case of error
20875	 * @protected
20876	 * @since 4.5.000 (2008-12-31)
20877	 */
20878	protected function getPageBuffer($page) {
20879		if (isset($this->pages[$page])) {
20880			return $this->pages[$page];
20881		}
20882		return false;
20883	}
20884
20885	/**
20886	 * Set image buffer content.
20887	 * @param $image (string) image key
20888	 * @param $data (array) image data
20889	 * @return int image index number
20890	 * @protected
20891	 * @since 4.5.000 (2008-12-31)
20892	 */
20893	protected function setImageBuffer($image, $data) {
20894		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20895			$this->imagekeys[$this->numimages] = $image;
20896			$data['i'] = $this->numimages;
20897			++$this->numimages;
20898		}
20899		$this->images[$image] = $data;
20900		return $data['i'];
20901	}
20902
20903	/**
20904	 * Set image buffer content for a specified sub-key.
20905	 * @param $image (string) image key
20906	 * @param $key (string) image sub-key
20907	 * @param $data (array) image data
20908	 * @protected
20909	 * @since 4.5.000 (2008-12-31)
20910	 */
20911	protected function setImageSubBuffer($image, $key, $data) {
20912		if (!isset($this->images[$image])) {
20913			$this->setImageBuffer($image, array());
20914		}
20915		$this->images[$image][$key] = $data;
20916	}
20917
20918	/**
20919	 * Get image buffer content.
20920	 * @param $image (string) image key
20921	 * @return string image buffer content or false in case of error
20922	 * @protected
20923	 * @since 4.5.000 (2008-12-31)
20924	 */
20925	protected function getImageBuffer($image) {
20926		if (isset($this->images[$image])) {
20927			return $this->images[$image];
20928		}
20929		return false;
20930	}
20931
20932	/**
20933	 * Set font buffer content.
20934	 * @param $font (string) font key
20935	 * @param $data (array) font data
20936	 * @protected
20937	 * @since 4.5.000 (2009-01-02)
20938	 */
20939	protected function setFontBuffer($font, $data) {
20940		$this->fonts[$font] = $data;
20941		if (!in_array($font, $this->fontkeys)) {
20942			$this->fontkeys[] = $font;
20943			// store object ID for current font
20944			++$this->n;
20945			$this->font_obj_ids[$font] = $this->n;
20946			$this->setFontSubBuffer($font, 'n', $this->n);
20947		}
20948	}
20949
20950	/**
20951	 * Set font buffer content.
20952	 * @param $font (string) font key
20953	 * @param $key (string) font sub-key
20954	 * @param $data (array) font data
20955	 * @protected
20956	 * @since 4.5.000 (2009-01-02)
20957	 */
20958	protected function setFontSubBuffer($font, $key, $data) {
20959		if (!isset($this->fonts[$font])) {
20960			$this->setFontBuffer($font, array());
20961		}
20962		$this->fonts[$font][$key] = $data;
20963	}
20964
20965	/**
20966	 * Get font buffer content.
20967	 * @param $font (string) font key
20968	 * @return string font buffer content or false in case of error
20969	 * @protected
20970	 * @since 4.5.000 (2009-01-02)
20971	 */
20972	protected function getFontBuffer($font) {
20973		if (isset($this->fonts[$font])) {
20974			return $this->fonts[$font];
20975		}
20976		return false;
20977	}
20978
20979	/**
20980	 * Move a page to a previous position.
20981	 * @param $frompage (int) number of the source page
20982	 * @param $topage (int) number of the destination page (must be less than $frompage)
20983	 * @return true in case of success, false in case of error.
20984	 * @public
20985	 * @since 4.5.000 (2009-01-02)
20986	 */
20987	public function movePage($frompage, $topage) {
20988		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20989			return false;
20990		}
20991		if ($frompage == $this->page) {
20992			// close the page before moving it
20993			$this->endPage();
20994		}
20995		// move all page-related states
20996		$tmppage = $this->getPageBuffer($frompage);
20997		$tmppagedim = $this->pagedim[$frompage];
20998		$tmppagelen = $this->pagelen[$frompage];
20999		$tmpintmrk = $this->intmrk[$frompage];
21000		$tmpbordermrk = $this->bordermrk[$frompage];
21001		$tmpcntmrk = $this->cntmrk[$frompage];
21002		$tmppageobjects = $this->pageobjects[$frompage];
21003		if (isset($this->footerpos[$frompage])) {
21004			$tmpfooterpos = $this->footerpos[$frompage];
21005		}
21006		if (isset($this->footerlen[$frompage])) {
21007			$tmpfooterlen = $this->footerlen[$frompage];
21008		}
21009		if (isset($this->transfmrk[$frompage])) {
21010			$tmptransfmrk = $this->transfmrk[$frompage];
21011		}
21012		if (isset($this->PageAnnots[$frompage])) {
21013			$tmpannots = $this->PageAnnots[$frompage];
21014		}
21015		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21016			for ($i = $frompage; $i > $topage; --$i) {
21017				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21018					--$this->pagegroups[$this->newpagegroup[$i]];
21019					break;
21020				}
21021			}
21022			for ($i = $topage; $i > 0; --$i) {
21023				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21024					++$this->pagegroups[$this->newpagegroup[$i]];
21025					break;
21026				}
21027			}
21028		}
21029		for ($i = $frompage; $i > $topage; --$i) {
21030			$j = $i - 1;
21031			// shift pages down
21032			$this->setPageBuffer($i, $this->getPageBuffer($j));
21033			$this->pagedim[$i] = $this->pagedim[$j];
21034			$this->pagelen[$i] = $this->pagelen[$j];
21035			$this->intmrk[$i] = $this->intmrk[$j];
21036			$this->bordermrk[$i] = $this->bordermrk[$j];
21037			$this->cntmrk[$i] = $this->cntmrk[$j];
21038			$this->pageobjects[$i] = $this->pageobjects[$j];
21039			if (isset($this->footerpos[$j])) {
21040				$this->footerpos[$i] = $this->footerpos[$j];
21041			} elseif (isset($this->footerpos[$i])) {
21042				unset($this->footerpos[$i]);
21043			}
21044			if (isset($this->footerlen[$j])) {
21045				$this->footerlen[$i] = $this->footerlen[$j];
21046			} elseif (isset($this->footerlen[$i])) {
21047				unset($this->footerlen[$i]);
21048			}
21049			if (isset($this->transfmrk[$j])) {
21050				$this->transfmrk[$i] = $this->transfmrk[$j];
21051			} elseif (isset($this->transfmrk[$i])) {
21052				unset($this->transfmrk[$i]);
21053			}
21054			if (isset($this->PageAnnots[$j])) {
21055				$this->PageAnnots[$i] = $this->PageAnnots[$j];
21056			} elseif (isset($this->PageAnnots[$i])) {
21057				unset($this->PageAnnots[$i]);
21058			}
21059			if (isset($this->newpagegroup[$j])) {
21060				$this->newpagegroup[$i] = $this->newpagegroup[$j];
21061				unset($this->newpagegroup[$j]);
21062			}
21063			if ($this->currpagegroup == $j) {
21064				$this->currpagegroup = $i;
21065			}
21066		}
21067		$this->setPageBuffer($topage, $tmppage);
21068		$this->pagedim[$topage] = $tmppagedim;
21069		$this->pagelen[$topage] = $tmppagelen;
21070		$this->intmrk[$topage] = $tmpintmrk;
21071		$this->bordermrk[$topage] = $tmpbordermrk;
21072		$this->cntmrk[$topage] = $tmpcntmrk;
21073		$this->pageobjects[$topage] = $tmppageobjects;
21074		if (isset($tmpfooterpos)) {
21075			$this->footerpos[$topage] = $tmpfooterpos;
21076		} elseif (isset($this->footerpos[$topage])) {
21077			unset($this->footerpos[$topage]);
21078		}
21079		if (isset($tmpfooterlen)) {
21080			$this->footerlen[$topage] = $tmpfooterlen;
21081		} elseif (isset($this->footerlen[$topage])) {
21082			unset($this->footerlen[$topage]);
21083		}
21084		if (isset($tmptransfmrk)) {
21085			$this->transfmrk[$topage] = $tmptransfmrk;
21086		} elseif (isset($this->transfmrk[$topage])) {
21087			unset($this->transfmrk[$topage]);
21088		}
21089		if (isset($tmpannots)) {
21090			$this->PageAnnots[$topage] = $tmpannots;
21091		} elseif (isset($this->PageAnnots[$topage])) {
21092			unset($this->PageAnnots[$topage]);
21093		}
21094		// adjust outlines
21095		$tmpoutlines = $this->outlines;
21096		foreach ($tmpoutlines as $key => $outline) {
21097			if (!$outline['f']) {
21098				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21099					$this->outlines[$key]['p'] = ($outline['p'] + 1);
21100				} elseif ($outline['p'] == $frompage) {
21101					$this->outlines[$key]['p'] = $topage;
21102				}
21103			}
21104		}
21105		// adjust dests
21106		$tmpdests = $this->dests;
21107		foreach ($tmpdests as $key => $dest) {
21108			if (!$dest['f']) {
21109				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21110					$this->dests[$key]['p'] = ($dest['p'] + 1);
21111				} elseif ($dest['p'] == $frompage) {
21112					$this->dests[$key]['p'] = $topage;
21113				}
21114			}
21115		}
21116		// adjust links
21117		$tmplinks = $this->links;
21118		foreach ($tmplinks as $key => $link) {
21119			if (!$link['f']) {
21120				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21121					$this->links[$key]['p'] = ($link['p'] + 1);
21122				} elseif ($link['p'] == $frompage) {
21123					$this->links[$key]['p'] = $topage;
21124				}
21125			}
21126		}
21127		// adjust javascript
21128		$jfrompage = $frompage;
21129		$jtopage = $topage;
21130		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21131			foreach($pamatch[0] as $pk => $pmatch) {
21132				$pagenum = intval($pamatch[3][$pk]) + 1;
21133				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21134					$newpage = ($pagenum + 1);
21135				} elseif ($pagenum == $jfrompage) {
21136					$newpage = $jtopage;
21137				} else {
21138					$newpage = $pagenum;
21139				}
21140				--$newpage;
21141				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21142				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21143			}
21144			unset($pamatch);
21145		}
21146		// return to last page
21147		$this->lastPage(true);
21148		return true;
21149	}
21150
21151	/**
21152	 * Remove the specified page.
21153	 * @param $page (int) page to remove
21154	 * @return true in case of success, false in case of error.
21155	 * @public
21156	 * @since 4.6.004 (2009-04-23)
21157	 */
21158	public function deletePage($page) {
21159		if (($page < 1) OR ($page > $this->numpages)) {
21160			return false;
21161		}
21162		// delete current page
21163		unset($this->pages[$page]);
21164		unset($this->pagedim[$page]);
21165		unset($this->pagelen[$page]);
21166		unset($this->intmrk[$page]);
21167		unset($this->bordermrk[$page]);
21168		unset($this->cntmrk[$page]);
21169		foreach ($this->pageobjects[$page] as $oid) {
21170			if (isset($this->offsets[$oid])){
21171				unset($this->offsets[$oid]);
21172			}
21173		}
21174		unset($this->pageobjects[$page]);
21175		if (isset($this->footerpos[$page])) {
21176			unset($this->footerpos[$page]);
21177		}
21178		if (isset($this->footerlen[$page])) {
21179			unset($this->footerlen[$page]);
21180		}
21181		if (isset($this->transfmrk[$page])) {
21182			unset($this->transfmrk[$page]);
21183		}
21184		if (isset($this->PageAnnots[$page])) {
21185			unset($this->PageAnnots[$page]);
21186		}
21187		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21188			for ($i = $page; $i > 0; --$i) {
21189				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21190					--$this->pagegroups[$this->newpagegroup[$i]];
21191					break;
21192				}
21193			}
21194		}
21195		if (isset($this->pageopen[$page])) {
21196			unset($this->pageopen[$page]);
21197		}
21198		if ($page < $this->numpages) {
21199			// update remaining pages
21200			for ($i = $page; $i < $this->numpages; ++$i) {
21201				$j = $i + 1;
21202				// shift pages
21203				$this->setPageBuffer($i, $this->getPageBuffer($j));
21204				$this->pagedim[$i] = $this->pagedim[$j];
21205				$this->pagelen[$i] = $this->pagelen[$j];
21206				$this->intmrk[$i] = $this->intmrk[$j];
21207				$this->bordermrk[$i] = $this->bordermrk[$j];
21208				$this->cntmrk[$i] = $this->cntmrk[$j];
21209				$this->pageobjects[$i] = $this->pageobjects[$j];
21210				if (isset($this->footerpos[$j])) {
21211					$this->footerpos[$i] = $this->footerpos[$j];
21212				} elseif (isset($this->footerpos[$i])) {
21213					unset($this->footerpos[$i]);
21214				}
21215				if (isset($this->footerlen[$j])) {
21216					$this->footerlen[$i] = $this->footerlen[$j];
21217				} elseif (isset($this->footerlen[$i])) {
21218					unset($this->footerlen[$i]);
21219				}
21220				if (isset($this->transfmrk[$j])) {
21221					$this->transfmrk[$i] = $this->transfmrk[$j];
21222				} elseif (isset($this->transfmrk[$i])) {
21223					unset($this->transfmrk[$i]);
21224				}
21225				if (isset($this->PageAnnots[$j])) {
21226					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21227				} elseif (isset($this->PageAnnots[$i])) {
21228					unset($this->PageAnnots[$i]);
21229				}
21230				if (isset($this->newpagegroup[$j])) {
21231					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21232					unset($this->newpagegroup[$j]);
21233				}
21234				if ($this->currpagegroup == $j) {
21235					$this->currpagegroup = $i;
21236				}
21237				if (isset($this->pageopen[$j])) {
21238					$this->pageopen[$i] = $this->pageopen[$j];
21239				} elseif (isset($this->pageopen[$i])) {
21240					unset($this->pageopen[$i]);
21241				}
21242			}
21243			// remove last page
21244			unset($this->pages[$this->numpages]);
21245			unset($this->pagedim[$this->numpages]);
21246			unset($this->pagelen[$this->numpages]);
21247			unset($this->intmrk[$this->numpages]);
21248			unset($this->bordermrk[$this->numpages]);
21249			unset($this->cntmrk[$this->numpages]);
21250			foreach ($this->pageobjects[$this->numpages] as $oid) {
21251				if (isset($this->offsets[$oid])){
21252					unset($this->offsets[$oid]);
21253				}
21254			}
21255			unset($this->pageobjects[$this->numpages]);
21256			if (isset($this->footerpos[$this->numpages])) {
21257				unset($this->footerpos[$this->numpages]);
21258			}
21259			if (isset($this->footerlen[$this->numpages])) {
21260				unset($this->footerlen[$this->numpages]);
21261			}
21262			if (isset($this->transfmrk[$this->numpages])) {
21263				unset($this->transfmrk[$this->numpages]);
21264			}
21265			if (isset($this->PageAnnots[$this->numpages])) {
21266				unset($this->PageAnnots[$this->numpages]);
21267			}
21268			if (isset($this->newpagegroup[$this->numpages])) {
21269				unset($this->newpagegroup[$this->numpages]);
21270			}
21271			if ($this->currpagegroup == $this->numpages) {
21272				$this->currpagegroup = ($this->numpages - 1);
21273			}
21274			if (isset($this->pagegroups[$this->numpages])) {
21275				unset($this->pagegroups[$this->numpages]);
21276			}
21277			if (isset($this->pageopen[$this->numpages])) {
21278				unset($this->pageopen[$this->numpages]);
21279			}
21280		}
21281		--$this->numpages;
21282		$this->page = $this->numpages;
21283		// adjust outlines
21284		$tmpoutlines = $this->outlines;
21285		foreach ($tmpoutlines as $key => $outline) {
21286			if (!$outline['f']) {
21287				if ($outline['p'] > $page) {
21288					$this->outlines[$key]['p'] = $outline['p'] - 1;
21289				} elseif ($outline['p'] == $page) {
21290					unset($this->outlines[$key]);
21291				}
21292			}
21293		}
21294		// adjust dests
21295		$tmpdests = $this->dests;
21296		foreach ($tmpdests as $key => $dest) {
21297			if (!$dest['f']) {
21298				if ($dest['p'] > $page) {
21299					$this->dests[$key]['p'] = $dest['p'] - 1;
21300				} elseif ($dest['p'] == $page) {
21301					unset($this->dests[$key]);
21302				}
21303			}
21304		}
21305		// adjust links
21306		$tmplinks = $this->links;
21307		foreach ($tmplinks as $key => $link) {
21308			if (!$link['f']) {
21309				if ($link['p'] > $page) {
21310					$this->links[$key]['p'] = $link['p'] - 1;
21311				} elseif ($link['p'] == $page) {
21312					unset($this->links[$key]);
21313				}
21314			}
21315		}
21316		// adjust javascript
21317		$jpage = $page;
21318		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21319			foreach($pamatch[0] as $pk => $pmatch) {
21320				$pagenum = intval($pamatch[3][$pk]) + 1;
21321				if ($pagenum >= $jpage) {
21322					$newpage = ($pagenum - 1);
21323				} elseif ($pagenum == $jpage) {
21324					$newpage = 1;
21325				} else {
21326					$newpage = $pagenum;
21327				}
21328				--$newpage;
21329				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21330				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21331			}
21332			unset($pamatch);
21333		}
21334		// return to last page
21335		if ($this->numpages > 0) {
21336			$this->lastPage(true);
21337		}
21338		return true;
21339	}
21340
21341	/**
21342	 * Clone the specified page to a new page.
21343	 * @param $page (int) number of page to copy (0 = current page)
21344	 * @return true in case of success, false in case of error.
21345	 * @public
21346	 * @since 4.9.015 (2010-04-20)
21347	 */
21348	public function copyPage($page=0) {
21349		if ($page == 0) {
21350			// default value
21351			$page = $this->page;
21352		}
21353		if (($page < 1) OR ($page > $this->numpages)) {
21354			return false;
21355		}
21356		// close the last page
21357		$this->endPage();
21358		// copy all page-related states
21359		++$this->numpages;
21360		$this->page = $this->numpages;
21361		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21362		$this->pagedim[$this->page] = $this->pagedim[$page];
21363		$this->pagelen[$this->page] = $this->pagelen[$page];
21364		$this->intmrk[$this->page] = $this->intmrk[$page];
21365		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21366		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21367		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21368		$this->pageopen[$this->page] = false;
21369		if (isset($this->footerpos[$page])) {
21370			$this->footerpos[$this->page] = $this->footerpos[$page];
21371		}
21372		if (isset($this->footerlen[$page])) {
21373			$this->footerlen[$this->page] = $this->footerlen[$page];
21374		}
21375		if (isset($this->transfmrk[$page])) {
21376			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21377		}
21378		if (isset($this->PageAnnots[$page])) {
21379			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21380		}
21381		if (isset($this->newpagegroup[$page])) {
21382			// start a new group
21383			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21384			$this->currpagegroup = $this->newpagegroup[$this->page];
21385			$this->pagegroups[$this->currpagegroup] = 1;
21386		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21387			++$this->pagegroups[$this->currpagegroup];
21388		}
21389		// copy outlines
21390		$tmpoutlines = $this->outlines;
21391		foreach ($tmpoutlines as $key => $outline) {
21392			if ($outline['p'] == $page) {
21393				$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']);
21394			}
21395		}
21396		// copy links
21397		$tmplinks = $this->links;
21398		foreach ($tmplinks as $key => $link) {
21399			if ($link['p'] == $page) {
21400				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21401			}
21402		}
21403		// return to last page
21404		$this->lastPage(true);
21405		return true;
21406	}
21407
21408	/**
21409	 * Output a Table of Content Index (TOC).
21410	 * This method must be called after all Bookmarks were set.
21411	 * Before calling this method you have to open the page using the addTOCPage() method.
21412	 * After calling this method you have to call endTOCPage() to close the TOC page.
21413	 * You can override this method to achieve different styles.
21414	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21415	 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21416	 * @param $filler (string) string used to fill the space between text and page number.
21417	 * @param $toc_name (string) name to use for TOC bookmark.
21418	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21419	 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21420	 * @public
21421	 * @author Nicola Asuni
21422	 * @since 4.5.000 (2009-01-02)
21423	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21424	 */
21425	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21426		$fontsize = $this->FontSizePt;
21427		$fontfamily = $this->FontFamily;
21428		$fontstyle = $this->FontStyle;
21429		$w = $this->w - $this->lMargin - $this->rMargin;
21430		$spacer = $this->GetStringWidth(chr(32)) * 4;
21431		$lmargin = $this->lMargin;
21432		$rmargin = $this->rMargin;
21433		$x_start = $this->GetX();
21434		$page_first = $this->page;
21435		$current_page = $this->page;
21436		$page_fill_start = false;
21437		$page_fill_end = false;
21438		$current_column = $this->current_column;
21439		if (TCPDF_STATIC::empty_string($numbersfont)) {
21440			$numbersfont = $this->default_monospaced_font;
21441		}
21442		if (TCPDF_STATIC::empty_string($filler)) {
21443			$filler = ' ';
21444		}
21445		if (TCPDF_STATIC::empty_string($page)) {
21446			$gap = ' ';
21447		} else {
21448			$gap = '';
21449			if ($page < 1) {
21450				$page = 1;
21451			}
21452		}
21453		$this->SetFont($numbersfont, $fontstyle, $fontsize);
21454		$numwidth = $this->GetStringWidth('00000');
21455		$maxpage = 0; //used for pages on attached documents
21456		foreach ($this->outlines as $key => $outline) {
21457			// check for extra pages (used for attachments)
21458			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21459				$outline['p'] += ($this->page - $page_first);
21460			}
21461			if ($this->rtl) {
21462				$aligntext = 'R';
21463				$alignnum = 'L';
21464			} else {
21465				$aligntext = 'L';
21466				$alignnum = 'R';
21467			}
21468			if ($outline['l'] == 0) {
21469				$this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21470			} else {
21471				$this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21472			}
21473			$this->SetTextColorArray($outline['c']);
21474			// check for page break
21475			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21476			// set margins and X position
21477			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21478				$this->lMargin = $lmargin;
21479				$this->rMargin = $rmargin;
21480			} else {
21481				if ($this->current_column != $current_column) {
21482					if ($this->rtl) {
21483						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21484					} else {
21485						$x_start = $this->columns[$this->current_column]['x'];
21486					}
21487				}
21488				$lmargin = $this->lMargin;
21489				$rmargin = $this->rMargin;
21490				$current_page = $this->page;
21491				$current_column = $this->current_column;
21492			}
21493			$this->SetX($x_start);
21494			$indent = ($spacer * $outline['l']);
21495			if ($this->rtl) {
21496				$this->x -= $indent;
21497				$this->rMargin = $this->w - $this->x;
21498			} else {
21499				$this->x += $indent;
21500				$this->lMargin = $this->x;
21501			}
21502			$link = $this->AddLink();
21503			$this->SetLink($link, $outline['y'], $outline['p']);
21504			// write the text
21505			if ($this->rtl) {
21506				$txt = ' '.$outline['t'];
21507			} else {
21508				$txt = $outline['t'].' ';
21509			}
21510			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21511			if ($this->rtl) {
21512				$tw = $this->x - $this->lMargin;
21513			} else {
21514				$tw = $this->w - $this->rMargin - $this->x;
21515			}
21516			$this->SetFont($numbersfont, $fontstyle, $fontsize);
21517			if (TCPDF_STATIC::empty_string($page)) {
21518				$pagenum = $outline['p'];
21519			} else {
21520				// placemark to be replaced with the correct number
21521				$pagenum = '{#'.($outline['p']).'}';
21522				if ($this->isUnicodeFont()) {
21523					$pagenum = '{'.$pagenum.'}';
21524				}
21525				$maxpage = max($maxpage, $outline['p']);
21526			}
21527			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21528			$wfiller = $this->GetStringWidth($filler);
21529			if ($wfiller > 0) {
21530				$numfills = floor($fw / $wfiller);
21531			} else {
21532				$numfills = 0;
21533			}
21534			if ($numfills > 0) {
21535				$rowfill = str_repeat($filler, $numfills);
21536			} else {
21537				$rowfill = '';
21538			}
21539			if ($this->rtl) {
21540				$pagenum = $pagenum.$gap.$rowfill;
21541			} else {
21542				$pagenum = $rowfill.$gap.$pagenum;
21543			}
21544			// write the number
21545			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21546		}
21547		$page_last = $this->getPage();
21548		$numpages = ($page_last - $page_first + 1);
21549		// account for booklet mode
21550		if ($this->booklet) {
21551			// check if a blank page is required before TOC
21552			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21553			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21554			if ($page_fill_start) {
21555				// add a page at the end (to be moved before TOC)
21556				$this->addPage();
21557				++$page_last;
21558				++$numpages;
21559			}
21560			if ($page_fill_end) {
21561				// add a page at the end
21562				$this->addPage();
21563				++$page_last;
21564				++$numpages;
21565			}
21566		}
21567		$maxpage = max($maxpage, $page_last);
21568		if (!TCPDF_STATIC::empty_string($page)) {
21569			for ($p = $page_first; $p <= $page_last; ++$p) {
21570				// get page data
21571				$temppage = $this->getPageBuffer($p);
21572				for ($n = 1; $n <= $maxpage; ++$n) {
21573					// update page numbers
21574					$a = '{#'.$n.'}';
21575					// get page number aliases
21576					$pnalias = $this->getInternalPageNumberAliases($a);
21577					// calculate replacement number
21578					if (($n >= $page) AND ($n <= $this->numpages)) {
21579						$np = $n + $numpages;
21580					} else {
21581						$np = $n;
21582					}
21583					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21584					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21585					// replace aliases with numbers
21586					foreach ($pnalias['u'] as $u) {
21587						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21588						if ($this->rtl) {
21589							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21590						} else {
21591							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21592						}
21593						$temppage = str_replace($u, $nr, $temppage);
21594					}
21595					foreach ($pnalias['a'] as $a) {
21596						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21597						if ($this->rtl) {
21598							$nr = $na.' '.$sfill;
21599						} else {
21600							$nr = $sfill.' '.$na;
21601						}
21602						$temppage = str_replace($a, $nr, $temppage);
21603					}
21604				}
21605				// save changes
21606				$this->setPageBuffer($p, $temppage);
21607			}
21608			// move pages
21609			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21610			if ($page_fill_start) {
21611				$this->movePage($page_last, $page_first);
21612			}
21613			for ($i = 0; $i < $numpages; ++$i) {
21614				$this->movePage($page_last, $page);
21615			}
21616		}
21617	}
21618
21619	/**
21620	 * Output a Table Of Content Index (TOC) using HTML templates.
21621	 * This method must be called after all Bookmarks were set.
21622	 * Before calling this method you have to open the page using the addTOCPage() method.
21623	 * After calling this method you have to call endTOCPage() to close the TOC page.
21624	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21625	 * @param $toc_name (string) name to use for TOC bookmark.
21626	 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21627	 * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21628	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21629	 * @param $color (array) RGB color array for title (values from 0 to 255).
21630	 * @public
21631	 * @author Nicola Asuni
21632	 * @since 5.0.001 (2010-05-06)
21633	 * @see addTOCPage(), endTOCPage(), addTOC()
21634	 */
21635	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21636		$filler = ' ';
21637		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21638		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21639		// set new style for link
21640		$this->htmlLinkColorArray = array();
21641		$this->htmlLinkFontStyle = '';
21642		$page_first = $this->getPage();
21643		$page_fill_start = false;
21644		$page_fill_end = false;
21645		// get the font type used for numbers in each template
21646		$current_font = $this->FontFamily;
21647		foreach ($templates as $level => $html) {
21648			$dom = $this->getHtmlDomArray($html);
21649			foreach ($dom as $key => $value) {
21650				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21651					$this->SetFont($dom[($key - 1)]['fontname']);
21652					$templates['F'.$level] = $this->isUnicodeFont();
21653				}
21654			}
21655		}
21656		$this->SetFont($current_font);
21657		$maxpage = 0; //used for pages on attached documents
21658		foreach ($this->outlines as $key => $outline) {
21659			// get HTML template
21660			$row = $templates[$outline['l']];
21661			if (TCPDF_STATIC::empty_string($page)) {
21662				$pagenum = $outline['p'];
21663			} else {
21664				// placemark to be replaced with the correct number
21665				$pagenum = '{#'.($outline['p']).'}';
21666				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21667					$pagenum = '{'.$pagenum.'}';
21668				}
21669				$maxpage = max($maxpage, $outline['p']);
21670			}
21671			// replace templates with current values
21672			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21673			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21674			// add link to page
21675			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21676			// write bookmark entry
21677			$this->writeHTML($row, false, false, true, false, '');
21678		}
21679		// restore link styles
21680		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21681		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21682		// move TOC page and replace numbers
21683		$page_last = $this->getPage();
21684		$numpages = ($page_last - $page_first + 1);
21685		// account for booklet mode
21686		if ($this->booklet) {
21687			// check if a blank page is required before TOC
21688			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21689			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21690			if ($page_fill_start) {
21691				// add a page at the end (to be moved before TOC)
21692				$this->addPage();
21693				++$page_last;
21694				++$numpages;
21695			}
21696			if ($page_fill_end) {
21697				// add a page at the end
21698				$this->addPage();
21699				++$page_last;
21700				++$numpages;
21701			}
21702		}
21703		$maxpage = max($maxpage, $page_last);
21704		if (!TCPDF_STATIC::empty_string($page)) {
21705			for ($p = $page_first; $p <= $page_last; ++$p) {
21706				// get page data
21707				$temppage = $this->getPageBuffer($p);
21708				for ($n = 1; $n <= $maxpage; ++$n) {
21709					// update page numbers
21710					$a = '{#'.$n.'}';
21711					// get page number aliases
21712					$pnalias = $this->getInternalPageNumberAliases($a);
21713					// calculate replacement number
21714					if ($n >= $page) {
21715						$np = $n + $numpages;
21716					} else {
21717						$np = $n;
21718					}
21719					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21720					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21721					// replace aliases with numbers
21722					foreach ($pnalias['u'] as $u) {
21723						if ($correct_align) {
21724							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21725							if ($this->rtl) {
21726								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21727							} else {
21728								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21729							}
21730						} else {
21731							$nr = $nu;
21732						}
21733						$temppage = str_replace($u, $nr, $temppage);
21734					}
21735					foreach ($pnalias['a'] as $a) {
21736						if ($correct_align) {
21737							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21738							if ($this->rtl) {
21739								$nr = $na.' '.$sfill;
21740							} else {
21741								$nr = $sfill.' '.$na;
21742							}
21743						} else {
21744							$nr = $na;
21745						}
21746						$temppage = str_replace($a, $nr, $temppage);
21747					}
21748				}
21749				// save changes
21750				$this->setPageBuffer($p, $temppage);
21751			}
21752			// move pages
21753			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21754			if ($page_fill_start) {
21755				$this->movePage($page_last, $page_first);
21756			}
21757			for ($i = 0; $i < $numpages; ++$i) {
21758				$this->movePage($page_last, $page);
21759			}
21760		}
21761	}
21762
21763	/**
21764	 * Stores a copy of the current TCPDF object used for undo operation.
21765	 * @public
21766	 * @since 4.5.029 (2009-03-19)
21767	 */
21768	public function startTransaction() {
21769		if (isset($this->objcopy)) {
21770			// remove previous copy
21771			$this->commitTransaction();
21772		}
21773		// record current page number and Y position
21774		$this->start_transaction_page = $this->page;
21775		$this->start_transaction_y = $this->y;
21776		// clone current object
21777		$this->objcopy = TCPDF_STATIC::objclone($this);
21778	}
21779
21780	/**
21781	 * Delete the copy of the current TCPDF object used for undo operation.
21782	 * @public
21783	 * @since 4.5.029 (2009-03-19)
21784	 */
21785	public function commitTransaction() {
21786		if (isset($this->objcopy)) {
21787			$this->objcopy->_destroy(true, true);
21788			/* The unique file_id should not be used during cleanup again */
21789			$this->objcopy->file_id = NULL;
21790			unset($this->objcopy);
21791		}
21792	}
21793
21794	/**
21795	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21796	 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21797	 * @return TCPDF object.
21798	 * @public
21799	 * @since 4.5.029 (2009-03-19)
21800	 */
21801	public function rollbackTransaction($self=false) {
21802		if (isset($this->objcopy)) {
21803			$objcopy = $this->objcopy;
21804			$this->_destroy(true, true);
21805			if ($self) {
21806				$objvars = get_object_vars($objcopy);
21807				foreach ($objvars as $key => $value) {
21808					$this->$key = $value;
21809				}
21810				$objcopy->_destroy(true, true);
21811				/* The unique file_id should not be used during cleanup again */
21812				$objcopy->file_id = NULL;
21813				unset($objcopy);
21814				return $this;
21815			}
21816			/* The unique file_id should not be used during cleanup again */
21817			$this->file_id = NULL;
21818			return $objcopy;
21819		}
21820		return $this;
21821	}
21822
21823	// --- MULTI COLUMNS METHODS -----------------------
21824
21825	/**
21826	 * Set multiple columns of the same size
21827	 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21828	 * @param $width (int) column width
21829	 * @param $y (int) column starting Y position (leave empty for current Y position)
21830	 * @public
21831	 * @since 4.9.001 (2010-03-28)
21832	 */
21833	public function setEqualColumns($numcols=0, $width=0, $y='') {
21834		$this->columns = array();
21835		if ($numcols < 2) {
21836			$numcols = 0;
21837			$this->columns = array();
21838		} else {
21839			// maximum column width
21840			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21841			if (($width == 0) OR ($width > $maxwidth)) {
21842				$width = $maxwidth;
21843			}
21844			if (TCPDF_STATIC::empty_string($y)) {
21845				$y = $this->y;
21846			}
21847			// space between columns
21848			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21849			// fill the columns array (with, space, starting Y position)
21850			for ($i = 0; $i < $numcols; ++$i) {
21851				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21852			}
21853		}
21854		$this->num_columns = $numcols;
21855		$this->current_column = 0;
21856		$this->column_start_page = $this->page;
21857		$this->selectColumn(0);
21858	}
21859
21860	/**
21861	 * Remove columns and reset page margins.
21862	 * @public
21863	 * @since 5.9.072 (2011-04-26)
21864	 */
21865	public function resetColumns() {
21866		$this->lMargin = $this->original_lMargin;
21867		$this->rMargin = $this->original_rMargin;
21868		$this->setEqualColumns();
21869	}
21870
21871	/**
21872	 * Set columns array.
21873	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21874	 * @param $columns (array)
21875	 * @public
21876	 * @since 4.9.001 (2010-03-28)
21877	 */
21878	public function setColumnsArray($columns) {
21879		$this->columns = $columns;
21880		$this->num_columns = count($columns);
21881		$this->current_column = 0;
21882		$this->column_start_page = $this->page;
21883		$this->selectColumn(0);
21884	}
21885
21886	/**
21887	 * Set position at a given column
21888	 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21889	 * @public
21890	 * @since 4.9.001 (2010-03-28)
21891	 */
21892	public function selectColumn($col='') {
21893		if (is_string($col)) {
21894			$col = $this->current_column;
21895		} elseif ($col >= $this->num_columns) {
21896			$col = 0;
21897		}
21898		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21899		$enable_thead = false;
21900		if ($this->num_columns > 1) {
21901			if ($col != $this->current_column) {
21902				// move Y pointer at the top of the column
21903				if ($this->column_start_page == $this->page) {
21904					$this->y = $this->columns[$col]['y'];
21905				} else {
21906					$this->y = $this->tMargin;
21907				}
21908				// Avoid to write table headers more than once
21909				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21910					$enable_thead = true;
21911					$this->maxselcol['page'] = $this->page;
21912					$this->maxselcol['column'] = $col;
21913				}
21914			}
21915			$xshift = $this->colxshift;
21916			// set X position of the current column by case
21917			$listindent = ($this->listindentlevel * $this->listindent);
21918			// calculate column X position
21919			$colpos = 0;
21920			for ($i = 0; $i < $col; ++$i) {
21921				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21922			}
21923			if ($this->rtl) {
21924				$x = $this->w - $this->original_rMargin - $colpos;
21925				$this->rMargin = ($this->w - $x + $listindent);
21926				$this->lMargin = ($x - $this->columns[$col]['w']);
21927				$this->x = $x - $listindent;
21928			} else {
21929				$x = $this->original_lMargin + $colpos;
21930				$this->lMargin = ($x + $listindent);
21931				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21932				$this->x = $x + $listindent;
21933			}
21934			$this->columns[$col]['x'] = $x;
21935		}
21936		$this->current_column = $col;
21937		// fix for HTML mode
21938		$this->newline = true;
21939		// print HTML table header (if any)
21940		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21941			if ($enable_thead) {
21942				// print table header
21943				$this->writeHTML($this->thead, false, false, false, false, '');
21944				$this->y += $xshift['s']['V'];
21945				// store end of header position
21946				if (!isset($this->columns[$col]['th'])) {
21947					$this->columns[$col]['th'] = array();
21948				}
21949				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21950				$this->lasth = 0;
21951			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21952				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21953			}
21954		}
21955		// account for an html table cell over multiple columns
21956		if ($this->rtl) {
21957			$this->rMargin += $xshift['x'];
21958			$this->x -= ($xshift['x'] + $xshift['p']['R']);
21959		} else {
21960			$this->lMargin += $xshift['x'];
21961			$this->x += $xshift['x'] + $xshift['p']['L'];
21962		}
21963	}
21964
21965	/**
21966	 * Return the current column number
21967	 * @return int current column number
21968	 * @public
21969	 * @since 5.5.011 (2010-07-08)
21970	 */
21971	public function getColumn() {
21972		return $this->current_column;
21973	}
21974
21975	/**
21976	 * Return the current number of columns.
21977	 * @return int number of columns
21978	 * @public
21979	 * @since 5.8.018 (2010-08-25)
21980	 */
21981	public function getNumberOfColumns() {
21982		return $this->num_columns;
21983	}
21984
21985	/**
21986	 * Set Text rendering mode.
21987	 * @param $stroke (int) outline size in user units (0 = disable).
21988	 * @param $fill (boolean) if true fills the text (default).
21989	 * @param $clip (boolean) if true activate clipping mode
21990	 * @public
21991	 * @since 4.9.008 (2009-04-02)
21992	 */
21993	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21994		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21995		// convert text rendering parameters
21996		if ($stroke < 0) {
21997			$stroke = 0;
21998		}
21999		if ($fill === true) {
22000			if ($stroke > 0) {
22001				if ($clip === true) {
22002					// Fill, then stroke text and add to path for clipping
22003					$textrendermode = 6;
22004				} else {
22005					// Fill, then stroke text
22006					$textrendermode = 2;
22007				}
22008				$textstrokewidth = $stroke;
22009			} else {
22010				if ($clip === true) {
22011					// Fill text and add to path for clipping
22012					$textrendermode = 4;
22013				} else {
22014					// Fill text
22015					$textrendermode = 0;
22016				}
22017			}
22018		} else {
22019			if ($stroke > 0) {
22020				if ($clip === true) {
22021					// Stroke text and add to path for clipping
22022					$textrendermode = 5;
22023				} else {
22024					// Stroke text
22025					$textrendermode = 1;
22026				}
22027				$textstrokewidth = $stroke;
22028			} else {
22029				if ($clip === true) {
22030					// Add text to path for clipping
22031					$textrendermode = 7;
22032				} else {
22033					// Neither fill nor stroke text (invisible)
22034					$textrendermode = 3;
22035				}
22036			}
22037		}
22038		$this->textrendermode = $textrendermode;
22039		$this->textstrokewidth = $stroke;
22040	}
22041
22042	/**
22043	 * Set parameters for drop shadow effect for text.
22044	 * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
22045	 * @since 5.9.174 (2012-07-25)
22046	 * @public
22047	*/
22048	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22049		if (isset($params['enabled'])) {
22050			$this->txtshadow['enabled'] = $params['enabled']?true:false;
22051		} else {
22052			$this->txtshadow['enabled'] = false;
22053		}
22054		if (isset($params['depth_w'])) {
22055			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
22056		} else {
22057			$this->txtshadow['depth_w'] = 0;
22058		}
22059		if (isset($params['depth_h'])) {
22060			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
22061		} else {
22062			$this->txtshadow['depth_h'] = 0;
22063		}
22064		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22065			$this->txtshadow['color'] = $params['color'];
22066		} else {
22067			$this->txtshadow['color'] = $this->strokecolor;
22068		}
22069		if (isset($params['opacity'])) {
22070			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22071		} else {
22072			$this->txtshadow['opacity'] = 1;
22073		}
22074		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'))) {
22075			$this->txtshadow['blend_mode'] = $params['blend_mode'];
22076		} else {
22077			$this->txtshadow['blend_mode'] = 'Normal';
22078		}
22079		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22080			$this->txtshadow['enabled'] = false;
22081		}
22082	}
22083
22084	/**
22085	 * Return the text shadow parameters array.
22086	 * @return Array of parameters.
22087	 * @since 5.9.174 (2012-07-25)
22088	 * @public
22089	 */
22090	public function getTextShadow() {
22091		return $this->txtshadow;
22092	}
22093
22094	/**
22095	 * Returns an array of chars containing soft hyphens.
22096	 * @param $word (array) array of chars
22097	 * @param $patterns (array) Array of hypenation patterns.
22098	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22099	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22100	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22101	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22102	 * @param $charmax (int) Maximum length of broken piece of word.
22103	 * @return array text with soft hyphens
22104	 * @author Nicola Asuni
22105	 * @since 4.9.012 (2010-04-12)
22106	 * @protected
22107	 */
22108	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22109		$hyphenword = array(); // hyphens positions
22110		$numchars = count($word);
22111		if ($numchars <= $charmin) {
22112			return $word;
22113		}
22114		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22115		// some words will be returned as-is
22116		$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})(\]?)$/';
22117		if (preg_match($pattern, $word_string) > 0) {
22118			// email
22119			return $word;
22120		}
22121		$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})(\]?)$/';
22122		if (preg_match($pattern, $word_string) > 0) {
22123			// URL
22124			return $word;
22125		}
22126		if (isset($dictionary[$word_string])) {
22127			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22128		}
22129		// surround word with '_' characters
22130		$tmpword = array_merge(array(46), $word, array(46));
22131		$tmpnumchars = $numchars + 2;
22132		$maxpos = $tmpnumchars - 1;
22133		for ($pos = 0; $pos < $maxpos; ++$pos) {
22134			$imax = min(($tmpnumchars - $pos), $charmax);
22135			for ($i = 1; $i <= $imax; ++$i) {
22136				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22137				if (isset($patterns[$subword])) {
22138					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22139					$pattern_length = count($pattern);
22140					$digits = 1;
22141					for ($j = 0; $j < $pattern_length; ++$j) {
22142						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22143						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22144							if ($j == 0) {
22145								$zero = $pos - 1;
22146							} else {
22147								$zero = $pos + $j - $digits;
22148							}
22149							// get hyphenation level
22150							$level = ($pattern[$j] - 48);
22151							// if two levels from two different patterns match at the same point, the higher one is selected.
22152							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22153								$hyphenword[$zero] = $level;
22154							}
22155							++$digits;
22156						}
22157					}
22158				}
22159			}
22160		}
22161		$inserted = 0;
22162		$maxpos = $numchars - $rightmin;
22163		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22164			// only odd levels indicate allowed hyphenation points
22165			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22166				// 173 = soft hyphen character
22167				array_splice($word, $i + $inserted, 0, 173);
22168				++$inserted;
22169			}
22170		}
22171		return $word;
22172	}
22173
22174	/**
22175	 * Returns text with soft hyphens.
22176	 * @param $text (string) text to process
22177	 * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22178	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22179	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22180	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22181	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22182	 * @param $charmax (int) Maximum length of broken piece of word.
22183	 * @return array text with soft hyphens
22184	 * @author Nicola Asuni
22185	 * @since 4.9.012 (2010-04-12)
22186	 * @public
22187	 */
22188	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22189		$text = $this->unhtmlentities($text);
22190		$word = array(); // last word
22191		$txtarr = array(); // text to be returned
22192		$intag = false; // true if we are inside an HTML tag
22193		$skip = false; // true to skip hyphenation
22194		if (!is_array($patterns)) {
22195			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22196		}
22197		// get array of characters
22198		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22199		// for each char
22200		foreach ($unichars as $char) {
22201			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22202				// letter character
22203				$word[] = $char;
22204			} else {
22205				// other type of character
22206				if (!TCPDF_STATIC::empty_string($word)) {
22207					// hypenate the word
22208					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22209					$word = array();
22210				}
22211				$txtarr[] = $char;
22212				if (chr($char) == '<') {
22213					// we are inside an HTML tag
22214					$intag = true;
22215				} elseif ($intag AND (chr($char) == '>')) {
22216					// end of HTML tag
22217					$intag = false;
22218					// check for style tag
22219					$expected = array(115, 116, 121, 108, 101); // = 'style'
22220					$current = array_slice($txtarr, -6, 5); // last 5 chars
22221					$compare = array_diff($expected, $current);
22222					if (empty($compare)) {
22223						// check if it is a closing tag
22224						$expected = array(47); // = '/'
22225						$current = array_slice($txtarr, -7, 1);
22226						$compare = array_diff($expected, $current);
22227						if (empty($compare)) {
22228							// closing style tag
22229							$skip = false;
22230						} else {
22231							// opening style tag
22232							$skip = true;
22233						}
22234					}
22235				}
22236			}
22237		}
22238		if (!TCPDF_STATIC::empty_string($word)) {
22239			// hypenate the word
22240			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22241		}
22242		// convert char array to string and return
22243		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22244	}
22245
22246	/**
22247	 * Enable/disable rasterization of vector images using ImageMagick library.
22248	 * @param $mode (boolean) if true enable rasterization, false otherwise.
22249	 * @public
22250	 * @since 5.0.000 (2010-04-27)
22251	 */
22252	public function setRasterizeVectorImages($mode) {
22253		$this->rasterize_vector_images = $mode;
22254	}
22255
22256	/**
22257	 * Enable or disable default option for font subsetting.
22258	 * @param $enable (boolean) if true enable font subsetting by default.
22259	 * @author Nicola Asuni
22260	 * @public
22261	 * @since 5.3.002 (2010-06-07)
22262	 */
22263	public function setFontSubsetting($enable=true) {
22264		if ($this->pdfa_mode) {
22265			$this->font_subsetting = false;
22266		} else {
22267			$this->font_subsetting = $enable ? true : false;
22268		}
22269	}
22270
22271	/**
22272	 * Return the default option for font subsetting.
22273	 * @return boolean default font subsetting state.
22274	 * @author Nicola Asuni
22275	 * @public
22276	 * @since 5.3.002 (2010-06-07)
22277	 */
22278	public function getFontSubsetting() {
22279		return $this->font_subsetting;
22280	}
22281
22282	/**
22283	 * Left trim the input string
22284	 * @param $str (string) string to trim
22285	 * @param $replace (string) string that replace spaces.
22286	 * @return left trimmed string
22287	 * @author Nicola Asuni
22288	 * @public
22289	 * @since 5.8.000 (2010-08-11)
22290	 */
22291	public function stringLeftTrim($str, $replace='') {
22292		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22293	}
22294
22295	/**
22296	 * Right trim the input string
22297	 * @param $str (string) string to trim
22298	 * @param $replace (string) string that replace spaces.
22299	 * @return right trimmed string
22300	 * @author Nicola Asuni
22301	 * @public
22302	 * @since 5.8.000 (2010-08-11)
22303	 */
22304	public function stringRightTrim($str, $replace='') {
22305		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22306	}
22307
22308	/**
22309	 * Trim the input string
22310	 * @param $str (string) string to trim
22311	 * @param $replace (string) string that replace spaces.
22312	 * @return trimmed string
22313	 * @author Nicola Asuni
22314	 * @public
22315	 * @since 5.8.000 (2010-08-11)
22316	 */
22317	public function stringTrim($str, $replace='') {
22318		$str = $this->stringLeftTrim($str, $replace);
22319		$str = $this->stringRightTrim($str, $replace);
22320		return $str;
22321	}
22322
22323	/**
22324	 * Return true if the current font is unicode type.
22325	 * @return true for unicode font, false otherwise.
22326	 * @author Nicola Asuni
22327	 * @public
22328	 * @since 5.8.002 (2010-08-14)
22329	 */
22330	public function isUnicodeFont() {
22331		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22332	}
22333
22334	/**
22335	 * Return normalized font name
22336	 * @param $fontfamily (string) property string containing font family names
22337	 * @return string normalized font name
22338	 * @author Nicola Asuni
22339	 * @public
22340	 * @since 5.8.004 (2010-08-17)
22341	 */
22342	public function getFontFamilyName($fontfamily) {
22343		// remove spaces and symbols
22344		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22345		// extract all font names
22346		$fontslist = preg_split('/[,]/', $fontfamily);
22347		// find first valid font name
22348		foreach ($fontslist as $font) {
22349			// replace font variations
22350			$font = preg_replace('/regular$/', '', $font);
22351			$font = preg_replace('/italic$/', 'I', $font);
22352			$font = preg_replace('/oblique$/', 'I', $font);
22353			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22354			// replace common family names and core fonts
22355			$pattern = array();
22356			$replacement = array();
22357			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22358			$replacement[] = 'times';
22359			$pattern[] = '/^sansserif/';
22360			$replacement[] = 'helvetica';
22361			$pattern[] = '/^monospace/';
22362			$replacement[] = 'courier';
22363			$font = preg_replace($pattern, $replacement, $font);
22364			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22365				return $font;
22366			}
22367		}
22368		// return current font as default
22369		return $this->CurrentFont['fontkey'];
22370	}
22371
22372	/**
22373	 * Start a new XObject Template.
22374	 * 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).
22375	 * 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.
22376	 * Note: X,Y coordinates will be reset to 0,0.
22377	 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22378	 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22379	 * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22380	 * @return int the XObject Template ID in case of success or false in case of error.
22381	 * @author Nicola Asuni
22382	 * @public
22383	 * @since 5.8.017 (2010-08-24)
22384	 * @see endTemplate(), printTemplate()
22385	 */
22386	public function startTemplate($w=0, $h=0, $group=false) {
22387		if ($this->inxobj) {
22388			// we are already inside an XObject template
22389			return false;
22390		}
22391		$this->inxobj = true;
22392		++$this->n;
22393		// XObject ID
22394		$this->xobjid = 'XT'.$this->n;
22395		// object ID
22396		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22397		// store current graphic state
22398		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22399		// initialize data
22400		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22401		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22402		$this->xobjects[$this->xobjid]['outdata'] = '';
22403		$this->xobjects[$this->xobjid]['xobjects'] = array();
22404		$this->xobjects[$this->xobjid]['images'] = array();
22405		$this->xobjects[$this->xobjid]['fonts'] = array();
22406		$this->xobjects[$this->xobjid]['annotations'] = array();
22407		$this->xobjects[$this->xobjid]['extgstates'] = array();
22408		$this->xobjects[$this->xobjid]['gradients'] = array();
22409		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22410		// set new environment
22411		$this->num_columns = 1;
22412		$this->current_column = 0;
22413		$this->SetAutoPageBreak(false);
22414		if (($w === '') OR ($w <= 0)) {
22415			$w = $this->w - $this->lMargin - $this->rMargin;
22416		}
22417		if (($h === '') OR ($h <= 0)) {
22418			$h = $this->h - $this->tMargin - $this->bMargin;
22419		}
22420		$this->xobjects[$this->xobjid]['x'] = 0;
22421		$this->xobjects[$this->xobjid]['y'] = 0;
22422		$this->xobjects[$this->xobjid]['w'] = $w;
22423		$this->xobjects[$this->xobjid]['h'] = $h;
22424		$this->w = $w;
22425		$this->h = $h;
22426		$this->wPt = $this->w * $this->k;
22427		$this->hPt = $this->h * $this->k;
22428		$this->fwPt = $this->wPt;
22429		$this->fhPt = $this->hPt;
22430		$this->x = 0;
22431		$this->y = 0;
22432		$this->lMargin = 0;
22433		$this->rMargin = 0;
22434		$this->tMargin = 0;
22435		$this->bMargin = 0;
22436		// set group mode
22437		$this->xobjects[$this->xobjid]['group'] = $group;
22438		return $this->xobjid;
22439	}
22440
22441	/**
22442	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22443	 * 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).
22444	 * 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.
22445	 * @return int the XObject Template ID in case of success or false in case of error.
22446	 * @author Nicola Asuni
22447	 * @public
22448	 * @since 5.8.017 (2010-08-24)
22449	 * @see startTemplate(), printTemplate()
22450	 */
22451	public function endTemplate() {
22452		if (!$this->inxobj) {
22453			// we are not inside a template
22454			return false;
22455		}
22456		$this->inxobj = false;
22457		// restore previous graphic state
22458		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22459		return $this->xobjid;
22460	}
22461
22462	/**
22463	 * Print an XObject Template.
22464	 * You can print an XObject Template inside the currently opened Template.
22465	 * 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).
22466	 * 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.
22467	 * @param $id (string) The ID of XObject Template to print.
22468	 * @param $x (int) X position in user units (empty string = current x position)
22469	 * @param $y (int) Y position in user units (empty string = current y position)
22470	 * @param $w (int) Width in user units (zero = remaining page width)
22471	 * @param $h (int) Height in user units (zero = remaining page height)
22472	 * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22473	 * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22474	 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22475	 * @author Nicola Asuni
22476	 * @public
22477	 * @since 5.8.017 (2010-08-24)
22478	 * @see startTemplate(), endTemplate()
22479	 */
22480	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22481		if ($this->state != 2) {
22482			 return;
22483		}
22484		if (!isset($this->xobjects[$id])) {
22485			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22486		}
22487		if ($this->inxobj) {
22488			if ($id == $this->xobjid) {
22489				// close current template
22490				$this->endTemplate();
22491			} else {
22492				// use the template as resource for the template currently opened
22493				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22494			}
22495		}
22496		// set default values
22497		if ($x === '') {
22498			$x = $this->x;
22499		}
22500		if ($y === '') {
22501			$y = $this->y;
22502		}
22503		// check page for no-write regions and adapt page margins if necessary
22504		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22505		$ow = $this->xobjects[$id]['w'];
22506		if ($ow <= 0) {
22507			$ow = 1;
22508		}
22509		$oh = $this->xobjects[$id]['h'];
22510		if ($oh <= 0) {
22511			$oh = 1;
22512		}
22513		// calculate template width and height on document
22514		if (($w <= 0) AND ($h <= 0)) {
22515			$w = $ow;
22516			$h = $oh;
22517		} elseif ($w <= 0) {
22518			$w = $h * $ow / $oh;
22519		} elseif ($h <= 0) {
22520			$h = $w * $oh / $ow;
22521		}
22522		// fit the template on available space
22523		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22524		// set page alignment
22525		$rb_y = $y + $h;
22526		// set alignment
22527		if ($this->rtl) {
22528			if ($palign == 'L') {
22529				$xt = $this->lMargin;
22530			} elseif ($palign == 'C') {
22531				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22532			} elseif ($palign == 'R') {
22533				$xt = $this->w - $this->rMargin - $w;
22534			} else {
22535				$xt = $x - $w;
22536			}
22537			$rb_x = $xt;
22538		} else {
22539			if ($palign == 'L') {
22540				$xt = $this->lMargin;
22541			} elseif ($palign == 'C') {
22542				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22543			} elseif ($palign == 'R') {
22544				$xt = $this->w - $this->rMargin - $w;
22545			} else {
22546				$xt = $x;
22547			}
22548			$rb_x = $xt + $w;
22549		}
22550		// print XObject Template + Transformation matrix
22551		$this->StartTransform();
22552		// translate and scale
22553		$sx = ($w / $ow);
22554		$sy = ($h / $oh);
22555		$tm = array();
22556		$tm[0] = $sx;
22557		$tm[1] = 0;
22558		$tm[2] = 0;
22559		$tm[3] = $sy;
22560		$tm[4] = $xt * $this->k;
22561		$tm[5] = ($this->h - $h - $y) * $this->k;
22562		$this->Transform($tm);
22563		// set object
22564		$this->_out('/'.$id.' Do');
22565		$this->StopTransform();
22566		// add annotations
22567		if (!empty($this->xobjects[$id]['annotations'])) {
22568			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22569				// transform original coordinates
22570				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22571				$ax = ($coordlt[4] / $this->k);
22572				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22573				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22574				$aw = ($coordrb[4] / $this->k) - $ax;
22575				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22576				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22577			}
22578		}
22579		// set pointer to align the next text/objects
22580		switch($align) {
22581			case 'T': {
22582				$this->y = $y;
22583				$this->x = $rb_x;
22584				break;
22585			}
22586			case 'M': {
22587				$this->y = $y + round($h/2);
22588				$this->x = $rb_x;
22589				break;
22590			}
22591			case 'B': {
22592				$this->y = $rb_y;
22593				$this->x = $rb_x;
22594				break;
22595			}
22596			case 'N': {
22597				$this->SetY($rb_y);
22598				break;
22599			}
22600			default:{
22601				break;
22602			}
22603		}
22604	}
22605
22606	/**
22607	 * Set the percentage of character stretching.
22608	 * @param $perc (int) percentage of stretching (100 = no stretching)
22609	 * @author Nicola Asuni
22610	 * @public
22611	 * @since 5.9.000 (2010-09-29)
22612	 */
22613	public function setFontStretching($perc=100) {
22614		$this->font_stretching = $perc;
22615	}
22616
22617	/**
22618	 * Get the percentage of character stretching.
22619	 * @return float stretching value
22620	 * @author Nicola Asuni
22621	 * @public
22622	 * @since 5.9.000 (2010-09-29)
22623	 */
22624	public function getFontStretching() {
22625		return $this->font_stretching;
22626	}
22627
22628	/**
22629	 * Set the amount to increase or decrease the space between characters in a text.
22630	 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22631	 * @author Nicola Asuni
22632	 * @public
22633	 * @since 5.9.000 (2010-09-29)
22634	 */
22635	public function setFontSpacing($spacing=0) {
22636		$this->font_spacing = $spacing;
22637	}
22638
22639	/**
22640	 * Get the amount to increase or decrease the space between characters in a text.
22641	 * @return int font spacing (tracking) value
22642	 * @author Nicola Asuni
22643	 * @public
22644	 * @since 5.9.000 (2010-09-29)
22645	 */
22646	public function getFontSpacing() {
22647		return $this->font_spacing;
22648	}
22649
22650	/**
22651	 * Return an array of no-write page regions
22652	 * @return array of no-write page regions
22653	 * @author Nicola Asuni
22654	 * @public
22655	 * @since 5.9.003 (2010-10-13)
22656	 * @see setPageRegions(), addPageRegion()
22657	 */
22658	public function getPageRegions() {
22659		return $this->page_regions;
22660	}
22661
22662	/**
22663	 * Set no-write regions on page.
22664	 * 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.
22665	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22666	 * You can set multiple regions for the same page.
22667	 * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22668	 * @author Nicola Asuni
22669	 * @public
22670	 * @since 5.9.003 (2010-10-13)
22671	 * @see addPageRegion(), getPageRegions()
22672	 */
22673	public function setPageRegions($regions=array()) {
22674		// empty current regions array
22675		$this->page_regions = array();
22676		// add regions
22677		foreach ($regions as $data) {
22678			$this->addPageRegion($data);
22679		}
22680	}
22681
22682	/**
22683	 * Add a single no-write region on selected page.
22684	 * 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.
22685	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22686	 * You can set multiple regions for the same page.
22687	 * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22688	 * @author Nicola Asuni
22689	 * @public
22690	 * @since 5.9.003 (2010-10-13)
22691	 * @see setPageRegions(), getPageRegions()
22692	 */
22693	public function addPageRegion($region) {
22694		if (!isset($region['page']) OR empty($region['page'])) {
22695			$region['page'] = $this->page;
22696		}
22697		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22698			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22699			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22700			$this->page_regions[] = $region;
22701		}
22702	}
22703
22704	/**
22705	 * Remove a single no-write region.
22706	 * @param $key (int) region key
22707	 * @author Nicola Asuni
22708	 * @public
22709	 * @since 5.9.003 (2010-10-13)
22710	 * @see setPageRegions(), getPageRegions()
22711	 */
22712	public function removePageRegion($key) {
22713		if (isset($this->page_regions[$key])) {
22714			unset($this->page_regions[$key]);
22715		}
22716	}
22717
22718	/**
22719	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22720	 * 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.
22721	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22722	 * @param $h (float) height of the text/image/object to print in user units
22723	 * @param $x (float) current X coordinate in user units
22724	 * @param $y (float) current Y coordinate in user units
22725	 * @return array($x, $y)
22726	 * @author Nicola Asuni
22727	 * @protected
22728	 * @since 5.9.003 (2010-10-13)
22729	 */
22730	protected function checkPageRegions($h, $x, $y) {
22731		// set default values
22732		if ($x === '') {
22733			$x = $this->x;
22734		}
22735		if ($y === '') {
22736			$y = $this->y;
22737		}
22738		if (!$this->check_page_regions OR empty($this->page_regions)) {
22739			// no page regions defined
22740			return array($x, $y);
22741		}
22742		if (empty($h)) {
22743			$h = $this->getCellHeight($this->FontSize);
22744		}
22745		// check for page break
22746		if ($this->checkPageBreak($h, $y)) {
22747			// the content will be printed on a new page
22748			$x = $this->x;
22749			$y = $this->y;
22750		}
22751		if ($this->num_columns > 1) {
22752			if ($this->rtl) {
22753				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22754			} else {
22755				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22756			}
22757		} else {
22758			if ($this->rtl) {
22759				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22760			} else {
22761				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22762			}
22763		}
22764		// adjust coordinates and page margins
22765		foreach ($this->page_regions as $regid => $regdata) {
22766			if ($regdata['page'] == $this->page) {
22767				// check region boundaries
22768				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22769					// Y is inside the region
22770					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22771					$yt = max($y, $regdata['yt']);
22772					$yb = min(($yt + $h), $regdata['yb']);
22773					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22774					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22775					if ($regdata['side'] == 'L') { // left side
22776						$new_margin = max($xt, $xb);
22777						if ($this->lMargin < $new_margin) {
22778							if ($this->rtl) {
22779								// adjust left page margin
22780								$this->lMargin = max(0, $new_margin);
22781							}
22782							if ($x < $new_margin) {
22783								// adjust x position
22784								$x = $new_margin;
22785								if ($new_margin > ($this->w - $this->rMargin)) {
22786									// adjust y position
22787									$y = $regdata['yb'] - $h;
22788								}
22789							}
22790						}
22791					} elseif ($regdata['side'] == 'R') { // right side
22792						$new_margin = min($xt, $xb);
22793						if (($this->w - $this->rMargin) > $new_margin) {
22794							if (!$this->rtl) {
22795								// adjust right page margin
22796								$this->rMargin = max(0, ($this->w - $new_margin));
22797							}
22798							if ($x > $new_margin) {
22799								// adjust x position
22800								$x = $new_margin;
22801								if ($new_margin > $this->lMargin) {
22802									// adjust y position
22803									$y = $regdata['yb'] - $h;
22804								}
22805							}
22806						}
22807					}
22808				}
22809			}
22810		}
22811		return array($x, $y);
22812	}
22813
22814	// --- SVG METHODS ---------------------------------------------------------
22815
22816	/**
22817	 * Embedd a Scalable Vector Graphics (SVG) image.
22818	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22819	 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22820	 * @param $x (float) Abscissa of the upper-left corner.
22821	 * @param $y (float) Ordinate of the upper-left corner.
22822	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22823	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22824	 * @param $link (mixed) URL or identifier returned by AddLink().
22825	 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
22826	 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22827	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22828	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22829	 * @author Nicola Asuni
22830	 * @since 5.0.000 (2010-05-02)
22831	 * @public
22832	 */
22833	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22834		if ($this->state != 2) {
22835			 return;
22836		}
22837		// reset SVG vars
22838		$this->svggradients = array();
22839		$this->svggradientid = 0;
22840		$this->svgdefsmode = false;
22841		$this->svgdefs = array();
22842		$this->svgclipmode = false;
22843		$this->svgclippaths = array();
22844		$this->svgcliptm = array();
22845		$this->svgclipid = 0;
22846		$this->svgtext = '';
22847		$this->svgtextmode = array();
22848		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22849			// convert SVG to raster image using GD or ImageMagick libraries
22850			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22851		}
22852		if ($file[0] === '@') { // image from string
22853			$this->svgdir = '';
22854			$svgdata = substr($file, 1);
22855		} else { // SVG file
22856			$this->svgdir = dirname($file);
22857            $svgdata = $this->getCachedFileContents($file);
22858		}
22859		if ($svgdata === FALSE) {
22860			$this->Error('SVG file not found: '.$file);
22861		}
22862		if ($x === '') {
22863			$x = $this->x;
22864		}
22865		if ($y === '') {
22866			$y = $this->y;
22867		}
22868		// check page for no-write regions and adapt page margins if necessary
22869		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22870		$k = $this->k;
22871		$ox = 0;
22872		$oy = 0;
22873		$ow = $w;
22874		$oh = $h;
22875		$aspect_ratio_align = 'xMidYMid';
22876		$aspect_ratio_ms = 'meet';
22877		$regs = array();
22878		// get original image width and height
22879		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22880		if (isset($regs[1]) AND !empty($regs[1])) {
22881			$tmp = array();
22882			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22883				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22884			}
22885			$tmp = array();
22886			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22887				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22888			}
22889			$tmp = array();
22890			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22891				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22892			}
22893			$tmp = array();
22894			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22895				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22896			}
22897			$tmp = array();
22898			$view_box = array();
22899			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22900				if (count($tmp) == 5) {
22901					array_shift($tmp);
22902					foreach ($tmp as $key => $val) {
22903						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22904					}
22905					$ox = $view_box[0];
22906					$oy = $view_box[1];
22907				}
22908				// get aspect ratio
22909				$tmp = array();
22910				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22911					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22912					switch (count($aspect_ratio)) {
22913						case 3: {
22914							$aspect_ratio_align = $aspect_ratio[1];
22915							$aspect_ratio_ms = $aspect_ratio[2];
22916							break;
22917						}
22918						case 2: {
22919							$aspect_ratio_align = $aspect_ratio[0];
22920							$aspect_ratio_ms = $aspect_ratio[1];
22921							break;
22922						}
22923						case 1: {
22924							$aspect_ratio_align = $aspect_ratio[0];
22925							$aspect_ratio_ms = 'meet';
22926							break;
22927						}
22928					}
22929				}
22930			}
22931		}
22932		if ($ow <= 0) {
22933			$ow = 1;
22934		}
22935		if ($oh <= 0) {
22936			$oh = 1;
22937		}
22938		// calculate image width and height on document
22939		if (($w <= 0) AND ($h <= 0)) {
22940			// convert image size to document unit
22941			$w = $ow;
22942			$h = $oh;
22943		} elseif ($w <= 0) {
22944			$w = $h * $ow / $oh;
22945		} elseif ($h <= 0) {
22946			$h = $w * $oh / $ow;
22947		}
22948		// fit the image on available space
22949		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22950		if ($this->rasterize_vector_images) {
22951			// convert SVG to raster image using GD or ImageMagick libraries
22952			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22953		}
22954		// set alignment
22955		$this->img_rb_y = $y + $h;
22956		// set alignment
22957		if ($this->rtl) {
22958			if ($palign == 'L') {
22959				$ximg = $this->lMargin;
22960			} elseif ($palign == 'C') {
22961				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22962			} elseif ($palign == 'R') {
22963				$ximg = $this->w - $this->rMargin - $w;
22964			} else {
22965				$ximg = $x - $w;
22966			}
22967			$this->img_rb_x = $ximg;
22968		} else {
22969			if ($palign == 'L') {
22970				$ximg = $this->lMargin;
22971			} elseif ($palign == 'C') {
22972				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22973			} elseif ($palign == 'R') {
22974				$ximg = $this->w - $this->rMargin - $w;
22975			} else {
22976				$ximg = $x;
22977			}
22978			$this->img_rb_x = $ximg + $w;
22979		}
22980		// store current graphic vars
22981		$gvars = $this->getGraphicVars();
22982		// store SVG position and scale factors
22983		$svgoffset_x = ($ximg - $ox) * $this->k;
22984		$svgoffset_y = -($y - $oy) * $this->k;
22985		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22986			$ow = $view_box[2];
22987			$oh = $view_box[3];
22988		} else {
22989			if ($ow <= 0) {
22990				$ow = $w;
22991			}
22992			if ($oh <= 0) {
22993				$oh = $h;
22994			}
22995		}
22996		$svgscale_x = $w / $ow;
22997		$svgscale_y = $h / $oh;
22998		// scaling and alignment
22999		if ($aspect_ratio_align != 'none') {
23000			// store current scaling values
23001			$svgscale_old_x = $svgscale_x;
23002			$svgscale_old_y = $svgscale_y;
23003			// force uniform scaling
23004			if ($aspect_ratio_ms == 'slice') {
23005				// the entire viewport is covered by the viewBox
23006				if ($svgscale_x > $svgscale_y) {
23007					$svgscale_y = $svgscale_x;
23008				} elseif ($svgscale_x < $svgscale_y) {
23009					$svgscale_x = $svgscale_y;
23010				}
23011			} else { // meet
23012				// the entire viewBox is visible within the viewport
23013				if ($svgscale_x < $svgscale_y) {
23014					$svgscale_y = $svgscale_x;
23015				} elseif ($svgscale_x > $svgscale_y) {
23016					$svgscale_x = $svgscale_y;
23017				}
23018			}
23019			// correct X alignment
23020			switch (substr($aspect_ratio_align, 1, 3)) {
23021				case 'Min': {
23022					// do nothing
23023					break;
23024				}
23025				case 'Max': {
23026					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23027					break;
23028				}
23029				default:
23030				case 'Mid': {
23031					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23032					break;
23033				}
23034			}
23035			// correct Y alignment
23036			switch (substr($aspect_ratio_align, 5)) {
23037				case 'Min': {
23038					// do nothing
23039					break;
23040				}
23041				case 'Max': {
23042					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23043					break;
23044				}
23045				default:
23046				case 'Mid': {
23047					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23048					break;
23049				}
23050			}
23051		}
23052		// store current page break mode
23053		$page_break_mode = $this->AutoPageBreak;
23054		$page_break_margin = $this->getBreakMargin();
23055		$cell_padding = $this->cell_padding;
23056		$this->SetCellPadding(0);
23057		$this->SetAutoPageBreak(false);
23058		// save the current graphic state
23059		$this->_out('q'.$this->epsmarker);
23060		// set initial clipping mask
23061		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23062		// scale and translate
23063		$e = $ox * $this->k * (1 - $svgscale_x);
23064		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23065		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23066		// creates a new XML parser to be used by the other XML functions
23067		$parser = xml_parser_create('UTF-8');
23068		// the following function allows to use parser inside object
23069		xml_set_object($parser, $this);
23070		// disable case-folding for this XML parser
23071		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23072		// sets the element handler functions for the XML parser
23073		xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler');
23074		// sets the character data handler function for the XML parser
23075		xml_set_character_data_handler($parser, 'segSVGContentHandler');
23076		// start parsing an XML document
23077		if (!xml_parse($parser, $svgdata)) {
23078			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23079			$this->Error($error_message);
23080		}
23081		// free this XML parser
23082		xml_parser_free($parser);
23083
23084		// >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23085		unset($parser);
23086
23087		// restore previous graphic state
23088		$this->_out($this->epsmarker.'Q');
23089		// restore graphic vars
23090		$this->setGraphicVars($gvars);
23091		$this->lasth = $gvars['lasth'];
23092		if (!empty($border)) {
23093			$bx = $this->x;
23094			$by = $this->y;
23095			$this->x = $ximg;
23096			if ($this->rtl) {
23097				$this->x += $w;
23098			}
23099			$this->y = $y;
23100			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23101			$this->x = $bx;
23102			$this->y = $by;
23103		}
23104		if ($link) {
23105			$this->Link($ximg, $y, $w, $h, $link, 0);
23106		}
23107		// set pointer to align the next text/objects
23108		switch($align) {
23109			case 'T':{
23110				$this->y = $y;
23111				$this->x = $this->img_rb_x;
23112				break;
23113			}
23114			case 'M':{
23115				$this->y = $y + round($h/2);
23116				$this->x = $this->img_rb_x;
23117				break;
23118			}
23119			case 'B':{
23120				$this->y = $this->img_rb_y;
23121				$this->x = $this->img_rb_x;
23122				break;
23123			}
23124			case 'N':{
23125				$this->SetY($this->img_rb_y);
23126				break;
23127			}
23128			default:{
23129				// restore pointer to starting position
23130				$this->x = $gvars['x'];
23131				$this->y = $gvars['y'];
23132				$this->page = $gvars['page'];
23133				$this->current_column = $gvars['current_column'];
23134				$this->tMargin = $gvars['tMargin'];
23135				$this->bMargin = $gvars['bMargin'];
23136				$this->w = $gvars['w'];
23137				$this->h = $gvars['h'];
23138				$this->wPt = $gvars['wPt'];
23139				$this->hPt = $gvars['hPt'];
23140				$this->fwPt = $gvars['fwPt'];
23141				$this->fhPt = $gvars['fhPt'];
23142				break;
23143			}
23144		}
23145		$this->endlinex = $this->img_rb_x;
23146		// restore page break
23147		$this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23148		$this->cell_padding = $cell_padding;
23149	}
23150
23151	/**
23152	 * Convert SVG transformation matrix to PDF.
23153	 * @param $tm (array) original SVG transformation matrix
23154	 * @return array transformation matrix
23155	 * @protected
23156	 * @since 5.0.000 (2010-05-02)
23157	 */
23158	protected function convertSVGtMatrix($tm) {
23159		$a = $tm[0];
23160		$b = -$tm[1];
23161		$c = -$tm[2];
23162		$d = $tm[3];
23163		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23164		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23165		$x = 0;
23166		$y = $this->h * $this->k;
23167		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23168		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23169		return array($a, $b, $c, $d, $e, $f);
23170	}
23171
23172	/**
23173	 * Apply SVG graphic transformation matrix.
23174	 * @param $tm (array) original SVG transformation matrix
23175	 * @protected
23176	 * @since 5.0.000 (2010-05-02)
23177	 */
23178	protected function SVGTransform($tm) {
23179		$this->Transform($this->convertSVGtMatrix($tm));
23180	}
23181
23182	/**
23183	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23184	 * @param $svgstyle (array) array of SVG styles to apply
23185	 * @param $prevsvgstyle (array) array of previous SVG style
23186	 * @param $x (int) X origin of the bounding box
23187	 * @param $y (int) Y origin of the bounding box
23188	 * @param $w (int) width of the bounding box
23189	 * @param $h (int) height of the bounding box
23190	 * @param $clip_function (string) clip function
23191	 * @param $clip_params (array) array of parameters for clipping function
23192	 * @return object style
23193	 * @author Nicola Asuni
23194	 * @since 5.0.000 (2010-05-02)
23195	 * @protected
23196	 */
23197	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23198		if ($this->state != 2) {
23199			 return;
23200		}
23201		$objstyle = '';
23202		$minlen = (0.01 / $this->k); // minimum acceptable length
23203		if (!isset($svgstyle['opacity'])) {
23204			return $objstyle;
23205		}
23206		// clip-path
23207		$regs = array();
23208		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23209			$clip_path = $this->svgclippaths[$regs[1]];
23210			foreach ($clip_path as $cp) {
23211				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23212			}
23213		}
23214		// opacity
23215		if ($svgstyle['opacity'] != 1) {
23216			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23217		}
23218		// color
23219		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23220		$this->SetFillColorArray($fill_color);
23221		// text color
23222		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23223		$this->SetTextColorArray($text_color);
23224		// clip
23225		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23226			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23227			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23228			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23229			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23230			$cx = $x + $left;
23231			$cy = $y + $top;
23232			$cw = $w - $left - $right;
23233			$ch = $h - $top - $bottom;
23234			if ($svgstyle['clip-rule'] == 'evenodd') {
23235				$clip_rule = 'CNZ';
23236			} else {
23237				$clip_rule = 'CEO';
23238			}
23239			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23240		}
23241		// fill
23242		$regs = array();
23243		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23244			// gradient
23245			$gradient = $this->svggradients[$regs[1]];
23246			if (isset($gradient['xref'])) {
23247				// reference to another gradient definition
23248				$newgradient = $this->svggradients[$gradient['xref']];
23249				$newgradient['coords'] = $gradient['coords'];
23250				$newgradient['mode'] = $gradient['mode'];
23251				$newgradient['type'] = $gradient['type'];
23252				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23253				if (isset($gradient['gradientTransform'])) {
23254					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23255				}
23256				$gradient = $newgradient;
23257			}
23258			//save current Graphic State
23259			$this->_outSaveGraphicsState();
23260			//set clipping area
23261			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23262				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23263				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23264					list($x, $y, $w, $h) = $bbox;
23265				}
23266			}
23267			if ($gradient['mode'] == 'measure') {
23268				if (!isset($gradient['coords'][4])) {
23269					$gradient['coords'][4] = 0.5;
23270				}
23271				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23272					$gtm = $gradient['gradientTransform'];
23273					// apply transformation matrix
23274					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23275					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23276					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23277					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23278					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23279					$gradient['coords'][0] = $xa;
23280					$gradient['coords'][1] = $ya;
23281					$gradient['coords'][2] = $xb;
23282					$gradient['coords'][3] = $yb;
23283					$gradient['coords'][4] = $r;
23284				}
23285				// convert SVG coordinates to user units
23286				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23287				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23288				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23289				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23290				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23291				if ($w <= $minlen) {
23292					$w = $minlen;
23293				}
23294				if ($h <= $minlen) {
23295					$h = $minlen;
23296				}
23297				// shift units
23298				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23299					// convert to SVG coordinate system
23300					$gradient['coords'][0] += $x;
23301					$gradient['coords'][1] += $y;
23302					$gradient['coords'][2] += $x;
23303					$gradient['coords'][3] += $y;
23304				}
23305				// calculate percentages
23306				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23307				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23308				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23309				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23310				$gradient['coords'][4] /= $w;
23311			} elseif ($gradient['mode'] == 'percentage') {
23312				foreach($gradient['coords'] as $key => $val) {
23313					$gradient['coords'][$key] = (intval($val) / 100);
23314					if ($val < 0) {
23315						$gradient['coords'][$key] = 0;
23316					} elseif ($val > 1) {
23317						$gradient['coords'][$key] = 1;
23318					}
23319				}
23320			}
23321			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23322				// single color (no shading)
23323				$gradient['coords'][0] = 1;
23324				$gradient['coords'][1] = 0;
23325				$gradient['coords'][2] = 0.999;
23326				$gradient['coords'][3] = 0;
23327			}
23328			// swap Y coordinates
23329			$tmp = $gradient['coords'][1];
23330			$gradient['coords'][1] = $gradient['coords'][3];
23331			$gradient['coords'][3] = $tmp;
23332			// set transformation map for gradient
23333			$cy = ($this->h - $y);
23334			if ($gradient['type'] == 3) {
23335				// circular gradient
23336				$cy -= ($gradient['coords'][1] * ($w + $h));
23337				$h = $w = max($w, $h);
23338			} else {
23339				$cy -= $h;
23340			}
23341			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23342			if (count($gradient['stops']) > 1) {
23343				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23344			}
23345		} elseif ($svgstyle['fill'] != 'none') {
23346			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23347			if ($svgstyle['fill-opacity'] != 1) {
23348				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23349			}
23350			$this->SetFillColorArray($fill_color);
23351			if ($svgstyle['fill-rule'] == 'evenodd') {
23352				$objstyle .= 'F*';
23353			} else {
23354				$objstyle .= 'F';
23355			}
23356		}
23357		// stroke
23358		if ($svgstyle['stroke'] != 'none') {
23359			if ($svgstyle['stroke-opacity'] != 1) {
23360				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23361			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23362				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23363			}
23364			$stroke_style = array(
23365				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23366				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23367				'cap' => $svgstyle['stroke-linecap'],
23368				'join' => $svgstyle['stroke-linejoin']
23369				);
23370			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23371				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23372			}
23373			$this->SetLineStyle($stroke_style);
23374			$objstyle .= 'D';
23375		}
23376		// font
23377		$regs = array();
23378		if (!empty($svgstyle['font'])) {
23379			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23380				$font_family = $this->getFontFamilyName($regs[1]);
23381			} else {
23382				$font_family = $svgstyle['font-family'];
23383			}
23384			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23385				$font_size = trim($regs[1]);
23386			} else {
23387				$font_size = $svgstyle['font-size'];
23388			}
23389			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23390				$font_style = trim($regs[1]);
23391			} else {
23392				$font_style = $svgstyle['font-style'];
23393			}
23394			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23395				$font_weight = trim($regs[1]);
23396			} else {
23397				$font_weight = $svgstyle['font-weight'];
23398			}
23399			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23400				$font_stretch = trim($regs[1]);
23401			} else {
23402				$font_stretch = $svgstyle['font-stretch'];
23403			}
23404			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23405				$font_spacing = trim($regs[1]);
23406			} else {
23407				$font_spacing = $svgstyle['letter-spacing'];
23408			}
23409		} else {
23410			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23411			$font_size = $svgstyle['font-size'];
23412			$font_style = $svgstyle['font-style'];
23413			$font_weight = $svgstyle['font-weight'];
23414			$font_stretch = $svgstyle['font-stretch'];
23415			$font_spacing = $svgstyle['letter-spacing'];
23416		}
23417		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23418		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23419		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23420		switch ($font_style) {
23421			case 'italic': {
23422				$font_style = 'I';
23423				break;
23424			}
23425			case 'oblique': {
23426				$font_style = 'I';
23427				break;
23428			}
23429			default:
23430			case 'normal': {
23431				$font_style = '';
23432				break;
23433			}
23434		}
23435		switch ($font_weight) {
23436			case 'bold':
23437			case 'bolder': {
23438				$font_style .= 'B';
23439				break;
23440			}
23441			case 'normal': {
23442				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23443					$font_family = substr($font_family, 0, -2).'I';
23444				} elseif (substr($font_family, -1) == 'B') {
23445					$font_family = substr($font_family, 0, -1);
23446				}
23447				break;
23448			}
23449		}
23450		switch ($svgstyle['text-decoration']) {
23451			case 'underline': {
23452				$font_style .= 'U';
23453				break;
23454			}
23455			case 'overline': {
23456				$font_style .= 'O';
23457				break;
23458			}
23459			case 'line-through': {
23460				$font_style .= 'D';
23461				break;
23462			}
23463			default:
23464			case 'none': {
23465				break;
23466			}
23467		}
23468		$this->SetFont($font_family, $font_style, $font_size);
23469		$this->setFontStretching($font_stretch);
23470		$this->setFontSpacing($font_spacing);
23471		return $objstyle;
23472	}
23473
23474	/**
23475	 * Draws an SVG path
23476	 * @param $d (string) attribute d of the path SVG element
23477	 * @param $style (string) Style of rendering. Possible values are:
23478	 * <ul>
23479	 *	 <li>D or empty string: Draw (default).</li>
23480	 *	 <li>F: Fill.</li>
23481	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23482	 *	 <li>DF or FD: Draw and fill.</li>
23483	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23484	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23485	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23486	 * </ul>
23487	 * @return array of container box measures (x, y, w, h)
23488	 * @author Nicola Asuni
23489	 * @since 5.0.000 (2010-05-02)
23490	 * @protected
23491	 */
23492	protected function SVGPath($d, $style='') {
23493		if ($this->state != 2) {
23494			return;
23495		}
23496		// set fill/stroke style
23497		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23498		if (empty($op)) {
23499			return;
23500		}
23501		$paths = array();
23502		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23503		$d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23504		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23505		$x = 0;
23506		$y = 0;
23507		$x1 = 0;
23508		$y1 = 0;
23509		$x2 = 0;
23510		$y2 = 0;
23511		$xmin = 2147483647;
23512		$xmax = 0;
23513		$ymin = 2147483647;
23514		$ymax = 0;
23515		$xinitial = 0;
23516		$yinitial = 0;
23517		$relcoord = false;
23518		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23519		$firstcmd = true; // used to print first point
23520		// draw curve pieces
23521		foreach ($paths as $key => $val) {
23522			// get curve type
23523			$cmd = trim($val[1]);
23524			if (strtolower($cmd) == $cmd) {
23525				// use relative coordinated instead of absolute
23526				$relcoord = true;
23527				$xoffset = $x;
23528				$yoffset = $y;
23529			} else {
23530				$relcoord = false;
23531				$xoffset = 0;
23532				$yoffset = 0;
23533			}
23534			$params = array();
23535			if (isset($val[2])) {
23536				// get curve parameters
23537				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23538				$params = array();
23539				foreach ($rawparams as $ck => $cp) {
23540					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23541					if (abs($params[$ck]) < $minlen) {
23542						// approximate little values to zero
23543						$params[$ck] = 0;
23544					}
23545				}
23546			}
23547			// store current origin point
23548			$x0 = $x;
23549			$y0 = $y;
23550			switch (strtoupper($cmd)) {
23551				case 'M': { // moveto
23552					foreach ($params as $ck => $cp) {
23553						if (($ck % 2) == 0) {
23554							$x = $cp + $xoffset;
23555						} else {
23556							$y = $cp + $yoffset;
23557							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23558								if ($ck == 1) {
23559									$this->_outPoint($x, $y);
23560									$firstcmd = false;
23561									$xinitial = $x;
23562									$yinitial = $y;
23563								} else {
23564									$this->_outLine($x, $y);
23565								}
23566								$x0 = $x;
23567								$y0 = $y;
23568							}
23569							$xmin = min($xmin, $x);
23570							$ymin = min($ymin, $y);
23571							$xmax = max($xmax, $x);
23572							$ymax = max($ymax, $y);
23573							if ($relcoord) {
23574								$xoffset = $x;
23575								$yoffset = $y;
23576							}
23577						}
23578					}
23579					break;
23580				}
23581				case 'L': { // lineto
23582					foreach ($params as $ck => $cp) {
23583						if (($ck % 2) == 0) {
23584							$x = $cp + $xoffset;
23585						} else {
23586							$y = $cp + $yoffset;
23587							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23588								$this->_outLine($x, $y);
23589								$x0 = $x;
23590								$y0 = $y;
23591							}
23592							$xmin = min($xmin, $x);
23593							$ymin = min($ymin, $y);
23594							$xmax = max($xmax, $x);
23595							$ymax = max($ymax, $y);
23596							if ($relcoord) {
23597								$xoffset = $x;
23598								$yoffset = $y;
23599							}
23600						}
23601					}
23602					break;
23603				}
23604				case 'H': { // horizontal lineto
23605					foreach ($params as $ck => $cp) {
23606						$x = $cp + $xoffset;
23607						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23608							$this->_outLine($x, $y);
23609							$x0 = $x;
23610							$y0 = $y;
23611						}
23612						$xmin = min($xmin, $x);
23613						$xmax = max($xmax, $x);
23614						if ($relcoord) {
23615							$xoffset = $x;
23616						}
23617					}
23618					break;
23619				}
23620				case 'V': { // vertical lineto
23621					foreach ($params as $ck => $cp) {
23622						$y = $cp + $yoffset;
23623						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23624							$this->_outLine($x, $y);
23625							$x0 = $x;
23626							$y0 = $y;
23627						}
23628						$ymin = min($ymin, $y);
23629						$ymax = max($ymax, $y);
23630						if ($relcoord) {
23631							$yoffset = $y;
23632						}
23633					}
23634					break;
23635				}
23636				case 'C': { // curveto
23637					foreach ($params as $ck => $cp) {
23638						$params[$ck] = $cp;
23639						if ((($ck + 1) % 6) == 0) {
23640							$x1 = $params[($ck - 5)] + $xoffset;
23641							$y1 = $params[($ck - 4)] + $yoffset;
23642							$x2 = $params[($ck - 3)] + $xoffset;
23643							$y2 = $params[($ck - 2)] + $yoffset;
23644							$x = $params[($ck - 1)] + $xoffset;
23645							$y = $params[($ck)] + $yoffset;
23646							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23647							$xmin = min($xmin, $x, $x1, $x2);
23648							$ymin = min($ymin, $y, $y1, $y2);
23649							$xmax = max($xmax, $x, $x1, $x2);
23650							$ymax = max($ymax, $y, $y1, $y2);
23651							if ($relcoord) {
23652								$xoffset = $x;
23653								$yoffset = $y;
23654							}
23655						}
23656					}
23657					break;
23658				}
23659				case 'S': { // shorthand/smooth curveto
23660					foreach ($params as $ck => $cp) {
23661						$params[$ck] = $cp;
23662						if ((($ck + 1) % 4) == 0) {
23663							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23664								$x1 = (2 * $x) - $x2;
23665								$y1 = (2 * $y) - $y2;
23666							} else {
23667								$x1 = $x;
23668								$y1 = $y;
23669							}
23670							$x2 = $params[($ck - 3)] + $xoffset;
23671							$y2 = $params[($ck - 2)] + $yoffset;
23672							$x = $params[($ck - 1)] + $xoffset;
23673							$y = $params[($ck)] + $yoffset;
23674							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23675							$xmin = min($xmin, $x, $x1, $x2);
23676							$ymin = min($ymin, $y, $y1, $y2);
23677							$xmax = max($xmax, $x, $x1, $x2);
23678							$ymax = max($ymax, $y, $y1, $y2);
23679							if ($relcoord) {
23680								$xoffset = $x;
23681								$yoffset = $y;
23682							}
23683						}
23684					}
23685					break;
23686				}
23687				case 'Q': { // quadratic Bezier curveto
23688					foreach ($params as $ck => $cp) {
23689						$params[$ck] = $cp;
23690						if ((($ck + 1) % 4) == 0) {
23691							// convert quadratic points to cubic points
23692							$x1 = $params[($ck - 3)] + $xoffset;
23693							$y1 = $params[($ck - 2)] + $yoffset;
23694							$xa = ($x + (2 * $x1)) / 3;
23695							$ya = ($y + (2 * $y1)) / 3;
23696							$x = $params[($ck - 1)] + $xoffset;
23697							$y = $params[($ck)] + $yoffset;
23698							$xb = ($x + (2 * $x1)) / 3;
23699							$yb = ($y + (2 * $y1)) / 3;
23700							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23701							$xmin = min($xmin, $x, $xa, $xb);
23702							$ymin = min($ymin, $y, $ya, $yb);
23703							$xmax = max($xmax, $x, $xa, $xb);
23704							$ymax = max($ymax, $y, $ya, $yb);
23705							if ($relcoord) {
23706								$xoffset = $x;
23707								$yoffset = $y;
23708							}
23709						}
23710					}
23711					break;
23712				}
23713				case 'T': { // shorthand/smooth quadratic Bezier curveto
23714					foreach ($params as $ck => $cp) {
23715						$params[$ck] = $cp;
23716						if (($ck % 2) != 0) {
23717							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23718								$x1 = (2 * $x) - $x1;
23719								$y1 = (2 * $y) - $y1;
23720							} else {
23721								$x1 = $x;
23722								$y1 = $y;
23723							}
23724							// convert quadratic points to cubic points
23725							$xa = ($x + (2 * $x1)) / 3;
23726							$ya = ($y + (2 * $y1)) / 3;
23727							$x = $params[($ck - 1)] + $xoffset;
23728							$y = $params[($ck)] + $yoffset;
23729							$xb = ($x + (2 * $x1)) / 3;
23730							$yb = ($y + (2 * $y1)) / 3;
23731							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23732							$xmin = min($xmin, $x, $xa, $xb);
23733							$ymin = min($ymin, $y, $ya, $yb);
23734							$xmax = max($xmax, $x, $xa, $xb);
23735							$ymax = max($ymax, $y, $ya, $yb);
23736							if ($relcoord) {
23737								$xoffset = $x;
23738								$yoffset = $y;
23739							}
23740						}
23741					}
23742					break;
23743				}
23744				case 'A': { // elliptical arc
23745					foreach ($params as $ck => $cp) {
23746						$params[$ck] = $cp;
23747						if ((($ck + 1) % 7) == 0) {
23748							$x0 = $x;
23749							$y0 = $y;
23750							$rx = max(abs($params[($ck - 6)]), .000000001);
23751							$ry = max(abs($params[($ck - 5)]), .000000001);
23752							$ang = -$rawparams[($ck - 4)];
23753							$angle = deg2rad($ang);
23754							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23755							$fs = $rawparams[($ck - 2)]; // sweep-flag
23756							$x = $params[($ck - 1)] + $xoffset;
23757							$y = $params[$ck] + $yoffset;
23758							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23759								// endpoints are almost identical
23760								$xmin = min($xmin, $x);
23761								$ymin = min($ymin, $y);
23762								$xmax = max($xmax, $x);
23763								$ymax = max($ymax, $y);
23764							} else {
23765								$cos_ang = cos($angle);
23766								$sin_ang = sin($angle);
23767								$a = (($x0 - $x) / 2);
23768								$b = (($y0 - $y) / 2);
23769								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23770								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23771								$rx2 = $rx * $rx;
23772								$ry2 = $ry * $ry;
23773								$xa2 = $xa * $xa;
23774								$ya2 = $ya * $ya;
23775								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23776								if ($delta > 1) {
23777									$rx *= sqrt($delta);
23778									$ry *= sqrt($delta);
23779									$rx2 = $rx * $rx;
23780									$ry2 = $ry * $ry;
23781								}
23782								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23783								if ($numerator < 0) {
23784									$root = 0;
23785								} else {
23786									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23787								}
23788								if ($fa == $fs){
23789									$root *= -1;
23790								}
23791								$cax = $root * (($rx * $ya) / $ry);
23792								$cay = -$root * (($ry * $xa) / $rx);
23793								// coordinates of ellipse center
23794								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23795								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23796								// get angles
23797								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23798								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23799								if (($fs == 0) AND ($dang > 0)) {
23800									$dang -= (2 * M_PI);
23801								} elseif (($fs == 1) AND ($dang < 0)) {
23802									$dang += (2 * M_PI);
23803								}
23804								$angf = $angs - $dang;
23805								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23806									// reverse angles
23807									$tmp = $angs;
23808									$angs = $angf;
23809									$angf = $tmp;
23810								}
23811								$angs = round(rad2deg($angs), 6);
23812								$angf = round(rad2deg($angf), 6);
23813								// covent angles to positive values
23814								if (($angs < 0) AND ($angf < 0)) {
23815									$angs += 360;
23816									$angf += 360;
23817								}
23818								$pie = false;
23819								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23820									$pie = true;
23821								}
23822								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23823								$xmin = min($xmin, $x, $axmin);
23824								$ymin = min($ymin, $y, $aymin);
23825								$xmax = max($xmax, $x, $axmax);
23826								$ymax = max($ymax, $y, $aymax);
23827							}
23828							if ($relcoord) {
23829								$xoffset = $x;
23830								$yoffset = $y;
23831							}
23832						}
23833					}
23834					break;
23835				}
23836				case 'Z': {
23837					$this->_out('h');
23838					$x = $x0 = $xinitial;
23839					$y = $y0 = $yinitial;
23840					break;
23841				}
23842			}
23843			$firstcmd = false;
23844		} // end foreach
23845		if (!empty($op)) {
23846			$this->_out($op);
23847		}
23848		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23849	}
23850
23851	/**
23852	 * Return the tag name without the namespace
23853	 * @param $name (string) Tag name
23854	 * @protected
23855	 */
23856	protected function removeTagNamespace($name) {
23857		if(strpos($name, ':') !== false) {
23858			$parts = explode(':', $name);
23859			return $parts[(sizeof($parts) - 1)];
23860		}
23861		return $name;
23862	}
23863
23864	/**
23865	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23866	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23867	 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
23868	 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
23869	 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23870	 * @author Nicola Asuni
23871	 * @since 5.0.000 (2010-05-02)
23872	 * @protected
23873	 */
23874	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23875		$name = $this->removeTagNamespace($name);
23876		// check if we are in clip mode
23877		if ($this->svgclipmode) {
23878			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23879			return;
23880		}
23881		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23882			if (isset($attribs['id'])) {
23883				$attribs['child_elements'] = array();
23884				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23885				return;
23886			}
23887			if (end($this->svgdefs) !== FALSE) {
23888				$last_svgdefs_id = key($this->svgdefs);
23889				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23890					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23891					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23892					return;
23893				}
23894			}
23895			return;
23896		}
23897		$clipping = false;
23898		if ($parser == 'clip-path') {
23899			// set clipping mode
23900			$clipping = true;
23901		}
23902		// get styling properties
23903		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23904		$svgstyle = $this->svgstyles[0]; // set default style
23905		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23906			// default fill attribute for clipping
23907			$attribs['fill'] = 'none';
23908		}
23909		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23910			// fix style for regular expression
23911			$attribs['style'] = ';'.$attribs['style'];
23912		}
23913		foreach ($prev_svgstyle as $key => $val) {
23914			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23915				// inherit previous value
23916				$svgstyle[$key] = $val;
23917			}
23918			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23919				// specific attribute settings
23920				if ($attribs[$key] == 'inherit') {
23921					$svgstyle[$key] = $val;
23922				} else {
23923					$svgstyle[$key] = $attribs[$key];
23924				}
23925			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23926				// CSS style syntax
23927				$attrval = array();
23928				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23929					if ($attrval[1] == 'inherit') {
23930						$svgstyle[$key] = $val;
23931					} else {
23932						$svgstyle[$key] = $attrval[1];
23933					}
23934				}
23935			}
23936		}
23937		// transformation matrix
23938		if (!empty($ctm)) {
23939			$tm = $ctm;
23940		} else {
23941			$tm = array(1,0,0,1,0,0);
23942		}
23943		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23944			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23945		}
23946		$svgstyle['transfmatrix'] = $tm;
23947		$invisible = false;
23948		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23949			// the current graphics element is invisible (nothing is painted)
23950			$invisible = true;
23951		}
23952		// process tag
23953		switch($name) {
23954			case 'defs': {
23955				$this->svgdefsmode = true;
23956				break;
23957			}
23958			// clipPath
23959			case 'clipPath': {
23960				if ($invisible) {
23961					break;
23962				}
23963				$this->svgclipmode = true;
23964				if (!isset($attribs['id'])) {
23965					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23966				}
23967				$this->svgclipid = $attribs['id'];
23968				$this->svgclippaths[$this->svgclipid] = array();
23969				$this->svgcliptm[$this->svgclipid] = $tm;
23970				break;
23971			}
23972			case 'svg': {
23973				// start of SVG object
23974				if(++$this->svg_tag_depth <= 1) {
23975					break;
23976				}
23977				// inner SVG
23978				array_push($this->svgstyles, $svgstyle);
23979				$this->StartTransform();
23980				$svgX = (isset($attribs['x'])?$attribs['x']:0);
23981				$svgY = (isset($attribs['y'])?$attribs['y']:0);
23982				$svgW = (isset($attribs['width'])?$attribs['width']:0);
23983				$svgH = (isset($attribs['height'])?$attribs['height']:0);
23984				// set x, y position using transform matrix
23985				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23986				$this->SVGTransform($tm);
23987				// set clipping for width and height
23988				$x = 0;
23989				$y = 0;
23990				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23991				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23992				// draw clipping rect
23993				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23994				// parse viewbox, calculate extra transformation matrix
23995				if (isset($attribs['viewBox'])) {
23996					$tmp = array();
23997					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23998					$tmp = $tmp[0];
23999					if (sizeof($tmp) == 4) {
24000						$vx = $tmp[0];
24001						$vy = $tmp[1];
24002						$vw = $tmp[2];
24003						$vh = $tmp[3];
24004						// get aspect ratio
24005						$tmp = array();
24006						$aspectX = 'xMid';
24007						$aspectY = 'YMid';
24008						$fit = 'meet';
24009						if (isset($attribs['preserveAspectRatio'])) {
24010							if($attribs['preserveAspectRatio'] == 'none') {
24011								$fit = 'none';
24012							} else {
24013								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24014								$tmp = $tmp[0];
24015								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24016									$aspectX = substr($tmp[0], 0, 4);
24017									$aspectY = substr($tmp[0], 4, 4);
24018									$fit = $tmp[1];
24019								}
24020							}
24021						}
24022						$wr = ($svgW / $vw);
24023						$hr = ($svgH / $vh);
24024						$ax = $ay = 0;
24025						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24026							if ($aspectX == 'xMax') {
24027								$ax = (($vw * ($wr / $hr)) - $vw);
24028							}
24029							if ($aspectX == 'xMid') {
24030								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24031							}
24032							$wr = $hr;
24033						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24034							if ($aspectY == 'YMax') {
24035								$ay = (($vh * ($hr / $wr)) - $vh);
24036							}
24037							if ($aspectY == 'YMid') {
24038								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24039							}
24040							$hr = $wr;
24041						}
24042						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24043						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24044						$this->SVGTransform($tm);
24045					}
24046				}
24047				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24048				break;
24049			}
24050			case 'g': {
24051				// group together related graphics elements
24052				array_push($this->svgstyles, $svgstyle);
24053				$this->StartTransform();
24054				$x = (isset($attribs['x'])?$attribs['x']:0);
24055				$y = (isset($attribs['y'])?$attribs['y']:0);
24056				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24057				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24058				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24059				$this->SVGTransform($tm);
24060				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24061				break;
24062			}
24063			case 'linearGradient': {
24064				if ($this->pdfa_mode) {
24065					break;
24066				}
24067				if (!isset($attribs['id'])) {
24068					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24069				}
24070				$this->svggradientid = $attribs['id'];
24071				$this->svggradients[$this->svggradientid] = array();
24072				$this->svggradients[$this->svggradientid]['type'] = 2;
24073				$this->svggradients[$this->svggradientid]['stops'] = array();
24074				if (isset($attribs['gradientUnits'])) {
24075					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24076				} else {
24077					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24078				}
24079				//$attribs['spreadMethod']
24080				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24081					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24082						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24083						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24084						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24085					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24086				} else {
24087					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24088				}
24089				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24090				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24091				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24092				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24093				if (isset($attribs['gradientTransform'])) {
24094					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24095				}
24096				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24097				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24098					// gradient is defined on another place
24099					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24100				}
24101				break;
24102			}
24103			case 'radialGradient': {
24104				if ($this->pdfa_mode) {
24105					break;
24106				}
24107				if (!isset($attribs['id'])) {
24108					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24109				}
24110				$this->svggradientid = $attribs['id'];
24111				$this->svggradients[$this->svggradientid] = array();
24112				$this->svggradients[$this->svggradientid]['type'] = 3;
24113				$this->svggradients[$this->svggradientid]['stops'] = array();
24114				if (isset($attribs['gradientUnits'])) {
24115					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24116				} else {
24117					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24118				}
24119				//$attribs['spreadMethod']
24120				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24121					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24122					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24123					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24124				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24125					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24126				} else {
24127					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24128				}
24129				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24130				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24131				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24132				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24133				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24134				if (isset($attribs['gradientTransform'])) {
24135					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24136				}
24137				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24138				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24139					// gradient is defined on another place
24140					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24141				}
24142				break;
24143			}
24144			case 'stop': {
24145				// gradient stops
24146				if (substr($attribs['offset'], -1) == '%') {
24147					$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24148				} else {
24149					$offset = floatval($attribs['offset']);
24150					if ($offset > 1) {
24151						$offset /= 100;
24152					}
24153				}
24154				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24155				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24156				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24157				break;
24158			}
24159			// paths
24160			case 'path': {
24161				if ($invisible) {
24162					break;
24163				}
24164				if (isset($attribs['d'])) {
24165					$d = trim($attribs['d']);
24166					if (!empty($d)) {
24167						$x = (isset($attribs['x'])?$attribs['x']:0);
24168						$y = (isset($attribs['y'])?$attribs['y']:0);
24169						$w = (isset($attribs['width'])?$attribs['width']:1);
24170						$h = (isset($attribs['height'])?$attribs['height']:1);
24171						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24172						if ($clipping) {
24173							$this->SVGTransform($tm);
24174							$this->SVGPath($d, 'CNZ');
24175						} else {
24176							$this->StartTransform();
24177							$this->SVGTransform($tm);
24178							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24179							if (!empty($obstyle)) {
24180								$this->SVGPath($d, $obstyle);
24181							}
24182							$this->StopTransform();
24183						}
24184					}
24185				}
24186				break;
24187			}
24188			// shapes
24189			case 'rect': {
24190				if ($invisible) {
24191					break;
24192				}
24193				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24194				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24195				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24196				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24197				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24198				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24199				if ($clipping) {
24200					$this->SVGTransform($tm);
24201					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24202				} else {
24203					$this->StartTransform();
24204					$this->SVGTransform($tm);
24205					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24206					if (!empty($obstyle)) {
24207						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24208					}
24209					$this->StopTransform();
24210				}
24211				break;
24212			}
24213			case 'circle': {
24214				if ($invisible) {
24215					break;
24216				}
24217				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24218				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24219				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24220				$x = ($cx - $r);
24221				$y = ($cy - $r);
24222				$w = (2 * $r);
24223				$h = $w;
24224				if ($clipping) {
24225					$this->SVGTransform($tm);
24226					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24227				} else {
24228					$this->StartTransform();
24229					$this->SVGTransform($tm);
24230					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24231					if (!empty($obstyle)) {
24232						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24233					}
24234					$this->StopTransform();
24235				}
24236				break;
24237			}
24238			case 'ellipse': {
24239				if ($invisible) {
24240					break;
24241				}
24242				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24243				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24244				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24245				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24246				$x = ($cx - $rx);
24247				$y = ($cy - $ry);
24248				$w = (2 * $rx);
24249				$h = (2 * $ry);
24250				if ($clipping) {
24251					$this->SVGTransform($tm);
24252					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24253				} else {
24254					$this->StartTransform();
24255					$this->SVGTransform($tm);
24256					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24257					if (!empty($obstyle)) {
24258						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24259					}
24260					$this->StopTransform();
24261				}
24262				break;
24263			}
24264			case 'line': {
24265				if ($invisible) {
24266					break;
24267				}
24268				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24269				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24270				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24271				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24272				$x = $x1;
24273				$y = $y1;
24274				$w = abs($x2 - $x1);
24275				$h = abs($y2 - $y1);
24276				if (!$clipping) {
24277					$this->StartTransform();
24278					$this->SVGTransform($tm);
24279					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24280					$this->Line($x1, $y1, $x2, $y2);
24281					$this->StopTransform();
24282				}
24283				break;
24284			}
24285			case 'polyline':
24286			case 'polygon': {
24287				if ($invisible) {
24288					break;
24289				}
24290				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24291				$points = trim($points);
24292				// note that point may use a complex syntax not covered here
24293				$points = preg_split('/[\,\s]+/si', $points);
24294				if (count($points) < 4) {
24295					break;
24296				}
24297				$p = array();
24298				$xmin = 2147483647;
24299				$xmax = 0;
24300				$ymin = 2147483647;
24301				$ymax = 0;
24302				foreach ($points as $key => $val) {
24303					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24304					if (($key % 2) == 0) {
24305						// X coordinate
24306						$xmin = min($xmin, $p[$key]);
24307						$xmax = max($xmax, $p[$key]);
24308					} else {
24309						// Y coordinate
24310						$ymin = min($ymin, $p[$key]);
24311						$ymax = max($ymax, $p[$key]);
24312					}
24313				}
24314				$x = $xmin;
24315				$y = $ymin;
24316				$w = ($xmax - $xmin);
24317				$h = ($ymax - $ymin);
24318				if ($name == 'polyline') {
24319					$this->StartTransform();
24320					$this->SVGTransform($tm);
24321					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24322					if (!empty($obstyle)) {
24323						$this->PolyLine($p, $obstyle, array(), array());
24324					}
24325					$this->StopTransform();
24326				} else { // polygon
24327					if ($clipping) {
24328						$this->SVGTransform($tm);
24329						$this->Polygon($p, 'CNZ', array(), array(), true);
24330					} else {
24331						$this->StartTransform();
24332						$this->SVGTransform($tm);
24333						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24334						if (!empty($obstyle)) {
24335							$this->Polygon($p, $obstyle, array(), array(), true);
24336						}
24337						$this->StopTransform();
24338					}
24339				}
24340				break;
24341			}
24342			// image
24343			case 'image': {
24344				if ($invisible) {
24345					break;
24346				}
24347				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24348					break;
24349				}
24350				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24351				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24352				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24353				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24354				$img = $attribs['xlink:href'];
24355				if (!$clipping) {
24356					$this->StartTransform();
24357					$this->SVGTransform($tm);
24358					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24359					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24360						// embedded image encoded as base64
24361						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24362					} else {
24363						// fix image path
24364						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24365							// replace relative path with full server path
24366							$img = $this->svgdir.'/'.$img;
24367						}
24368						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24369							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24370							if (($findroot === false) OR ($findroot > 1)) {
24371								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24372									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24373								} else {
24374									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24375								}
24376							}
24377						}
24378						$img = urldecode($img);
24379						$testscrtype = @parse_url($img);
24380						if (empty($testscrtype['query'])) {
24381							// convert URL to server path
24382							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24383						} elseif (preg_match('|^https?://|', $img) !== 1) {
24384							// convert server path to URL
24385							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24386						}
24387					}
24388					// get image type
24389					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24390					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24391						$this->ImageEps($img, $x, $y, $w, $h);
24392					} elseif ($imgtype == 'svg') {
24393						// store SVG vars
24394						$svggradients = $this->svggradients;
24395						$svggradientid = $this->svggradientid;
24396						$svgdefsmode = $this->svgdefsmode;
24397						$svgdefs = $this->svgdefs;
24398						$svgclipmode = $this->svgclipmode;
24399						$svgclippaths = $this->svgclippaths;
24400						$svgcliptm = $this->svgcliptm;
24401						$svgclipid = $this->svgclipid;
24402						$svgtext = $this->svgtext;
24403						$svgtextmode = $this->svgtextmode;
24404						$this->ImageSVG($img, $x, $y, $w, $h);
24405						// restore SVG vars
24406						$this->svggradients = $svggradients;
24407						$this->svggradientid = $svggradientid;
24408						$this->svgdefsmode = $svgdefsmode;
24409						$this->svgdefs = $svgdefs;
24410						$this->svgclipmode = $svgclipmode;
24411						$this->svgclippaths = $svgclippaths;
24412						$this->svgcliptm = $svgcliptm;
24413						$this->svgclipid = $svgclipid;
24414						$this->svgtext = $svgtext;
24415						$this->svgtextmode = $svgtextmode;
24416					} else {
24417						$this->Image($img, $x, $y, $w, $h);
24418					}
24419					$this->StopTransform();
24420				}
24421				break;
24422			}
24423			// text
24424			case 'text':
24425			case 'tspan': {
24426				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24427					// @TODO: unsupported feature
24428				}
24429				// only basic support - advanced features must be implemented
24430				$this->svgtextmode['invisible'] = $invisible;
24431				if ($invisible) {
24432					break;
24433				}
24434				array_push($this->svgstyles, $svgstyle);
24435				if (isset($attribs['x'])) {
24436					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24437				} elseif ($name == 'tspan') {
24438					$x = $this->x;
24439				} else {
24440					$x = 0;
24441				}
24442				if (isset($attribs['dx'])) {
24443					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24444				}
24445				if (isset($attribs['y'])) {
24446					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24447				} elseif ($name == 'tspan') {
24448					$y = $this->y;
24449				} else {
24450					$y = 0;
24451				}
24452				if (isset($attribs['dy'])) {
24453					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24454				}
24455				$svgstyle['text-color'] = $svgstyle['fill'];
24456				$this->svgtext = '';
24457				if (isset($svgstyle['text-anchor'])) {
24458					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24459				} else {
24460					$this->svgtextmode['text-anchor'] = 'start';
24461				}
24462				if (isset($svgstyle['direction'])) {
24463					if ($svgstyle['direction'] == 'rtl') {
24464						$this->svgtextmode['rtl'] = true;
24465					} else {
24466						$this->svgtextmode['rtl'] = false;
24467					}
24468				} else {
24469					$this->svgtextmode['rtl'] = false;
24470				}
24471				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24472					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24473				} else {
24474					$this->svgtextmode['stroke'] = false;
24475				}
24476				$this->StartTransform();
24477				$this->SVGTransform($tm);
24478				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24479				$this->x = $x;
24480				$this->y = $y;
24481				break;
24482			}
24483			// use
24484			case 'use': {
24485				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24486					$svgdefid = substr($attribs['xlink:href'], 1);
24487					if (isset($this->svgdefs[$svgdefid])) {
24488						$use = $this->svgdefs[$svgdefid];
24489						if (isset($attribs['xlink:href'])) {
24490							unset($attribs['xlink:href']);
24491						}
24492						if (isset($attribs['id'])) {
24493							unset($attribs['id']);
24494						}
24495						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24496							$attribs['x'] += $use['attribs']['x'];
24497						}
24498						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24499							$attribs['y'] += $use['attribs']['y'];
24500						}
24501						if (empty($attribs['style'])) {
24502							$attribs['style'] = '';
24503						}
24504						if (!empty($use['attribs']['style'])) {
24505							// merge styles
24506							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24507						}
24508						$attribs = array_merge($use['attribs'], $attribs);
24509						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24510						return;
24511					}
24512				}
24513				break;
24514			}
24515			default: {
24516				break;
24517			}
24518		} // end of switch
24519		// process child elements
24520		if (!empty($attribs['child_elements'])) {
24521			$child_elements = $attribs['child_elements'];
24522			unset($attribs['child_elements']);
24523			foreach($child_elements as $child_element) {
24524				if (empty($child_element['attribs']['closing_tag'])) {
24525					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24526				} else {
24527					if (isset($child_element['attribs']['content'])) {
24528						$this->svgtext = $child_element['attribs']['content'];
24529					}
24530					$this->endSVGElementHandler('child-tag', $child_element['name']);
24531				}
24532			}
24533		}
24534	}
24535
24536	/**
24537	 * Sets the closing SVG element handler function for the XML parser.
24538	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24539	 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24540	 * @author Nicola Asuni
24541	 * @since 5.0.000 (2010-05-02)
24542	 * @protected
24543	 */
24544	protected function endSVGElementHandler($parser, $name) {
24545		$name = $this->removeTagNamespace($name);
24546		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24547			if (end($this->svgdefs) !== FALSE) {
24548				$last_svgdefs_id = key($this->svgdefs);
24549				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24550					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24551						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24552							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24553							return;
24554						}
24555					}
24556					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24557						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24558						return;
24559					}
24560				}
24561			}
24562			return;
24563		}
24564		switch($name) {
24565			case 'defs': {
24566				$this->svgdefsmode = false;
24567				break;
24568			}
24569			// clipPath
24570			case 'clipPath': {
24571				$this->svgclipmode = false;
24572				break;
24573			}
24574			case 'svg': {
24575				if (--$this->svg_tag_depth <= 0) {
24576					break;
24577				}
24578			}
24579			case 'g': {
24580				// ungroup: remove last style from array
24581				array_pop($this->svgstyles);
24582				$this->StopTransform();
24583				break;
24584			}
24585			case 'text':
24586			case 'tspan': {
24587				if ($this->svgtextmode['invisible']) {
24588					// This implementation must be fixed to following the rule:
24589					// 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.
24590					break;
24591				}
24592				// print text
24593				$text = $this->svgtext;
24594				//$text = $this->stringTrim($text);
24595				$textlen = $this->GetStringWidth($text);
24596				if ($this->svgtextmode['text-anchor'] != 'start') {
24597					// check if string is RTL text
24598					if ($this->svgtextmode['text-anchor'] == 'end') {
24599						if ($this->svgtextmode['rtl']) {
24600							$this->x += $textlen;
24601						} else {
24602							$this->x -= $textlen;
24603						}
24604					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24605						if ($this->svgtextmode['rtl']) {
24606							$this->x += ($textlen / 2);
24607						} else {
24608							$this->x -= ($textlen / 2);
24609						}
24610					}
24611				}
24612				$textrendermode = $this->textrendermode;
24613				$textstrokewidth = $this->textstrokewidth;
24614				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24615				if ($name == 'text') {
24616					// store current coordinates
24617					$tmpx = $this->x;
24618					$tmpy = $this->y;
24619				}
24620				// print the text
24621				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24622				if ($name == 'text') {
24623					// restore coordinates
24624					$this->x = $tmpx;
24625					$this->y = $tmpy;
24626				}
24627				// restore previous rendering mode
24628				$this->textrendermode = $textrendermode;
24629				$this->textstrokewidth = $textstrokewidth;
24630				$this->svgtext = '';
24631				$this->StopTransform();
24632				if (!$this->svgdefsmode) {
24633					array_pop($this->svgstyles);
24634				}
24635				break;
24636			}
24637			default: {
24638				break;
24639			}
24640		}
24641	}
24642
24643	/**
24644	 * Sets the character data handler function for the XML parser.
24645	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24646	 * @param $data (string) The second parameter, data, contains the character data as a string.
24647	 * @author Nicola Asuni
24648	 * @since 5.0.000 (2010-05-02)
24649	 * @protected
24650	 */
24651	protected function segSVGContentHandler($parser, $data) {
24652		$this->svgtext .= $data;
24653	}
24654
24655	// --- END SVG METHODS -----------------------------------------------------
24656
24657    /**
24658     * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24659     * @param string $file
24660     * @return string
24661     */
24662    protected function getCachedFileContents($file)
24663    {
24664        if (!isset($this->fileContentCache[$file])) {
24665            $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24666        }
24667        return $this->fileContentCache[$file];
24668    }
24669
24670    /**
24671     * Avoid multiple calls to an external server to see if a file exists
24672     * @param string $file
24673     * @return bool
24674     */
24675    protected function fileExists($file)
24676    {
24677        if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24678            return true;
24679        }
24680
24681        return TCPDF_STATIC::file_exists($file);
24682    }
24683
24684} // END OF TCPDF CLASS
24685
24686//============================================================+
24687// END OF FILE
24688//============================================================+
24689