1<?php
2//============================================================+
3// File name   : tcpdf.php
4// Version     : 6.2.26
5// Begin       : 2002-08-03
6// Last Update : 2018-09-14
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-2018 Nicola Asuni - Tecnick.com LTD
11//
12// This file is part of TCPDF software library.
13//
14// TCPDF is free software: you can redistribute it and/or modify it
15// under the terms of the GNU Lesser General Public License as
16// published by the Free Software Foundation, either version 3 of the
17// License, or (at your option) any later version.
18//
19// TCPDF is distributed in the hope that it will be useful, but
20// WITHOUT ANY WARRANTY; without even the implied warranty of
21// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22// See the GNU Lesser General Public License for more details.
23//
24// You should have received a copy of the License
25// along with TCPDF. If not, see
26// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27//
28// See LICENSE.TXT file for more information.
29// -------------------------------------------------------------------
30//
31// Description :
32//   This is a PHP class for generating PDF documents without requiring external extensions.
33//
34// NOTE:
35//   This class was originally derived in 2002 from the Public
36//   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37//   but now is almost entirely rewritten and contains thousands of
38//   new lines of code and hundreds new features.
39//
40// Main features:
41//  * no external libraries are required for the basic functions;
42//  * all standard page formats, custom page formats, custom margins and units of measure;
43//  * UTF-8 Unicode and Right-To-Left languages;
44//  * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45//  * font subsetting;
46//  * methods to publish some XHTML + CSS code, Javascript and Forms;
47//  * images, graphic (geometric figures) and transformation methods;
48//  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
49//  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50//  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51//  * automatic page header and footer management;
52//  * document encryption up to 256 bit and digital signature certifications;
53//  * transactions to UNDO commands;
54//  * PDF annotations, including links, text and file attachments;
55//  * text rendering modes (fill, stroke and clipping);
56//  * multiple columns mode;
57//  * no-write page regions;
58//  * bookmarks, named destinations and table of content;
59//  * text hyphenation;
60//  * text stretching and spacing (tracking);
61//  * automatic page break, line break and text alignments including justification;
62//  * automatic page numbering and page groups;
63//  * move and delete pages;
64//  * page compression (requires php-zlib extension);
65//  * XOBject Templates;
66//  * Layers and object visibility.
67//	* PDF/A-1b support
68//============================================================+
69
70/**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.2.26
108 */
109
110// TCPDF configuration
111require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112// TCPDF static font methods and data
113require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114// TCPDF static font methods and data
115require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116// TCPDF static color methods and data
117require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118// TCPDF static image methods and data
119require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120// TCPDF static methods and data
121require_once(dirname(__FILE__).'/include/tcpdf_static.php');
122
123// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
124
125/**
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.2.26
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	 * PHP internal encoding.
778	 * @protected
779	 * @since 1.53.0.TC016
780	 */
781	protected $internal_encoding;
782
783	/**
784	 * Boolean flag to indicate if the document language is Right-To-Left.
785	 * @protected
786	 * @since 2.0.000
787	 */
788	protected $rtl = false;
789
790	/**
791	 * Boolean flag used to force RTL or LTR string direction.
792	 * @protected
793	 * @since 2.0.000
794	 */
795	protected $tmprtl = false;
796
797	// --- Variables used for document encryption:
798
799	/**
800	 * IBoolean flag indicating whether document is protected.
801	 * @protected
802	 * @since 2.0.000 (2008-01-02)
803	 */
804	protected $encrypted;
805
806	/**
807	 * Array containing encryption settings.
808	 * @protected
809	 * @since 5.0.005 (2010-05-11)
810	 */
811	protected $encryptdata = array();
812
813	/**
814	 * Last RC4 key encrypted (cached for optimisation).
815	 * @protected
816	 * @since 2.0.000 (2008-01-02)
817	 */
818	protected $last_enc_key;
819
820	/**
821	 * Last RC4 computed key.
822	 * @protected
823	 * @since 2.0.000 (2008-01-02)
824	 */
825	protected $last_enc_key_c;
826
827	/**
828	 * File ID (used on document trailer).
829	 * @protected
830	 * @since 5.0.005 (2010-05-12)
831	 */
832	protected $file_id;
833
834	// --- bookmark ---
835
836	/**
837	 * Outlines for bookmark.
838	 * @protected
839	 * @since 2.1.002 (2008-02-12)
840	 */
841	protected $outlines = array();
842
843	/**
844	 * Outline root for bookmark.
845	 * @protected
846	 * @since 2.1.002 (2008-02-12)
847	 */
848	protected $OutlineRoot;
849
850	// --- javascript and form ---
851
852	/**
853	 * Javascript code.
854	 * @protected
855	 * @since 2.1.002 (2008-02-12)
856	 */
857	protected $javascript = '';
858
859	/**
860	 * Javascript counter.
861	 * @protected
862	 * @since 2.1.002 (2008-02-12)
863	 */
864	protected $n_js;
865
866	/**
867	 * line through state
868	 * @protected
869	 * @since 2.8.000 (2008-03-19)
870	 */
871	protected $linethrough;
872
873	/**
874	 * Array with additional document-wide usage rights for the document.
875	 * @protected
876	 * @since 5.8.014 (2010-08-23)
877	 */
878	protected $ur = array();
879
880	/**
881	 * DPI (Dot Per Inch) Document Resolution (do not change).
882	 * @protected
883	 * @since 3.0.000 (2008-03-27)
884	 */
885	protected $dpi = 72;
886
887	/**
888	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
889	 * @protected
890	 * @since 3.0.000 (2008-03-27)
891	 */
892	protected $newpagegroup = array();
893
894	/**
895	 * Array that contains the number of pages in each page group.
896	 * @protected
897	 * @since 3.0.000 (2008-03-27)
898	 */
899	protected $pagegroups = array();
900
901	/**
902	 * Current page group number.
903	 * @protected
904	 * @since 3.0.000 (2008-03-27)
905	 */
906	protected $currpagegroup = 0;
907
908	/**
909	 * Array of transparency objects and parameters.
910	 * @protected
911	 * @since 3.0.000 (2008-03-27)
912	 */
913	protected $extgstates;
914
915	/**
916	 * Set the default JPEG compression quality (1-100).
917	 * @protected
918	 * @since 3.0.000 (2008-03-27)
919	 */
920	protected $jpeg_quality;
921
922	/**
923	 * Default cell height ratio.
924	 * @protected
925	 * @since 3.0.014 (2008-05-23)
926	 */
927	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
928
929	/**
930	 * PDF viewer preferences.
931	 * @protected
932	 * @since 3.1.000 (2008-06-09)
933	 */
934	protected $viewer_preferences;
935
936	/**
937	 * A name object specifying how the document should be displayed when opened.
938	 * @protected
939	 * @since 3.1.000 (2008-06-09)
940	 */
941	protected $PageMode;
942
943	/**
944	 * Array for storing gradient information.
945	 * @protected
946	 * @since 3.1.000 (2008-06-09)
947	 */
948	protected $gradients = array();
949
950	/**
951	 * Array used to store positions inside the pages buffer (keys are the page numbers).
952	 * @protected
953	 * @since 3.2.000 (2008-06-26)
954	 */
955	protected $intmrk = array();
956
957	/**
958	 * Array used to store positions inside the pages buffer (keys are the page numbers).
959	 * @protected
960	 * @since 5.7.000 (2010-08-03)
961	 */
962	protected $bordermrk = array();
963
964	/**
965	 * Array used to store page positions to track empty pages (keys are the page numbers).
966	 * @protected
967	 * @since 5.8.007 (2010-08-18)
968	 */
969	protected $emptypagemrk = array();
970
971	/**
972	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
973	 * @protected
974	 * @since 4.6.021 (2009-07-20)
975	 */
976	protected $cntmrk = array();
977
978	/**
979	 * Array used to store footer positions of each page.
980	 * @protected
981	 * @since 3.2.000 (2008-07-01)
982	 */
983	protected $footerpos = array();
984
985	/**
986	 * Array used to store footer length of each page.
987	 * @protected
988	 * @since 4.0.014 (2008-07-29)
989	 */
990	protected $footerlen = array();
991
992	/**
993	 * Boolean flag to indicate if a new line is created.
994	 * @protected
995	 * @since 3.2.000 (2008-07-01)
996	 */
997	protected $newline = true;
998
999	/**
1000	 * End position of the latest inserted line.
1001	 * @protected
1002	 * @since 3.2.000 (2008-07-01)
1003	 */
1004	protected $endlinex = 0;
1005
1006	/**
1007	 * PDF string for width value of the last line.
1008	 * @protected
1009	 * @since 4.0.006 (2008-07-16)
1010	 */
1011	protected $linestyleWidth = '';
1012
1013	/**
1014	 * PDF string for CAP value of the last line.
1015	 * @protected
1016	 * @since 4.0.006 (2008-07-16)
1017	 */
1018	protected $linestyleCap = '0 J';
1019
1020	/**
1021	 * PDF string for join value of the last line.
1022	 * @protected
1023	 * @since 4.0.006 (2008-07-16)
1024	 */
1025	protected $linestyleJoin = '0 j';
1026
1027	/**
1028	 * PDF string for dash value of the last line.
1029	 * @protected
1030	 * @since 4.0.006 (2008-07-16)
1031	 */
1032	protected $linestyleDash = '[] 0 d';
1033
1034	/**
1035	 * Boolean flag to indicate if marked-content sequence is open.
1036	 * @protected
1037	 * @since 4.0.013 (2008-07-28)
1038	 */
1039	protected $openMarkedContent = false;
1040
1041	/**
1042	 * Count the latest inserted vertical spaces on HTML.
1043	 * @protected
1044	 * @since 4.0.021 (2008-08-24)
1045	 */
1046	protected $htmlvspace = 0;
1047
1048	/**
1049	 * Array of Spot colors.
1050	 * @protected
1051	 * @since 4.0.024 (2008-09-12)
1052	 */
1053	protected $spot_colors = array();
1054
1055	/**
1056	 * Symbol used for HTML unordered list items.
1057	 * @protected
1058	 * @since 4.0.028 (2008-09-26)
1059	 */
1060	protected $lisymbol = '';
1061
1062	/**
1063	 * String used to mark the beginning and end of EPS image blocks.
1064	 * @protected
1065	 * @since 4.1.000 (2008-10-18)
1066	 */
1067	protected $epsmarker = 'x#!#EPS#!#x';
1068
1069	/**
1070	 * Array of transformation matrix.
1071	 * @protected
1072	 * @since 4.2.000 (2008-10-29)
1073	 */
1074	protected $transfmatrix = array();
1075
1076	/**
1077	 * Current key for transformation matrix.
1078	 * @protected
1079	 * @since 4.8.005 (2009-09-17)
1080	 */
1081	protected $transfmatrix_key = 0;
1082
1083	/**
1084	 * Booklet mode for double-sided pages.
1085	 * @protected
1086	 * @since 4.2.000 (2008-10-29)
1087	 */
1088	protected $booklet = false;
1089
1090	/**
1091	 * Epsilon value used for float calculations.
1092	 * @protected
1093	 * @since 4.2.000 (2008-10-29)
1094	 */
1095	protected $feps = 0.005;
1096
1097	/**
1098	 * Array used for custom vertical spaces for HTML tags.
1099	 * @protected
1100	 * @since 4.2.001 (2008-10-30)
1101	 */
1102	protected $tagvspaces = array();
1103
1104	/**
1105	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1106	 * @protected
1107	 * @since 4.2.007 (2008-11-12)
1108	 */
1109	protected $customlistindent = -1;
1110
1111	/**
1112	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1113	 * @protected
1114	 * @since 4.2.010 (2008-11-14)
1115	 */
1116	protected $opencell = true;
1117
1118	/**
1119	 * Array of files to embedd.
1120	 * @protected
1121	 * @since 4.4.000 (2008-12-07)
1122	 */
1123	protected $embeddedfiles = array();
1124
1125	/**
1126	 * Boolean flag to indicate if we are inside a PRE tag.
1127	 * @protected
1128	 * @since 4.4.001 (2008-12-08)
1129	 */
1130	protected $premode = false;
1131
1132	/**
1133	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1134	 * keys are the page numbers
1135	 * @protected
1136	 * @since 4.4.002 (2008-12-09)
1137	 */
1138	protected $transfmrk = array();
1139
1140	/**
1141	 * Default color for html links.
1142	 * @protected
1143	 * @since 4.4.003 (2008-12-09)
1144	 */
1145	protected $htmlLinkColorArray = array(0, 0, 255);
1146
1147	/**
1148	 * Default font style to add to html links.
1149	 * @protected
1150	 * @since 4.4.003 (2008-12-09)
1151	 */
1152	protected $htmlLinkFontStyle = 'U';
1153
1154	/**
1155	 * Counts the number of pages.
1156	 * @protected
1157	 * @since 4.5.000 (2008-12-31)
1158	 */
1159	protected $numpages = 0;
1160
1161	/**
1162	 * Array containing page lengths in bytes.
1163	 * @protected
1164	 * @since 4.5.000 (2008-12-31)
1165	 */
1166	protected $pagelen = array();
1167
1168	/**
1169	 * Counts the number of pages.
1170	 * @protected
1171	 * @since 4.5.000 (2008-12-31)
1172	 */
1173	protected $numimages = 0;
1174
1175	/**
1176	 * Store the image keys.
1177	 * @protected
1178	 * @since 4.5.000 (2008-12-31)
1179	 */
1180	protected $imagekeys = array();
1181
1182	/**
1183	 * Length of the buffer in bytes.
1184	 * @protected
1185	 * @since 4.5.000 (2008-12-31)
1186	 */
1187	protected $bufferlen = 0;
1188
1189	/**
1190	 * Counts the number of fonts.
1191	 * @protected
1192	 * @since 4.5.000 (2009-01-02)
1193	 */
1194	protected $numfonts = 0;
1195
1196	/**
1197	 * Store the font keys.
1198	 * @protected
1199	 * @since 4.5.000 (2009-01-02)
1200	 */
1201	protected $fontkeys = array();
1202
1203	/**
1204	 * Store the font object IDs.
1205	 * @protected
1206	 * @since 4.8.001 (2009-09-09)
1207	 */
1208	protected $font_obj_ids = array();
1209
1210	/**
1211	 * Store the fage status (true when opened, false when closed).
1212	 * @protected
1213	 * @since 4.5.000 (2009-01-02)
1214	 */
1215	protected $pageopen = array();
1216
1217	/**
1218	 * Default monospace font.
1219	 * @protected
1220	 * @since 4.5.025 (2009-03-10)
1221	 */
1222	protected $default_monospaced_font = 'courier';
1223
1224	/**
1225	 * Cloned copy of the current class object.
1226	 * @protected
1227	 * @since 4.5.029 (2009-03-19)
1228	 */
1229	protected $objcopy;
1230
1231	/**
1232	 * Array used to store the lengths of cache files.
1233	 * @protected
1234	 * @since 4.5.029 (2009-03-19)
1235	 */
1236	protected $cache_file_length = array();
1237
1238	/**
1239	 * Table header content to be repeated on each new page.
1240	 * @protected
1241	 * @since 4.5.030 (2009-03-20)
1242	 */
1243	protected $thead = '';
1244
1245	/**
1246	 * Margins used for table header.
1247	 * @protected
1248	 * @since 4.5.030 (2009-03-20)
1249	 */
1250	protected $theadMargins = array();
1251
1252	/**
1253	 * Boolean flag to enable document digital signature.
1254	 * @protected
1255	 * @since 4.6.005 (2009-04-24)
1256	 */
1257	protected $sign = false;
1258
1259	/**
1260	 * Digital signature data.
1261	 * @protected
1262	 * @since 4.6.005 (2009-04-24)
1263	 */
1264	protected $signature_data = array();
1265
1266	/**
1267	 * Digital signature max length.
1268	 * @protected
1269	 * @since 4.6.005 (2009-04-24)
1270	 */
1271	protected $signature_max_length = 11742;
1272
1273	/**
1274	 * Data for digital signature appearance.
1275	 * @protected
1276	 * @since 5.3.011 (2010-06-16)
1277	 */
1278	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1279
1280	/**
1281	 * Array of empty digital signature appearances.
1282	 * @protected
1283	 * @since 5.9.101 (2011-07-06)
1284	 */
1285	protected $empty_signature_appearance = array();
1286
1287	/**
1288	 * Boolean flag to enable document timestamping with TSA.
1289	 * @protected
1290	 * @since 6.0.085 (2014-06-19)
1291	 */
1292	protected $tsa_timestamp = false;
1293
1294	/**
1295	 * Timestamping data.
1296	 * @protected
1297	 * @since 6.0.085 (2014-06-19)
1298	 */
1299	protected $tsa_data = array();
1300
1301	/**
1302	 * Regular expression used to find blank characters (required for word-wrapping).
1303	 * @protected
1304	 * @since 4.6.006 (2009-04-28)
1305	 */
1306	protected $re_spaces = '/[^\S\xa0]/';
1307
1308	/**
1309	 * Array of $re_spaces parts.
1310	 * @protected
1311	 * @since 5.5.011 (2010-07-09)
1312	 */
1313	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1314
1315	/**
1316	 * Digital signature object ID.
1317	 * @protected
1318	 * @since 4.6.022 (2009-06-23)
1319	 */
1320	protected $sig_obj_id = 0;
1321
1322	/**
1323	 * ID of page objects.
1324	 * @protected
1325	 * @since 4.7.000 (2009-08-29)
1326	 */
1327	protected $page_obj_id = array();
1328
1329	/**
1330	 * List of form annotations IDs.
1331	 * @protected
1332	 * @since 4.8.000 (2009-09-07)
1333	 */
1334	protected $form_obj_id = array();
1335
1336	/**
1337	 * 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.
1338	 * @protected
1339	 * @since 4.8.000 (2009-09-07)
1340	 */
1341	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1342
1343	/**
1344	 * Javascript objects array.
1345	 * @protected
1346	 * @since 4.8.000 (2009-09-07)
1347	 */
1348	protected $js_objects = array();
1349
1350	/**
1351	 * Current form action (used during XHTML rendering).
1352	 * @protected
1353	 * @since 4.8.000 (2009-09-07)
1354	 */
1355	protected $form_action = '';
1356
1357	/**
1358	 * Current form encryption type (used during XHTML rendering).
1359	 * @protected
1360	 * @since 4.8.000 (2009-09-07)
1361	 */
1362	protected $form_enctype = 'application/x-www-form-urlencoded';
1363
1364	/**
1365	 * Current method to submit forms.
1366	 * @protected
1367	 * @since 4.8.000 (2009-09-07)
1368	 */
1369	protected $form_mode = 'post';
1370
1371	/**
1372	 * List of fonts used on form fields (fontname => fontkey).
1373	 * @protected
1374	 * @since 4.8.001 (2009-09-09)
1375	 */
1376	protected $annotation_fonts = array();
1377
1378	/**
1379	 * List of radio buttons parent objects.
1380	 * @protected
1381	 * @since 4.8.001 (2009-09-09)
1382	 */
1383	protected $radiobutton_groups = array();
1384
1385	/**
1386	 * List of radio group objects IDs.
1387	 * @protected
1388	 * @since 4.8.001 (2009-09-09)
1389	 */
1390	protected $radio_groups = array();
1391
1392	/**
1393	 * Text indentation value (used for text-indent CSS attribute).
1394	 * @protected
1395	 * @since 4.8.006 (2009-09-23)
1396	 */
1397	protected $textindent = 0;
1398
1399	/**
1400	 * Store page number when startTransaction() is called.
1401	 * @protected
1402	 * @since 4.8.006 (2009-09-23)
1403	 */
1404	protected $start_transaction_page = 0;
1405
1406	/**
1407	 * Store Y position when startTransaction() is called.
1408	 * @protected
1409	 * @since 4.9.001 (2010-03-28)
1410	 */
1411	protected $start_transaction_y = 0;
1412
1413	/**
1414	 * True when we are printing the thead section on a new page.
1415	 * @protected
1416	 * @since 4.8.027 (2010-01-25)
1417	 */
1418	protected $inthead = false;
1419
1420	/**
1421	 * Array of column measures (width, space, starting Y position).
1422	 * @protected
1423	 * @since 4.9.001 (2010-03-28)
1424	 */
1425	protected $columns = array();
1426
1427	/**
1428	 * Number of colums.
1429	 * @protected
1430	 * @since 4.9.001 (2010-03-28)
1431	 */
1432	protected $num_columns = 1;
1433
1434	/**
1435	 * Current column number.
1436	 * @protected
1437	 * @since 4.9.001 (2010-03-28)
1438	 */
1439	protected $current_column = 0;
1440
1441	/**
1442	 * Starting page for columns.
1443	 * @protected
1444	 * @since 4.9.001 (2010-03-28)
1445	 */
1446	protected $column_start_page = 0;
1447
1448	/**
1449	 * Maximum page and column selected.
1450	 * @protected
1451	 * @since 5.8.000 (2010-08-11)
1452	 */
1453	protected $maxselcol = array('page' => 0, 'column' => 0);
1454
1455	/**
1456	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1457	 * @protected
1458	 * @since 5.8.000 (2010-08-11)
1459	 */
1460	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1461
1462	/**
1463	 * 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.
1464	 * @protected
1465	 * @since 4.9.008 (2010-04-03)
1466	 */
1467	protected $textrendermode = 0;
1468
1469	/**
1470	 * Text stroke width in doc units.
1471	 * @protected
1472	 * @since 4.9.008 (2010-04-03)
1473	 */
1474	protected $textstrokewidth = 0;
1475
1476	/**
1477	 * Current stroke color.
1478	 * @protected
1479	 * @since 4.9.008 (2010-04-03)
1480	 */
1481	protected $strokecolor;
1482
1483	/**
1484	 * Default unit of measure for document.
1485	 * @protected
1486	 * @since 5.0.000 (2010-04-22)
1487	 */
1488	protected $pdfunit = 'mm';
1489
1490	/**
1491	 * Boolean flag true when we are on TOC (Table Of Content) page.
1492	 * @protected
1493	 */
1494	protected $tocpage = false;
1495
1496	/**
1497	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1498	 * @protected
1499	 * @since 5.0.000 (2010-04-26)
1500	 */
1501	protected $rasterize_vector_images = false;
1502
1503	/**
1504	 * Boolean flag: if true enables font subsetting by default.
1505	 * @protected
1506	 * @since 5.3.002 (2010-06-07)
1507	 */
1508	protected $font_subsetting = true;
1509
1510	/**
1511	 * Array of default graphic settings.
1512	 * @protected
1513	 * @since 5.5.008 (2010-07-02)
1514	 */
1515	protected $default_graphic_vars = array();
1516
1517	/**
1518	 * Array of XObjects.
1519	 * @protected
1520	 * @since 5.8.014 (2010-08-23)
1521	 */
1522	protected $xobjects = array();
1523
1524	/**
1525	 * Boolean value true when we are inside an XObject.
1526	 * @protected
1527	 * @since 5.8.017 (2010-08-24)
1528	 */
1529	protected $inxobj = false;
1530
1531	/**
1532	 * Current XObject ID.
1533	 * @protected
1534	 * @since 5.8.017 (2010-08-24)
1535	 */
1536	protected $xobjid = '';
1537
1538	/**
1539	 * Percentage of character stretching.
1540	 * @protected
1541	 * @since 5.9.000 (2010-09-29)
1542	 */
1543	protected $font_stretching = 100;
1544
1545	/**
1546	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1547	 * @protected
1548	 * @since 5.9.000 (2010-09-29)
1549	 */
1550	protected $font_spacing = 0;
1551
1552	/**
1553	 * Array of no-write regions.
1554	 * ('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)
1555	 * @protected
1556	 * @since 5.9.003 (2010-10-14)
1557	 */
1558	protected $page_regions = array();
1559
1560	/**
1561	 * Boolean value true when page region check is active.
1562	 * @protected
1563	 */
1564	protected $check_page_regions = true;
1565
1566	/**
1567	 * Array of PDF layers data.
1568	 * @protected
1569	 * @since 5.9.102 (2011-07-13)
1570	 */
1571	protected $pdflayers = array();
1572
1573	/**
1574	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1575	 * @protected
1576	 * @since 5.9.097 (2011-06-23)
1577	 */
1578	protected $dests = array();
1579
1580	/**
1581	 * Object ID for Named Destinations
1582	 * @protected
1583	 * @since 5.9.097 (2011-06-23)
1584	 */
1585	protected $n_dests;
1586
1587	/**
1588	 * Embedded Files Names
1589	 * @protected
1590	 * @since 5.9.204 (2013-01-23)
1591	 */
1592	protected $efnames = array();
1593
1594	/**
1595	 * Directory used for the last SVG image.
1596	 * @protected
1597	 * @since 5.0.000 (2010-05-05)
1598	 */
1599	protected $svgdir = '';
1600
1601	/**
1602	 *  Deafult unit of measure for SVG.
1603	 * @protected
1604	 * @since 5.0.000 (2010-05-02)
1605	 */
1606	protected $svgunit = 'px';
1607
1608	/**
1609	 * Array of SVG gradients.
1610	 * @protected
1611	 * @since 5.0.000 (2010-05-02)
1612	 */
1613	protected $svggradients = array();
1614
1615	/**
1616	 * ID of last SVG gradient.
1617	 * @protected
1618	 * @since 5.0.000 (2010-05-02)
1619	 */
1620	protected $svggradientid = 0;
1621
1622	/**
1623	 * Boolean value true when in SVG defs group.
1624	 * @protected
1625	 * @since 5.0.000 (2010-05-02)
1626	 */
1627	protected $svgdefsmode = false;
1628
1629	/**
1630	 * Array of SVG defs.
1631	 * @protected
1632	 * @since 5.0.000 (2010-05-02)
1633	 */
1634	protected $svgdefs = array();
1635
1636	/**
1637	 * Boolean value true when in SVG clipPath tag.
1638	 * @protected
1639	 * @since 5.0.000 (2010-04-26)
1640	 */
1641	protected $svgclipmode = false;
1642
1643	/**
1644	 * Array of SVG clipPath commands.
1645	 * @protected
1646	 * @since 5.0.000 (2010-05-02)
1647	 */
1648	protected $svgclippaths = array();
1649
1650	/**
1651	 * Array of SVG clipPath tranformation matrix.
1652	 * @protected
1653	 * @since 5.8.022 (2010-08-31)
1654	 */
1655	protected $svgcliptm = array();
1656
1657	/**
1658	 * ID of last SVG clipPath.
1659	 * @protected
1660	 * @since 5.0.000 (2010-05-02)
1661	 */
1662	protected $svgclipid = 0;
1663
1664	/**
1665	 * SVG text.
1666	 * @protected
1667	 * @since 5.0.000 (2010-05-02)
1668	 */
1669	protected $svgtext = '';
1670
1671	/**
1672	 * SVG text properties.
1673	 * @protected
1674	 * @since 5.8.013 (2010-08-23)
1675	 */
1676	protected $svgtextmode = array();
1677
1678	/**
1679	 * Array of SVG properties.
1680	 * @protected
1681	 * @since 5.0.000 (2010-05-02)
1682	 */
1683	protected $svgstyles = array(array(
1684		'alignment-baseline' => 'auto',
1685		'baseline-shift' => 'baseline',
1686		'clip' => 'auto',
1687		'clip-path' => 'none',
1688		'clip-rule' => 'nonzero',
1689		'color' => 'black',
1690		'color-interpolation' => 'sRGB',
1691		'color-interpolation-filters' => 'linearRGB',
1692		'color-profile' => 'auto',
1693		'color-rendering' => 'auto',
1694		'cursor' => 'auto',
1695		'direction' => 'ltr',
1696		'display' => 'inline',
1697		'dominant-baseline' => 'auto',
1698		'enable-background' => 'accumulate',
1699		'fill' => 'black',
1700		'fill-opacity' => 1,
1701		'fill-rule' => 'nonzero',
1702		'filter' => 'none',
1703		'flood-color' => 'black',
1704		'flood-opacity' => 1,
1705		'font' => '',
1706		'font-family' => 'helvetica',
1707		'font-size' => 'medium',
1708		'font-size-adjust' => 'none',
1709		'font-stretch' => 'normal',
1710		'font-style' => 'normal',
1711		'font-variant' => 'normal',
1712		'font-weight' => 'normal',
1713		'glyph-orientation-horizontal' => '0deg',
1714		'glyph-orientation-vertical' => 'auto',
1715		'image-rendering' => 'auto',
1716		'kerning' => 'auto',
1717		'letter-spacing' => 'normal',
1718		'lighting-color' => 'white',
1719		'marker' => '',
1720		'marker-end' => 'none',
1721		'marker-mid' => 'none',
1722		'marker-start' => 'none',
1723		'mask' => 'none',
1724		'opacity' => 1,
1725		'overflow' => 'auto',
1726		'pointer-events' => 'visiblePainted',
1727		'shape-rendering' => 'auto',
1728		'stop-color' => 'black',
1729		'stop-opacity' => 1,
1730		'stroke' => 'none',
1731		'stroke-dasharray' => 'none',
1732		'stroke-dashoffset' => 0,
1733		'stroke-linecap' => 'butt',
1734		'stroke-linejoin' => 'miter',
1735		'stroke-miterlimit' => 4,
1736		'stroke-opacity' => 1,
1737		'stroke-width' => 1,
1738		'text-anchor' => 'start',
1739		'text-decoration' => 'none',
1740		'text-rendering' => 'auto',
1741		'unicode-bidi' => 'normal',
1742		'visibility' => 'visible',
1743		'word-spacing' => 'normal',
1744		'writing-mode' => 'lr-tb',
1745		'text-color' => 'black',
1746		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1747		));
1748
1749	/**
1750	 * If true force sRGB color profile for all document.
1751	 * @protected
1752	 * @since 5.9.121 (2011-09-28)
1753	 */
1754	protected $force_srgb = false;
1755
1756	/**
1757	 * If true set the document to PDF/A mode.
1758	 * @protected
1759	 * @since 5.9.121 (2011-09-27)
1760	 */
1761	protected $pdfa_mode = false;
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	 * Overprint mode array.
1786	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1787	 * @protected
1788	 * @since 5.9.152 (2012-03-23)
1789	 */
1790	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1791
1792	/**
1793	 * Alpha mode array.
1794	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1795	 * @protected
1796	 * @since 5.9.152 (2012-03-23)
1797	 */
1798	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1799
1800	/**
1801	 * Define the page boundaries boxes to be set on document.
1802	 * @protected
1803	 * @since 5.9.152 (2012-03-23)
1804	 */
1805	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1806
1807	/**
1808	 * If true print TCPDF meta link.
1809	 * @protected
1810	 * @since 5.9.152 (2012-03-23)
1811	 */
1812	protected $tcpdflink = true;
1813
1814	/**
1815	 * Cache array for computed GD gamma values.
1816	 * @protected
1817	 * @since 5.9.1632 (2012-06-05)
1818	 */
1819	protected $gdgammacache = array();
1820
1821	//------------------------------------------------------------
1822	// METHODS
1823	//------------------------------------------------------------
1824
1825	/**
1826	 * This is the class constructor.
1827	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1828	 *
1829	 * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
1830	 *
1831	 * @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>
1832	 * @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.
1833	 * @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().
1834	 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1835	 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1836	 * @param $diskcache (boolean) DEPRECATED FEATURE
1837	 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1838	 * @public
1839	 * @see getPageSizeFromFormat(), setPageFormat()
1840	 */
1841	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1842		/* Set internal character encoding to ASCII */
1843		if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1844			$this->internal_encoding = mb_internal_encoding();
1845			mb_internal_encoding('ASCII');
1846		}
1847		// set file ID for trailer
1848		$serformat = (is_array($format) ? json_encode($format) : $format);
1849		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1850		$this->font_obj_ids = array();
1851		$this->page_obj_id = array();
1852		$this->form_obj_id = array();
1853		// set pdf/a mode
1854		$this->pdfa_mode = $pdfa;
1855		$this->force_srgb = false;
1856		// set language direction
1857		$this->rtl = false;
1858		$this->tmprtl = false;
1859		// some checks
1860		$this->_dochecks();
1861		// initialization of properties
1862		$this->isunicode = $unicode;
1863		$this->page = 0;
1864		$this->transfmrk[0] = array();
1865		$this->pagedim = array();
1866		$this->n = 2;
1867		$this->buffer = '';
1868		$this->pages = array();
1869		$this->state = 0;
1870		$this->fonts = array();
1871		$this->FontFiles = array();
1872		$this->diffs = array();
1873		$this->images = array();
1874		$this->links = array();
1875		$this->gradients = array();
1876		$this->InFooter = false;
1877		$this->lasth = 0;
1878		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1879		$this->FontStyle = '';
1880		$this->FontSizePt = 12;
1881		$this->underline = false;
1882		$this->overline = false;
1883		$this->linethrough = false;
1884		$this->DrawColor = '0 G';
1885		$this->FillColor = '0 g';
1886		$this->TextColor = '0 g';
1887		$this->ColorFlag = false;
1888		$this->pdflayers = array();
1889		// encryption values
1890		$this->encrypted = false;
1891		$this->last_enc_key = '';
1892		// standard Unicode fonts
1893		$this->CoreFonts = array(
1894			'courier'=>'Courier',
1895			'courierB'=>'Courier-Bold',
1896			'courierI'=>'Courier-Oblique',
1897			'courierBI'=>'Courier-BoldOblique',
1898			'helvetica'=>'Helvetica',
1899			'helveticaB'=>'Helvetica-Bold',
1900			'helveticaI'=>'Helvetica-Oblique',
1901			'helveticaBI'=>'Helvetica-BoldOblique',
1902			'times'=>'Times-Roman',
1903			'timesB'=>'Times-Bold',
1904			'timesI'=>'Times-Italic',
1905			'timesBI'=>'Times-BoldItalic',
1906			'symbol'=>'Symbol',
1907			'zapfdingbats'=>'ZapfDingbats'
1908		);
1909		// set scale factor
1910		$this->setPageUnit($unit);
1911		// set page format and orientation
1912		$this->setPageFormat($format, $orientation);
1913		// page margins (1 cm)
1914		$margin = 28.35 / $this->k;
1915		$this->SetMargins($margin, $margin);
1916		$this->clMargin = $this->lMargin;
1917		$this->crMargin = $this->rMargin;
1918		// internal cell padding
1919		$cpadding = $margin / 10;
1920		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1921		// cell margins
1922		$this->setCellMargins(0, 0, 0, 0);
1923		// line width (0.2 mm)
1924		$this->LineWidth = 0.57 / $this->k;
1925		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1926		$this->linestyleCap = '0 J';
1927		$this->linestyleJoin = '0 j';
1928		$this->linestyleDash = '[] 0 d';
1929		// automatic page break
1930		$this->SetAutoPageBreak(true, (2 * $margin));
1931		// full width display mode
1932		$this->SetDisplayMode('fullwidth');
1933		// compression
1934		$this->SetCompression();
1935		// set default PDF version number
1936		$this->setPDFVersion();
1937		$this->tcpdflink = true;
1938		$this->encoding = $encoding;
1939		$this->HREF = array();
1940		$this->getFontsList();
1941		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1942		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1943		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1944		$this->extgstates = array();
1945		$this->setTextShadow();
1946		// signature
1947		$this->sign = false;
1948		$this->tsa_timestamp = false;
1949		$this->tsa_data = array();
1950		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1951		$this->empty_signature_appearance = array();
1952		// user's rights
1953		$this->ur['enabled'] = false;
1954		$this->ur['document'] = '/FullSave';
1955		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1956		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1957		$this->ur['signature'] = '/Modify';
1958		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1959		$this->ur['formex'] = '';
1960		// set default JPEG quality
1961		$this->jpeg_quality = 75;
1962		// initialize some settings
1963		TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1964		// set default font
1965		$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1966		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1967		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1968		// check if PCRE Unicode support is enabled
1969		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1970			// PCRE unicode support is turned ON
1971			// \s     : any whitespace character
1972			// \p{Z}  : any separator
1973			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1974			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1975			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1976			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1977		} else {
1978			// PCRE unicode support is turned OFF
1979			$this->setSpacesRE('/[^\S\xa0]/');
1980		}
1981		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1982		// set document creation and modification timestamp
1983		$this->doc_creation_timestamp = time();
1984		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
1985		// get default graphic vars
1986		$this->default_graphic_vars = $this->getGraphicVars();
1987		$this->header_xobj_autoreset = false;
1988		$this->custom_xmp = '';
1989		// Call cleanup method after script execution finishes or exit() is called.
1990		// NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
1991		register_shutdown_function(array($this, '_destroy'), true);
1992	}
1993
1994	/**
1995	 * Default destructor.
1996	 * @public
1997	 * @since 1.53.0.TC016
1998	 */
1999	public function __destruct() {
2000		// cleanup
2001		$this->_destroy(true);
2002	}
2003
2004	/**
2005	 * Set the units of measure for the document.
2006	 * @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.
2007	 * @public
2008	 * @since 3.0.015 (2008-06-06)
2009	 */
2010	public function setPageUnit($unit) {
2011		$unit = strtolower($unit);
2012		//Set scale factor
2013		switch ($unit) {
2014			// points
2015			case 'px':
2016			case 'pt': {
2017				$this->k = 1;
2018				break;
2019			}
2020			// millimeters
2021			case 'mm': {
2022				$this->k = $this->dpi / 25.4;
2023				break;
2024			}
2025			// centimeters
2026			case 'cm': {
2027				$this->k = $this->dpi / 2.54;
2028				break;
2029			}
2030			// inches
2031			case 'in': {
2032				$this->k = $this->dpi;
2033				break;
2034			}
2035			// unsupported unit
2036			default : {
2037				$this->Error('Incorrect unit: '.$unit);
2038				break;
2039			}
2040		}
2041		$this->pdfunit = $unit;
2042		if (isset($this->CurOrientation)) {
2043			$this->setPageOrientation($this->CurOrientation);
2044		}
2045	}
2046
2047	/**
2048	 * Change the format of the current page
2049	 * @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>
2050	 * <li>['format'] = page format name (one of the above);</li>
2051	 * <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>
2052	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2053	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2054	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2055	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2056	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2057	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2058	 * <li>['CropBox'] : the visible region of default user space:</li>
2059	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2060	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2061	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2062	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2063	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2064	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2065	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2066	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2067	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2068	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2069	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2070	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2071	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2072	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2073	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2074	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2075	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2076	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2077	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2078	 * <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>
2079	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2080	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2081	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2082	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2083	 * <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>
2084	 * <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>
2085	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2086	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2087	 * <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>
2088	 * <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>
2089	 * <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>
2090	 * <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>
2091	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2092	 * </ul>
2093	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2094	 * <li>P or Portrait (default)</li>
2095	 * <li>L or Landscape</li>
2096	 * <li>'' (empty string) for automatic orientation</li>
2097	 * </ul>
2098	 * @protected
2099	 * @since 3.0.015 (2008-06-06)
2100	 * @see getPageSizeFromFormat()
2101	 */
2102	protected function setPageFormat($format, $orientation='P') {
2103		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2104			// remove inherited values
2105			unset($this->pagedim[$this->page]);
2106		}
2107		if (is_string($format)) {
2108			// get page measures from format name
2109			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2110			$this->fwPt = $pf[0];
2111			$this->fhPt = $pf[1];
2112		} else {
2113			// the boundaries of the physical medium on which the page shall be displayed or printed
2114			if (isset($format['MediaBox'])) {
2115				$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);
2116				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2117				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2118			} else {
2119				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2120					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2121				} else {
2122					if (!isset($format['format'])) {
2123						// default value
2124						$format['format'] = 'A4';
2125					}
2126					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2127				}
2128				$this->fwPt = $pf[0];
2129				$this->fhPt = $pf[1];
2130				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2131			}
2132			// the visible region of default user space
2133			if (isset($format['CropBox'])) {
2134				$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);
2135			}
2136			// the region to which the contents of the page shall be clipped when output in a production environment
2137			if (isset($format['BleedBox'])) {
2138				$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);
2139			}
2140			// the intended dimensions of the finished page after trimming
2141			if (isset($format['TrimBox'])) {
2142				$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);
2143			}
2144			// the page's meaningful content (including potential white space)
2145			if (isset($format['ArtBox'])) {
2146				$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);
2147			}
2148			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2149			if (isset($format['BoxColorInfo'])) {
2150				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2151			}
2152			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2153				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2154				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2155			}
2156			if (isset($format['PZ'])) {
2157				// The page's preferred zoom (magnification) factor
2158				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2159			}
2160			if (isset($format['trans'])) {
2161				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2162				if (isset($format['trans']['Dur'])) {
2163					// The page's display duration
2164					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2165				}
2166				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2167				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2168					// The transition style that shall be used when moving to this page from another during a presentation
2169					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2170					$valid_effect = array('Split', 'Blinds');
2171					$valid_vals = array('H', 'V');
2172					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2173						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2174					}
2175					$valid_effect = array('Split', 'Box', 'Fly');
2176					$valid_vals = array('I', 'O');
2177					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2178						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2179					}
2180					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2181					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2182						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2183							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2184							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2185							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2186						}
2187					}
2188					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2189						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2190					}
2191					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2192						$this->pagedim[$this->page]['trans']['B'] = 'true';
2193					}
2194				} else {
2195					$this->pagedim[$this->page]['trans']['S'] = 'R';
2196				}
2197				if (isset($format['trans']['D'])) {
2198					// The duration of the transition effect, in seconds
2199					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2200				} else {
2201					$this->pagedim[$this->page]['trans']['D'] = 1;
2202				}
2203			}
2204		}
2205		$this->setPageOrientation($orientation);
2206	}
2207
2208	/**
2209	 * Set page orientation.
2210	 * @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>
2211	 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2212	 * @param $bottommargin (float) bottom margin of the page.
2213	 * @public
2214	 * @since 3.0.015 (2008-06-06)
2215	 */
2216	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2217		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2218			// the boundaries of the physical medium on which the page shall be displayed or printed
2219			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2220		}
2221		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2222			// the visible region of default user space
2223			$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);
2224		}
2225		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2226			// the region to which the contents of the page shall be clipped when output in a production environment
2227			$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);
2228		}
2229		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2230			// the intended dimensions of the finished page after trimming
2231			$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);
2232		}
2233		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2234			// the page's meaningful content (including potential white space)
2235			$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);
2236		}
2237		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2238			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2239			$this->pagedim[$this->page]['Rotate'] = 0;
2240		}
2241		if (!isset($this->pagedim[$this->page]['PZ'])) {
2242			// The page's preferred zoom (magnification) factor
2243			$this->pagedim[$this->page]['PZ'] = 1;
2244		}
2245		if ($this->fwPt > $this->fhPt) {
2246			// landscape
2247			$default_orientation = 'L';
2248		} else {
2249			// portrait
2250			$default_orientation = 'P';
2251		}
2252		$valid_orientations = array('P', 'L');
2253		if (empty($orientation)) {
2254			$orientation = $default_orientation;
2255		} else {
2256			$orientation = strtoupper($orientation[0]);
2257		}
2258		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2259			$this->CurOrientation = $orientation;
2260			$this->wPt = $this->fhPt;
2261			$this->hPt = $this->fwPt;
2262		} else {
2263			$this->CurOrientation = $default_orientation;
2264			$this->wPt = $this->fwPt;
2265			$this->hPt = $this->fhPt;
2266		}
2267		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2268			// swap X and Y coordinates (change page orientation)
2269			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2270		}
2271		$this->w = ($this->wPt / $this->k);
2272		$this->h = ($this->hPt / $this->k);
2273		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2274			if (isset($this->AutoPageBreak)) {
2275				$autopagebreak = $this->AutoPageBreak;
2276			} else {
2277				$autopagebreak = true;
2278			}
2279		}
2280		if (TCPDF_STATIC::empty_string($bottommargin)) {
2281			if (isset($this->bMargin)) {
2282				$bottommargin = $this->bMargin;
2283			} else {
2284				// default value = 2 cm
2285				$bottommargin = 2 * 28.35 / $this->k;
2286			}
2287		}
2288		$this->SetAutoPageBreak($autopagebreak, $bottommargin);
2289		// store page dimensions
2290		$this->pagedim[$this->page]['w'] = $this->wPt;
2291		$this->pagedim[$this->page]['h'] = $this->hPt;
2292		$this->pagedim[$this->page]['wk'] = $this->w;
2293		$this->pagedim[$this->page]['hk'] = $this->h;
2294		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2295		$this->pagedim[$this->page]['bm'] = $bottommargin;
2296		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2297		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2298		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2299		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2300		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2301		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2302	}
2303
2304	/**
2305	 * Set regular expression to detect withespaces or word separators.
2306	 * The pattern delimiter must be the forward-slash character "/".
2307	 * Some example patterns are:
2308	 * <pre>
2309	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2310	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2311	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2312	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2313	 *      \s     : any whitespace character
2314	 *      \p{Z}  : any separator
2315	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2316	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2317	 * </pre>
2318	 * @param $re (string) regular expression (leave empty for default).
2319	 * @public
2320	 * @since 4.6.016 (2009-06-15)
2321	 */
2322	public function setSpacesRE($re='/[^\S\xa0]/') {
2323		$this->re_spaces = $re;
2324		$re_parts = explode('/', $re);
2325		// get pattern parts
2326		$this->re_space = array();
2327		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2328			$this->re_space['p'] = $re_parts[1];
2329		} else {
2330			$this->re_space['p'] = '[\s]';
2331		}
2332		// set pattern modifiers
2333		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2334			$this->re_space['m'] = $re_parts[2];
2335		} else {
2336			$this->re_space['m'] = '';
2337		}
2338	}
2339
2340	/**
2341	 * Enable or disable Right-To-Left language mode
2342	 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2343	 * @param $resetx (Boolean) if true reset the X position on direction change.
2344	 * @public
2345	 * @since 2.0.000 (2008-01-03)
2346	 */
2347	public function setRTL($enable, $resetx=true) {
2348		$enable = $enable ? true : false;
2349		$resetx = ($resetx AND ($enable != $this->rtl));
2350		$this->rtl = $enable;
2351		$this->tmprtl = false;
2352		if ($resetx) {
2353			$this->Ln(0);
2354		}
2355	}
2356
2357	/**
2358	 * Return the RTL status
2359	 * @return boolean
2360	 * @public
2361	 * @since 4.0.012 (2008-07-24)
2362	 */
2363	public function getRTL() {
2364		return $this->rtl;
2365	}
2366
2367	/**
2368	 * Force temporary RTL language direction
2369	 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2370	 * @public
2371	 * @since 2.1.000 (2008-01-09)
2372	 */
2373	public function setTempRTL($mode) {
2374		$newmode = false;
2375		switch (strtoupper($mode)) {
2376			case 'LTR':
2377			case 'L': {
2378				if ($this->rtl) {
2379					$newmode = 'L';
2380				}
2381				break;
2382			}
2383			case 'RTL':
2384			case 'R': {
2385				if (!$this->rtl) {
2386					$newmode = 'R';
2387				}
2388				break;
2389			}
2390			case false:
2391			default: {
2392				$newmode = false;
2393				break;
2394			}
2395		}
2396		$this->tmprtl = $newmode;
2397	}
2398
2399	/**
2400	 * Return the current temporary RTL status
2401	 * @return boolean
2402	 * @public
2403	 * @since 4.8.014 (2009-11-04)
2404	 */
2405	public function isRTLTextDir() {
2406		return ($this->rtl OR ($this->tmprtl == 'R'));
2407	}
2408
2409	/**
2410	 * Set the last cell height.
2411	 * @param $h (float) cell height.
2412	 * @author Nicola Asuni
2413	 * @public
2414	 * @since 1.53.0.TC034
2415	 */
2416	public function setLastH($h) {
2417		$this->lasth = $h;
2418	}
2419
2420	/**
2421	 * Return the cell height
2422	 * @param $fontsize (int) Font size in internal units
2423	 * @param $padding (boolean) If true add cell padding
2424	 * @public
2425	 */
2426	public function getCellHeight($fontsize, $padding=TRUE) {
2427		$height = ($fontsize * $this->cell_height_ratio);
2428		if ($padding) {
2429			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2430		}
2431		return round($height, 6);
2432	}
2433
2434	/**
2435	 * Reset the last cell height.
2436	 * @public
2437	 * @since 5.9.000 (2010-10-03)
2438	 */
2439	public function resetLastH() {
2440		$this->lasth = $this->getCellHeight($this->FontSize);
2441	}
2442
2443	/**
2444	 * Get the last cell height.
2445	 * @return last cell height
2446	 * @public
2447	 * @since 4.0.017 (2008-08-05)
2448	 */
2449	public function getLastH() {
2450		return $this->lasth;
2451	}
2452
2453	/**
2454	 * Set the adjusting factor to convert pixels to user units.
2455	 * @param $scale (float) adjusting factor to convert pixels to user units.
2456	 * @author Nicola Asuni
2457	 * @public
2458	 * @since 1.5.2
2459	 */
2460	public function setImageScale($scale) {
2461		$this->imgscale = $scale;
2462	}
2463
2464	/**
2465	 * Returns the adjusting factor to convert pixels to user units.
2466	 * @return float adjusting factor to convert pixels to user units.
2467	 * @author Nicola Asuni
2468	 * @public
2469	 * @since 1.5.2
2470	 */
2471	public function getImageScale() {
2472		return $this->imgscale;
2473	}
2474
2475	/**
2476	 * Returns an array of page dimensions:
2477	 * <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>
2478	 * @param $pagenum (int) page number (empty = current page)
2479	 * @return array of page dimensions.
2480	 * @author Nicola Asuni
2481	 * @public
2482	 * @since 4.5.027 (2009-03-16)
2483	 */
2484	public function getPageDimensions($pagenum='') {
2485		if (empty($pagenum)) {
2486			$pagenum = $this->page;
2487		}
2488		return $this->pagedim[$pagenum];
2489	}
2490
2491	/**
2492	 * Returns the page width in units.
2493	 * @param $pagenum (int) page number (empty = current page)
2494	 * @return int page width.
2495	 * @author Nicola Asuni
2496	 * @public
2497	 * @since 1.5.2
2498	 * @see getPageDimensions()
2499	 */
2500	public function getPageWidth($pagenum='') {
2501		if (empty($pagenum)) {
2502			return $this->w;
2503		}
2504		return $this->pagedim[$pagenum]['w'];
2505	}
2506
2507	/**
2508	 * Returns the page height in units.
2509	 * @param $pagenum (int) page number (empty = current page)
2510	 * @return int page height.
2511	 * @author Nicola Asuni
2512	 * @public
2513	 * @since 1.5.2
2514	 * @see getPageDimensions()
2515	 */
2516	public function getPageHeight($pagenum='') {
2517		if (empty($pagenum)) {
2518			return $this->h;
2519		}
2520		return $this->pagedim[$pagenum]['h'];
2521	}
2522
2523	/**
2524	 * Returns the page break margin.
2525	 * @param $pagenum (int) page number (empty = current page)
2526	 * @return int page break margin.
2527	 * @author Nicola Asuni
2528	 * @public
2529	 * @since 1.5.2
2530	 * @see getPageDimensions()
2531	 */
2532	public function getBreakMargin($pagenum='') {
2533		if (empty($pagenum)) {
2534			return $this->bMargin;
2535		}
2536		return $this->pagedim[$pagenum]['bm'];
2537	}
2538
2539	/**
2540	 * Returns the scale factor (number of points in user unit).
2541	 * @return int scale factor.
2542	 * @author Nicola Asuni
2543	 * @public
2544	 * @since 1.5.2
2545	 */
2546	public function getScaleFactor() {
2547		return $this->k;
2548	}
2549
2550	/**
2551	 * Defines the left, top and right margins.
2552	 * @param $left (float) Left margin.
2553	 * @param $top (float) Top margin.
2554	 * @param $right (float) Right margin. Default value is the left one.
2555	 * @param $keepmargins (boolean) if true overwrites the default page margins
2556	 * @public
2557	 * @since 1.0
2558	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2559	 */
2560	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2561		//Set left, top and right margins
2562		$this->lMargin = $left;
2563		$this->tMargin = $top;
2564		if ($right == -1) {
2565			$right = $left;
2566		}
2567		$this->rMargin = $right;
2568		if ($keepmargins) {
2569			// overwrite original values
2570			$this->original_lMargin = $this->lMargin;
2571			$this->original_rMargin = $this->rMargin;
2572		}
2573	}
2574
2575	/**
2576	 * 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.
2577	 * @param $margin (float) The margin.
2578	 * @public
2579	 * @since 1.4
2580	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2581	 */
2582	public function SetLeftMargin($margin) {
2583		//Set left margin
2584		$this->lMargin = $margin;
2585		if (($this->page > 0) AND ($this->x < $margin)) {
2586			$this->x = $margin;
2587		}
2588	}
2589
2590	/**
2591	 * Defines the top margin. The method can be called before creating the first page.
2592	 * @param $margin (float) The margin.
2593	 * @public
2594	 * @since 1.5
2595	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2596	 */
2597	public function SetTopMargin($margin) {
2598		//Set top margin
2599		$this->tMargin = $margin;
2600		if (($this->page > 0) AND ($this->y < $margin)) {
2601			$this->y = $margin;
2602		}
2603	}
2604
2605	/**
2606	 * Defines the right margin. The method can be called before creating the first page.
2607	 * @param $margin (float) The margin.
2608	 * @public
2609	 * @since 1.5
2610	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2611	 */
2612	public function SetRightMargin($margin) {
2613		$this->rMargin = $margin;
2614		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2615			$this->x = $this->w - $margin;
2616		}
2617	}
2618
2619	/**
2620	 * Set the same internal Cell padding for top, right, bottom, left-
2621	 * @param $pad (float) internal padding.
2622	 * @public
2623	 * @since 2.1.000 (2008-01-09)
2624	 * @see getCellPaddings(), setCellPaddings()
2625	 */
2626	public function SetCellPadding($pad) {
2627		if ($pad >= 0) {
2628			$this->cell_padding['L'] = $pad;
2629			$this->cell_padding['T'] = $pad;
2630			$this->cell_padding['R'] = $pad;
2631			$this->cell_padding['B'] = $pad;
2632		}
2633	}
2634
2635	/**
2636	 * Set the internal Cell paddings.
2637	 * @param $left (float) left padding
2638	 * @param $top (float) top padding
2639	 * @param $right (float) right padding
2640	 * @param $bottom (float) bottom padding
2641	 * @public
2642	 * @since 5.9.000 (2010-10-03)
2643	 * @see getCellPaddings(), SetCellPadding()
2644	 */
2645	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2646		if (($left !== '') AND ($left >= 0)) {
2647			$this->cell_padding['L'] = $left;
2648		}
2649		if (($top !== '') AND ($top >= 0)) {
2650			$this->cell_padding['T'] = $top;
2651		}
2652		if (($right !== '') AND ($right >= 0)) {
2653			$this->cell_padding['R'] = $right;
2654		}
2655		if (($bottom !== '') AND ($bottom >= 0)) {
2656			$this->cell_padding['B'] = $bottom;
2657		}
2658	}
2659
2660	/**
2661	 * Get the internal Cell padding array.
2662	 * @return array of padding values
2663	 * @public
2664	 * @since 5.9.000 (2010-10-03)
2665	 * @see setCellPaddings(), SetCellPadding()
2666	 */
2667	public function getCellPaddings() {
2668		return $this->cell_padding;
2669	}
2670
2671	/**
2672	 * Set the internal Cell margins.
2673	 * @param $left (float) left margin
2674	 * @param $top (float) top margin
2675	 * @param $right (float) right margin
2676	 * @param $bottom (float) bottom margin
2677	 * @public
2678	 * @since 5.9.000 (2010-10-03)
2679	 * @see getCellMargins()
2680	 */
2681	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2682		if (($left !== '') AND ($left >= 0)) {
2683			$this->cell_margin['L'] = $left;
2684		}
2685		if (($top !== '') AND ($top >= 0)) {
2686			$this->cell_margin['T'] = $top;
2687		}
2688		if (($right !== '') AND ($right >= 0)) {
2689			$this->cell_margin['R'] = $right;
2690		}
2691		if (($bottom !== '') AND ($bottom >= 0)) {
2692			$this->cell_margin['B'] = $bottom;
2693		}
2694	}
2695
2696	/**
2697	 * Get the internal Cell margin array.
2698	 * @return array of margin values
2699	 * @public
2700	 * @since 5.9.000 (2010-10-03)
2701	 * @see setCellMargins()
2702	 */
2703	public function getCellMargins() {
2704		return $this->cell_margin;
2705	}
2706
2707	/**
2708	 * Adjust the internal Cell padding array to take account of the line width.
2709	 * @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)))
2710	 * @return array of adjustments
2711	 * @public
2712	 * @since 5.9.000 (2010-10-03)
2713	 */
2714	protected function adjustCellPadding($brd=0) {
2715		if (empty($brd)) {
2716			return;
2717		}
2718		if (is_string($brd)) {
2719			// convert string to array
2720			$slen = strlen($brd);
2721			$newbrd = array();
2722			for ($i = 0; $i < $slen; ++$i) {
2723				$newbrd[$brd[$i]] = true;
2724			}
2725			$brd = $newbrd;
2726		} elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2727			$brd = array('LRTB' => true);
2728		}
2729		if (!is_array($brd)) {
2730			return;
2731		}
2732		// store current cell padding
2733		$cp = $this->cell_padding;
2734		// select border mode
2735		if (isset($brd['mode'])) {
2736			$mode = $brd['mode'];
2737			unset($brd['mode']);
2738		} else {
2739			$mode = 'normal';
2740		}
2741		// process borders
2742		foreach ($brd as $border => $style) {
2743			$line_width = $this->LineWidth;
2744			if (is_array($style) AND isset($style['width'])) {
2745				// get border width
2746				$line_width = $style['width'];
2747			}
2748			$adj = 0; // line width inside the cell
2749			switch ($mode) {
2750				case 'ext': {
2751					$adj = 0;
2752					break;
2753				}
2754				case 'int': {
2755					$adj = $line_width;
2756					break;
2757				}
2758				case 'normal':
2759				default: {
2760					$adj = ($line_width / 2);
2761					break;
2762				}
2763			}
2764			// correct internal cell padding if required to avoid overlap between text and lines
2765			if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2766				$this->cell_padding['T'] = $adj;
2767			}
2768			if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2769				$this->cell_padding['R'] = $adj;
2770			}
2771			if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2772				$this->cell_padding['B'] = $adj;
2773			}
2774			if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2775				$this->cell_padding['L'] = $adj;
2776			}
2777		}
2778		return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
2779	}
2780
2781	/**
2782	 * 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.
2783	 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2784	 * @param $margin (float) Distance from the bottom of the page.
2785	 * @public
2786	 * @since 1.0
2787	 * @see Cell(), MultiCell(), AcceptPageBreak()
2788	 */
2789	public function SetAutoPageBreak($auto, $margin=0) {
2790		$this->AutoPageBreak = $auto ? true : false;
2791		$this->bMargin = $margin;
2792		$this->PageBreakTrigger = $this->h - $margin;
2793	}
2794
2795	/**
2796	 * Return the auto-page-break mode (true or false).
2797	 * @return boolean auto-page-break mode
2798	 * @public
2799	 * @since 5.9.088
2800	 */
2801	public function getAutoPageBreak() {
2802		return $this->AutoPageBreak;
2803	}
2804
2805	/**
2806	 * Defines the way the document is to be displayed by the viewer.
2807	 * @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>
2808	 * @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>
2809	 * @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>
2810	 * @public
2811	 * @since 1.2
2812	 */
2813	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2814		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2815			$this->ZoomMode = $zoom;
2816		} else {
2817			$this->Error('Incorrect zoom display mode: '.$zoom);
2818		}
2819		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2820		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2821	}
2822
2823	/**
2824	 * 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.
2825	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2826	 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2827	 * @public
2828	 * @since 1.4
2829	 */
2830	public function SetCompression($compress=true) {
2831		if (function_exists('gzcompress')) {
2832			$this->compress = $compress ? true : false;
2833		} else {
2834			$this->compress = false;
2835		}
2836	}
2837
2838	/**
2839	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2840	 * @param $mode (boolean) If true force sRGB output intent.
2841	 * @public
2842	 * @since 5.9.121 (2011-09-28)
2843	 */
2844	public function setSRGBmode($mode=false) {
2845		$this->force_srgb = $mode ? true : false;
2846	}
2847
2848	/**
2849	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2850	 * This has effect only when unicode mode is set to false.
2851	 * @param $unicode (boolean) if true set the meta information in Unicode
2852	 * @since 5.9.027 (2010-12-01)
2853	 * @public
2854	 */
2855	public function SetDocInfoUnicode($unicode=true) {
2856		$this->docinfounicode = $unicode ? true : false;
2857	}
2858
2859	/**
2860	 * Defines the title of the document.
2861	 * @param $title (string) The title.
2862	 * @public
2863	 * @since 1.2
2864	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2865	 */
2866	public function SetTitle($title) {
2867		$this->title = $title;
2868	}
2869
2870	/**
2871	 * Defines the subject of the document.
2872	 * @param $subject (string) The subject.
2873	 * @public
2874	 * @since 1.2
2875	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2876	 */
2877	public function SetSubject($subject) {
2878		$this->subject = $subject;
2879	}
2880
2881	/**
2882	 * Defines the author of the document.
2883	 * @param $author (string) The name of the author.
2884	 * @public
2885	 * @since 1.2
2886	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2887	 */
2888	public function SetAuthor($author) {
2889		$this->author = $author;
2890	}
2891
2892	/**
2893	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2894	 * @param $keywords (string) The list of keywords.
2895	 * @public
2896	 * @since 1.2
2897	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2898	 */
2899	public function SetKeywords($keywords) {
2900		$this->keywords = $keywords;
2901	}
2902
2903	/**
2904	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2905	 * @param $creator (string) The name of the creator.
2906	 * @public
2907	 * @since 1.2
2908	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2909	 */
2910	public function SetCreator($creator) {
2911		$this->creator = $creator;
2912	}
2913
2914	/**
2915	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2916	 * @param $msg (string) The error message
2917	 * @public
2918	 * @since 1.0
2919	 */
2920	public function Error($msg) {
2921		// unset all class variables
2922		$this->_destroy(true);
2923		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2924			die('<strong>TCPDF ERROR: </strong>'.$msg);
2925		} else {
2926			throw new Exception('TCPDF ERROR: '.$msg);
2927		}
2928	}
2929
2930	/**
2931	 * This method begins the generation of the PDF document.
2932	 * It is not necessary to call it explicitly because AddPage() does it automatically.
2933	 * Note: no page is created by this method
2934	 * @public
2935	 * @since 1.0
2936	 * @see AddPage(), Close()
2937	 */
2938	public function Open() {
2939		$this->state = 1;
2940	}
2941
2942	/**
2943	 * Terminates the PDF document.
2944	 * It is not necessary to call this method explicitly because Output() does it automatically.
2945	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2946	 * @public
2947	 * @since 1.0
2948	 * @see Open(), Output()
2949	 */
2950	public function Close() {
2951		if ($this->state == 3) {
2952			return;
2953		}
2954		if ($this->page == 0) {
2955			$this->AddPage();
2956		}
2957		$this->endLayer();
2958		if ($this->tcpdflink) {
2959			// save current graphic settings
2960			$gvars = $this->getGraphicVars();
2961			$this->setEqualColumns();
2962			$this->lastpage(true);
2963			$this->SetAutoPageBreak(false);
2964			$this->x = 0;
2965			$this->y = $this->h - (1 / $this->k);
2966			$this->lMargin = 0;
2967			$this->_outSaveGraphicsState();
2968			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2969			$this->SetFont($font, '', 1);
2970			$this->setTextRenderingMode(0, false, false);
2971			$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";
2972			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2973			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2974			$this->_outRestoreGraphicsState();
2975			// restore graphic settings
2976			$this->setGraphicVars($gvars);
2977		}
2978		// close page
2979		$this->endPage();
2980		// close document
2981		$this->_enddoc();
2982		// unset all class variables (except critical ones)
2983		$this->_destroy(false);
2984	}
2985
2986	/**
2987	 * Move pointer at the specified document page and update page dimensions.
2988	 * @param $pnum (int) page number (1 ... numpages)
2989	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2990	 * @public
2991	 * @since 2.1.000 (2008-01-07)
2992	 * @see getPage(), lastpage(), getNumPages()
2993	 */
2994	public function setPage($pnum, $resetmargins=false) {
2995		if (($pnum == $this->page) AND ($this->state == 2)) {
2996			return;
2997		}
2998		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
2999			$this->state = 2;
3000			// save current graphic settings
3001			//$gvars = $this->getGraphicVars();
3002			$oldpage = $this->page;
3003			$this->page = $pnum;
3004			$this->wPt = $this->pagedim[$this->page]['w'];
3005			$this->hPt = $this->pagedim[$this->page]['h'];
3006			$this->w = $this->pagedim[$this->page]['wk'];
3007			$this->h = $this->pagedim[$this->page]['hk'];
3008			$this->tMargin = $this->pagedim[$this->page]['tm'];
3009			$this->bMargin = $this->pagedim[$this->page]['bm'];
3010			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3011			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3012			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3013			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3014			$this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3015			// restore graphic settings
3016			//$this->setGraphicVars($gvars);
3017			if ($resetmargins) {
3018				$this->lMargin = $this->pagedim[$this->page]['olm'];
3019				$this->rMargin = $this->pagedim[$this->page]['orm'];
3020				$this->SetY($this->tMargin);
3021			} else {
3022				// account for booklet mode
3023				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3024					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3025					$this->lMargin += $deltam;
3026					$this->rMargin -= $deltam;
3027				}
3028			}
3029		} else {
3030			$this->Error('Wrong page number on setPage() function: '.$pnum);
3031		}
3032	}
3033
3034	/**
3035	 * Reset pointer to the last document page.
3036	 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3037	 * @public
3038	 * @since 2.0.000 (2008-01-04)
3039	 * @see setPage(), getPage(), getNumPages()
3040	 */
3041	public function lastPage($resetmargins=false) {
3042		$this->setPage($this->getNumPages(), $resetmargins);
3043	}
3044
3045	/**
3046	 * Get current document page number.
3047	 * @return int page number
3048	 * @public
3049	 * @since 2.1.000 (2008-01-07)
3050	 * @see setPage(), lastpage(), getNumPages()
3051	 */
3052	public function getPage() {
3053		return $this->page;
3054	}
3055
3056	/**
3057	 * Get the total number of insered pages.
3058	 * @return int number of pages
3059	 * @public
3060	 * @since 2.1.000 (2008-01-07)
3061	 * @see setPage(), getPage(), lastpage()
3062	 */
3063	public function getNumPages() {
3064		return $this->numpages;
3065	}
3066
3067	/**
3068	 * Adds a new TOC (Table Of Content) page to the document.
3069	 * @param $orientation (string) page orientation.
3070	 * @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().
3071	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3072	 * @public
3073	 * @since 5.0.001 (2010-05-06)
3074	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3075	 */
3076	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3077		$this->AddPage($orientation, $format, $keepmargins, true);
3078	}
3079
3080	/**
3081	 * Terminate the current TOC (Table Of Content) page
3082	 * @public
3083	 * @since 5.0.001 (2010-05-06)
3084	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3085	 */
3086	public function endTOCPage() {
3087		$this->endPage(true);
3088	}
3089
3090	/**
3091	 * 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).
3092	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3093	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3094	 * @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().
3095	 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3096	 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3097	 * @public
3098	 * @since 1.0
3099	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3100	 */
3101	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3102		if ($this->inxobj) {
3103			// we are inside an XObject template
3104			return;
3105		}
3106		if (!isset($this->original_lMargin) OR $keepmargins) {
3107			$this->original_lMargin = $this->lMargin;
3108		}
3109		if (!isset($this->original_rMargin) OR $keepmargins) {
3110			$this->original_rMargin = $this->rMargin;
3111		}
3112		// terminate previous page
3113		$this->endPage();
3114		// start new page
3115		$this->startPage($orientation, $format, $tocpage);
3116	}
3117
3118	/**
3119	 * Terminate the current page
3120	 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3121	 * @public
3122	 * @since 4.2.010 (2008-11-14)
3123	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3124	 */
3125	public function endPage($tocpage=false) {
3126		// check if page is already closed
3127		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3128			return;
3129		}
3130		// print page footer
3131		$this->setFooter();
3132		// close page
3133		$this->_endpage();
3134		// mark page as closed
3135		$this->pageopen[$this->page] = false;
3136		if ($tocpage) {
3137			$this->tocpage = false;
3138		}
3139	}
3140
3141	/**
3142	 * Starts a new page to the document. The page must be closed using the endPage() function.
3143	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3144	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3145	 * @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().
3146	 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3147	 * @since 4.2.010 (2008-11-14)
3148	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3149	 * @public
3150	 */
3151	public function startPage($orientation='', $format='', $tocpage=false) {
3152		if ($tocpage) {
3153			$this->tocpage = true;
3154		}
3155		// move page numbers of documents to be attached
3156		if ($this->tocpage) {
3157			// move reference to unexistent pages (used for page attachments)
3158			// adjust outlines
3159			$tmpoutlines = $this->outlines;
3160			foreach ($tmpoutlines as $key => $outline) {
3161				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3162					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3163				}
3164			}
3165			// adjust dests
3166			$tmpdests = $this->dests;
3167			foreach ($tmpdests as $key => $dest) {
3168				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3169					$this->dests[$key]['p'] = ($dest['p'] + 1);
3170				}
3171			}
3172			// adjust links
3173			$tmplinks = $this->links;
3174			foreach ($tmplinks as $key => $link) {
3175				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3176					$this->links[$key]['p'] = ($link['p'] + 1);
3177				}
3178			}
3179		}
3180		if ($this->numpages > $this->page) {
3181			// this page has been already added
3182			$this->setPage($this->page + 1);
3183			$this->SetY($this->tMargin);
3184			return;
3185		}
3186		// start a new page
3187		if ($this->state == 0) {
3188			$this->Open();
3189		}
3190		++$this->numpages;
3191		$this->swapMargins($this->booklet);
3192		// save current graphic settings
3193		$gvars = $this->getGraphicVars();
3194		// start new page
3195		$this->_beginpage($orientation, $format);
3196		// mark page as open
3197		$this->pageopen[$this->page] = true;
3198		// restore graphic settings
3199		$this->setGraphicVars($gvars);
3200		// mark this point
3201		$this->setPageMark();
3202		// print page header
3203		$this->setHeader();
3204		// restore graphic settings
3205		$this->setGraphicVars($gvars);
3206		// mark this point
3207		$this->setPageMark();
3208		// print table header (if any)
3209		$this->setTableHeader();
3210		// set mark for empty page check
3211		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3212	}
3213
3214	/**
3215	 * Set start-writing mark on current page stream used to put borders and fills.
3216	 * Borders and fills are always created after content and inserted on the position marked by this method.
3217	 * This function must be called after calling Image() function for a background image.
3218	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3219	 * @public
3220	 * @since 4.0.016 (2008-07-30)
3221	 */
3222	public function setPageMark() {
3223		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3224		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3225		$this->setContentMark();
3226	}
3227
3228	/**
3229	 * Set start-writing mark on selected page.
3230	 * Borders and fills are always created after content and inserted on the position marked by this method.
3231	 * @param $page (int) page number (default is the current page)
3232	 * @protected
3233	 * @since 4.6.021 (2009-07-20)
3234	 */
3235	protected function setContentMark($page=0) {
3236		if ($page <= 0) {
3237			$page = $this->page;
3238		}
3239		if (isset($this->footerlen[$page])) {
3240			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3241		} else {
3242			$this->cntmrk[$page] = $this->pagelen[$page];
3243		}
3244	}
3245
3246	/**
3247	 * Set header data.
3248	 * @param $ln (string) header image logo
3249	 * @param $lw (string) header image logo width in mm
3250	 * @param $ht (string) string to print as title on document header
3251	 * @param $hs (string) string to print on document header
3252	 * @param $tc (array) RGB array color for text.
3253	 * @param $lc (array) RGB array color for line.
3254	 * @public
3255	 */
3256	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3257		$this->header_logo = $ln;
3258		$this->header_logo_width = $lw;
3259		$this->header_title = $ht;
3260		$this->header_string = $hs;
3261		$this->header_text_color = $tc;
3262		$this->header_line_color = $lc;
3263	}
3264
3265	/**
3266	 * Set footer data.
3267	 * @param $tc (array) RGB array color for text.
3268	 * @param $lc (array) RGB array color for line.
3269	 * @public
3270	 */
3271	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3272		$this->footer_text_color = $tc;
3273		$this->footer_line_color = $lc;
3274	}
3275
3276	/**
3277	 * Returns header data:
3278	 * <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>
3279	 * @return array()
3280	 * @public
3281	 * @since 4.0.012 (2008-07-24)
3282	 */
3283	public function getHeaderData() {
3284		$ret = array();
3285		$ret['logo'] = $this->header_logo;
3286		$ret['logo_width'] = $this->header_logo_width;
3287		$ret['title'] = $this->header_title;
3288		$ret['string'] = $this->header_string;
3289		$ret['text_color'] = $this->header_text_color;
3290		$ret['line_color'] = $this->header_line_color;
3291		return $ret;
3292	}
3293
3294	/**
3295	 * Set header margin.
3296	 * (minimum distance between header and top page margin)
3297	 * @param $hm (int) distance in user units
3298	 * @public
3299	 */
3300	public function setHeaderMargin($hm=10) {
3301		$this->header_margin = $hm;
3302	}
3303
3304	/**
3305	 * Returns header margin in user units.
3306	 * @return float
3307	 * @since 4.0.012 (2008-07-24)
3308	 * @public
3309	 */
3310	public function getHeaderMargin() {
3311		return $this->header_margin;
3312	}
3313
3314	/**
3315	 * Set footer margin.
3316	 * (minimum distance between footer and bottom page margin)
3317	 * @param $fm (int) distance in user units
3318	 * @public
3319	 */
3320	public function setFooterMargin($fm=10) {
3321		$this->footer_margin = $fm;
3322	}
3323
3324	/**
3325	 * Returns footer margin in user units.
3326	 * @return float
3327	 * @since 4.0.012 (2008-07-24)
3328	 * @public
3329	 */
3330	public function getFooterMargin() {
3331		return $this->footer_margin;
3332	}
3333	/**
3334	 * Set a flag to print page header.
3335	 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3336	 * @public
3337	 */
3338	public function setPrintHeader($val=true) {
3339		$this->print_header = $val ? true : false;
3340	}
3341
3342	/**
3343	 * Set a flag to print page footer.
3344	 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3345	 * @public
3346	 */
3347	public function setPrintFooter($val=true) {
3348		$this->print_footer = $val ? true : false;
3349	}
3350
3351	/**
3352	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3353	 * @return float
3354	 * @public
3355	 */
3356	public function getImageRBX() {
3357		return $this->img_rb_x;
3358	}
3359
3360	/**
3361	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3362	 * @return float
3363	 * @public
3364	 */
3365	public function getImageRBY() {
3366		return $this->img_rb_y;
3367	}
3368
3369	/**
3370	 * Reset the xobject template used by Header() method.
3371	 * @public
3372	 */
3373	public function resetHeaderTemplate() {
3374		$this->header_xobjid = false;
3375	}
3376
3377	/**
3378	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3379	 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3380	 * @public
3381	 */
3382	public function setHeaderTemplateAutoreset($val=true) {
3383		$this->header_xobj_autoreset = $val ? true : false;
3384	}
3385
3386	/**
3387	 * This method is used to render the page header.
3388	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3389	 * @public
3390	 */
3391	public function Header() {
3392		if ($this->header_xobjid === false) {
3393			// start a new XObject Template
3394			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3395			$headerfont = $this->getHeaderFont();
3396			$headerdata = $this->getHeaderData();
3397			$this->y = $this->header_margin;
3398			if ($this->rtl) {
3399				$this->x = $this->w - $this->original_rMargin;
3400			} else {
3401				$this->x = $this->original_lMargin;
3402			}
3403			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3404				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3405				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3406					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3407				} elseif ($imgtype == 'svg') {
3408					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3409				} else {
3410					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3411				}
3412				$imgy = $this->getImageRBY();
3413			} else {
3414				$imgy = $this->y;
3415			}
3416			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3417			// set starting margin for text data cell
3418			if ($this->getRTL()) {
3419				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3420			} else {
3421				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3422			}
3423			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3424			$this->SetTextColorArray($this->header_text_color);
3425			// header title
3426			$this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3427			$this->SetX($header_x);
3428			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3429			// header string
3430			$this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3431			$this->SetX($header_x);
3432			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3433			// print an ending header line
3434			$this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3435			$this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3436			if ($this->rtl) {
3437				$this->SetX($this->original_rMargin);
3438			} else {
3439				$this->SetX($this->original_lMargin);
3440			}
3441			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3442			$this->endTemplate();
3443		}
3444		// print header template
3445		$x = 0;
3446		$dx = 0;
3447		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3448			// adjust margins for booklet mode
3449			$dx = ($this->original_lMargin - $this->original_rMargin);
3450		}
3451		if ($this->rtl) {
3452			$x = $this->w + $dx;
3453		} else {
3454			$x = 0 + $dx;
3455		}
3456		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3457		if ($this->header_xobj_autoreset) {
3458			// reset header xobject template at each page
3459			$this->header_xobjid = false;
3460		}
3461	}
3462
3463	/**
3464	 * This method is used to render the page footer.
3465	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3466	 * @public
3467	 */
3468	public function Footer() {
3469		$cur_y = $this->y;
3470		$this->SetTextColorArray($this->footer_text_color);
3471		//set style for cell border
3472		$line_width = (0.85 / $this->k);
3473		$this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3474		//print document barcode
3475		$barcode = $this->getBarcode();
3476		if (!empty($barcode)) {
3477			$this->Ln($line_width);
3478			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3479			$style = array(
3480				'position' => $this->rtl?'R':'L',
3481				'align' => $this->rtl?'R':'L',
3482				'stretch' => false,
3483				'fitwidth' => true,
3484				'cellfitalign' => '',
3485				'border' => false,
3486				'padding' => 0,
3487				'fgcolor' => array(0,0,0),
3488				'bgcolor' => false,
3489				'text' => false
3490			);
3491			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3492		}
3493		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3494		if (empty($this->pagegroups)) {
3495			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3496		} else {
3497			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3498		}
3499		$this->SetY($cur_y);
3500		//Print page number
3501		if ($this->getRTL()) {
3502			$this->SetX($this->original_rMargin);
3503			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3504		} else {
3505			$this->SetX($this->original_lMargin);
3506			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3507		}
3508	}
3509
3510	/**
3511	 * This method is used to render the page header.
3512	 * @protected
3513	 * @since 4.0.012 (2008-07-24)
3514	 */
3515	protected function setHeader() {
3516		if (!$this->print_header OR ($this->state != 2)) {
3517			return;
3518		}
3519		$this->InHeader = true;
3520		$this->setGraphicVars($this->default_graphic_vars);
3521		$temp_thead = $this->thead;
3522		$temp_theadMargins = $this->theadMargins;
3523		$lasth = $this->lasth;
3524		$newline = $this->newline;
3525		$this->_outSaveGraphicsState();
3526		$this->rMargin = $this->original_rMargin;
3527		$this->lMargin = $this->original_lMargin;
3528		$this->SetCellPadding(0);
3529		//set current position
3530		if ($this->rtl) {
3531			$this->SetXY($this->original_rMargin, $this->header_margin);
3532		} else {
3533			$this->SetXY($this->original_lMargin, $this->header_margin);
3534		}
3535		$this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3536		$this->Header();
3537		//restore position
3538		if ($this->rtl) {
3539			$this->SetXY($this->original_rMargin, $this->tMargin);
3540		} else {
3541			$this->SetXY($this->original_lMargin, $this->tMargin);
3542		}
3543		$this->_outRestoreGraphicsState();
3544		$this->lasth = $lasth;
3545		$this->thead = $temp_thead;
3546		$this->theadMargins = $temp_theadMargins;
3547		$this->newline = $newline;
3548		$this->InHeader = false;
3549	}
3550
3551	/**
3552	 * This method is used to render the page footer.
3553	 * @protected
3554	 * @since 4.0.012 (2008-07-24)
3555	 */
3556	protected function setFooter() {
3557		if ($this->state != 2) {
3558			return;
3559		}
3560		$this->InFooter = true;
3561		// save current graphic settings
3562		$gvars = $this->getGraphicVars();
3563		// mark this point
3564		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3565		$this->_out("\n");
3566		if ($this->print_footer) {
3567			$this->setGraphicVars($this->default_graphic_vars);
3568			$this->current_column = 0;
3569			$this->num_columns = 1;
3570			$temp_thead = $this->thead;
3571			$temp_theadMargins = $this->theadMargins;
3572			$lasth = $this->lasth;
3573			$this->_outSaveGraphicsState();
3574			$this->rMargin = $this->original_rMargin;
3575			$this->lMargin = $this->original_lMargin;
3576			$this->SetCellPadding(0);
3577			//set current position
3578			$footer_y = $this->h - $this->footer_margin;
3579			if ($this->rtl) {
3580				$this->SetXY($this->original_rMargin, $footer_y);
3581			} else {
3582				$this->SetXY($this->original_lMargin, $footer_y);
3583			}
3584			$this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3585			$this->Footer();
3586			//restore position
3587			if ($this->rtl) {
3588				$this->SetXY($this->original_rMargin, $this->tMargin);
3589			} else {
3590				$this->SetXY($this->original_lMargin, $this->tMargin);
3591			}
3592			$this->_outRestoreGraphicsState();
3593			$this->lasth = $lasth;
3594			$this->thead = $temp_thead;
3595			$this->theadMargins = $temp_theadMargins;
3596		}
3597		// restore graphic settings
3598		$this->setGraphicVars($gvars);
3599		$this->current_column = $gvars['current_column'];
3600		$this->num_columns = $gvars['num_columns'];
3601		// calculate footer length
3602		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3603		$this->InFooter = false;
3604	}
3605
3606	/**
3607	 * Check if we are on the page body (excluding page header and footer).
3608	 * @return true if we are not in page header nor in page footer, false otherwise.
3609	 * @protected
3610	 * @since 5.9.091 (2011-06-15)
3611	 */
3612	protected function inPageBody() {
3613		return (($this->InHeader === false) AND ($this->InFooter === false));
3614	}
3615
3616	/**
3617	 * This method is used to render the table header on new page (if any).
3618	 * @protected
3619	 * @since 4.5.030 (2009-03-25)
3620	 */
3621	protected function setTableHeader() {
3622		if ($this->num_columns > 1) {
3623			// multi column mode
3624			return;
3625		}
3626		if (isset($this->theadMargins['top'])) {
3627			// restore the original top-margin
3628			$this->tMargin = $this->theadMargins['top'];
3629			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3630			$this->y = $this->tMargin;
3631		}
3632		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3633			// set margins
3634			$prev_lMargin = $this->lMargin;
3635			$prev_rMargin = $this->rMargin;
3636			$prev_cell_padding = $this->cell_padding;
3637			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3638			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3639			$this->cell_padding = $this->theadMargins['cell_padding'];
3640			if ($this->rtl) {
3641				$this->x = $this->w - $this->rMargin;
3642			} else {
3643				$this->x = $this->lMargin;
3644			}
3645			// account for special "cell" mode
3646			if ($this->theadMargins['cell']) {
3647				if ($this->rtl) {
3648					$this->x -= $this->cell_padding['R'];
3649				} else {
3650					$this->x += $this->cell_padding['L'];
3651				}
3652			}
3653			$gvars = $this->getGraphicVars();
3654			if (!empty($this->theadMargins['gvars'])) {
3655				// set the correct graphic style
3656				$this->setGraphicVars($this->theadMargins['gvars']);
3657				$this->rMargin = $gvars['rMargin'];
3658				$this->lMargin = $gvars['lMargin'];
3659			}
3660			// print table header
3661			$this->writeHTML($this->thead, false, false, false, false, '');
3662			$this->setGraphicVars($gvars);
3663			// set new top margin to skip the table headers
3664			if (!isset($this->theadMargins['top'])) {
3665				$this->theadMargins['top'] = $this->tMargin;
3666			}
3667			// store end of header position
3668			if (!isset($this->columns[0]['th'])) {
3669				$this->columns[0]['th'] = array();
3670			}
3671			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3672			$this->tMargin = $this->y;
3673			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3674			$this->lasth = 0;
3675			$this->lMargin = $prev_lMargin;
3676			$this->rMargin = $prev_rMargin;
3677			$this->cell_padding = $prev_cell_padding;
3678		}
3679	}
3680
3681	/**
3682	 * Returns the current page number.
3683	 * @return int page number
3684	 * @public
3685	 * @since 1.0
3686	 * @see getAliasNbPages()
3687	 */
3688	public function PageNo() {
3689		return $this->page;
3690	}
3691
3692	/**
3693	 * Returns the array of spot colors.
3694	 * @return (array) Spot colors array.
3695	 * @public
3696	 * @since 6.0.038 (2013-09-30)
3697	 */
3698	public function getAllSpotColors() {
3699		return $this->spot_colors;
3700	}
3701
3702	/**
3703	 * Defines a new spot color.
3704	 * It can be expressed in RGB components or gray scale.
3705	 * The method can be called before the first page is created and the value is retained from page to page.
3706	 * @param $name (string) Full name of the spot color.
3707	 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3708	 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3709	 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3710	 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3711	 * @public
3712	 * @since 4.0.024 (2008-09-12)
3713	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3714	 */
3715	public function AddSpotColor($name, $c, $m, $y, $k) {
3716		if (!isset($this->spot_colors[$name])) {
3717			$i = (1 + count($this->spot_colors));
3718			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3719		}
3720	}
3721
3722	/**
3723	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3724	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3725	 * @param $name (string) Name of the spot color.
3726	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3727	 * @return (string) PDF color command.
3728	 * @public
3729	 * @since 5.9.125 (2011-10-03)
3730	 */
3731	public function setSpotColor($type, $name, $tint=100) {
3732		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3733		if ($spotcolor === false) {
3734			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3735		}
3736		$tint = (max(0, min(100, $tint)) / 100);
3737		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3738		switch ($type) {
3739			case 'draw': {
3740				$pdfcolor .= sprintf('CS %F SCN', $tint);
3741				$this->DrawColor = $pdfcolor;
3742				$this->strokecolor = $spotcolor;
3743				break;
3744			}
3745			case 'fill': {
3746				$pdfcolor .= sprintf('cs %F scn', $tint);
3747				$this->FillColor = $pdfcolor;
3748				$this->bgcolor = $spotcolor;
3749				break;
3750			}
3751			case 'text': {
3752				$pdfcolor .= sprintf('cs %F scn', $tint);
3753				$this->TextColor = $pdfcolor;
3754				$this->fgcolor = $spotcolor;
3755				break;
3756			}
3757		}
3758		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3759		if ($this->state == 2) {
3760			$this->_out($pdfcolor);
3761		}
3762		if ($this->inxobj) {
3763			// we are inside an XObject template
3764			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3765		}
3766		return $pdfcolor;
3767	}
3768
3769	/**
3770	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3771	 * @param $name (string) Name of the spot color.
3772	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3773	 * @public
3774	 * @since 4.0.024 (2008-09-12)
3775	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3776	 */
3777	public function SetDrawSpotColor($name, $tint=100) {
3778		$this->setSpotColor('draw', $name, $tint);
3779	}
3780
3781	/**
3782	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3783	 * @param $name (string) Name of the spot color.
3784	 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3785	 * @public
3786	 * @since 4.0.024 (2008-09-12)
3787	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3788	 */
3789	public function SetFillSpotColor($name, $tint=100) {
3790		$this->setSpotColor('fill', $name, $tint);
3791	}
3792
3793	/**
3794	 * Defines the spot color used for text.
3795	 * @param $name (string) Name of the spot color.
3796	 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3797	 * @public
3798	 * @since 4.0.024 (2008-09-12)
3799	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3800	 */
3801	public function SetTextSpotColor($name, $tint=100) {
3802		$this->setSpotColor('text', $name, $tint);
3803	}
3804
3805	/**
3806	 * Set the color array for the specified type ('draw', 'fill', 'text').
3807	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3808	 * The method can be called before the first page is created and the value is retained from page to page.
3809	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3810	 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3811	 * @param $ret (boolean) If true do not send the PDF command.
3812	 * @return (string) The PDF command or empty string.
3813	 * @public
3814	 * @since 3.1.000 (2008-06-11)
3815	 */
3816	public function setColorArray($type, $color, $ret=false) {
3817		if (is_array($color)) {
3818			$color = array_values($color);
3819			// component: grey, RGB red or CMYK cyan
3820			$c = isset($color[0]) ? $color[0] : -1;
3821			// component: RGB green or CMYK magenta
3822			$m = isset($color[1]) ? $color[1] : -1;
3823			// component: RGB blue or CMYK yellow
3824			$y = isset($color[2]) ? $color[2] : -1;
3825			// component: CMYK black
3826			$k = isset($color[3]) ? $color[3] : -1;
3827			// color name
3828			$name = isset($color[4]) ? $color[4] : '';
3829			if ($c >= 0) {
3830				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3831			}
3832		}
3833		return '';
3834	}
3835
3836	/**
3837	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3838	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3839	 * The method can be called before the first page is created and the value is retained from page to page.
3840	 * @param $color (array) Array of colors (1, 3 or 4 values).
3841	 * @param $ret (boolean) If true do not send the PDF command.
3842	 * @return string the PDF command
3843	 * @public
3844	 * @since 3.1.000 (2008-06-11)
3845	 * @see SetDrawColor()
3846	 */
3847	public function SetDrawColorArray($color, $ret=false) {
3848		return $this->setColorArray('draw', $color, $ret);
3849	}
3850
3851	/**
3852	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3853	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3854	 * The method can be called before the first page is created and the value is retained from page to page.
3855	 * @param $color (array) Array of colors (1, 3 or 4 values).
3856	 * @param $ret (boolean) If true do not send the PDF command.
3857	 * @public
3858	 * @since 3.1.000 (2008-6-11)
3859	 * @see SetFillColor()
3860	 */
3861	public function SetFillColorArray($color, $ret=false) {
3862		return $this->setColorArray('fill', $color, $ret);
3863	}
3864
3865	/**
3866	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3867	 * The method can be called before the first page is created and the value is retained from page to page.
3868	 * @param $color (array) Array of colors (1, 3 or 4 values).
3869	 * @param $ret (boolean) If true do not send the PDF command.
3870	 * @public
3871	 * @since 3.1.000 (2008-6-11)
3872	 * @see SetFillColor()
3873	 */
3874	public function SetTextColorArray($color, $ret=false) {
3875		return $this->setColorArray('text', $color, $ret);
3876	}
3877
3878	/**
3879	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3880	 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3881	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3882	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3883	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3884	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3885	 * @param $ret (boolean) If true do not send the command.
3886	 * @param $name (string) spot color name (if any)
3887	 * @return (string) The PDF command or empty string.
3888	 * @public
3889	 * @since 5.9.125 (2011-10-03)
3890	 */
3891	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3892		// set default values
3893		if (!is_numeric($col1)) {
3894			$col1 = 0;
3895		}
3896		if (!is_numeric($col2)) {
3897			$col2 = -1;
3898		}
3899		if (!is_numeric($col3)) {
3900			$col3 = -1;
3901		}
3902		if (!is_numeric($col4)) {
3903			$col4 = -1;
3904		}
3905		// set color by case
3906		$suffix = '';
3907		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3908			// Grey scale
3909			$col1 = max(0, min(255, $col1));
3910			$intcolor = array('G' => $col1);
3911			$pdfcolor = sprintf('%F ', ($col1 / 255));
3912			$suffix = 'g';
3913		} elseif ($col4 == -1) {
3914			// RGB
3915			$col1 = max(0, min(255, $col1));
3916			$col2 = max(0, min(255, $col2));
3917			$col3 = max(0, min(255, $col3));
3918			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3919			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3920			$suffix = 'rg';
3921		} else {
3922			$col1 = max(0, min(100, $col1));
3923			$col2 = max(0, min(100, $col2));
3924			$col3 = max(0, min(100, $col3));
3925			$col4 = max(0, min(100, $col4));
3926			if (empty($name)) {
3927				// CMYK
3928				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3929				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3930				$suffix = 'k';
3931			} else {
3932				// SPOT COLOR
3933				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3934				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3935				$pdfcolor = $this->setSpotColor($type, $name, 100);
3936			}
3937		}
3938		switch ($type) {
3939			case 'draw': {
3940				$pdfcolor .= strtoupper($suffix);
3941				$this->DrawColor = $pdfcolor;
3942				$this->strokecolor = $intcolor;
3943				break;
3944			}
3945			case 'fill': {
3946				$pdfcolor .= $suffix;
3947				$this->FillColor = $pdfcolor;
3948				$this->bgcolor = $intcolor;
3949				break;
3950			}
3951			case 'text': {
3952				$pdfcolor .= $suffix;
3953				$this->TextColor = $pdfcolor;
3954				$this->fgcolor = $intcolor;
3955				break;
3956			}
3957		}
3958		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3959		if (($type != 'text') AND ($this->state == 2)) {
3960			if (!$ret) {
3961				$this->_out($pdfcolor);
3962			}
3963			return $pdfcolor;
3964		}
3965		return '';
3966	}
3967
3968	/**
3969	 * 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.
3970	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3971	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3972	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3973	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3974	 * @param $ret (boolean) If true do not send the command.
3975	 * @param $name (string) spot color name (if any)
3976	 * @return string the PDF command
3977	 * @public
3978	 * @since 1.3
3979	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3980	 */
3981	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3982		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3983	}
3984
3985	/**
3986	 * 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.
3987	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3988	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3989	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3990	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3991	 * @param $ret (boolean) If true do not send the command.
3992	 * @param $name (string) Spot color name (if any).
3993	 * @return (string) The PDF command.
3994	 * @public
3995	 * @since 1.3
3996	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
3997	 */
3998	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3999		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4000	}
4001
4002	/**
4003	 * 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.
4004	 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4005	 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4006	 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4007	 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4008	 * @param $ret (boolean) If true do not send the command.
4009	 * @param $name (string) Spot color name (if any).
4010	 * @return (string) Empty string.
4011	 * @public
4012	 * @since 1.3
4013	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4014	 */
4015	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4016		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4017	}
4018
4019	/**
4020	 * Returns the length of a string in user unit. A font must be selected.<br>
4021	 * @param $s (string) The string whose length is to be computed
4022	 * @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.
4023	 * @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.
4024	 * @param $fontsize (float) Font size in points. The default value is the current size.
4025	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4026	 * @return mixed int total string length or array of characted widths
4027	 * @author Nicola Asuni
4028	 * @public
4029	 * @since 1.2
4030	 */
4031	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4032		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);
4033	}
4034
4035	/**
4036	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4037	 * @param $sa (string) The array of chars whose total length is to be computed
4038	 * @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.
4039	 * @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.
4040	 * @param $fontsize (float) Font size in points. The default value is the current size.
4041	 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4042	 * @return mixed int total string length or array of characted widths
4043	 * @author Nicola Asuni
4044	 * @public
4045	 * @since 2.4.000 (2008-03-06)
4046	 */
4047	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4048		// store current values
4049		if (!TCPDF_STATIC::empty_string($fontname)) {
4050			$prev_FontFamily = $this->FontFamily;
4051			$prev_FontStyle = $this->FontStyle;
4052			$prev_FontSizePt = $this->FontSizePt;
4053			$this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4054		}
4055		// convert UTF-8 array to Latin1 if required
4056		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4057			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4058		}
4059		$w = 0; // total width
4060		$wa = array(); // array of characters widths
4061		foreach ($sa as $ck => $char) {
4062			// character width
4063			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4064			$wa[] = $cw;
4065			$w += $cw;
4066		}
4067		// restore previous values
4068		if (!TCPDF_STATIC::empty_string($fontname)) {
4069			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4070		}
4071		if ($getarray) {
4072			return $wa;
4073		}
4074		return $w;
4075	}
4076
4077	/**
4078	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4079	 * @param $char (int) The char code whose length is to be returned
4080	 * @param $notlast (boolean) If false ignore the font-spacing.
4081	 * @return float char width
4082	 * @author Nicola Asuni
4083	 * @public
4084	 * @since 2.4.000 (2008-03-06)
4085	 */
4086	public function GetCharWidth($char, $notlast=true) {
4087		// get raw width
4088		$chw = $this->getRawCharWidth($char);
4089		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4090			// increase/decrease font spacing
4091			$chw += $this->font_spacing;
4092		}
4093		if ($this->font_stretching != 100) {
4094			// fixed stretching mode
4095			$chw *= ($this->font_stretching / 100);
4096		}
4097		return $chw;
4098	}
4099
4100	/**
4101	 * Returns the length of the char in user unit for the current font.
4102	 * @param $char (int) The char code whose length is to be returned
4103	 * @return float char width
4104	 * @author Nicola Asuni
4105	 * @public
4106	 * @since 5.9.000 (2010-09-28)
4107	 */
4108	public function getRawCharWidth($char) {
4109		if ($char == 173) {
4110			// SHY character will not be printed
4111			return (0);
4112		}
4113		if (isset($this->CurrentFont['cw'][$char])) {
4114			$w = $this->CurrentFont['cw'][$char];
4115		} elseif (isset($this->CurrentFont['dw'])) {
4116			// default width
4117			$w = $this->CurrentFont['dw'];
4118		} elseif (isset($this->CurrentFont['cw'][32])) {
4119			// default width
4120			$w = $this->CurrentFont['cw'][32];
4121		} else {
4122			$w = 600;
4123		}
4124		return $this->getAbsFontMeasure($w);
4125	}
4126
4127	/**
4128	 * Returns the numbero of characters in a string.
4129	 * @param $s (string) The input string.
4130	 * @return int number of characters
4131	 * @public
4132	 * @since 2.0.0001 (2008-01-07)
4133	 */
4134	public function GetNumChars($s) {
4135		if ($this->isUnicodeFont()) {
4136			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4137		}
4138		return strlen($s);
4139	}
4140
4141	/**
4142	 * Fill the list of available fonts ($this->fontlist).
4143	 * @protected
4144	 * @since 4.0.013 (2008-07-28)
4145	 */
4146	protected function getFontsList() {
4147		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4148			while (($file = readdir($fontsdir)) !== false) {
4149				if (substr($file, -4) == '.php') {
4150					array_push($this->fontlist, strtolower(basename($file, '.php')));
4151				}
4152			}
4153			closedir($fontsdir);
4154		}
4155	}
4156
4157	/**
4158	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4159	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4160	 * 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.
4161	 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4162	 * @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>
4163	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4164	 * @return array containing the font data, or false in case of error.
4165	 * @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.
4166	 * @public
4167	 * @since 1.5
4168	 * @see SetFont(), setFontSubsetting()
4169	 */
4170	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4171		if ($subset === 'default') {
4172			$subset = $this->font_subsetting;
4173		}
4174		if ($this->pdfa_mode) {
4175			$subset = false;
4176		}
4177		if (TCPDF_STATIC::empty_string($family)) {
4178			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4179				$family = $this->FontFamily;
4180			} else {
4181				$this->Error('Empty font family');
4182			}
4183		}
4184		// move embedded styles on $style
4185		if (substr($family, -1) == 'I') {
4186			$style .= 'I';
4187			$family = substr($family, 0, -1);
4188		}
4189		if (substr($family, -1) == 'B') {
4190			$style .= 'B';
4191			$family = substr($family, 0, -1);
4192		}
4193		// normalize family name
4194		$family = strtolower($family);
4195		if ((!$this->isunicode) AND ($family == 'arial')) {
4196			$family = 'helvetica';
4197		}
4198		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4199			$style = '';
4200		}
4201		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4202			// all fonts must be embedded
4203			$family = 'pdfa'.$family;
4204		}
4205		$tempstyle = strtoupper($style);
4206		$style = '';
4207		// underline
4208		if (strpos($tempstyle, 'U') !== false) {
4209			$this->underline = true;
4210		} else {
4211			$this->underline = false;
4212		}
4213		// line-through (deleted)
4214		if (strpos($tempstyle, 'D') !== false) {
4215			$this->linethrough = true;
4216		} else {
4217			$this->linethrough = false;
4218		}
4219		// overline
4220		if (strpos($tempstyle, 'O') !== false) {
4221			$this->overline = true;
4222		} else {
4223			$this->overline = false;
4224		}
4225		// bold
4226		if (strpos($tempstyle, 'B') !== false) {
4227			$style .= 'B';
4228		}
4229		// oblique
4230		if (strpos($tempstyle, 'I') !== false) {
4231			$style .= 'I';
4232		}
4233		$bistyle = $style;
4234		$fontkey = $family.$style;
4235		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4236		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4237		// check if the font has been already added
4238		$fb = $this->getFontBuffer($fontkey);
4239		if ($fb !== false) {
4240			if ($this->inxobj) {
4241				// we are inside an XObject template
4242				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4243			}
4244			return $fontdata;
4245		}
4246		// get specified font directory (if any)
4247		$fontdir = false;
4248		if (!TCPDF_STATIC::empty_string($fontfile)) {
4249			$fontdir = dirname($fontfile);
4250			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4251				$fontdir = '';
4252			} else {
4253				$fontdir .= '/';
4254			}
4255		}
4256		// true when the font style variation is missing
4257		$missing_style = false;
4258		// search and include font file
4259		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4260			// build a standard filenames for specified font
4261			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4262			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4263			if (TCPDF_STATIC::empty_string($fontfile)) {
4264				$missing_style = true;
4265				// try to remove the style part
4266				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4267				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4268			}
4269		}
4270		// include font file
4271		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4272			include($fontfile);
4273		} else {
4274			$this->Error('Could not include font definition file: '.$family.'');
4275		}
4276		// check font parameters
4277		if ((!isset($type)) OR (!isset($cw))) {
4278			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4279		}
4280		// SET default parameters
4281		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4282			$file = '';
4283		}
4284		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4285			$enc = '';
4286		}
4287		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4288			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4289			$cidinfo['uni2cid'] = array();
4290		}
4291		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4292			$ctg = '';
4293		}
4294		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4295			$desc = array();
4296		}
4297		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4298			$up = -100;
4299		}
4300		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4301			$ut = 50;
4302		}
4303		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4304			$cw = array();
4305		}
4306		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4307			// set default width
4308			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4309				$dw = $desc['MissingWidth'];
4310			} elseif (isset($cw[32])) {
4311				$dw = $cw[32];
4312			} else {
4313				$dw = 600;
4314			}
4315		}
4316		++$this->numfonts;
4317		if ($type == 'core') {
4318			$name = $this->CoreFonts[$fontkey];
4319			$subset = false;
4320		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4321			$subset = false;
4322		} elseif ($type == 'TrueTypeUnicode') {
4323			$enc = 'Identity-H';
4324		} elseif ($type == 'cidfont0') {
4325			if ($this->pdfa_mode) {
4326				$this->Error('All fonts must be embedded in PDF/A mode!');
4327			}
4328		} else {
4329			$this->Error('Unknow font type: '.$type.'');
4330		}
4331		// set name if unset
4332		if (!isset($name) OR empty($name)) {
4333			$name = $fontkey;
4334		}
4335		// create artificial font style variations if missing (only works with non-embedded fonts)
4336		if (($type != 'core') AND $missing_style) {
4337			// style variations
4338			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4339			$name .= $styles[$bistyle];
4340			// artificial bold
4341			if (strpos($bistyle, 'B') !== false) {
4342				if (isset($desc['StemV'])) {
4343					// from normal to bold
4344					$desc['StemV'] = round($desc['StemV'] * 1.75);
4345				} else {
4346					// bold
4347					$desc['StemV'] = 123;
4348				}
4349			}
4350			// artificial italic
4351			if (strpos($bistyle, 'I') !== false) {
4352				if (isset($desc['ItalicAngle'])) {
4353					$desc['ItalicAngle'] -= 11;
4354				} else {
4355					$desc['ItalicAngle'] = -11;
4356				}
4357				if (isset($desc['Flags'])) {
4358					$desc['Flags'] |= 64; //bit 7
4359				} else {
4360					$desc['Flags'] = 64;
4361				}
4362			}
4363		}
4364		// check if the array of characters bounding boxes is defined
4365		if (!isset($cbbox)) {
4366			$cbbox = array();
4367		}
4368		// initialize subsetchars
4369		$subsetchars = array_fill(0, 255, true);
4370		$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));
4371		if ($this->inxobj) {
4372			// we are inside an XObject template
4373			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4374		}
4375		if (isset($diff) AND (!empty($diff))) {
4376			//Search existing encodings
4377			$d = 0;
4378			$nb = count($this->diffs);
4379			for ($i=1; $i <= $nb; ++$i) {
4380				if ($this->diffs[$i] == $diff) {
4381					$d = $i;
4382					break;
4383				}
4384			}
4385			if ($d == 0) {
4386				$d = $nb + 1;
4387				$this->diffs[$d] = $diff;
4388			}
4389			$this->setFontSubBuffer($fontkey, 'diff', $d);
4390		}
4391		if (!TCPDF_STATIC::empty_string($file)) {
4392			if (!isset($this->FontFiles[$file])) {
4393				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4394					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4395				} elseif ($type != 'core') {
4396					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4397				}
4398			} else {
4399				// update fontkeys that are sharing this font file
4400				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4401				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4402					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4403				}
4404			}
4405		}
4406		return $fontdata;
4407	}
4408
4409	/**
4410	 * Sets the font used to print character strings.
4411	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4412	 * The method can be called before the first page is created and the font is retained from page to page.
4413	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4414	 * 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 />
4415	 * @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.
4416	 * @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.
4417	 * @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
4418	 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4419	 * @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.
4420	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4421	 * @author Nicola Asuni
4422	 * @public
4423	 * @since 1.0
4424	 * @see AddFont(), SetFontSize()
4425	 */
4426	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4427		//Select a font; size given in points
4428		if ($size === null) {
4429			$size = $this->FontSizePt;
4430		}
4431		if ($size < 0) {
4432			$size = 0;
4433		}
4434		// try to add font (if not already added)
4435		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4436		$this->FontFamily = $fontdata['family'];
4437		$this->FontStyle = $fontdata['style'];
4438		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4439			// save subset chars of the previous font
4440			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4441		}
4442		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4443		$this->SetFontSize($size, $out);
4444	}
4445
4446	/**
4447	 * Defines the size of the current font.
4448	 * @param $size (float) The font size in points.
4449	 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4450	 * @public
4451	 * @since 1.0
4452	 * @see SetFont()
4453	 */
4454	public function SetFontSize($size, $out=true) {
4455		$size = (float)$size;
4456		// font size in points
4457		$this->FontSizePt = $size;
4458		// font size in user units
4459		$this->FontSize = $size / $this->k;
4460		// calculate some font metrics
4461		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4462			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4463			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4464		} else {
4465			$font_height = $size * 1.219;
4466		}
4467		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4468			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4469		}
4470		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4471			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4472		}
4473		if (!isset($font_ascent) AND !isset($font_descent)) {
4474			// core font
4475			$font_ascent = 0.76 * $font_height;
4476			$font_descent = $font_height - $font_ascent;
4477		} elseif (!isset($font_descent)) {
4478			$font_descent = $font_height - $font_ascent;
4479		} elseif (!isset($font_ascent)) {
4480			$font_ascent = $font_height - $font_descent;
4481		}
4482		$this->FontAscent = ($font_ascent / $this->k);
4483		$this->FontDescent = ($font_descent / $this->k);
4484		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4485			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4486		}
4487	}
4488
4489	/**
4490	 * Returns the bounding box of the current font in user units.
4491	 * @return array
4492	 * @public
4493	 * @since 5.9.152 (2012-03-23)
4494	 */
4495	public function getFontBBox() {
4496		$fbbox = array();
4497		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4498			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4499			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4500		} else {
4501			// Find max width
4502			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4503				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4504			} else {
4505				$maxw = 0;
4506				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4507					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4508				}
4509				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4510					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4511				}
4512				if (isset($this->CurrentFont['dw'])) {
4513					$maxw = max($maxw, $this->CurrentFont['dw']);
4514				}
4515				foreach ($this->CurrentFont['cw'] as $char => $w) {
4516					$maxw = max($maxw, $w);
4517				}
4518				if ($maxw == 0) {
4519					$maxw = 600;
4520				}
4521				$maxw = $this->getAbsFontMeasure($maxw);
4522			}
4523			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4524		}
4525		return $fbbox;
4526	}
4527
4528	/**
4529	 * Convert a relative font measure into absolute value.
4530	 * @param $s (int) Font measure.
4531	 * @return float Absolute measure.
4532	 * @since 5.9.186 (2012-09-13)
4533	 */
4534	public function getAbsFontMeasure($s) {
4535		return ($s * $this->FontSize / 1000);
4536	}
4537
4538	/**
4539	 * Returns the glyph bounding box of the specified character in the current font in user units.
4540	 * @param $char (int) Input character code.
4541	 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4542	 * @since 5.9.186 (2012-09-13)
4543	 */
4544	public function getCharBBox($char) {
4545		$c = intval($char);
4546		if (isset($this->CurrentFont['cw'][$c])) {
4547			// glyph is defined ... use zero width & height for glyphs without outlines
4548			$result = array(0,0,0,0);
4549			if (isset($this->CurrentFont['cbbox'][$c])) {
4550				$result = $this->CurrentFont['cbbox'][$c];
4551			}
4552			return array_map(array($this,'getAbsFontMeasure'), $result);
4553		}
4554		return false;
4555	}
4556
4557	/**
4558	 * Return the font descent value
4559	 * @param $font (string) font name
4560	 * @param $style (string) font style
4561	 * @param $size (float) The size (in points)
4562	 * @return int font descent
4563	 * @public
4564	 * @author Nicola Asuni
4565	 * @since 4.9.003 (2010-03-30)
4566	 */
4567	public function getFontDescent($font, $style='', $size=0) {
4568		$fontdata = $this->AddFont($font, $style);
4569		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4570		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4571			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4572		} else {
4573			$descent = (1.219 * 0.24 * $size);
4574		}
4575		return ($descent / $this->k);
4576	}
4577
4578	/**
4579	 * Return the font ascent value.
4580	 * @param $font (string) font name
4581	 * @param $style (string) font style
4582	 * @param $size (float) The size (in points)
4583	 * @return int font ascent
4584	 * @public
4585	 * @author Nicola Asuni
4586	 * @since 4.9.003 (2010-03-30)
4587	 */
4588	public function getFontAscent($font, $style='', $size=0) {
4589		$fontdata = $this->AddFont($font, $style);
4590		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4591		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4592			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4593		} else {
4594			$ascent = 1.219 * 0.76 * $size;
4595		}
4596		return ($ascent / $this->k);
4597	}
4598
4599	/**
4600	 * Return true in the character is present in the specified font.
4601	 * @param $char (mixed) Character to check (integer value or string)
4602	 * @param $font (string) Font name (family name).
4603	 * @param $style (string) Font style.
4604	 * @return (boolean) true if the char is defined, false otherwise.
4605	 * @public
4606	 * @since 5.9.153 (2012-03-28)
4607	 */
4608	public function isCharDefined($char, $font='', $style='') {
4609		if (is_string($char)) {
4610			// get character code
4611			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4612			$char = $char[0];
4613		}
4614		if (TCPDF_STATIC::empty_string($font)) {
4615			if (TCPDF_STATIC::empty_string($style)) {
4616				return (isset($this->CurrentFont['cw'][intval($char)]));
4617			}
4618			$font = $this->FontFamily;
4619		}
4620		$fontdata = $this->AddFont($font, $style);
4621		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4622		return (isset($fontinfo['cw'][intval($char)]));
4623	}
4624
4625	/**
4626	 * Replace missing font characters on selected font with specified substitutions.
4627	 * @param $text (string) Text to process.
4628	 * @param $font (string) Font name (family name).
4629	 * @param $style (string) Font style.
4630	 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4631	 * @return (string) Processed text.
4632	 * @public
4633	 * @since 5.9.153 (2012-03-28)
4634	 */
4635	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4636		if (empty($subs)) {
4637			return $text;
4638		}
4639		if (TCPDF_STATIC::empty_string($font)) {
4640			$font = $this->FontFamily;
4641		}
4642		$fontdata = $this->AddFont($font, $style);
4643		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4644		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4645		foreach ($uniarr as $k => $chr) {
4646			if (!isset($fontinfo['cw'][$chr])) {
4647				// this character is missing on the selected font
4648				if (isset($subs[$chr])) {
4649					// we have available substitutions
4650					if (is_array($subs[$chr])) {
4651						foreach($subs[$chr] as $s) {
4652							if (isset($fontinfo['cw'][$s])) {
4653								$uniarr[$k] = $s;
4654								break;
4655							}
4656						}
4657					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4658						$uniarr[$k] = $subs[$chr];
4659					}
4660				}
4661			}
4662		}
4663		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4664	}
4665
4666	/**
4667	 * Defines the default monospaced font.
4668	 * @param $font (string) Font name.
4669	 * @public
4670	 * @since 4.5.025
4671	 */
4672	public function SetDefaultMonospacedFont($font) {
4673		$this->default_monospaced_font = $font;
4674	}
4675
4676	/**
4677	 * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
4678	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4679	 * @public
4680	 * @since 1.5
4681	 * @see Cell(), Write(), Image(), Link(), SetLink()
4682	 */
4683	public function AddLink() {
4684		// create a new internal link
4685		$n = count($this->links) + 1;
4686		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4687		return $n;
4688	}
4689
4690	/**
4691	 * Defines the page and position a link points to.
4692	 * @param $link (int) The link identifier returned by AddLink()
4693	 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4694	 * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4695	 * @public
4696	 * @since 1.5
4697	 * @see AddLink()
4698	 */
4699	public function SetLink($link, $y=0, $page=-1) {
4700		$fixed = false;
4701		if (!empty($page) AND ($page[0] == '*')) {
4702			$page = intval(substr($page, 1));
4703			// this page number will not be changed when moving/add/deleting pages
4704			$fixed = true;
4705		}
4706		if ($page < 0) {
4707			$page = $this->page;
4708		}
4709		if ($y == -1) {
4710			$y = $this->y;
4711		}
4712		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4713	}
4714
4715	/**
4716	 * Puts a link on a rectangular area of the page.
4717	 * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
4718	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4719	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4720	 * @param $w (float) Width of the rectangle
4721	 * @param $h (float) Height of the rectangle
4722	 * @param $link (mixed) URL or identifier returned by AddLink()
4723	 * @param $spaces (int) number of spaces on the text to link
4724	 * @public
4725	 * @since 1.5
4726	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4727	 */
4728	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4729		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4730	}
4731
4732	/**
4733	 * Puts a markup annotation on a rectangular area of the page.
4734	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4735	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4736	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4737	 * @param $w (float) Width of the rectangle
4738	 * @param $h (float) Height of the rectangle
4739	 * @param $text (string) annotation text or alternate content
4740	 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4741	 * @param $spaces (int) number of spaces on the text to link
4742	 * @public
4743	 * @since 4.0.018 (2008-08-06)
4744	 */
4745	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4746		if ($this->inxobj) {
4747			// store parameters for later use on template
4748			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4749			return;
4750		}
4751		if ($x === '') {
4752			$x = $this->x;
4753		}
4754		if ($y === '') {
4755			$y = $this->y;
4756		}
4757		// check page for no-write regions and adapt page margins if necessary
4758		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4759		// recalculate coordinates to account for graphic transformations
4760		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4761			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4762				$maxid = count($this->transfmatrix[$i]) - 1;
4763				for ($j=$maxid; $j >= 0; --$j) {
4764					$ctm = $this->transfmatrix[$i][$j];
4765					if (isset($ctm['a'])) {
4766						$x = $x * $this->k;
4767						$y = ($this->h - $y) * $this->k;
4768						$w = $w * $this->k;
4769						$h = $h * $this->k;
4770						// top left
4771						$xt = $x;
4772						$yt = $y;
4773						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4774						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4775						// top right
4776						$xt = $x + $w;
4777						$yt = $y;
4778						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4779						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4780						// bottom left
4781						$xt = $x;
4782						$yt = $y - $h;
4783						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4784						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4785						// bottom right
4786						$xt = $x + $w;
4787						$yt = $y - $h;
4788						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4789						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4790						// new coordinates (rectangle area)
4791						$x = min($x1, $x2, $x3, $x4);
4792						$y = max($y1, $y2, $y3, $y4);
4793						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4794						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4795						$x = $x / $this->k;
4796						$y = $this->h - ($y / $this->k);
4797					}
4798				}
4799			}
4800		}
4801		if ($this->page <= 0) {
4802			$page = 1;
4803		} else {
4804			$page = $this->page;
4805		}
4806		if (!isset($this->PageAnnots[$page])) {
4807			$this->PageAnnots[$page] = array();
4808		}
4809		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4810		if (!$this->pdfa_mode) {
4811			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4812				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4813				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4814				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4815			}
4816		}
4817		// Add widgets annotation's icons
4818		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4819			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4820		}
4821		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4822			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4823		}
4824		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4825			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4826		}
4827	}
4828
4829	/**
4830	 * Embedd the attached files.
4831	 * @since 4.4.000 (2008-12-07)
4832	 * @protected
4833	 * @see Annotation()
4834	 */
4835	protected function _putEmbeddedFiles() {
4836		if ($this->pdfa_mode) {
4837			// embedded files are not allowed in PDF/A mode
4838			return;
4839		}
4840		reset($this->embeddedfiles);
4841		foreach ($this->embeddedfiles as $filename => $filedata) {
4842			$data = TCPDF_STATIC::fileGetContents($filedata['file']);
4843			if ($data !== FALSE) {
4844				$rawsize = strlen($data);
4845				if ($rawsize > 0) {
4846					// update name tree
4847					$this->efnames[$filename] = $filedata['f'].' 0 R';
4848					// embedded file specification object
4849					$out = $this->_getobj($filedata['f'])."\n";
4850					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4851					$out .= "\n".'endobj';
4852					$this->_out($out);
4853					// embedded file object
4854					$filter = '';
4855					if ($this->compress) {
4856						$data = gzcompress($data);
4857						$filter = ' /Filter /FlateDecode';
4858					}
4859					$stream = $this->_getrawstream($data, $filedata['n']);
4860					$out = $this->_getobj($filedata['n'])."\n";
4861					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4862					$out .= ' stream'."\n".$stream."\n".'endstream';
4863					$out .= "\n".'endobj';
4864					$this->_out($out);
4865				}
4866			}
4867		}
4868	}
4869
4870	/**
4871	 * Prints a text cell at the specified position.
4872	 * This method allows to place a string precisely on the page.
4873	 * @param $x (float) Abscissa of the cell origin
4874	 * @param $y (float) Ordinate of the cell origin
4875	 * @param $txt (string) String to print
4876	 * @param $fstroke (int) outline size in user units (false = disable)
4877	 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4878	 * @param $ffill (boolean) if true fills the text
4879	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4880	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4881	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4882	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4883	 * @param $link (mixed) URL or identifier returned by AddLink().
4884	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4885	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4886	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4887	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4888	 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4889	 * @public
4890	 * @since 1.0
4891	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4892	 */
4893	public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
4894		$textrendermode = $this->textrendermode;
4895		$textstrokewidth = $this->textstrokewidth;
4896		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
4897		$this->SetXY($x, $y, $rtloff);
4898		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4899		// restore previous rendering mode
4900		$this->textrendermode = $textrendermode;
4901		$this->textstrokewidth = $textstrokewidth;
4902	}
4903
4904	/**
4905	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4906	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4907	 * This method is called automatically and should not be called directly by the application.
4908	 * @return boolean
4909	 * @public
4910	 * @since 1.4
4911	 * @see SetAutoPageBreak()
4912	 */
4913	public function AcceptPageBreak() {
4914		if ($this->num_columns > 1) {
4915			// multi column mode
4916			if ($this->current_column < ($this->num_columns - 1)) {
4917				// go to next column
4918				$this->selectColumn($this->current_column + 1);
4919			} elseif ($this->AutoPageBreak) {
4920				// add a new page
4921				$this->AddPage();
4922				// set first column
4923				$this->selectColumn(0);
4924			}
4925			// avoid page breaking from checkPageBreak()
4926			return false;
4927		}
4928		return $this->AutoPageBreak;
4929	}
4930
4931	/**
4932	 * Add page if needed.
4933	 * @param $h (float) Cell height. Default value: 0.
4934	 * @param $y (mixed) starting y position, leave empty for current position.
4935	 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4936	 * @return boolean true in case of page break, false otherwise.
4937	 * @since 3.2.000 (2008-07-01)
4938	 * @protected
4939	 */
4940	protected function checkPageBreak($h=0, $y='', $addpage=true) {
4941		if (TCPDF_STATIC::empty_string($y)) {
4942			$y = $this->y;
4943		}
4944		$current_page = $this->page;
4945		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4946			if ($addpage) {
4947				//Automatic page break
4948				$x = $this->x;
4949				$this->AddPage($this->CurOrientation);
4950				$this->y = $this->tMargin;
4951				$oldpage = $this->page - 1;
4952				if ($this->rtl) {
4953					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4954						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4955					} else {
4956						$this->x = $x;
4957					}
4958				} else {
4959					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4960						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4961					} else {
4962						$this->x = $x;
4963					}
4964				}
4965			}
4966			return true;
4967		}
4968		if ($current_page != $this->page) {
4969			// account for columns mode
4970			return true;
4971		}
4972		return false;
4973	}
4974
4975	/**
4976	 * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
4977	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
4978	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
4979	 * @param $h (float) Cell height. Default value: 0.
4980	 * @param $txt (string) String to print. Default value: empty string.
4981	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4982	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4983	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4984	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4985	 * @param $link (mixed) URL or identifier returned by AddLink().
4986	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4987	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4988	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
4989	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4990	 * @public
4991	 * @since 1.0
4992	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
4993	 */
4994	public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
4995		$prev_cell_margin = $this->cell_margin;
4996		$prev_cell_padding = $this->cell_padding;
4997		$this->adjustCellPadding($border);
4998		if (!$ignore_min_height) {
4999			$min_cell_height = $this->getCellHeight($this->FontSize);
5000			if ($h < $min_cell_height) {
5001				$h = $min_cell_height;
5002			}
5003		}
5004		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5005		// apply text shadow if enabled
5006		if ($this->txtshadow['enabled']) {
5007			// save data
5008			$x = $this->x;
5009			$y = $this->y;
5010			$bc = $this->bgcolor;
5011			$fc = $this->fgcolor;
5012			$sc = $this->strokecolor;
5013			$alpha = $this->alpha;
5014			// print shadow
5015			$this->x += $this->txtshadow['depth_w'];
5016			$this->y += $this->txtshadow['depth_h'];
5017			$this->SetFillColorArray($this->txtshadow['color']);
5018			$this->SetTextColorArray($this->txtshadow['color']);
5019			$this->SetDrawColorArray($this->txtshadow['color']);
5020			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5021				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5022			}
5023			if ($this->state == 2) {
5024				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5025			}
5026			//restore data
5027			$this->x = $x;
5028			$this->y = $y;
5029			$this->SetFillColorArray($bc);
5030			$this->SetTextColorArray($fc);
5031			$this->SetDrawColorArray($sc);
5032			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5033				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5034			}
5035		}
5036		if ($this->state == 2) {
5037			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5038		}
5039		$this->cell_padding = $prev_cell_padding;
5040		$this->cell_margin = $prev_cell_margin;
5041	}
5042
5043	/**
5044	 * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
5045	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5046	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5047	 * @param $h (float) Cell height. Default value: 0.
5048	 * @param $txt (string) String to print. Default value: empty string.
5049	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5050	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5051	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5052	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5053	 * @param $link (mixed) URL or identifier returned by AddLink().
5054	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5055	 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5056	 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5057	 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5058	 * @return string containing cell code
5059	 * @protected
5060	 * @since 1.0
5061	 * @see Cell()
5062	 */
5063	protected function getCellCode($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
5064		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5065		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5066		$prev_cell_margin = $this->cell_margin;
5067		$prev_cell_padding = $this->cell_padding;
5068		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5069		$rs = ''; //string to be returned
5070		$this->adjustCellPadding($border);
5071		if (!$ignore_min_height) {
5072			$min_cell_height = $this->getCellHeight($this->FontSize);
5073			if ($h < $min_cell_height) {
5074				$h = $min_cell_height;
5075			}
5076		}
5077		$k = $this->k;
5078		// check page for no-write regions and adapt page margins if necessary
5079		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5080		if ($this->rtl) {
5081			$x = $this->x - $this->cell_margin['R'];
5082		} else {
5083			$x = $this->x + $this->cell_margin['L'];
5084		}
5085		$y = $this->y + $this->cell_margin['T'];
5086		$prev_font_stretching = $this->font_stretching;
5087		$prev_font_spacing = $this->font_spacing;
5088		// cell vertical alignment
5089		switch ($calign) {
5090			case 'A': {
5091				// font top
5092				switch ($valign) {
5093					case 'T': {
5094						// top
5095						$y -= $this->cell_padding['T'];
5096						break;
5097					}
5098					case 'B': {
5099						// bottom
5100						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5101						break;
5102					}
5103					default:
5104					case 'C':
5105					case 'M': {
5106						// center
5107						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5108						break;
5109					}
5110				}
5111				break;
5112			}
5113			case 'L': {
5114				// font baseline
5115				switch ($valign) {
5116					case 'T': {
5117						// top
5118						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5119						break;
5120					}
5121					case 'B': {
5122						// bottom
5123						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5124						break;
5125					}
5126					default:
5127					case 'C':
5128					case 'M': {
5129						// center
5130						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5131						break;
5132					}
5133				}
5134				break;
5135			}
5136			case 'D': {
5137				// font bottom
5138				switch ($valign) {
5139					case 'T': {
5140						// top
5141						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5142						break;
5143					}
5144					case 'B': {
5145						// bottom
5146						$y -= ($h - $this->cell_padding['B']);
5147						break;
5148					}
5149					default:
5150					case 'C':
5151					case 'M': {
5152						// center
5153						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5154						break;
5155					}
5156				}
5157				break;
5158			}
5159			case 'B': {
5160				// cell bottom
5161				$y -= $h;
5162				break;
5163			}
5164			case 'C':
5165			case 'M': {
5166				// cell center
5167				$y -= ($h / 2);
5168				break;
5169			}
5170			default:
5171			case 'T': {
5172				// cell top
5173				break;
5174			}
5175		}
5176		// text vertical alignment
5177		switch ($valign) {
5178			case 'T': {
5179				// top
5180				$yt = $y + $this->cell_padding['T'];
5181				break;
5182			}
5183			case 'B': {
5184				// bottom
5185				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5186				break;
5187			}
5188			default:
5189			case 'C':
5190			case 'M': {
5191				// center
5192				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5193				break;
5194			}
5195		}
5196		$basefonty = $yt + $this->FontAscent;
5197		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5198			if ($this->rtl) {
5199				$w = $x - $this->lMargin;
5200			} else {
5201				$w = $this->w - $this->rMargin - $x;
5202			}
5203		}
5204		$s = '';
5205		// fill and borders
5206		if (is_string($border) AND (strlen($border) == 4)) {
5207			// full border
5208			$border = 1;
5209		}
5210		if ($fill OR ($border == 1)) {
5211			if ($fill) {
5212				$op = ($border == 1) ? 'B' : 'f';
5213			} else {
5214				$op = 'S';
5215			}
5216			if ($this->rtl) {
5217				$xk = (($x - $w) * $k);
5218			} else {
5219				$xk = ($x * $k);
5220			}
5221			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5222		}
5223		// draw borders
5224		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5225		if ($txt != '') {
5226			$txt2 = $txt;
5227			if ($this->isunicode) {
5228				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5229					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5230				} else {
5231					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5232					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5233					// replace thai chars (if any)
5234					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5235						// number of chars
5236						$numchars = count($unicode);
5237						// po pla, for far, for fan
5238						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5239						// do chada, to patak
5240						$lowtail = array(0x0e0e, 0x0e0f);
5241						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5242						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5243						// mai ek, mai tho, mai tri, mai chattawa, karan
5244						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5245						// sara u, sara uu, pinthu
5246						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5247						$output = array();
5248						for ($i = 0; $i < $numchars; $i++) {
5249							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5250								$ch0 = $unicode[$i];
5251								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5252								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5253								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5254								if (in_array($ch0, $tonemark)) {
5255									if ($chn == 0x0e33) {
5256										// sara um
5257										if (in_array($ch1, $longtail)) {
5258											// tonemark at upper left
5259											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5260										} else {
5261											// tonemark at upper right (normal position)
5262											$output[] = $ch0;
5263										}
5264									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5265										// tonemark at lower left
5266										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5267									} elseif (in_array($ch1, $upvowel)) {
5268										if (in_array($ch2, $longtail)) {
5269											// tonemark at upper left
5270											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5271										} else {
5272											// tonemark at upper right (normal position)
5273											$output[] = $ch0;
5274										}
5275									} else {
5276										// tonemark at lower right
5277										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5278									}
5279								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5280									// add lower left nikhahit and sara aa
5281									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5282										$output[] = 0xf711;
5283										$this->CurrentFont['subsetchars'][0xf711] = true;
5284										$output[] = 0x0e32;
5285										$this->CurrentFont['subsetchars'][0x0e32] = true;
5286									} else {
5287										$output[] = $ch0;
5288									}
5289								} elseif (in_array($ch1, $longtail)) {
5290									if ($ch0 == 0x0e31) {
5291										// lower left mai hun arkad
5292										$output[] = $this->replaceChar($ch0, 0xf710);
5293									} elseif (in_array($ch0, $upvowel)) {
5294										// lower left
5295										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5296									} elseif ($ch0 == 0x0e47) {
5297										// lower left mai tai koo
5298										$output[] = $this->replaceChar($ch0, 0xf712);
5299									} else {
5300										// normal character
5301										$output[] = $ch0;
5302									}
5303								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5304									// lower vowel
5305									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5306								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5307									// yo ying without lower part
5308									$output[] = $this->replaceChar($ch0, 0xf70f);
5309								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5310									// tho santan without lower part
5311									$output[] = $this->replaceChar($ch0, 0xf700);
5312								} else {
5313									$output[] = $ch0;
5314								}
5315							} else {
5316								// non-thai character
5317								$output[] = $unicode[$i];
5318							}
5319						}
5320						$unicode = $output;
5321						// update font subsetchars
5322						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5323					} // end of K_THAI_TOPCHARS
5324					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5325				}
5326			}
5327			$txt2 = TCPDF_STATIC::_escape($txt2);
5328			// get current text width (considering general font stretching and spacing)
5329			$txwidth = $this->GetStringWidth($txt);
5330			$width = $txwidth;
5331			// check for stretch mode
5332			if ($stretch > 0) {
5333				// calculate ratio between cell width and text width
5334				if ($width <= 0) {
5335					$ratio = 1;
5336				} else {
5337					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5338				}
5339				// check if stretching is required
5340				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5341					// the text will be stretched to fit cell width
5342					if ($stretch > 2) {
5343						// set new character spacing
5344						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5345					} else {
5346						// set new horizontal stretching
5347						$this->font_stretching *= $ratio;
5348					}
5349					// recalculate text width (the text fills the entire cell)
5350					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5351					// reset alignment
5352					$align = '';
5353				}
5354			}
5355			if ($this->font_stretching != 100) {
5356				// apply font stretching
5357				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5358			}
5359			if ($this->font_spacing != 0) {
5360				// increase/decrease font spacing
5361				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5362			}
5363			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5364				$s .= 'q '.$this->TextColor.' ';
5365			}
5366			// rendering mode
5367			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5368			// count number of spaces
5369			$ns = substr_count($txt, chr(32));
5370			// Justification
5371			$spacewidth = 0;
5372			if (($align == 'J') AND ($ns > 0)) {
5373				if ($this->isUnicodeFont()) {
5374					// get string width without spaces
5375					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5376					// calculate average space width
5377					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5378					if ($this->font_stretching != 100) {
5379						// word spacing is affected by stretching
5380						$spacewidth /= ($this->font_stretching / 100);
5381					}
5382					// set word position to be used with TJ operator
5383					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5384					$unicode_justification = true;
5385				} else {
5386					// get string width
5387					$width = $txwidth;
5388					// new space width
5389					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5390					if ($this->font_stretching != 100) {
5391						// word spacing (Tw) is affected by stretching
5392						$spacewidth /= ($this->font_stretching / 100);
5393					}
5394					// set word spacing
5395					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5396				}
5397				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5398			}
5399			// replace carriage return characters
5400			$txt2 = str_replace("\r", ' ', $txt2);
5401			switch ($align) {
5402				case 'C': {
5403					$dx = ($w - $width) / 2;
5404					break;
5405				}
5406				case 'R': {
5407					if ($this->rtl) {
5408						$dx = $this->cell_padding['R'];
5409					} else {
5410						$dx = $w - $width - $this->cell_padding['R'];
5411					}
5412					break;
5413				}
5414				case 'L': {
5415					if ($this->rtl) {
5416						$dx = $w - $width - $this->cell_padding['L'];
5417					} else {
5418						$dx = $this->cell_padding['L'];
5419					}
5420					break;
5421				}
5422				case 'J':
5423				default: {
5424					if ($this->rtl) {
5425						$dx = $this->cell_padding['R'];
5426					} else {
5427						$dx = $this->cell_padding['L'];
5428					}
5429					break;
5430				}
5431			}
5432			if ($this->rtl) {
5433				$xdx = $x - $dx - $width;
5434			} else {
5435				$xdx = $x + $dx;
5436			}
5437			$xdk = $xdx * $k;
5438			// print text
5439			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5440			if (isset($uniblock)) {
5441				// print overlapping characters as separate string
5442				$xshift = 0; // horizontal shift
5443				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5444				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5445				foreach ($uniblock as $uk => $uniarr) {
5446					if (($uk % 2) == 0) {
5447						// x space to skip
5448						if ($spacewidth != 0) {
5449							// justification shift
5450							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5451						}
5452						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5453					} else {
5454						// character to print
5455						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5456						$topchr = TCPDF_STATIC::_escape($topchr);
5457						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5458					}
5459				}
5460			}
5461			if ($this->underline) {
5462				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5463			}
5464			if ($this->linethrough) {
5465				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5466			}
5467			if ($this->overline) {
5468				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5469			}
5470			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5471				$s .= ' Q';
5472			}
5473			if ($link) {
5474				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5475			}
5476		}
5477		// output cell
5478		if ($s) {
5479			// output cell
5480			$rs .= $s;
5481			if ($this->font_spacing != 0) {
5482				// reset font spacing mode
5483				$rs .= ' BT 0 Tc ET';
5484			}
5485			if ($this->font_stretching != 100) {
5486				// reset font stretching mode
5487				$rs .= ' BT 100 Tz ET';
5488			}
5489		}
5490		// reset word spacing
5491		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5492			$rs .= ' BT 0 Tw ET';
5493		}
5494		// reset stretching and spacing
5495		$this->font_stretching = $prev_font_stretching;
5496		$this->font_spacing = $prev_font_spacing;
5497		$this->lasth = $h;
5498		if ($ln > 0) {
5499			//Go to the beginning of the next line
5500			$this->y = $y + $h + $this->cell_margin['B'];
5501			if ($ln == 1) {
5502				if ($this->rtl) {
5503					$this->x = $this->w - $this->rMargin;
5504				} else {
5505					$this->x = $this->lMargin;
5506				}
5507			}
5508		} else {
5509			// go left or right by case
5510			if ($this->rtl) {
5511				$this->x = $x - $w - $this->cell_margin['L'];
5512			} else {
5513				$this->x = $x + $w + $this->cell_margin['R'];
5514			}
5515		}
5516		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5517		$rs = $gstyles.$rs;
5518		$this->cell_padding = $prev_cell_padding;
5519		$this->cell_margin = $prev_cell_margin;
5520		return $rs;
5521	}
5522
5523	/**
5524	 * Replace a char if is defined on the current font.
5525	 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5526	 * @param $newchar (int) Integer code (unicode) of the new character.
5527	 * @return int the replaced char or the old char in case the new char i not defined
5528	 * @protected
5529	 * @since 5.9.167 (2012-06-22)
5530	 */
5531	protected function replaceChar($oldchar, $newchar) {
5532		if ($this->isCharDefined($newchar)) {
5533			// add the new char on the subset list
5534			$this->CurrentFont['subsetchars'][$newchar] = true;
5535			// return the new character
5536			return $newchar;
5537		}
5538		// return the old char
5539		return $oldchar;
5540	}
5541
5542	/**
5543	 * Returns the code to draw the cell border
5544	 * @param $x (float) X coordinate.
5545	 * @param $y (float) Y coordinate.
5546	 * @param $w (float) Cell width.
5547	 * @param $h (float) Cell height.
5548	 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5549	 * @return string containing cell border code
5550	 * @protected
5551	 * @see SetLineStyle()
5552	 * @since 5.7.000 (2010-08-02)
5553	 */
5554	protected function getCellBorder($x, $y, $w, $h, $brd) {
5555		$s = ''; // string to be returned
5556		if (empty($brd)) {
5557			return $s;
5558		}
5559		if ($brd == 1) {
5560			$brd = array('LRTB' => true);
5561		}
5562		// calculate coordinates for border
5563		$k = $this->k;
5564		if ($this->rtl) {
5565			$xeL = ($x - $w) * $k;
5566			$xeR = $x * $k;
5567		} else {
5568			$xeL = $x * $k;
5569			$xeR = ($x + $w) * $k;
5570		}
5571		$yeL = (($this->h - ($y + $h)) * $k);
5572		$yeT = (($this->h - $y) * $k);
5573		$xeT = $xeL;
5574		$xeB = $xeR;
5575		$yeR = $yeT;
5576		$yeB = $yeL;
5577		if (is_string($brd)) {
5578			// convert string to array
5579			$slen = strlen($brd);
5580			$newbrd = array();
5581			for ($i = 0; $i < $slen; ++$i) {
5582				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5583			}
5584			$brd = $newbrd;
5585		}
5586		if (isset($brd['mode'])) {
5587			$mode = $brd['mode'];
5588			unset($brd['mode']);
5589		} else {
5590			$mode = 'normal';
5591		}
5592		foreach ($brd as $border => $style) {
5593			if (is_array($style) AND !empty($style)) {
5594				// apply border style
5595				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5596				$s .= $this->SetLineStyle($style, true)."\n";
5597			}
5598			switch ($mode) {
5599				case 'ext': {
5600					$off = (($this->LineWidth / 2) * $k);
5601					$xL = $xeL - $off;
5602					$xR = $xeR + $off;
5603					$yT = $yeT + $off;
5604					$yL = $yeL - $off;
5605					$xT = $xL;
5606					$xB = $xR;
5607					$yR = $yT;
5608					$yB = $yL;
5609					$w += $this->LineWidth;
5610					$h += $this->LineWidth;
5611					break;
5612				}
5613				case 'int': {
5614					$off = ($this->LineWidth / 2) * $k;
5615					$xL = $xeL + $off;
5616					$xR = $xeR - $off;
5617					$yT = $yeT - $off;
5618					$yL = $yeL + $off;
5619					$xT = $xL;
5620					$xB = $xR;
5621					$yR = $yT;
5622					$yB = $yL;
5623					$w -= $this->LineWidth;
5624					$h -= $this->LineWidth;
5625					break;
5626				}
5627				case 'normal':
5628				default: {
5629					$xL = $xeL;
5630					$xT = $xeT;
5631					$xB = $xeB;
5632					$xR = $xeR;
5633					$yL = $yeL;
5634					$yT = $yeT;
5635					$yB = $yeB;
5636					$yR = $yeR;
5637					break;
5638				}
5639			}
5640			// draw borders by case
5641			if (strlen($border) == 4) {
5642				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5643			} elseif (strlen($border) == 3) {
5644				if (strpos($border,'B') === false) { // LTR
5645					$s .= sprintf('%F %F m ', $xL, $yL);
5646					$s .= sprintf('%F %F l ', $xT, $yT);
5647					$s .= sprintf('%F %F l ', $xR, $yR);
5648					$s .= sprintf('%F %F l ', $xB, $yB);
5649					$s .= 'S ';
5650				} elseif (strpos($border,'L') === false) { // TRB
5651					$s .= sprintf('%F %F m ', $xT, $yT);
5652					$s .= sprintf('%F %F l ', $xR, $yR);
5653					$s .= sprintf('%F %F l ', $xB, $yB);
5654					$s .= sprintf('%F %F l ', $xL, $yL);
5655					$s .= 'S ';
5656				} elseif (strpos($border,'T') === false) { // RBL
5657					$s .= sprintf('%F %F m ', $xR, $yR);
5658					$s .= sprintf('%F %F l ', $xB, $yB);
5659					$s .= sprintf('%F %F l ', $xL, $yL);
5660					$s .= sprintf('%F %F l ', $xT, $yT);
5661					$s .= 'S ';
5662				} elseif (strpos($border,'R') === false) { // BLT
5663					$s .= sprintf('%F %F m ', $xB, $yB);
5664					$s .= sprintf('%F %F l ', $xL, $yL);
5665					$s .= sprintf('%F %F l ', $xT, $yT);
5666					$s .= sprintf('%F %F l ', $xR, $yR);
5667					$s .= 'S ';
5668				}
5669			} elseif (strlen($border) == 2) {
5670				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5671					$s .= sprintf('%F %F m ', $xL, $yL);
5672					$s .= sprintf('%F %F l ', $xT, $yT);
5673					$s .= sprintf('%F %F l ', $xR, $yR);
5674					$s .= 'S ';
5675				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5676					$s .= sprintf('%F %F m ', $xT, $yT);
5677					$s .= sprintf('%F %F l ', $xR, $yR);
5678					$s .= sprintf('%F %F l ', $xB, $yB);
5679					$s .= 'S ';
5680				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5681					$s .= sprintf('%F %F m ', $xR, $yR);
5682					$s .= sprintf('%F %F l ', $xB, $yB);
5683					$s .= sprintf('%F %F l ', $xL, $yL);
5684					$s .= 'S ';
5685				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5686					$s .= sprintf('%F %F m ', $xB, $yB);
5687					$s .= sprintf('%F %F l ', $xL, $yL);
5688					$s .= sprintf('%F %F l ', $xT, $yT);
5689					$s .= 'S ';
5690				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5691					$s .= sprintf('%F %F m ', $xL, $yL);
5692					$s .= sprintf('%F %F l ', $xT, $yT);
5693					$s .= 'S ';
5694					$s .= sprintf('%F %F m ', $xR, $yR);
5695					$s .= sprintf('%F %F l ', $xB, $yB);
5696					$s .= 'S ';
5697				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5698					$s .= sprintf('%F %F m ', $xT, $yT);
5699					$s .= sprintf('%F %F l ', $xR, $yR);
5700					$s .= 'S ';
5701					$s .= sprintf('%F %F m ', $xB, $yB);
5702					$s .= sprintf('%F %F l ', $xL, $yL);
5703					$s .= 'S ';
5704				}
5705			} else { // strlen($border) == 1
5706				if (strpos($border,'L') !== false) { // L
5707					$s .= sprintf('%F %F m ', $xL, $yL);
5708					$s .= sprintf('%F %F l ', $xT, $yT);
5709					$s .= 'S ';
5710				} elseif (strpos($border,'T') !== false) { // T
5711					$s .= sprintf('%F %F m ', $xT, $yT);
5712					$s .= sprintf('%F %F l ', $xR, $yR);
5713					$s .= 'S ';
5714				} elseif (strpos($border,'R') !== false) { // R
5715					$s .= sprintf('%F %F m ', $xR, $yR);
5716					$s .= sprintf('%F %F l ', $xB, $yB);
5717					$s .= 'S ';
5718				} elseif (strpos($border,'B') !== false) { // B
5719					$s .= sprintf('%F %F m ', $xB, $yB);
5720					$s .= sprintf('%F %F l ', $xL, $yL);
5721					$s .= 'S ';
5722				}
5723			}
5724			if (is_array($style) AND !empty($style)) {
5725				// reset border style to previous value
5726				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5727			}
5728		}
5729		return $s;
5730	}
5731
5732	/**
5733	 * This method allows printing text with line breaks.
5734	 * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
5735	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5736	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5737	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5738	 * @param $txt (string) String to print
5739	 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5740	 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5741	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5742	 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5743	 * @param $x (float) x position in user units
5744	 * @param $y (float) y position in user units
5745	 * @param $reseth (boolean) if true reset the last cell height (default true).
5746	 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5747	 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5748	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5749	 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5750	 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5751	 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5752	 * @return int Return the number of cells or 1 for html mode.
5753	 * @public
5754	 * @since 1.3
5755	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5756	 */
5757	public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5758		$prev_cell_margin = $this->cell_margin;
5759		$prev_cell_padding = $this->cell_padding;
5760		// adjust internal padding
5761		$this->adjustCellPadding($border);
5762		$mc_padding = $this->cell_padding;
5763		$mc_margin = $this->cell_margin;
5764		$this->cell_padding['T'] = 0;
5765		$this->cell_padding['B'] = 0;
5766		$this->setCellMargins(0, 0, 0, 0);
5767		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5768			// reset row height
5769			$this->resetLastH();
5770		}
5771		if (!TCPDF_STATIC::empty_string($y)) {
5772			$this->SetY($y); // set y in order to convert negative y values to positive ones
5773		}
5774		$y = $this->GetY();
5775		$resth = 0;
5776		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5777			// spit cell in more pages/columns
5778			$newh = ($this->PageBreakTrigger - $y);
5779			$resth = ($h - $newh); // cell to be printed on the next page/column
5780			$h = $newh;
5781		}
5782		// get current page number
5783		$startpage = $this->page;
5784		// get current column
5785		$startcolumn = $this->current_column;
5786		if (!TCPDF_STATIC::empty_string($x)) {
5787			$this->SetX($x);
5788		} else {
5789			$x = $this->GetX();
5790		}
5791		// check page for no-write regions and adapt page margins if necessary
5792		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5793		// apply margins
5794		$oy = $y + $mc_margin['T'];
5795		if ($this->rtl) {
5796			$ox = ($this->w - $x - $mc_margin['R']);
5797		} else {
5798			$ox = ($x + $mc_margin['L']);
5799		}
5800		$this->x = $ox;
5801		$this->y = $oy;
5802		// set width
5803		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5804			if ($this->rtl) {
5805				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5806			} else {
5807				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5808			}
5809		}
5810		// store original margin values
5811		$lMargin = $this->lMargin;
5812		$rMargin = $this->rMargin;
5813		if ($this->rtl) {
5814			$this->rMargin = ($this->w - $this->x);
5815			$this->lMargin = ($this->x - $w);
5816		} else {
5817			$this->lMargin = ($this->x);
5818			$this->rMargin = ($this->w - $this->x - $w);
5819		}
5820		$this->clMargin = $this->lMargin;
5821		$this->crMargin = $this->rMargin;
5822		if ($autopadding) {
5823			// add top padding
5824			$this->y += $mc_padding['T'];
5825		}
5826		if ($ishtml) { // ******* Write HTML text
5827			$this->writeHTML($txt, true, false, $reseth, true, $align);
5828			$nl = 1;
5829		} else { // ******* Write simple text
5830			$prev_FontSizePt = $this->FontSizePt;
5831			if ($fitcell) {
5832				// ajust height values
5833				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5834				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5835			}
5836			// vertical alignment
5837			if ($maxh > 0) {
5838				// get text height
5839				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5840				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5841					// try to reduce font size to fit text on cell (use a quick search algorithm)
5842					$fmin = 1;
5843					$fmax = $this->FontSizePt;
5844					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5845					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5846					while ($maxit >= 0) {
5847						$fmid = (($fmax + $fmin) / 2);
5848						$this->SetFontSize($fmid, false);
5849						$this->resetLastH();
5850						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5851						$diff = ($maxh - $text_height);
5852						if ($diff >= 0) {
5853							if ($diff <= $diff_epsilon) {
5854								break;
5855							}
5856							$fmin = $fmid;
5857						} else {
5858							$fmax = $fmid;
5859						}
5860						--$maxit;
5861					}
5862					if ($maxit < 0) {
5863						// premature exit, we get the minimum font value to fit the cell
5864						$this->SetFontSize($fmin);
5865						$this->resetLastH();
5866						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5867					} else {
5868						$this->SetFontSize($fmid);
5869						$this->resetLastH();
5870					}
5871				}
5872				if ($text_height < $maxh) {
5873					if ($valign == 'M') {
5874						// text vertically centered
5875						$this->y += (($maxh - $text_height) / 2);
5876					} elseif ($valign == 'B') {
5877						// text vertically aligned on bottom
5878						$this->y += ($maxh - $text_height);
5879					}
5880				}
5881			}
5882			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5883			if ($fitcell) {
5884				// restore font size
5885				$this->SetFontSize($prev_FontSizePt);
5886			}
5887		}
5888		if ($autopadding) {
5889			// add bottom padding
5890			$this->y += $mc_padding['B'];
5891		}
5892		// Get end-of-text Y position
5893		$currentY = $this->y;
5894		// get latest page number
5895		$endpage = $this->page;
5896		if ($resth > 0) {
5897			$skip = ($endpage - $startpage);
5898			$tmpresth = $resth;
5899			while ($tmpresth > 0) {
5900				if ($skip <= 0) {
5901					// add a page (or trig AcceptPageBreak() for multicolumn mode)
5902					$this->checkPageBreak($this->PageBreakTrigger + 1);
5903				}
5904				if ($this->num_columns > 1) {
5905					$tmpresth -= ($this->h - $this->y - $this->bMargin);
5906				} else {
5907					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5908				}
5909				--$skip;
5910			}
5911			$currentY = $this->y;
5912			$endpage = $this->page;
5913		}
5914		// get latest column
5915		$endcolumn = $this->current_column;
5916		if ($this->num_columns == 0) {
5917			$this->num_columns = 1;
5918		}
5919		// disable page regions check
5920		$check_page_regions = $this->check_page_regions;
5921		$this->check_page_regions = false;
5922		// get border modes
5923		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5924		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5925		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5926		// design borders around HTML cells.
5927		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5928			$ccode = '';
5929			$this->setPage($page);
5930			if ($this->num_columns < 2) {
5931				// single-column mode
5932				$this->SetX($x);
5933				$this->y = $this->tMargin;
5934			}
5935			// account for margin changes
5936			if ($page > $startpage) {
5937				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5938					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5939				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5940					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5941				}
5942			}
5943			if ($startpage == $endpage) {
5944				// single page
5945				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5946					if ($column != $this->current_column) {
5947						$this->selectColumn($column);
5948					}
5949					if ($this->rtl) {
5950						$this->x -= $mc_margin['R'];
5951					} else {
5952						$this->x += $mc_margin['L'];
5953					}
5954					if ($startcolumn == $endcolumn) { // single column
5955						$cborder = $border;
5956						$h = max($h, ($currentY - $oy));
5957						$this->y = $oy;
5958					} elseif ($column == $startcolumn) { // first column
5959						$cborder = $border_start;
5960						$this->y = $oy;
5961						$h = $this->h - $this->y - $this->bMargin;
5962					} elseif ($column == $endcolumn) { // end column
5963						$cborder = $border_end;
5964						$h = $currentY - $this->y;
5965						if ($resth > $h) {
5966							$h = $resth;
5967						}
5968					} else { // middle column
5969						$cborder = $border_middle;
5970						$h = $this->h - $this->y - $this->bMargin;
5971						$resth -= $h;
5972					}
5973					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5974				} // end for each column
5975			} elseif ($page == $startpage) { // first page
5976				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
5977					if ($column != $this->current_column) {
5978						$this->selectColumn($column);
5979					}
5980					if ($this->rtl) {
5981						$this->x -= $mc_margin['R'];
5982					} else {
5983						$this->x += $mc_margin['L'];
5984					}
5985					if ($column == $startcolumn) { // first column
5986						$cborder = $border_start;
5987						$this->y = $oy;
5988						$h = $this->h - $this->y - $this->bMargin;
5989					} else { // middle column
5990						$cborder = $border_middle;
5991						$h = $this->h - $this->y - $this->bMargin;
5992						$resth -= $h;
5993					}
5994					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5995				} // end for each column
5996			} elseif ($page == $endpage) { // last page
5997				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
5998					if ($column != $this->current_column) {
5999						$this->selectColumn($column);
6000					}
6001					if ($this->rtl) {
6002						$this->x -= $mc_margin['R'];
6003					} else {
6004						$this->x += $mc_margin['L'];
6005					}
6006					if ($column == $endcolumn) {
6007						// end column
6008						$cborder = $border_end;
6009						$h = $currentY - $this->y;
6010						if ($resth > $h) {
6011							$h = $resth;
6012						}
6013					} else {
6014						// middle column
6015						$cborder = $border_middle;
6016						$h = $this->h - $this->y - $this->bMargin;
6017						$resth -= $h;
6018					}
6019					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6020				} // end for each column
6021			} else { // middle page
6022				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6023					$this->selectColumn($column);
6024					if ($this->rtl) {
6025						$this->x -= $mc_margin['R'];
6026					} else {
6027						$this->x += $mc_margin['L'];
6028					}
6029					$cborder = $border_middle;
6030					$h = $this->h - $this->y - $this->bMargin;
6031					$resth -= $h;
6032					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6033				} // end for each column
6034			}
6035			if ($cborder OR $fill) {
6036				$offsetlen = strlen($ccode);
6037				// draw border and fill
6038				if ($this->inxobj) {
6039					// we are inside an XObject template
6040					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6041						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6042						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6043						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6044					} else {
6045						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6046						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6047					}
6048					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6049					$pstart = substr($pagebuff, 0, $pagemark);
6050					$pend = substr($pagebuff, $pagemark);
6051					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6052				} else {
6053					if (end($this->transfmrk[$this->page]) !== false) {
6054						$pagemarkkey = key($this->transfmrk[$this->page]);
6055						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6056						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6057					} elseif ($this->InFooter) {
6058						$pagemark = $this->footerpos[$this->page];
6059						$this->footerpos[$this->page] += $offsetlen;
6060					} else {
6061						$pagemark = $this->intmrk[$this->page];
6062						$this->intmrk[$this->page] += $offsetlen;
6063					}
6064					$pagebuff = $this->getPageBuffer($this->page);
6065					$pstart = substr($pagebuff, 0, $pagemark);
6066					$pend = substr($pagebuff, $pagemark);
6067					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6068				}
6069			}
6070		} // end for each page
6071		// restore page regions check
6072		$this->check_page_regions = $check_page_regions;
6073		// Get end-of-cell Y position
6074		$currentY = $this->GetY();
6075		// restore previous values
6076		if ($this->num_columns > 1) {
6077			$this->selectColumn();
6078		} else {
6079			// restore original margins
6080			$this->lMargin = $lMargin;
6081			$this->rMargin = $rMargin;
6082			if ($this->page > $startpage) {
6083				// check for margin variations between pages (i.e. booklet mode)
6084				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6085				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6086				if (($dl != 0) OR ($dr != 0)) {
6087					$this->lMargin += $dl;
6088					$this->rMargin += $dr;
6089				}
6090			}
6091		}
6092		if ($ln > 0) {
6093			//Go to the beginning of the next line
6094			$this->SetY($currentY + $mc_margin['B']);
6095			if ($ln == 2) {
6096				$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6097			}
6098		} else {
6099			// go left or right by case
6100			$this->setPage($startpage);
6101			$this->y = $y;
6102			$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6103		}
6104		$this->setContentMark();
6105		$this->cell_padding = $prev_cell_padding;
6106		$this->cell_margin = $prev_cell_margin;
6107		$this->clMargin = $this->lMargin;
6108		$this->crMargin = $this->rMargin;
6109		return $nl;
6110	}
6111
6112	/**
6113	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6114	 * @param $txt (string) String for calculating his height
6115	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6116	 * @param $reseth (boolean) if true reset the last cell height (default false).
6117	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6118	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6119	 * @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)))
6120	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6121	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6122	 * @public
6123	 * @since 4.5.011
6124	 */
6125	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6126		if ($txt === NULL) {
6127			return 0;
6128		}
6129		if ($txt === '') {
6130			// empty string
6131			return 1;
6132		}
6133		// adjust internal padding
6134		$prev_cell_padding = $this->cell_padding;
6135		$prev_lasth = $this->lasth;
6136		if (is_array($cellpadding)) {
6137			$this->cell_padding = $cellpadding;
6138		}
6139		$this->adjustCellPadding($border);
6140		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6141			if ($this->rtl) {
6142				$w = $this->x - $this->lMargin;
6143			} else {
6144				$w = $this->w - $this->rMargin - $this->x;
6145			}
6146		}
6147		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6148		if ($reseth) {
6149			// reset row height
6150			$this->resetLastH();
6151		}
6152		$lines = 1;
6153		$sum = 0;
6154		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6155		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6156		$length = count($chars);
6157		$lastSeparator = -1;
6158		for ($i = 0; $i < $length; ++$i) {
6159			$c = $chars[$i];
6160			$charWidth = $charsWidth[$i];
6161			if (($c != 160)
6162					AND (($c == 173)
6163						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6164						OR (($c == 45)
6165							AND ($i > 0) AND ($i < ($length - 1))
6166							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6167							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6168						)
6169					)
6170				) {
6171				$lastSeparator = $i;
6172			}
6173			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6174				++$lines;
6175				if ($c == 10) {
6176					$lastSeparator = -1;
6177					$sum = 0;
6178				} elseif ($lastSeparator != -1) {
6179					$i = $lastSeparator;
6180					$lastSeparator = -1;
6181					$sum = 0;
6182				} else {
6183					$sum = $charWidth;
6184				}
6185			} else {
6186				$sum += $charWidth;
6187			}
6188		}
6189		if ($chars[($length - 1)] == 10) {
6190			--$lines;
6191		}
6192		$this->cell_padding = $prev_cell_padding;
6193		$this->lasth = $prev_lasth;
6194		return $lines;
6195	}
6196
6197	/**
6198	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6199	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6200	 * @pre
6201	 *  // store current object
6202	 *  $pdf->startTransaction();
6203	 *  // store starting values
6204	 *  $start_y = $pdf->GetY();
6205	 *  $start_page = $pdf->getPage();
6206	 *  // call your printing functions with your parameters
6207	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6208	 *  $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);
6209	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6210	 *  // get the new Y
6211	 *  $end_y = $pdf->GetY();
6212	 *  $end_page = $pdf->getPage();
6213	 *  // calculate height
6214	 *  $height = 0;
6215	 *  if ($end_page == $start_page) {
6216	 *  	$height = $end_y - $start_y;
6217	 *  } else {
6218	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6219	 *  		$this->setPage($page);
6220	 *  		if ($page == $start_page) {
6221	 *  			// first page
6222	 *  			$height = $this->h - $start_y - $this->bMargin;
6223	 *  		} elseif ($page == $end_page) {
6224	 *  			// last page
6225	 *  			$height = $end_y - $this->tMargin;
6226	 *  		} else {
6227	 *  			$height = $this->h - $this->tMargin - $this->bMargin;
6228	 *  		}
6229	 *  	}
6230	 *  }
6231	 *  // restore previous object
6232	 *  $pdf = $pdf->rollbackTransaction();
6233	 *
6234	 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6235	 * @param $txt (string) String for calculating his height
6236	 * @param $reseth (boolean) if true reset the last cell height (default false).
6237	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6238	 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6239	 * @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)))
6240	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6241	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6242	 * @public
6243	 */
6244	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6245		// adjust internal padding
6246		$prev_cell_padding = $this->cell_padding;
6247		$prev_lasth = $this->lasth;
6248		if (is_array($cellpadding)) {
6249			$this->cell_padding = $cellpadding;
6250		}
6251		$this->adjustCellPadding($border);
6252		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6253		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6254		$this->cell_padding = $prev_cell_padding;
6255		$this->lasth = $prev_lasth;
6256		return $height;
6257	}
6258
6259	/**
6260	 * This method prints text from the current position.<br />
6261	 * @param $h (float) Line height
6262	 * @param $txt (string) String to print
6263	 * @param $link (mixed) URL or identifier returned by AddLink()
6264	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6265	 * @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>
6266	 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6267	 * @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.
6268	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6269	 * @param $firstblock (boolean) if true the string is the starting of a line.
6270	 * @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.
6271	 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6272	 * @param $margin (array) margin array of the parent container
6273	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6274	 * @public
6275	 * @since 1.5
6276	 */
6277	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6278		// check page for no-write regions and adapt page margins if necessary
6279		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6280		if (strlen($txt) == 0) {
6281			// fix empty text
6282			$txt = ' ';
6283		}
6284		if ($margin === '') {
6285			// set default margins
6286			$margin = $this->cell_margin;
6287		}
6288		// remove carriage returns
6289		$s = str_replace("\r", '', $txt);
6290		// check if string contains arabic text
6291		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6292			$arabic = true;
6293		} else {
6294			$arabic = false;
6295		}
6296		// check if string contains RTL text
6297		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6298			$rtlmode = true;
6299		} else {
6300			$rtlmode = false;
6301		}
6302		// get a char width
6303		$chrwidth = $this->GetCharWidth(46); // dot character
6304		// get array of unicode values
6305		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6306		// calculate maximum width for a single character on string
6307		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6308		array_walk($chrw, array($this, 'getRawCharWidth'));
6309		$maxchwidth = max($chrw);
6310		// get array of chars
6311		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6312		// get the number of characters
6313		$nb = count($chars);
6314		// replacement for SHY character (minus symbol)
6315		$shy_replacement = 45;
6316		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6317		// widht for SHY replacement
6318		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6319		// page width
6320		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6321		// calculate remaining line width ($w)
6322		if ($this->rtl) {
6323			$w = $this->x - $this->lMargin;
6324		} else {
6325			$w = $this->w - $this->rMargin - $this->x;
6326		}
6327		// max column width
6328		$wmax = ($w - $wadj);
6329		if (!$firstline) {
6330			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6331		}
6332		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6333			// the maximum width character do not fit on column
6334			return '';
6335		}
6336		// minimum row height
6337		$row_height = max($h, $this->getCellHeight($this->FontSize));
6338		// max Y
6339		$maxy = $this->y + $maxh - max($row_height, $h);
6340		$start_page = $this->page;
6341		$i = 0; // character position
6342		$j = 0; // current starting position
6343		$sep = -1; // position of the last blank space
6344		$prevsep = $sep; // previous separator
6345		$shy = false; // true if the last blank is a soft hypen (SHY)
6346		$prevshy = $shy; // previous shy mode
6347		$l = 0; // current string length
6348		$nl = 0; //number of lines
6349		$linebreak = false;
6350		$pc = 0; // previous character
6351		// for each character
6352		while ($i < $nb) {
6353			if (($maxh > 0) AND ($this->y > $maxy) ) {
6354				break;
6355			}
6356			//Get the current character
6357			$c = $chars[$i];
6358			if ($c == 10) { // 10 = "\n" = new line
6359				//Explicit line break
6360				if ($align == 'J') {
6361					if ($this->rtl) {
6362						$talign = 'R';
6363					} else {
6364						$talign = 'L';
6365					}
6366				} else {
6367					$talign = $align;
6368				}
6369				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6370				if ($firstline) {
6371					$startx = $this->x;
6372					$tmparr = array_slice($chars, $j, ($i - $j));
6373					if ($rtlmode) {
6374						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6375					}
6376					$linew = $this->GetArrStringWidth($tmparr);
6377					unset($tmparr);
6378					if ($this->rtl) {
6379						$this->endlinex = $startx - $linew;
6380					} else {
6381						$this->endlinex = $startx + $linew;
6382					}
6383					$w = $linew;
6384					$tmpcellpadding = $this->cell_padding;
6385					if ($maxh == 0) {
6386						$this->SetCellPadding(0);
6387					}
6388				}
6389				if ($firstblock AND $this->isRTLTextDir()) {
6390					$tmpstr = $this->stringRightTrim($tmpstr);
6391				}
6392				// Skip newlines at the beginning of a page or column
6393				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6394					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6395				}
6396				unset($tmpstr);
6397				if ($firstline) {
6398					$this->cell_padding = $tmpcellpadding;
6399					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6400				}
6401				++$nl;
6402				$j = $i + 1;
6403				$l = 0;
6404				$sep = -1;
6405				$prevsep = $sep;
6406				$shy = false;
6407				// account for margin changes
6408				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6409					$this->AcceptPageBreak();
6410					if ($this->rtl) {
6411						$this->x -= $margin['R'];
6412					} else {
6413						$this->x += $margin['L'];
6414					}
6415					$this->lMargin += $margin['L'];
6416					$this->rMargin += $margin['R'];
6417				}
6418				$w = $this->getRemainingWidth();
6419				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6420			} else {
6421				// 160 is the non-breaking space.
6422				// 173 is SHY (Soft Hypen).
6423				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6424				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6425				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6426				if (($c != 160)
6427					AND (($c == 173)
6428						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6429						OR (($c == 45)
6430							AND ($i < ($nb - 1))
6431							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6432							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6433						)
6434					)
6435				) {
6436					// update last blank space position
6437					$prevsep = $sep;
6438					$sep = $i;
6439					// check if is a SHY
6440					if (($c == 173) OR ($c == 45)) {
6441						$prevshy = $shy;
6442						$shy = true;
6443						if ($pc == 45) {
6444							$tmp_shy_replacement_width = 0;
6445							$tmp_shy_replacement_char = '';
6446						} else {
6447							$tmp_shy_replacement_width = $shy_replacement_width;
6448							$tmp_shy_replacement_char = $shy_replacement_char;
6449						}
6450					} else {
6451						$shy = false;
6452					}
6453				}
6454				// update string length
6455				if ($this->isUnicodeFont() AND ($arabic)) {
6456					// with bidirectional algorithm some chars may be changed affecting the line length
6457					// *** very slow ***
6458					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6459				} else {
6460					$l += $this->GetCharWidth($c);
6461				}
6462				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6463					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6464						$sep = $prevsep;
6465						$shy = $prevshy;
6466					}
6467					// we have reached the end of column
6468					if ($sep == -1) {
6469						// check if the line was already started
6470						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6471							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6472							// print a void cell and go to next line
6473							$this->Cell($w, $h, '', 0, 1);
6474							$linebreak = true;
6475							if ($firstline) {
6476								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6477							}
6478						} else {
6479							// truncate the word because do not fit on column
6480							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6481							if ($firstline) {
6482								$startx = $this->x;
6483								$tmparr = array_slice($chars, $j, ($i - $j));
6484								if ($rtlmode) {
6485									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6486								}
6487								$linew = $this->GetArrStringWidth($tmparr);
6488								unset($tmparr);
6489								if ($this->rtl) {
6490									$this->endlinex = $startx - $linew;
6491								} else {
6492									$this->endlinex = $startx + $linew;
6493								}
6494								$w = $linew;
6495								$tmpcellpadding = $this->cell_padding;
6496								if ($maxh == 0) {
6497									$this->SetCellPadding(0);
6498								}
6499							}
6500							if ($firstblock AND $this->isRTLTextDir()) {
6501								$tmpstr = $this->stringRightTrim($tmpstr);
6502							}
6503							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6504							unset($tmpstr);
6505							if ($firstline) {
6506								$this->cell_padding = $tmpcellpadding;
6507								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6508							}
6509							$j = $i;
6510							--$i;
6511						}
6512					} else {
6513						// word wrapping
6514						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6515							$endspace = 1;
6516						} else {
6517							$endspace = 0;
6518						}
6519						// check the length of the next string
6520						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6521						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6522						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6523							// truncate the word because do not fit on a full page width
6524							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6525							if ($firstline) {
6526								$startx = $this->x;
6527								$tmparr = array_slice($chars, $j, ($i - $j));
6528								if ($rtlmode) {
6529									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6530								}
6531								$linew = $this->GetArrStringWidth($tmparr);
6532								unset($tmparr);
6533								if ($this->rtl) {
6534									$this->endlinex = ($startx - $linew);
6535								} else {
6536									$this->endlinex = ($startx + $linew);
6537								}
6538								$w = $linew;
6539								$tmpcellpadding = $this->cell_padding;
6540								if ($maxh == 0) {
6541									$this->SetCellPadding(0);
6542								}
6543							}
6544							if ($firstblock AND $this->isRTLTextDir()) {
6545								$tmpstr = $this->stringRightTrim($tmpstr);
6546							}
6547							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6548							unset($tmpstr);
6549							if ($firstline) {
6550								$this->cell_padding = $tmpcellpadding;
6551								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6552							}
6553							$j = $i;
6554							--$i;
6555						} else {
6556							// word wrapping
6557							if ($shy) {
6558								// add hypen (minus symbol) at the end of the line
6559								$shy_width = $tmp_shy_replacement_width;
6560								if ($this->rtl) {
6561									$shy_char_left = $tmp_shy_replacement_char;
6562									$shy_char_right = '';
6563								} else {
6564									$shy_char_left = '';
6565									$shy_char_right = $tmp_shy_replacement_char;
6566								}
6567							} else {
6568								$shy_width = 0;
6569								$shy_char_left = '';
6570								$shy_char_right = '';
6571							}
6572							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6573							if ($firstline) {
6574								$startx = $this->x;
6575								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6576								if ($rtlmode) {
6577									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6578								}
6579								$linew = $this->GetArrStringWidth($tmparr);
6580								unset($tmparr);
6581								if ($this->rtl) {
6582									$this->endlinex = $startx - $linew - $shy_width;
6583								} else {
6584									$this->endlinex = $startx + $linew + $shy_width;
6585								}
6586								$w = $linew;
6587								$tmpcellpadding = $this->cell_padding;
6588								if ($maxh == 0) {
6589									$this->SetCellPadding(0);
6590								}
6591							}
6592							// print the line
6593							if ($firstblock AND $this->isRTLTextDir()) {
6594								$tmpstr = $this->stringRightTrim($tmpstr);
6595							}
6596							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6597							unset($tmpstr);
6598							if ($firstline) {
6599								if ($chars[$sep] == 45) {
6600									$endspace += 1;
6601								}
6602								// return the remaining text
6603								$this->cell_padding = $tmpcellpadding;
6604								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6605							}
6606							$i = $sep;
6607							$sep = -1;
6608							$shy = false;
6609							$j = ($i + 1);
6610						}
6611					}
6612					// account for margin changes
6613					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6614						$this->AcceptPageBreak();
6615						if ($this->rtl) {
6616							$this->x -= $margin['R'];
6617						} else {
6618							$this->x += $margin['L'];
6619						}
6620						$this->lMargin += $margin['L'];
6621						$this->rMargin += $margin['R'];
6622					}
6623					$w = $this->getRemainingWidth();
6624					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6625					if ($linebreak) {
6626						$linebreak = false;
6627					} else {
6628						++$nl;
6629						$l = 0;
6630					}
6631				}
6632			}
6633			// save last character
6634			$pc = $c;
6635			++$i;
6636		} // end while i < nb
6637		// print last substring (if any)
6638		if ($l > 0) {
6639			switch ($align) {
6640				case 'J':
6641				case 'C': {
6642					$w = $w;
6643					break;
6644				}
6645				case 'L': {
6646					if ($this->rtl) {
6647						$w = $w;
6648					} else {
6649						$w = $l;
6650					}
6651					break;
6652				}
6653				case 'R': {
6654					if ($this->rtl) {
6655						$w = $l;
6656					} else {
6657						$w = $w;
6658					}
6659					break;
6660				}
6661				default: {
6662					$w = $l;
6663					break;
6664				}
6665			}
6666			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6667			if ($firstline) {
6668				$startx = $this->x;
6669				$tmparr = array_slice($chars, $j, ($nb - $j));
6670				if ($rtlmode) {
6671					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6672				}
6673				$linew = $this->GetArrStringWidth($tmparr);
6674				unset($tmparr);
6675				if ($this->rtl) {
6676					$this->endlinex = $startx - $linew;
6677				} else {
6678					$this->endlinex = $startx + $linew;
6679				}
6680				$w = $linew;
6681				$tmpcellpadding = $this->cell_padding;
6682				if ($maxh == 0) {
6683					$this->SetCellPadding(0);
6684				}
6685			}
6686			if ($firstblock AND $this->isRTLTextDir()) {
6687				$tmpstr = $this->stringRightTrim($tmpstr);
6688			}
6689			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6690			unset($tmpstr);
6691			if ($firstline) {
6692				$this->cell_padding = $tmpcellpadding;
6693				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6694			}
6695			++$nl;
6696		}
6697		if ($firstline) {
6698			return '';
6699		}
6700		return $nl;
6701	}
6702
6703	/**
6704	 * Returns the remaining width between the current position and margins.
6705	 * @return int Return the remaining width
6706	 * @protected
6707	 */
6708	protected function getRemainingWidth() {
6709		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6710		if ($this->rtl) {
6711			return ($this->x - $this->lMargin);
6712		} else {
6713			return ($this->w - $this->rMargin - $this->x);
6714		}
6715	}
6716
6717	/**
6718	 * Set the block dimensions accounting for page breaks and page/column fitting
6719	 * @param $w (float) width
6720	 * @param $h (float) height
6721	 * @param $x (float) X coordinate
6722	 * @param $y (float) Y coodiante
6723	 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6724	 * @return array($w, $h, $x, $y)
6725	 * @protected
6726	 * @since 5.5.009 (2010-07-05)
6727	 */
6728	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6729		if ($w <= 0) {
6730			// set maximum width
6731			$w = ($this->w - $this->lMargin - $this->rMargin);
6732			if ($w <= 0) {
6733				$w = 1;
6734			}
6735		}
6736		if ($h <= 0) {
6737			// set maximum height
6738			$h = ($this->PageBreakTrigger - $this->tMargin);
6739			if ($h <= 0) {
6740				$h = 1;
6741			}
6742		}
6743		// resize the block to be vertically contained on a single page or single column
6744		if ($fitonpage OR $this->AutoPageBreak) {
6745			$ratio_wh = ($w / $h);
6746			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6747				$h = $this->PageBreakTrigger - $this->tMargin;
6748				$w = ($h * $ratio_wh);
6749			}
6750			// resize the block to be horizontally contained on a single page or single column
6751			if ($fitonpage) {
6752				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6753				if ($w > $maxw) {
6754					$w = $maxw;
6755					$h = ($w / $ratio_wh);
6756				}
6757			}
6758		}
6759		// Check whether we need a new page or new column first as this does not fit
6760		$prev_x = $this->x;
6761		$prev_y = $this->y;
6762		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6763			$y = $this->y;
6764			if ($this->rtl) {
6765				$x += ($prev_x - $this->x);
6766			} else {
6767				$x += ($this->x - $prev_x);
6768			}
6769			$this->newline = true;
6770		}
6771		// resize the block to be contained on the remaining available page or column space
6772		if ($fitonpage) {
6773			$ratio_wh = ($w / $h);
6774			if (($y + $h) > $this->PageBreakTrigger) {
6775				$h = $this->PageBreakTrigger - $y;
6776				$w = ($h * $ratio_wh);
6777			}
6778			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6779				$w = $this->w - $this->rMargin - $x;
6780				$h = ($w / $ratio_wh);
6781			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6782				$w = $x - $this->lMargin;
6783				$h = ($w / $ratio_wh);
6784			}
6785		}
6786		return array($w, $h, $x, $y);
6787	}
6788
6789	/**
6790	 * Puts an image in the page.
6791	 * The upper-left corner must be given.
6792	 * The dimensions can be specified in different ways:<ul>
6793	 * <li>explicit width and height (expressed in user unit)</li>
6794	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6795	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6796	 * 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;
6797	 * The format can be specified explicitly or inferred from the file extension.<br />
6798	 * It is possible to put a link on the image.<br />
6799	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6800	 * @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').
6801	 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6802	 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6803	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6804	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6805	 * @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.
6806	 * @param $link (mixed) URL or identifier returned by AddLink().
6807	 * @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>
6808	 * @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).
6809	 * @param $dpi (int) dot-per-inch resolution used on resize
6810	 * @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>
6811	 * @param $ismask (boolean) true if this image is a mask, false otherwise
6812	 * @param $imgmask (mixed) image object returned by this function or false
6813	 * @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)))
6814	 * @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).
6815	 * @param $hidden (boolean) If true do not display the image.
6816	 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6817	 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6818	 * @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.
6819	 * @return image information
6820	 * @public
6821	 * @since 1.1
6822	 */
6823	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()) {
6824		if ($this->state != 2) {
6825			return;
6826		}
6827		if (strcmp($x, '') === 0) {
6828			$x = $this->x;
6829		}
6830		if (strcmp($y, '') === 0) {
6831			$y = $this->y;
6832		}
6833		// check page for no-write regions and adapt page margins if necessary
6834		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6835		$exurl = ''; // external streams
6836		$imsize = FALSE;
6837		// check if we are passing an image as file or string
6838		if ($file[0] === '@') {
6839			// image from string
6840			$imgdata = substr($file, 1);
6841		} else { // image file
6842			if ($file[0] === '*') {
6843				// image as external stream
6844				$file = substr($file, 1);
6845				$exurl = $file;
6846			}
6847			// check if file exist and it is valid
6848			if (!@TCPDF_STATIC::file_exists($file)) {
6849				return false;
6850			}
6851			if (($imsize = @getimagesize($file)) === FALSE) {
6852				if (in_array($file, $this->imagekeys)) {
6853					// get existing image data
6854					$info = $this->getImageBuffer($file);
6855					$imsize = array($info['w'], $info['h']);
6856				} elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
6857					$imgdata = TCPDF_STATIC::fileGetContents($file);
6858				}
6859			}
6860		}
6861		if (!empty($imgdata)) {
6862			// copy image to cache
6863			$original_file = $file;
6864			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6865			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6866			if (!$fp) {
6867				$this->Error('Unable to write file: '.$file);
6868			}
6869			fwrite($fp, $imgdata);
6870			fclose($fp);
6871			unset($imgdata);
6872			$imsize = @getimagesize($file);
6873			if ($imsize === FALSE) {
6874				unlink($file);
6875				$file = $original_file;
6876			}
6877		}
6878		if ($imsize === FALSE) {
6879			if (($w > 0) AND ($h > 0)) {
6880				// get measures from specified data
6881				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6882				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6883				$imsize = array($pw, $ph);
6884			} else {
6885				$this->Error('[Image] Unable to get the size of the image: '.$file);
6886			}
6887		}
6888		// file hash
6889		$filehash = md5($file);
6890		// get original image width and height in pixels
6891		list($pixw, $pixh) = $imsize;
6892		// calculate image width and height on document
6893		if (($w <= 0) AND ($h <= 0)) {
6894			// convert image size to document unit
6895			$w = $this->pixelsToUnits($pixw);
6896			$h = $this->pixelsToUnits($pixh);
6897		} elseif ($w <= 0) {
6898			$w = $h * $pixw / $pixh;
6899		} elseif ($h <= 0) {
6900			$h = $w * $pixh / $pixw;
6901		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6902			if (strlen($fitbox) !== 2) {
6903				// set default alignment
6904				$fitbox = '--';
6905			}
6906			// scale image dimensions proportionally to fit within the ($w, $h) box
6907			if ((($w * $pixh) / ($h * $pixw)) < 1) {
6908				// store current height
6909				$oldh = $h;
6910				// calculate new height
6911				$h = $w * $pixh / $pixw;
6912				// height difference
6913				$hdiff = ($oldh - $h);
6914				// vertical alignment
6915				switch (strtoupper($fitbox[1])) {
6916					case 'T': {
6917						break;
6918					}
6919					case 'M': {
6920						$y += ($hdiff / 2);
6921						break;
6922					}
6923					case 'B': {
6924						$y += $hdiff;
6925						break;
6926					}
6927				}
6928			} else {
6929				// store current width
6930				$oldw = $w;
6931				// calculate new width
6932				$w = $h * $pixw / $pixh;
6933				// width difference
6934				$wdiff = ($oldw - $w);
6935				// horizontal alignment
6936				switch (strtoupper($fitbox[0])) {
6937					case 'L': {
6938						if ($this->rtl) {
6939							$x -= $wdiff;
6940						}
6941						break;
6942					}
6943					case 'C': {
6944						if ($this->rtl) {
6945							$x -= ($wdiff / 2);
6946						} else {
6947							$x += ($wdiff / 2);
6948						}
6949						break;
6950					}
6951					case 'R': {
6952						if (!$this->rtl) {
6953							$x += $wdiff;
6954						}
6955						break;
6956					}
6957				}
6958			}
6959		}
6960		// fit the image on available space
6961		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6962		// calculate new minimum dimensions in pixels
6963		$neww = round($w * $this->k * $dpi / $this->dpi);
6964		$newh = round($h * $this->k * $dpi / $this->dpi);
6965		// check if resize is necessary (resize is used only to reduce the image)
6966		$newsize = ($neww * $newh);
6967		$pixsize = ($pixw * $pixh);
6968		if (intval($resize) == 2) {
6969			$resize = true;
6970		} elseif ($newsize >= $pixsize) {
6971			$resize = false;
6972		}
6973		// check if image has been already added on document
6974		$newimage = true;
6975		if (in_array($file, $this->imagekeys)) {
6976			$newimage = false;
6977			// get existing image data
6978			$info = $this->getImageBuffer($file);
6979			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
6980				// check if the newer image is larger
6981				$oldsize = ($info['w'] * $info['h']);
6982				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6983					$newimage = true;
6984				}
6985			}
6986		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
6987			// create temp image file (without alpha channel)
6988			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
6989			// create temp alpha file
6990			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
6991			// check for cached images
6992			if (in_array($tempfile_plain, $this->imagekeys)) {
6993				// get existing image data
6994				$info = $this->getImageBuffer($tempfile_plain);
6995				// check if the newer image is larger
6996				$oldsize = ($info['w'] * $info['h']);
6997				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6998					$newimage = true;
6999				} else {
7000					$newimage = false;
7001					// embed mask image
7002					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7003					// embed image, masked with previously embedded mask
7004					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7005				}
7006			}
7007		}
7008		if ($newimage) {
7009			//First use of image, get info
7010			$type = strtolower($type);
7011			if ($type == '') {
7012				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7013			} elseif ($type == 'jpg') {
7014				$type = 'jpeg';
7015			}
7016			$mqr = TCPDF_STATIC::get_mqr();
7017			TCPDF_STATIC::set_mqr(false);
7018			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7019			$mtd = '_parse'.$type;
7020			// GD image handler function
7021			$gdfunction = 'imagecreatefrom'.$type;
7022			$info = false;
7023			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7024				// TCPDF image functions
7025				$info = TCPDF_IMAGES::$mtd($file);
7026				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7027					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7028					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7029				}
7030			}
7031			if (($info === false) AND function_exists($gdfunction)) {
7032				try {
7033					// GD library
7034					$img = $gdfunction($file);
7035					if ($img !== false) {
7036						if ($resize) {
7037							$imgr = imagecreatetruecolor($neww, $newh);
7038							if (($type == 'gif') OR ($type == 'png')) {
7039								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7040							}
7041							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7042							$img = $imgr;
7043						}
7044						if (($type == 'gif') OR ($type == 'png')) {
7045							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7046						} else {
7047							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7048						}
7049					}
7050				} catch(Exception $e) {
7051					$info = false;
7052				}
7053			}
7054			if (($info === false) AND extension_loaded('imagick')) {
7055				try {
7056					// ImageMagick library
7057					$img = new Imagick();
7058					if ($type == 'svg') {
7059						if ($file[0] === '@') {
7060							// image from string
7061							$svgimg = substr($file, 1);
7062						} else {
7063							// get SVG file content
7064							$svgimg = TCPDF_STATIC::fileGetContents($file);
7065						}
7066						if ($svgimg !== FALSE) {
7067							// get width and height
7068							$regs = array();
7069							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7070								$svgtag = $regs[1];
7071								$tmp = array();
7072								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7073									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7074									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7075									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7076								} else {
7077									$ow = $w;
7078								}
7079								$tmp = array();
7080								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7081									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7082									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7083									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7084								} else {
7085									$oh = $h;
7086								}
7087								$tmp = array();
7088								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7089									$vbw = ($ow * $this->imgscale * $this->k);
7090									$vbh = ($oh * $this->imgscale * $this->k);
7091									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7092									$svgtag = $vbox.$svgtag;
7093								}
7094								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7095							}
7096							$img->readImageBlob($svgimg);
7097						}
7098					} else {
7099						$img->readImage($file);
7100					}
7101					if ($resize) {
7102						$img->resizeImage($neww, $newh, 10, 1, false);
7103					}
7104					$img->setCompressionQuality($this->jpeg_quality);
7105					$img->setImageFormat('jpeg');
7106					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7107					$img->writeImage($tempname);
7108					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7109					unlink($tempname);
7110					$img->destroy();
7111				} catch(Exception $e) {
7112					$info = false;
7113				}
7114			}
7115			if ($info === false) {
7116				// unable to process image
7117				return;
7118			}
7119			TCPDF_STATIC::set_mqr($mqr);
7120			if ($ismask) {
7121				// force grayscale
7122				$info['cs'] = 'DeviceGray';
7123			}
7124			if ($imgmask !== false) {
7125				$info['masked'] = $imgmask;
7126			}
7127			if (!empty($exurl)) {
7128				$info['exurl'] = $exurl;
7129			}
7130			// array of alternative images
7131			$info['altimgs'] = $altimgs;
7132			// add image to document
7133			$info['i'] = $this->setImageBuffer($file, $info);
7134		}
7135		// set alignment
7136		$this->img_rb_y = $y + $h;
7137		// set alignment
7138		if ($this->rtl) {
7139			if ($palign == 'L') {
7140				$ximg = $this->lMargin;
7141			} elseif ($palign == 'C') {
7142				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7143			} elseif ($palign == 'R') {
7144				$ximg = $this->w - $this->rMargin - $w;
7145			} else {
7146				$ximg = $x - $w;
7147			}
7148			$this->img_rb_x = $ximg;
7149		} else {
7150			if ($palign == 'L') {
7151				$ximg = $this->lMargin;
7152			} elseif ($palign == 'C') {
7153				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7154			} elseif ($palign == 'R') {
7155				$ximg = $this->w - $this->rMargin - $w;
7156			} else {
7157				$ximg = $x;
7158			}
7159			$this->img_rb_x = $ximg + $w;
7160		}
7161		if ($ismask OR $hidden) {
7162			// image is not displayed
7163			return $info['i'];
7164		}
7165		$xkimg = $ximg * $this->k;
7166		if (!$alt) {
7167			// only non-alternative immages will be set
7168			$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']));
7169		}
7170		if (!empty($border)) {
7171			$bx = $this->x;
7172			$by = $this->y;
7173			$this->x = $ximg;
7174			if ($this->rtl) {
7175				$this->x += $w;
7176			}
7177			$this->y = $y;
7178			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7179			$this->x = $bx;
7180			$this->y = $by;
7181		}
7182		if ($link) {
7183			$this->Link($ximg, $y, $w, $h, $link, 0);
7184		}
7185		// set pointer to align the next text/objects
7186		switch($align) {
7187			case 'T': {
7188				$this->y = $y;
7189				$this->x = $this->img_rb_x;
7190				break;
7191			}
7192			case 'M': {
7193				$this->y = $y + round($h/2);
7194				$this->x = $this->img_rb_x;
7195				break;
7196			}
7197			case 'B': {
7198				$this->y = $this->img_rb_y;
7199				$this->x = $this->img_rb_x;
7200				break;
7201			}
7202			case 'N': {
7203				$this->SetY($this->img_rb_y);
7204				break;
7205			}
7206			default:{
7207				break;
7208			}
7209		}
7210		$this->endlinex = $this->img_rb_x;
7211		if ($this->inxobj) {
7212			// we are inside an XObject template
7213			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7214		}
7215		return $info['i'];
7216	}
7217
7218	/**
7219	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7220	 * @param $file (string) Name of the file containing the image.
7221	 * @param $x (float) Abscissa of the upper-left corner.
7222	 * @param $y (float) Ordinate of the upper-left corner.
7223	 * @param $wpx (float) Original width of the image in pixels.
7224	 * @param $hpx (float) original height of the image in pixels.
7225	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7226	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7227	 * @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.
7228	 * @param $link (mixed) URL or identifier returned by AddLink().
7229	 * @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>
7230	 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7231	 * @param $dpi (int) dot-per-inch resolution used on resize
7232	 * @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>
7233	 * @param $filehash (string) File hash used to build unique file names.
7234	 * @author Nicola Asuni
7235	 * @protected
7236	 * @since 4.3.007 (2008-12-04)
7237	 * @see Image()
7238	 */
7239	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7240		// create temp images
7241		if (empty($filehash)) {
7242			$filehash = md5($file);
7243		}
7244		// create temp image file (without alpha channel)
7245		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7246		// create temp alpha file
7247		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7248		$parsed = false;
7249		$parse_error = '';
7250		// ImageMagick extension
7251		if (($parsed === false) AND extension_loaded('imagick')) {
7252			try {
7253				// ImageMagick library
7254				$img = new Imagick();
7255				$img->readImage($file);
7256				// clone image object
7257				$imga = TCPDF_STATIC::objclone($img);
7258				// extract alpha channel
7259				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7260					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7261				} else {
7262					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7263					$img->negateImage(true);
7264				}
7265				$img->setImageFormat('png');
7266				$img->writeImage($tempfile_alpha);
7267				// remove alpha channel
7268				if (method_exists($imga, 'setImageMatte')) {
7269					$imga->setImageMatte(false);
7270				} else {
7271					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7272				}
7273				$imga->setImageFormat('png');
7274				$imga->writeImage($tempfile_plain);
7275				$parsed = true;
7276			} catch (Exception $e) {
7277				// Imagemagick fails, try with GD
7278				$parse_error = 'Imagick library error: '.$e->getMessage();
7279			}
7280		}
7281		// GD extension
7282		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7283			try {
7284				// generate images
7285				$img = imagecreatefrompng($file);
7286				$imgalpha = imagecreate($wpx, $hpx);
7287				// generate gray scale palette (0 -> 255)
7288				for ($c = 0; $c < 256; ++$c) {
7289					ImageColorAllocate($imgalpha, $c, $c, $c);
7290				}
7291				// extract alpha channel
7292				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7293					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7294						$color = imagecolorat($img, $xpx, $ypx);
7295						// get and correct gamma color
7296						$alpha = $this->getGDgamma($img, $color);
7297						imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7298					}
7299				}
7300				imagepng($imgalpha, $tempfile_alpha);
7301				imagedestroy($imgalpha);
7302				// extract image without alpha channel
7303				$imgplain = imagecreatetruecolor($wpx, $hpx);
7304				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7305				imagepng($imgplain, $tempfile_plain);
7306				imagedestroy($imgplain);
7307				$parsed = true;
7308			} catch (Exception $e) {
7309				// GD fails
7310				$parse_error = 'GD library error: '.$e->getMessage();
7311			}
7312		}
7313		if ($parsed === false) {
7314			if (empty($parse_error)) {
7315				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7316			} else {
7317				$this->Error($parse_error);
7318			}
7319		}
7320		// embed mask image
7321		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7322		// embed image, masked with previously embedded mask
7323		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7324	}
7325
7326	/**
7327	 * Get the GD-corrected PNG gamma value from alpha color
7328	 * @param $img (int) GD image Resource ID.
7329	 * @param $c (int) alpha color
7330	 * @protected
7331	 * @since 4.3.007 (2008-12-04)
7332	 */
7333	protected function getGDgamma($img, $c) {
7334		if (!isset($this->gdgammacache['#'.$c])) {
7335			$colors = imagecolorsforindex($img, $c);
7336			// GD alpha is only 7 bit (0 -> 127)
7337			$this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7338			// correct gamma
7339			$this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7340			// store the latest values on cache to improve performances
7341			if (count($this->gdgammacache) > 8) {
7342				// remove one element from the cache array
7343				array_shift($this->gdgammacache);
7344			}
7345		}
7346		return $this->gdgammacache['#'.$c];
7347	}
7348
7349	/**
7350	 * Performs a line break.
7351	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7352	 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7353	 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7354	 * @public
7355	 * @since 1.0
7356	 * @see Cell()
7357	 */
7358	public function Ln($h='', $cell=false) {
7359		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'])) {
7360			// revove vertical space from the top of the column
7361			return;
7362		}
7363		if ($cell) {
7364			if ($this->rtl) {
7365				$cellpadding = $this->cell_padding['R'];
7366			} else {
7367				$cellpadding = $this->cell_padding['L'];
7368			}
7369		} else {
7370			$cellpadding = 0;
7371		}
7372		if ($this->rtl) {
7373			$this->x = $this->w - $this->rMargin - $cellpadding;
7374		} else {
7375			$this->x = $this->lMargin + $cellpadding;
7376		}
7377		if (is_string($h)) {
7378			$h = $this->lasth;
7379		}
7380		$this->y += $h;
7381		$this->newline = true;
7382	}
7383
7384	/**
7385	 * Returns the relative X value of current position.
7386	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7387	 * @return float
7388	 * @public
7389	 * @since 1.2
7390	 * @see SetX(), GetY(), SetY()
7391	 */
7392	public function GetX() {
7393		//Get x position
7394		if ($this->rtl) {
7395			return ($this->w - $this->x);
7396		} else {
7397			return $this->x;
7398		}
7399	}
7400
7401	/**
7402	 * Returns the absolute X value of current position.
7403	 * @return float
7404	 * @public
7405	 * @since 1.2
7406	 * @see SetX(), GetY(), SetY()
7407	 */
7408	public function GetAbsX() {
7409		return $this->x;
7410	}
7411
7412	/**
7413	 * Returns the ordinate of the current position.
7414	 * @return float
7415	 * @public
7416	 * @since 1.0
7417	 * @see SetY(), GetX(), SetX()
7418	 */
7419	public function GetY() {
7420		return $this->y;
7421	}
7422
7423	/**
7424	 * Defines the abscissa of the current position.
7425	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7426	 * @param $x (float) The value of the abscissa in user units.
7427	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7428	 * @public
7429	 * @since 1.2
7430	 * @see GetX(), GetY(), SetY(), SetXY()
7431	 */
7432	public function SetX($x, $rtloff=false) {
7433		$x = floatval($x);
7434		if (!$rtloff AND $this->rtl) {
7435			if ($x >= 0) {
7436				$this->x = $this->w - $x;
7437			} else {
7438				$this->x = abs($x);
7439			}
7440		} else {
7441			if ($x >= 0) {
7442				$this->x = $x;
7443			} else {
7444				$this->x = $this->w + $x;
7445			}
7446		}
7447		if ($this->x < 0) {
7448			$this->x = 0;
7449		}
7450		if ($this->x > $this->w) {
7451			$this->x = $this->w;
7452		}
7453	}
7454
7455	/**
7456	 * Moves the current abscissa back to the left margin and sets the ordinate.
7457	 * If the passed value is negative, it is relative to the bottom of the page.
7458	 * @param $y (float) The value of the ordinate in user units.
7459	 * @param $resetx (bool) if true (default) reset the X position.
7460	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7461	 * @public
7462	 * @since 1.0
7463	 * @see GetX(), GetY(), SetY(), SetXY()
7464	 */
7465	public function SetY($y, $resetx=true, $rtloff=false) {
7466		$y = floatval($y);
7467		if ($resetx) {
7468			//reset x
7469			if (!$rtloff AND $this->rtl) {
7470				$this->x = $this->w - $this->rMargin;
7471			} else {
7472				$this->x = $this->lMargin;
7473			}
7474		}
7475		if ($y >= 0) {
7476			$this->y = $y;
7477		} else {
7478			$this->y = $this->h + $y;
7479		}
7480		if ($this->y < 0) {
7481			$this->y = 0;
7482		}
7483		if ($this->y > $this->h) {
7484			$this->y = $this->h;
7485		}
7486	}
7487
7488	/**
7489	 * Defines the abscissa and ordinate of the current position.
7490	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7491	 * @param $x (float) The value of the abscissa.
7492	 * @param $y (float) The value of the ordinate.
7493	 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7494	 * @public
7495	 * @since 1.2
7496	 * @see SetX(), SetY()
7497	 */
7498	public function SetXY($x, $y, $rtloff=false) {
7499		$this->SetY($y, false, $rtloff);
7500		$this->SetX($x, $rtloff);
7501	}
7502
7503	/**
7504	 * Set the absolute X coordinate of the current pointer.
7505	 * @param $x (float) The value of the abscissa in user units.
7506	 * @public
7507	 * @since 5.9.186 (2012-09-13)
7508	 * @see setAbsX(), setAbsY(), SetAbsXY()
7509	 */
7510	public function SetAbsX($x) {
7511		$this->x = floatval($x);
7512	}
7513
7514	/**
7515	 * Set the absolute Y coordinate of the current pointer.
7516	 * @param $y (float) (float) The value of the ordinate in user units.
7517	 * @public
7518	 * @since 5.9.186 (2012-09-13)
7519	 * @see setAbsX(), setAbsY(), SetAbsXY()
7520	 */
7521	public function SetAbsY($y) {
7522		$this->y = floatval($y);
7523	}
7524
7525	/**
7526	 * Set the absolute X and Y coordinates of the current pointer.
7527	 * @param $x (float) The value of the abscissa in user units.
7528	 * @param $y (float) (float) The value of the ordinate in user units.
7529	 * @public
7530	 * @since 5.9.186 (2012-09-13)
7531	 * @see setAbsX(), setAbsY(), SetAbsXY()
7532	 */
7533	public function SetAbsXY($x, $y) {
7534		$this->SetAbsX($x);
7535		$this->SetAbsY($y);
7536	}
7537
7538	/**
7539	 * Send the document to a given destination: string, local file or browser.
7540	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7541	 * The method first calls Close() if necessary to terminate the document.
7542	 * @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.
7543	 * @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>
7544	 * @return string
7545	 * @public
7546	 * @since 1.0
7547	 * @see Close()
7548	 */
7549	public function Output($name='doc.pdf', $dest='I') {
7550		//Output PDF to some destination
7551		//Finish document if necessary
7552		if ($this->state < 3) {
7553			$this->Close();
7554		}
7555		//Normalize parameters
7556		if (is_bool($dest)) {
7557			$dest = $dest ? 'D' : 'F';
7558		}
7559		$dest = strtoupper($dest);
7560		if ($dest[0] != 'F') {
7561			$name = preg_replace('/[\s]+/', '_', $name);
7562			$name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7563		}
7564		if ($this->sign) {
7565			// *** apply digital signature to the document ***
7566			// get the document content
7567			$pdfdoc = $this->getBuffer();
7568			// remove last newline
7569			$pdfdoc = substr($pdfdoc, 0, -1);
7570			// remove filler space
7571			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7572			// define the ByteRange
7573			$byte_range = array();
7574			$byte_range[0] = 0;
7575			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7576			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7577			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7578			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7579			// replace the ByteRange
7580			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7581			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7582			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7583			// write the document to a temporary folder
7584			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7585			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7586			if (!$f) {
7587				$this->Error('Unable to create temporary file: '.$tempdoc);
7588			}
7589			$pdfdoc_length = strlen($pdfdoc);
7590			fwrite($f, $pdfdoc, $pdfdoc_length);
7591			fclose($f);
7592			// get digital signature via openssl library
7593			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7594			if (empty($this->signature_data['extracerts'])) {
7595				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7596			} else {
7597				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']);
7598			}
7599			// read signature
7600			$signature = file_get_contents($tempsign);
7601			// extract signature
7602			$signature = substr($signature, $pdfdoc_length);
7603			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7604			$tmparr = explode("\n\n", $signature);
7605			$signature = $tmparr[1];
7606			// decode signature
7607			$signature = base64_decode(trim($signature));
7608			// add TSA timestamp to signature
7609			$signature = $this->applyTSA($signature);
7610			// convert signature to hex
7611			$signature = current(unpack('H*', $signature));
7612			$signature = str_pad($signature, $this->signature_max_length, '0');
7613			// Add signature to the document
7614			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7615			$this->bufferlen = strlen($this->buffer);
7616		}
7617		switch($dest) {
7618			case 'I': {
7619				// Send PDF to the standard output
7620				if (ob_get_contents()) {
7621					$this->Error('Some data has already been output, can\'t send PDF file');
7622				}
7623				if (php_sapi_name() != 'cli') {
7624					// send output to a browser
7625					header('Content-Type: application/pdf');
7626					if (headers_sent()) {
7627						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7628					}
7629					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7630					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7631					header('Pragma: public');
7632					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7633					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7634					header('Content-Disposition: inline; filename="'.basename($name).'"');
7635					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7636				} else {
7637					echo $this->getBuffer();
7638				}
7639				break;
7640			}
7641			case 'D': {
7642				// download PDF as file
7643				if (ob_get_contents()) {
7644					$this->Error('Some data has already been output, can\'t send PDF file');
7645				}
7646				header('Content-Description: File Transfer');
7647				if (headers_sent()) {
7648					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7649				}
7650				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7651				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7652				header('Pragma: public');
7653				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7654				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7655				// force download dialog
7656				if (strpos(php_sapi_name(), 'cgi') === false) {
7657					header('Content-Type: application/force-download');
7658					header('Content-Type: application/octet-stream', false);
7659					header('Content-Type: application/download', false);
7660					header('Content-Type: application/pdf', false);
7661				} else {
7662					header('Content-Type: application/pdf');
7663				}
7664				// use the Content-Disposition header to supply a recommended filename
7665				header('Content-Disposition: attachment; filename="'.basename($name).'"');
7666				header('Content-Transfer-Encoding: binary');
7667				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7668				break;
7669			}
7670			case 'F':
7671			case 'FI':
7672			case 'FD': {
7673				// save PDF to a local file
7674				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7675				if (!$f) {
7676					$this->Error('Unable to create output file: '.$name);
7677				}
7678				fwrite($f, $this->getBuffer(), $this->bufferlen);
7679				fclose($f);
7680				if ($dest == 'FI') {
7681					// send headers to browser
7682					header('Content-Type: application/pdf');
7683					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7684					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7685					header('Pragma: public');
7686					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7687					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7688					header('Content-Disposition: inline; filename="'.basename($name).'"');
7689					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7690				} elseif ($dest == 'FD') {
7691					// send headers to browser
7692					if (ob_get_contents()) {
7693						$this->Error('Some data has already been output, can\'t send PDF file');
7694					}
7695					header('Content-Description: File Transfer');
7696					if (headers_sent()) {
7697						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7698					}
7699					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7700					header('Pragma: public');
7701					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7702					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7703					// force download dialog
7704					if (strpos(php_sapi_name(), 'cgi') === false) {
7705						header('Content-Type: application/force-download');
7706						header('Content-Type: application/octet-stream', false);
7707						header('Content-Type: application/download', false);
7708						header('Content-Type: application/pdf', false);
7709					} else {
7710						header('Content-Type: application/pdf');
7711					}
7712					// use the Content-Disposition header to supply a recommended filename
7713					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7714					header('Content-Transfer-Encoding: binary');
7715					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7716				}
7717				break;
7718			}
7719			case 'E': {
7720				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7721				$retval = 'Content-Type: application/pdf;'."\r\n";
7722				$retval .= ' name="'.$name.'"'."\r\n";
7723				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7724				$retval .= 'Content-Disposition: attachment;'."\r\n";
7725				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7726				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7727				return $retval;
7728			}
7729			case 'S': {
7730				// returns PDF as a string
7731				return $this->getBuffer();
7732			}
7733			default: {
7734				$this->Error('Incorrect output destination: '.$dest);
7735			}
7736		}
7737		return '';
7738	}
7739
7740	/**
7741	 * Unset all class variables except the following critical variables.
7742	 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7743	 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7744	 * @public
7745	 * @since 4.5.016 (2009-02-24)
7746	 */
7747	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7748		// restore internal encoding
7749		if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
7750			mb_internal_encoding($this->internal_encoding);
7751		}
7752		if ($destroyall AND !$preserve_objcopy) {
7753			// remove all temporary files
7754			$tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
7755			if (!empty($tmpfiles)) {
7756				array_map('unlink', $tmpfiles);
7757			}
7758		}
7759		$preserve = array(
7760			'file_id',
7761			'internal_encoding',
7762			'state',
7763			'bufferlen',
7764			'buffer',
7765			'cached_files',
7766			'sign',
7767			'signature_data',
7768			'signature_max_length',
7769			'byterange_string',
7770			'tsa_timestamp',
7771			'tsa_data'
7772		);
7773		foreach (array_keys(get_object_vars($this)) as $val) {
7774			if ($destroyall OR !in_array($val, $preserve)) {
7775				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7776					unset($this->$val);
7777				}
7778			}
7779		}
7780	}
7781
7782	/**
7783	 * Check for locale-related bug
7784	 * @protected
7785	 */
7786	protected function _dochecks() {
7787		//Check for locale-related bug
7788		if (1.1 == 1) {
7789			$this->Error('Don\'t alter the locale before including class file');
7790		}
7791		//Check for decimal separator
7792		if (sprintf('%.1F', 1.0) != '1.0') {
7793			setlocale(LC_NUMERIC, 'C');
7794		}
7795	}
7796
7797	/**
7798	 * Return an array containing variations for the basic page number alias.
7799	 * @param $a (string) Base alias.
7800	 * @return array of page number aliases
7801	 * @protected
7802	 */
7803	protected function getInternalPageNumberAliases($a= '') {
7804		$alias = array();
7805		// build array of Unicode + ASCII variants (the order is important)
7806		$alias = array('u' => array(), 'a' => array());
7807		$u = '{'.$a.'}';
7808		$alias['u'][] = TCPDF_STATIC::_escape($u);
7809		if ($this->isunicode) {
7810			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7811			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7812			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7813			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7814		}
7815		$alias['a'][] = TCPDF_STATIC::_escape($a);
7816		return $alias;
7817	}
7818
7819	/**
7820	 * Return an array containing all internal page aliases.
7821	 * @return array of page number aliases
7822	 * @protected
7823	 */
7824	protected function getAllInternalPageNumberAliases() {
7825		$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);
7826		$pnalias = array();
7827		foreach($basic_alias as $k => $a) {
7828			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7829		}
7830		return $pnalias;
7831	}
7832
7833	/**
7834	 * Replace right shift page number aliases with spaces to correct right alignment.
7835	 * This works perfectly only when using monospaced fonts.
7836	 * @param $page (string) Page content.
7837	 * @param $aliases (array) Array of page aliases.
7838	 * @param $diff (int) initial difference to add.
7839	 * @return replaced page content.
7840	 * @protected
7841	 */
7842	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7843		foreach ($aliases as $type => $alias) {
7844			foreach ($alias as $a) {
7845				// find position of compensation factor
7846				$startnum = (strpos($a, ':') + 1);
7847				$a = substr($a, 0, $startnum);
7848				if (($pos = strpos($page, $a)) !== false) {
7849					// end of alias
7850					$endnum = strpos($page, '}', $pos);
7851					// string to be replaced
7852					$aa = substr($page, $pos, ($endnum - $pos + 1));
7853					// get compensation factor
7854					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7855					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7856					$ratio = floatval($ratio);
7857					if ($type == 'u') {
7858						$chrdiff = floor(($diff + 12) * $ratio);
7859						$shift = str_repeat(' ', $chrdiff);
7860						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7861					} else {
7862						$chrdiff = floor(($diff + 11) * $ratio);
7863						$shift = str_repeat(' ', $chrdiff);
7864					}
7865					$page = str_replace($aa, $shift, $page);
7866				}
7867			}
7868		}
7869		return $page;
7870	}
7871
7872	/**
7873	 * Set page boxes to be included on page descriptions.
7874	 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7875	 * @protected
7876	 */
7877	protected function setPageBoxTypes($boxes) {
7878		$this->page_boxes = array();
7879		foreach ($boxes as $box) {
7880			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7881				$this->page_boxes[] = $box;
7882			}
7883		}
7884	}
7885
7886	/**
7887	 * Output pages (and replace page number aliases).
7888	 * @protected
7889	 */
7890	protected function _putpages() {
7891		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7892		// get internal aliases for page numbers
7893		$pnalias = $this->getAllInternalPageNumberAliases();
7894		$num_pages = $this->numpages;
7895		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7896		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7897		$ptp_num_chars = $this->GetNumChars($ptpa);
7898		$pagegroupnum = 0;
7899		$groupnum = 0;
7900		$ptgu = 1;
7901		$ptga = 1;
7902		$ptg_num_chars = 1;
7903		for ($n = 1; $n <= $num_pages; ++$n) {
7904			// get current page
7905			$temppage = $this->getPageBuffer($n);
7906			$pagelen = strlen($temppage);
7907			// set replacements for total pages number
7908			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7909			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7910			$pnp_num_chars = $this->GetNumChars($pnpa);
7911			$pdiff = 0; // difference used for right shift alignment of page numbers
7912			$gdiff = 0; // difference used for right shift alignment of page group numbers
7913			if (!empty($this->pagegroups)) {
7914				if (isset($this->newpagegroup[$n])) {
7915					$pagegroupnum = 0;
7916					++$groupnum;
7917					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7918					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7919					$ptg_num_chars = $this->GetNumChars($ptga);
7920				}
7921				++$pagegroupnum;
7922				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7923				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7924				$png_num_chars = $this->GetNumChars($pnga);
7925				// replace page numbers
7926				$replace = array();
7927				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7928				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7929				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7930				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7931				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7932			}
7933			// replace page numbers
7934			$replace = array();
7935			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7936			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7937			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7938			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7939			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7940			// replace right shift alias
7941			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7942			// replace EPS marker
7943			$temppage = str_replace($this->epsmarker, '', $temppage);
7944			//Page
7945			$this->page_obj_id[$n] = $this->_newobj();
7946			$out = '<<';
7947			$out .= ' /Type /Page';
7948			$out .= ' /Parent 1 0 R';
7949			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
7950				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7951			}
7952			$out .= ' /Resources 2 0 R';
7953			foreach ($this->page_boxes as $box) {
7954				$out .= ' /'.$box;
7955				$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']);
7956			}
7957			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
7958				$out .= ' /BoxColorInfo <<';
7959				foreach ($this->page_boxes as $box) {
7960					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
7961						$out .= ' /'.$box.' <<';
7962						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
7963							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
7964							$out .= ' /C [';
7965							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
7966							$out .= ' ]';
7967						}
7968						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
7969							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
7970						}
7971						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
7972							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
7973						}
7974						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
7975							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
7976							$out .= ' /D [';
7977							foreach ($dashes as $dash) {
7978								$out .= sprintf(' %F', ($dash * $this->k));
7979							}
7980							$out .= ' ]';
7981						}
7982						$out .= ' >>';
7983					}
7984				}
7985				$out .= ' >>';
7986			}
7987			$out .= ' /Contents '.($this->n + 1).' 0 R';
7988			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
7989			if (!$this->pdfa_mode) {
7990				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
7991			}
7992			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
7993				// page transitions
7994				if (isset($this->pagedim[$n]['trans']['Dur'])) {
7995					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
7996				}
7997				$out .= ' /Trans <<';
7998				$out .= ' /Type /Trans';
7999				if (isset($this->pagedim[$n]['trans']['S'])) {
8000					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8001				}
8002				if (isset($this->pagedim[$n]['trans']['D'])) {
8003					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8004				}
8005				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8006					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8007				}
8008				if (isset($this->pagedim[$n]['trans']['M'])) {
8009					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8010				}
8011				if (isset($this->pagedim[$n]['trans']['Di'])) {
8012					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8013				}
8014				if (isset($this->pagedim[$n]['trans']['SS'])) {
8015					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8016				}
8017				if (isset($this->pagedim[$n]['trans']['B'])) {
8018					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8019				}
8020				$out .= ' >>';
8021			}
8022			$out .= $this->_getannotsrefs($n);
8023			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8024			$out .= ' >>';
8025			$out .= "\n".'endobj';
8026			$this->_out($out);
8027			//Page content
8028			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8029			$this->_newobj();
8030			$p = $this->_getrawstream($p);
8031			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8032		}
8033		//Pages root
8034		$out = $this->_getobj(1)."\n";
8035		$out .= '<< /Type /Pages /Kids [';
8036		foreach($this->page_obj_id as $page_obj) {
8037			$out .= ' '.$page_obj.' 0 R';
8038		}
8039		$out .= ' ] /Count '.$num_pages.' >>';
8040		$out .= "\n".'endobj';
8041		$this->_out($out);
8042	}
8043
8044	/**
8045	 * Get references to page annotations.
8046	 * @param $n (int) page number
8047	 * @return string
8048	 * @protected
8049	 * @author Nicola Asuni
8050	 * @since 5.0.010 (2010-05-17)
8051	 */
8052	protected function _getannotsrefs($n) {
8053		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8054			return '';
8055		}
8056		$out = ' /Annots [';
8057		if (isset($this->PageAnnots[$n])) {
8058			foreach ($this->PageAnnots[$n] as $key => $val) {
8059				if (!in_array($val['n'], $this->radio_groups)) {
8060					$out .= ' '.$val['n'].' 0 R';
8061				}
8062			}
8063			// add radiobutton groups
8064			if (isset($this->radiobutton_groups[$n])) {
8065				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8066					if (isset($data['n'])) {
8067						$out .= ' '.$data['n'].' 0 R';
8068					}
8069				}
8070			}
8071		}
8072		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8073			// set reference for signature object
8074			$out .= ' '.$this->sig_obj_id.' 0 R';
8075		}
8076		if (!empty($this->empty_signature_appearance)) {
8077			foreach ($this->empty_signature_appearance as $esa) {
8078				if ($esa['page'] == $n) {
8079					// set reference for empty signature objects
8080					$out .= ' '.$esa['objid'].' 0 R';
8081				}
8082			}
8083		}
8084		$out .= ' ]';
8085		return $out;
8086	}
8087
8088	/**
8089	 * Output annotations objects for all pages.
8090	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8091	 * See section 12.5 of PDF 32000_2008 reference.
8092	 * @protected
8093	 * @author Nicola Asuni
8094	 * @since 4.0.018 (2008-08-06)
8095	 */
8096	protected function _putannotsobjs() {
8097		// reset object counter
8098		for ($n=1; $n <= $this->numpages; ++$n) {
8099			if (isset($this->PageAnnots[$n])) {
8100				// set page annotations
8101				foreach ($this->PageAnnots[$n] as $key => $pl) {
8102					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8103					// create annotation object for grouping radiobuttons
8104					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8105						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8106						$annots = '<<';
8107						$annots .= ' /Type /Annot';
8108						$annots .= ' /Subtype /Widget';
8109						$annots .= ' /Rect [0 0 0 0]';
8110						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8111							// read only
8112							$annots .= ' /F 68';
8113							$annots .= ' /Ff 49153';
8114						} else {
8115							$annots .= ' /F 4'; // default print for PDF/A
8116							$annots .= ' /Ff 49152';
8117						}
8118						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8119						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8120							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8121						}
8122						$annots .= ' /FT /Btn';
8123						$annots .= ' /Kids [';
8124						$defval = '';
8125						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8126							if (isset($data['kid'])) {
8127								$annots .= ' '.$data['kid'].' 0 R';
8128								if ($data['def'] !== 'Off') {
8129									$defval = $data['def'];
8130								}
8131							}
8132						}
8133						$annots .= ' ]';
8134						if (!empty($defval)) {
8135							$annots .= ' /V /'.$defval;
8136						}
8137						$annots .= ' >>';
8138						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8139						$this->form_obj_id[] = $radio_button_obj_id;
8140						// store object id to be used on Parent entry of Kids
8141						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8142					}
8143					$formfield = false;
8144					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8145					$a = $pl['x'] * $this->k;
8146					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8147					$c = $pl['w'] * $this->k;
8148					$d = $pl['h'] * $this->k;
8149					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8150					// create new annotation object
8151					$annots = '<</Type /Annot';
8152					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8153					$annots .= ' /Rect ['.$rect.']';
8154					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8155					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8156						$annots .= ' /FT /'.$pl['opt']['ft'];
8157						$formfield = true;
8158					}
8159					if ($pl['opt']['subtype'] !== 'Link') {
8160						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8161					}
8162					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8163					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8164					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8165					if (isset($pl['opt']['f'])) {
8166						$fval = 0;
8167						if (is_array($pl['opt']['f'])) {
8168							foreach ($pl['opt']['f'] as $f) {
8169								switch (strtolower($f)) {
8170									case 'invisible': {
8171										$fval += 1 << 0;
8172										break;
8173									}
8174									case 'hidden': {
8175										$fval += 1 << 1;
8176										break;
8177									}
8178									case 'print': {
8179										$fval += 1 << 2;
8180										break;
8181									}
8182									case 'nozoom': {
8183										$fval += 1 << 3;
8184										break;
8185									}
8186									case 'norotate': {
8187										$fval += 1 << 4;
8188										break;
8189									}
8190									case 'noview': {
8191										$fval += 1 << 5;
8192										break;
8193									}
8194									case 'readonly': {
8195										$fval += 1 << 6;
8196										break;
8197									}
8198									case 'locked': {
8199										$fval += 1 << 8;
8200										break;
8201									}
8202									case 'togglenoview': {
8203										$fval += 1 << 9;
8204										break;
8205									}
8206									case 'lockedcontents': {
8207										$fval += 1 << 10;
8208										break;
8209									}
8210									default: {
8211										break;
8212									}
8213								}
8214							}
8215						} else {
8216							$fval = intval($pl['opt']['f']);
8217						}
8218					} else {
8219						$fval = 4;
8220					}
8221					if ($this->pdfa_mode) {
8222						// force print flag for PDF/A mode
8223						$fval |= 4;
8224					}
8225					$annots .= ' /F '.intval($fval);
8226					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8227						$annots .= ' /AS /'.$pl['opt']['as'];
8228					}
8229					if (isset($pl['opt']['ap'])) {
8230						// appearance stream
8231						$annots .= ' /AP <<';
8232						if (is_array($pl['opt']['ap'])) {
8233							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8234								// $apmode can be: n = normal; r = rollover; d = down;
8235								$annots .= ' /'.strtoupper($apmode);
8236								if (is_array($apdef)) {
8237									$annots .= ' <<';
8238									foreach ($apdef as $apstate => $stream) {
8239										// reference to XObject that define the appearance for this mode-state
8240										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8241										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8242									}
8243									$annots .= ' >>';
8244								} else {
8245									// reference to XObject that define the appearance for this mode
8246									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8247									$annots .= ' '.$apsobjid.' 0 R';
8248								}
8249							}
8250						} else {
8251							$annots .= $pl['opt']['ap'];
8252						}
8253						$annots .= ' >>';
8254					}
8255					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8256						$annots .= ' /BS <<';
8257						$annots .= ' /Type /Border';
8258						if (isset($pl['opt']['bs']['w'])) {
8259							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8260						}
8261						$bstyles = array('S', 'D', 'B', 'I', 'U');
8262						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8263							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8264						}
8265						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8266							$annots .= ' /D [';
8267							foreach ($pl['opt']['bs']['d'] as $cord) {
8268								$annots .= ' '.intval($cord);
8269							}
8270							$annots .= ']';
8271						}
8272						$annots .= ' >>';
8273					} else {
8274						$annots .= ' /Border [';
8275						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8276							$annots .= intval($pl['opt']['border'][0]).' ';
8277							$annots .= intval($pl['opt']['border'][1]).' ';
8278							$annots .= intval($pl['opt']['border'][2]);
8279							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8280								$annots .= ' [';
8281								foreach ($pl['opt']['border'][3] as $dash) {
8282									$annots .= intval($dash).' ';
8283								}
8284								$annots .= ']';
8285							}
8286						} else {
8287							$annots .= '0 0 0';
8288						}
8289						$annots .= ']';
8290					}
8291					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8292						$annots .= ' /BE <<';
8293						$bstyles = array('S', 'C');
8294						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8295							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8296						} else {
8297							$annots .= ' /S /S';
8298						}
8299						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8300							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8301						}
8302						$annots .= '>>';
8303					}
8304					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8305						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8306					}
8307					//$annots .= ' /StructParent ';
8308					//$annots .= ' /OC ';
8309					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8310					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8311						// this is a markup type
8312						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8313							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8314						}
8315						//$annots .= ' /Popup ';
8316						if (isset($pl['opt']['ca'])) {
8317							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8318						}
8319						if (isset($pl['opt']['rc'])) {
8320							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8321						}
8322						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8323						//$annots .= ' /IRT ';
8324						if (isset($pl['opt']['subj'])) {
8325							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8326						}
8327						//$annots .= ' /RT ';
8328						//$annots .= ' /IT ';
8329						//$annots .= ' /ExData ';
8330					}
8331					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8332					// Annotation types
8333					switch (strtolower($pl['opt']['subtype'])) {
8334						case 'text': {
8335							if (isset($pl['opt']['open'])) {
8336								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8337							}
8338							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8339							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8340								$annots .= ' /Name /'.$pl['opt']['name'];
8341							} else {
8342								$annots .= ' /Name /Note';
8343							}
8344							$statemodels = array('Marked', 'Review');
8345							if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8346								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8347							} else {
8348								$pl['opt']['statemodel'] = 'Marked';
8349								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8350							}
8351							if ($pl['opt']['statemodel'] == 'Marked') {
8352								$states = array('Accepted', 'Unmarked');
8353							} else {
8354								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8355							}
8356							if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8357								$annots .= ' /State /'.$pl['opt']['state'];
8358							} else {
8359								if ($pl['opt']['statemodel'] == 'Marked') {
8360									$annots .= ' /State /Unmarked';
8361								} else {
8362									$annots .= ' /State /None';
8363								}
8364							}
8365							break;
8366						}
8367						case 'link': {
8368							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8369								if ($pl['txt'][0] == '#') {
8370									// internal destination
8371									$annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8372								} elseif ($pl['txt'][0] == '%') {
8373									// embedded PDF file
8374									$filename = basename(substr($pl['txt'], 1));
8375									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8376								} elseif ($pl['txt'][0] == '*') {
8377									// embedded generic file
8378									$filename = basename(substr($pl['txt'], 1));
8379									$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});';
8380									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8381								} else {
8382									$parsedUrl = parse_url($pl['txt']);
8383									if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8384										// relative link to a PDF file
8385										$dest = '[0 /Fit]'; // default page 0
8386										if (!empty($parsedUrl['fragment'])) {
8387											// check for named destination
8388											$tmp = explode('=', $parsedUrl['fragment']);
8389											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8390										}
8391										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8392									} else {
8393										// external URI link
8394										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8395									}
8396								}
8397							} elseif (isset($this->links[$pl['txt']])) {
8398								// internal link ID
8399								$l = $this->links[$pl['txt']];
8400								if (isset($this->page_obj_id[($l['p'])])) {
8401									$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)));
8402								}
8403							}
8404							$hmodes = array('N', 'I', 'O', 'P');
8405							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8406								$annots .= ' /H /'.$pl['opt']['h'];
8407							} else {
8408								$annots .= ' /H /I';
8409							}
8410							//$annots .= ' /PA ';
8411							//$annots .= ' /Quadpoints ';
8412							break;
8413						}
8414						case 'freetext': {
8415							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8416								$annots .= ' /DA ('.$pl['opt']['da'].')';
8417							}
8418							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8419								$annots .= ' /Q '.intval($pl['opt']['q']);
8420							}
8421							if (isset($pl['opt']['rc'])) {
8422								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8423							}
8424							if (isset($pl['opt']['ds'])) {
8425								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8426							}
8427							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8428								$annots .= ' /CL [';
8429								foreach ($pl['opt']['cl'] as $cl) {
8430									$annots .= sprintf('%F ', $cl * $this->k);
8431								}
8432								$annots .= ']';
8433							}
8434							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8435							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8436								$annots .= ' /IT /'.$pl['opt']['it'];
8437							}
8438							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8439								$l = $pl['opt']['rd'][0] * $this->k;
8440								$r = $pl['opt']['rd'][1] * $this->k;
8441								$t = $pl['opt']['rd'][2] * $this->k;
8442								$b = $pl['opt']['rd'][3] * $this->k;
8443								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8444							}
8445							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8446								$annots .= ' /LE /'.$pl['opt']['le'];
8447							}
8448							break;
8449						}
8450						case 'line': {
8451							break;
8452						}
8453						case 'square': {
8454							break;
8455						}
8456						case 'circle': {
8457							break;
8458						}
8459						case 'polygon': {
8460							break;
8461						}
8462						case 'polyline': {
8463							break;
8464						}
8465						case 'highlight': {
8466							break;
8467						}
8468						case 'underline': {
8469							break;
8470						}
8471						case 'squiggly': {
8472							break;
8473						}
8474						case 'strikeout': {
8475							break;
8476						}
8477						case 'stamp': {
8478							break;
8479						}
8480						case 'caret': {
8481							break;
8482						}
8483						case 'ink': {
8484							break;
8485						}
8486						case 'popup': {
8487							break;
8488						}
8489						case 'fileattachment': {
8490							if ($this->pdfa_mode) {
8491								// embedded files are not allowed in PDF/A mode
8492								break;
8493							}
8494							if (!isset($pl['opt']['fs'])) {
8495								break;
8496							}
8497							$filename = basename($pl['opt']['fs']);
8498							if (isset($this->embeddedfiles[$filename]['f'])) {
8499								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8500								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8501								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8502									$annots .= ' /Name /'.$pl['opt']['name'];
8503								} else {
8504									$annots .= ' /Name /PushPin';
8505								}
8506								// index (zero-based) of the annotation in the Annots array of this page
8507								$this->embeddedfiles[$filename]['a'] = $key;
8508							}
8509							break;
8510						}
8511						case 'sound': {
8512							if (!isset($pl['opt']['fs'])) {
8513								break;
8514							}
8515							$filename = basename($pl['opt']['fs']);
8516							if (isset($this->embeddedfiles[$filename]['f'])) {
8517								// ... TO BE COMPLETED ...
8518								// /R /C /B /E /CO /CP
8519								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8520								$iconsapp = array('Speaker', 'Mic');
8521								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8522									$annots .= ' /Name /'.$pl['opt']['name'];
8523								} else {
8524									$annots .= ' /Name /Speaker';
8525								}
8526							}
8527							break;
8528						}
8529						case 'movie': {
8530							break;
8531						}
8532						case 'widget': {
8533							$hmode = array('N', 'I', 'O', 'P', 'T');
8534							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8535								$annots .= ' /H /'.$pl['opt']['h'];
8536							}
8537							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8538								$annots .= ' /MK <<';
8539								if (isset($pl['opt']['mk']['r'])) {
8540									$annots .= ' /R '.$pl['opt']['mk']['r'];
8541								}
8542								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8543									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8544								}
8545								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8546									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8547								}
8548								if (isset($pl['opt']['mk']['ca'])) {
8549									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8550								}
8551								if (isset($pl['opt']['mk']['rc'])) {
8552									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8553								}
8554								if (isset($pl['opt']['mk']['ac'])) {
8555									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8556								}
8557								if (isset($pl['opt']['mk']['i'])) {
8558									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8559									if ($info !== false) {
8560										$annots .= ' /I '.$info['n'].' 0 R';
8561									}
8562								}
8563								if (isset($pl['opt']['mk']['ri'])) {
8564									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8565									if ($info !== false) {
8566										$annots .= ' /RI '.$info['n'].' 0 R';
8567									}
8568								}
8569								if (isset($pl['opt']['mk']['ix'])) {
8570									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8571									if ($info !== false) {
8572										$annots .= ' /IX '.$info['n'].' 0 R';
8573									}
8574								}
8575								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8576									$annots .= ' /IF <<';
8577									$if_sw = array('A', 'B', 'S', 'N');
8578									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8579										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8580									}
8581									$if_s = array('A', 'P');
8582									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8583										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8584									}
8585									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8586										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8587									}
8588									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8589										$annots .= ' /FB true';
8590									}
8591									$annots .= '>>';
8592								}
8593								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8594									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8595								}
8596								$annots .= '>>';
8597							} // end MK
8598							// --- Entries for field dictionaries ---
8599							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8600								// set parent
8601								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8602							}
8603							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8604								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8605							}
8606							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8607								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8608							}
8609							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8610								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8611							}
8612							if (isset($pl['opt']['ff'])) {
8613								if (is_array($pl['opt']['ff'])) {
8614									// array of bit settings
8615									$flag = 0;
8616									foreach($pl['opt']['ff'] as $val) {
8617										$flag += 1 << ($val - 1);
8618									}
8619								} else {
8620									$flag = intval($pl['opt']['ff']);
8621								}
8622								$annots .= ' /Ff '.$flag;
8623							}
8624							if (isset($pl['opt']['maxlen'])) {
8625								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8626							}
8627							if (isset($pl['opt']['v'])) {
8628								$annots .= ' /V';
8629								if (is_array($pl['opt']['v'])) {
8630									foreach ($pl['opt']['v'] AS $optval) {
8631										if (is_float($optval)) {
8632											$optval = sprintf('%F', $optval);
8633										}
8634										$annots .= ' '.$optval;
8635									}
8636								} else {
8637									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8638								}
8639							}
8640							if (isset($pl['opt']['dv'])) {
8641								$annots .= ' /DV';
8642								if (is_array($pl['opt']['dv'])) {
8643									foreach ($pl['opt']['dv'] AS $optval) {
8644										if (is_float($optval)) {
8645											$optval = sprintf('%F', $optval);
8646										}
8647										$annots .= ' '.$optval;
8648									}
8649								} else {
8650									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8651								}
8652							}
8653							if (isset($pl['opt']['rv'])) {
8654								$annots .= ' /RV';
8655								if (is_array($pl['opt']['rv'])) {
8656									foreach ($pl['opt']['rv'] AS $optval) {
8657										if (is_float($optval)) {
8658											$optval = sprintf('%F', $optval);
8659										}
8660										$annots .= ' '.$optval;
8661									}
8662								} else {
8663									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8664								}
8665							}
8666							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8667								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8668							}
8669							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8670								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8671							}
8672							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8673								$annots .= ' /DA ('.$pl['opt']['da'].')';
8674							}
8675							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8676								$annots .= ' /Q '.intval($pl['opt']['q']);
8677							}
8678							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8679								$annots .= ' /Opt [';
8680								foreach($pl['opt']['opt'] AS $copt) {
8681									if (is_array($copt)) {
8682										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8683									} else {
8684										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8685									}
8686								}
8687								$annots .= ']';
8688							}
8689							if (isset($pl['opt']['ti'])) {
8690								$annots .= ' /TI '.intval($pl['opt']['ti']);
8691							}
8692							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8693								$annots .= ' /I [';
8694								foreach($pl['opt']['i'] AS $copt) {
8695									$annots .= intval($copt).' ';
8696								}
8697								$annots .= ']';
8698							}
8699							break;
8700						}
8701						case 'screen': {
8702							break;
8703						}
8704						case 'printermark': {
8705							break;
8706						}
8707						case 'trapnet': {
8708							break;
8709						}
8710						case 'watermark': {
8711							break;
8712						}
8713						case '3d': {
8714							break;
8715						}
8716						default: {
8717							break;
8718						}
8719					}
8720					$annots .= '>>';
8721					// create new annotation object
8722					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8723					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8724						// store reference of form object
8725						$this->form_obj_id[] = $annot_obj_id;
8726					}
8727				}
8728			}
8729		} // end for each page
8730	}
8731
8732	/**
8733	 * Put appearance streams XObject used to define annotation's appearance states.
8734	 * @param $w (int) annotation width
8735	 * @param $h (int) annotation height
8736	 * @param $stream (string) appearance stream
8737	 * @return int object ID
8738	 * @protected
8739	 * @since 4.8.001 (2009-09-09)
8740	 */
8741	protected function _putAPXObject($w=0, $h=0, $stream='') {
8742		$stream = trim($stream);
8743		$out = $this->_getobj()."\n";
8744		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8745		$out .= '<<';
8746		$out .= ' /Type /XObject';
8747		$out .= ' /Subtype /Form';
8748		$out .= ' /FormType 1';
8749		if ($this->compress) {
8750			$stream = gzcompress($stream);
8751			$out .= ' /Filter /FlateDecode';
8752		}
8753		$rect = sprintf('%F %F', $w, $h);
8754		$out .= ' /BBox [0 0 '.$rect.']';
8755		$out .= ' /Matrix [1 0 0 1 0 0]';
8756		$out .= ' /Resources 2 0 R';
8757		$stream = $this->_getrawstream($stream);
8758		$out .= ' /Length '.strlen($stream);
8759		$out .= ' >>';
8760		$out .= ' stream'."\n".$stream."\n".'endstream';
8761		$out .= "\n".'endobj';
8762		$this->_out($out);
8763		return $this->n;
8764	}
8765
8766	/**
8767	 * Output fonts.
8768	 * @author Nicola Asuni
8769	 * @protected
8770	 */
8771	protected function _putfonts() {
8772		$nf = $this->n;
8773		foreach ($this->diffs as $diff) {
8774			//Encodings
8775			$this->_newobj();
8776			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8777		}
8778		$mqr = TCPDF_STATIC::get_mqr();
8779		TCPDF_STATIC::set_mqr(false);
8780		foreach ($this->FontFiles as $file => $info) {
8781			// search and get font file to embedd
8782			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8783			if (!TCPDF_STATIC::empty_string($fontfile)) {
8784				$font = file_get_contents($fontfile);
8785				$compressed = (substr($file, -2) == '.z');
8786				if ((!$compressed) AND (isset($info['length2']))) {
8787					$header = (ord($font[0]) == 128);
8788					if ($header) {
8789						// strip first binary header
8790						$font = substr($font, 6);
8791					}
8792					if ($header AND (ord($font[$info['length1']]) == 128)) {
8793						// strip second binary header
8794						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8795					}
8796				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8797					if ($compressed) {
8798						// uncompress font
8799						$font = gzuncompress($font);
8800					}
8801					// merge subset characters
8802					$subsetchars = array(); // used chars
8803					foreach ($info['fontkeys'] as $fontkey) {
8804						$fontinfo = $this->getFontBuffer($fontkey);
8805						$subsetchars += $fontinfo['subsetchars'];
8806					}
8807					// rebuild a font subset
8808					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8809					// calculate new font length
8810					$info['length1'] = strlen($font);
8811					if ($compressed) {
8812						// recompress font
8813						$font = gzcompress($font);
8814					}
8815				}
8816				$this->_newobj();
8817				$this->FontFiles[$file]['n'] = $this->n;
8818				$stream = $this->_getrawstream($font);
8819				$out = '<< /Length '.strlen($stream);
8820				if ($compressed) {
8821					$out .= ' /Filter /FlateDecode';
8822				}
8823				$out .= ' /Length1 '.$info['length1'];
8824				if (isset($info['length2'])) {
8825					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8826				}
8827				$out .= ' >>';
8828				$out .= ' stream'."\n".$stream."\n".'endstream';
8829				$out .= "\n".'endobj';
8830				$this->_out($out);
8831			}
8832		}
8833		TCPDF_STATIC::set_mqr($mqr);
8834		foreach ($this->fontkeys as $k) {
8835			//Font objects
8836			$font = $this->getFontBuffer($k);
8837			$type = $font['type'];
8838			$name = $font['name'];
8839			if ($type == 'core') {
8840				// standard core font
8841				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8842				$out .= '<</Type /Font';
8843				$out .= ' /Subtype /Type1';
8844				$out .= ' /BaseFont /'.$name;
8845				$out .= ' /Name /F'.$font['i'];
8846				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8847					$out .= ' /Encoding /WinAnsiEncoding';
8848				}
8849				if ($k == 'helvetica') {
8850					// add default font for annotations
8851					$this->annotation_fonts[$k] = $font['i'];
8852				}
8853				$out .= ' >>';
8854				$out .= "\n".'endobj';
8855				$this->_out($out);
8856			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8857				// additional Type1 or TrueType font
8858				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8859				$out .= '<</Type /Font';
8860				$out .= ' /Subtype /'.$type;
8861				$out .= ' /BaseFont /'.$name;
8862				$out .= ' /Name /F'.$font['i'];
8863				$out .= ' /FirstChar 32 /LastChar 255';
8864				$out .= ' /Widths '.($this->n + 1).' 0 R';
8865				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8866				if ($font['enc']) {
8867					if (isset($font['diff'])) {
8868						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8869					} else {
8870						$out .= ' /Encoding /WinAnsiEncoding';
8871					}
8872				}
8873				$out .= ' >>';
8874				$out .= "\n".'endobj';
8875				$this->_out($out);
8876				// Widths
8877				$this->_newobj();
8878				$s = '[';
8879				for ($i = 32; $i < 256; ++$i) {
8880					if (isset($font['cw'][$i])) {
8881						$s .= $font['cw'][$i].' ';
8882					} else {
8883						$s .= $font['dw'].' ';
8884					}
8885				}
8886				$s .= ']';
8887				$s .= "\n".'endobj';
8888				$this->_out($s);
8889				//Descriptor
8890				$this->_newobj();
8891				$s = '<</Type /FontDescriptor /FontName /'.$name;
8892				foreach ($font['desc'] as $fdk => $fdv) {
8893					if (is_float($fdv)) {
8894						$fdv = sprintf('%F', $fdv);
8895					}
8896					$s .= ' /'.$fdk.' '.$fdv.'';
8897				}
8898				if (!TCPDF_STATIC::empty_string($font['file'])) {
8899					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8900				}
8901				$s .= '>>';
8902				$s .= "\n".'endobj';
8903				$this->_out($s);
8904			} else {
8905				// additional types
8906				$mtd = '_put'.strtolower($type);
8907				if (!method_exists($this, $mtd)) {
8908					$this->Error('Unsupported font type: '.$type);
8909				}
8910				$this->$mtd($font);
8911			}
8912		}
8913	}
8914
8915	/**
8916	 * Adds unicode fonts.<br>
8917	 * Based on PDF Reference 1.3 (section 5)
8918	 * @param $font (array) font data
8919	 * @protected
8920	 * @author Nicola Asuni
8921	 * @since 1.52.0.TC005 (2005-01-05)
8922	 */
8923	protected function _puttruetypeunicode($font) {
8924		$fontname = '';
8925		if ($font['subset']) {
8926			// change name for font subsetting
8927			$subtag = sprintf('%06u', $font['i']);
8928			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8929			$fontname .= $subtag.'+';
8930		}
8931		$fontname .= $font['name'];
8932		// Type0 Font
8933		// A composite font composed of other fonts, organized hierarchically
8934		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8935		$out .= '<< /Type /Font';
8936		$out .= ' /Subtype /Type0';
8937		$out .= ' /BaseFont /'.$fontname;
8938		$out .= ' /Name /F'.$font['i'];
8939		$out .= ' /Encoding /'.$font['enc'];
8940		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8941		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8942		$out .= ' >>';
8943		$out .= "\n".'endobj';
8944		$this->_out($out);
8945		// ToUnicode map for Identity-H
8946		$stream = TCPDF_FONT_DATA::$uni_identity_h;
8947		// ToUnicode Object
8948		$this->_newobj();
8949		$stream = ($this->compress) ? gzcompress($stream) : $stream;
8950		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8951		$stream = $this->_getrawstream($stream);
8952		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
8953		// CIDFontType2
8954		// A CIDFont whose glyph descriptions are based on TrueType font technology
8955		$oid = $this->_newobj();
8956		$out = '<< /Type /Font';
8957		$out .= ' /Subtype /CIDFontType2';
8958		$out .= ' /BaseFont /'.$fontname;
8959		// A dictionary containing entries that define the character collection of the CIDFont.
8960		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
8961		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
8962		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
8963		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
8964		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
8965		$out .= ' /DW '.$font['dw']; // default width
8966		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
8967		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8968			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
8969		}
8970		$out .= ' >>';
8971		$out .= "\n".'endobj';
8972		$this->_out($out);
8973		// Font descriptor
8974		// A font descriptor describing the CIDFont default metrics other than its glyph widths
8975		$this->_newobj();
8976		$out = '<< /Type /FontDescriptor';
8977		$out .= ' /FontName /'.$fontname;
8978		foreach ($font['desc'] as $key => $value) {
8979			if (is_float($value)) {
8980				$value = sprintf('%F', $value);
8981			}
8982			$out .= ' /'.$key.' '.$value;
8983		}
8984		$fontdir = false;
8985		if (!TCPDF_STATIC::empty_string($font['file'])) {
8986			// A stream containing a TrueType font
8987			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
8988			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
8989		}
8990		$out .= ' >>';
8991		$out .= "\n".'endobj';
8992		$this->_out($out);
8993		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8994			$this->_newobj();
8995			// Embed CIDToGIDMap
8996			// A specification of the mapping from CIDs to glyph indices
8997			// search and get CTG font file to embedd
8998			$ctgfile = strtolower($font['ctg']);
8999			// search and get ctg font file to embedd
9000			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9001			if (TCPDF_STATIC::empty_string($fontfile)) {
9002				$this->Error('Font file not found: '.$ctgfile);
9003			}
9004			$stream = $this->_getrawstream(file_get_contents($fontfile));
9005			$out = '<< /Length '.strlen($stream).'';
9006			if (substr($fontfile, -2) == '.z') { // check file extension
9007				// Decompresses data encoded using the public-domain
9008				// zlib/deflate compression method, reproducing the
9009				// original text or binary data
9010				$out .= ' /Filter /FlateDecode';
9011			}
9012			$out .= ' >>';
9013			$out .= ' stream'."\n".$stream."\n".'endstream';
9014			$out .= "\n".'endobj';
9015			$this->_out($out);
9016		}
9017	}
9018
9019	/**
9020	 * Output CID-0 fonts.
9021	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9022	 * @param $font (array) font data
9023	 * @protected
9024	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9025	 * @since 3.2.000 (2008-06-23)
9026	 */
9027	protected function _putcidfont0($font) {
9028		$cidoffset = 0;
9029		if (!isset($font['cw'][1])) {
9030			$cidoffset = 31;
9031		}
9032		if (isset($font['cidinfo']['uni2cid'])) {
9033			// convert unicode to cid.
9034			$uni2cid = $font['cidinfo']['uni2cid'];
9035			$cw = array();
9036			foreach ($font['cw'] as $uni => $width) {
9037				if (isset($uni2cid[$uni])) {
9038					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9039				} elseif ($uni < 256) {
9040					$cw[$uni] = $width;
9041				} // else unknown character
9042			}
9043			$font = array_merge($font, array('cw' => $cw));
9044		}
9045		$name = $font['name'];
9046		$enc = $font['enc'];
9047		if ($enc) {
9048			$longname = $name.'-'.$enc;
9049		} else {
9050			$longname = $name;
9051		}
9052		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9053		$out .= '<</Type /Font';
9054		$out .= ' /Subtype /Type0';
9055		$out .= ' /BaseFont /'.$longname;
9056		$out .= ' /Name /F'.$font['i'];
9057		if ($enc) {
9058			$out .= ' /Encoding /'.$enc;
9059		}
9060		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9061		$out .= ' >>';
9062		$out .= "\n".'endobj';
9063		$this->_out($out);
9064		$oid = $this->_newobj();
9065		$out = '<</Type /Font';
9066		$out .= ' /Subtype /CIDFontType0';
9067		$out .= ' /BaseFont /'.$name;
9068		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9069		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9070		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9071		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9072		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9073		$out .= ' /DW '.$font['dw'];
9074		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9075		$out .= ' >>';
9076		$out .= "\n".'endobj';
9077		$this->_out($out);
9078		$this->_newobj();
9079		$s = '<</Type /FontDescriptor /FontName /'.$name;
9080		foreach ($font['desc'] as $k => $v) {
9081			if ($k != 'Style') {
9082				if (is_float($v)) {
9083					$v = sprintf('%F', $v);
9084				}
9085				$s .= ' /'.$k.' '.$v.'';
9086			}
9087		}
9088		$s .= '>>';
9089		$s .= "\n".'endobj';
9090		$this->_out($s);
9091	}
9092
9093	/**
9094	 * Output images.
9095	 * @protected
9096	 */
9097	protected function _putimages() {
9098		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9099		foreach ($this->imagekeys as $file) {
9100			$info = $this->getImageBuffer($file);
9101			// set object for alternate images array
9102			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9103				$altoid = $this->_newobj();
9104				$out = '[';
9105				foreach ($info['altimgs'] as $altimage) {
9106					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9107						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9108						$out .= ' /DefaultForPrinting';
9109						if ($altimage[1] === true) {
9110							$out .= ' true';
9111						} else {
9112							$out .= ' false';
9113						}
9114						$out .= ' >>';
9115					}
9116				}
9117				$out .= ' ]';
9118				$out .= "\n".'endobj';
9119				$this->_out($out);
9120			}
9121			// set image object
9122			$oid = $this->_newobj();
9123			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9124			$this->setImageSubBuffer($file, 'n', $this->n);
9125			$out = '<</Type /XObject';
9126			$out .= ' /Subtype /Image';
9127			$out .= ' /Width '.$info['w'];
9128			$out .= ' /Height '.$info['h'];
9129			if (array_key_exists('masked', $info)) {
9130				$out .= ' /SMask '.($this->n - 1).' 0 R';
9131			}
9132			// set color space
9133			$icc = false;
9134			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9135				// ICC Colour Space
9136				$icc = true;
9137				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9138			} elseif ($info['cs'] == 'Indexed') {
9139				// Indexed Colour Space
9140				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9141			} else {
9142				// Device Colour Space
9143				$out .= ' /ColorSpace /'.$info['cs'];
9144			}
9145			if ($info['cs'] == 'DeviceCMYK') {
9146				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9147			}
9148			$out .= ' /BitsPerComponent '.$info['bpc'];
9149			if (isset($altoid) AND ($altoid > 0)) {
9150				// reference to alternate images dictionary
9151				$out .= ' /Alternates '.$altoid.' 0 R';
9152			}
9153			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9154				// external stream
9155				$out .= ' /Length 0';
9156				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9157				if (isset($info['f'])) {
9158					$out .= ' /FFilter /'.$info['f'];
9159				}
9160				$out .= ' >>';
9161				$out .= ' stream'."\n".'endstream';
9162			} else {
9163				if (isset($info['f'])) {
9164					$out .= ' /Filter /'.$info['f'];
9165				}
9166				if (isset($info['parms'])) {
9167					$out .= ' '.$info['parms'];
9168				}
9169				if (isset($info['trns']) AND is_array($info['trns'])) {
9170					$trns = '';
9171					$count_info = count($info['trns']);
9172					if ($info['cs'] == 'Indexed') {
9173						$maxval =(pow(2, $info['bpc']) - 1);
9174						for ($i = 0; $i < $count_info; ++$i) {
9175							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9176								// this is not a binary type mask @TODO: create a SMask
9177								$trns = '';
9178								break;
9179							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9180								// store the first fully transparent value
9181								$trns .= $i.' '.$i.' ';
9182							}
9183						}
9184					} else {
9185						// grayscale or RGB
9186						for ($i = 0; $i < $count_info; ++$i) {
9187							if ($info['trns'][$i] == 0) {
9188								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9189							}
9190						}
9191					}
9192					// Colour Key Masking
9193					if (!empty($trns)) {
9194						$out .= ' /Mask ['.$trns.']';
9195					}
9196				}
9197				$stream = $this->_getrawstream($info['data']);
9198				$out .= ' /Length '.strlen($stream).' >>';
9199				$out .= ' stream'."\n".$stream."\n".'endstream';
9200			}
9201			$out .= "\n".'endobj';
9202			$this->_out($out);
9203			if ($icc) {
9204				// ICC colour profile
9205				$this->_newobj();
9206				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9207				$icc = $this->_getrawstream($icc);
9208				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9209			} elseif ($info['cs'] == 'Indexed') {
9210				// colour palette
9211				$this->_newobj();
9212				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9213				$pal = $this->_getrawstream($pal);
9214				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9215			}
9216		}
9217	}
9218
9219	/**
9220	 * Output Form XObjects Templates.
9221	 * @author Nicola Asuni
9222	 * @since 5.8.017 (2010-08-24)
9223	 * @protected
9224	 * @see startTemplate(), endTemplate(), printTemplate()
9225	 */
9226	protected function _putxobjects() {
9227		foreach ($this->xobjects as $key => $data) {
9228			if (isset($data['outdata'])) {
9229				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9230				$out = $this->_getobj($data['n'])."\n";
9231				$out .= '<<';
9232				$out .= ' /Type /XObject';
9233				$out .= ' /Subtype /Form';
9234				$out .= ' /FormType 1';
9235				if ($this->compress) {
9236					$stream = gzcompress($stream);
9237					$out .= ' /Filter /FlateDecode';
9238				}
9239				$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));
9240				$out .= ' /Matrix [1 0 0 1 0 0]';
9241				$out .= ' /Resources <<';
9242				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9243				if (!$this->pdfa_mode) {
9244					// transparency
9245					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9246						$out .= ' /ExtGState <<';
9247						foreach ($data['extgstates'] as $k => $extgstate) {
9248							if (isset($this->extgstates[$k]['name'])) {
9249								$out .= ' /'.$this->extgstates[$k]['name'];
9250							} else {
9251								$out .= ' /GS'.$k;
9252							}
9253							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9254						}
9255						$out .= ' >>';
9256					}
9257					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9258						$gp = '';
9259						$gs = '';
9260						foreach ($data['gradients'] as $id => $grad) {
9261							// gradient patterns
9262							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9263							// gradient shadings
9264							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9265						}
9266						$out .= ' /Pattern <<'.$gp.' >>';
9267						$out .= ' /Shading <<'.$gs.' >>';
9268					}
9269				}
9270				// spot colors
9271				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9272					$out .= ' /ColorSpace <<';
9273					foreach ($data['spot_colors'] as $name => $color) {
9274						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9275					}
9276					$out .= ' >>';
9277				}
9278				// fonts
9279				if (!empty($data['fonts'])) {
9280					$out .= ' /Font <<';
9281					foreach ($data['fonts'] as $fontkey => $fontid) {
9282						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9283					}
9284					$out .= ' >>';
9285				}
9286				// images or nested xobjects
9287				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9288					$out .= ' /XObject <<';
9289					foreach ($data['images'] as $imgid) {
9290						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9291					}
9292					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9293						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9294					}
9295					$out .= ' >>';
9296				}
9297				$out .= ' >>'; //end resources
9298				if (isset($data['group']) AND ($data['group'] !== false)) {
9299					// set transparency group
9300					$out .= ' /Group << /Type /Group /S /Transparency';
9301					if (is_array($data['group'])) {
9302						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9303							$out .= ' /CS /'.$data['group']['CS'];
9304						}
9305						if (isset($data['group']['I'])) {
9306							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9307						}
9308						if (isset($data['group']['K'])) {
9309							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9310						}
9311					}
9312					$out .= ' >>';
9313				}
9314				$stream = $this->_getrawstream($stream, $data['n']);
9315				$out .= ' /Length '.strlen($stream);
9316				$out .= ' >>';
9317				$out .= ' stream'."\n".$stream."\n".'endstream';
9318				$out .= "\n".'endobj';
9319				$this->_out($out);
9320			}
9321		}
9322	}
9323
9324	/**
9325	 * Output Spot Colors Resources.
9326	 * @protected
9327	 * @since 4.0.024 (2008-09-12)
9328	 */
9329	protected function _putspotcolors() {
9330		foreach ($this->spot_colors as $name => $color) {
9331			$this->_newobj();
9332			$this->spot_colors[$name]['n'] = $this->n;
9333			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9334			$out .= ' /DeviceCMYK <<';
9335			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9336			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9337			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9338			$out .= "\n".'endobj';
9339			$this->_out($out);
9340		}
9341	}
9342
9343	/**
9344	 * Return XObjects Dictionary.
9345	 * @return string XObjects dictionary
9346	 * @protected
9347	 * @since 5.8.014 (2010-08-23)
9348	 */
9349	protected function _getxobjectdict() {
9350		$out = '';
9351		foreach ($this->xobjects as $id => $objid) {
9352			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9353		}
9354		return $out;
9355	}
9356
9357	/**
9358	 * Output Resources Dictionary.
9359	 * @protected
9360	 */
9361	protected function _putresourcedict() {
9362		$out = $this->_getobj(2)."\n";
9363		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9364		$out .= ' /Font <<';
9365		foreach ($this->fontkeys as $fontkey) {
9366			$font = $this->getFontBuffer($fontkey);
9367			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9368		}
9369		$out .= ' >>';
9370		$out .= ' /XObject <<';
9371		$out .= $this->_getxobjectdict();
9372		$out .= ' >>';
9373		// layers
9374		if (!empty($this->pdflayers)) {
9375			$out .= ' /Properties <<';
9376			foreach ($this->pdflayers as $layer) {
9377				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9378			}
9379			$out .= ' >>';
9380		}
9381		if (!$this->pdfa_mode) {
9382			// transparency
9383			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9384				$out .= ' /ExtGState <<';
9385				foreach ($this->extgstates as $k => $extgstate) {
9386					if (isset($extgstate['name'])) {
9387						$out .= ' /'.$extgstate['name'];
9388					} else {
9389						$out .= ' /GS'.$k;
9390					}
9391					$out .= ' '.$extgstate['n'].' 0 R';
9392				}
9393				$out .= ' >>';
9394			}
9395			if (isset($this->gradients) AND !empty($this->gradients)) {
9396				$gp = '';
9397				$gs = '';
9398				foreach ($this->gradients as $id => $grad) {
9399					// gradient patterns
9400					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9401					// gradient shadings
9402					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9403				}
9404				$out .= ' /Pattern <<'.$gp.' >>';
9405				$out .= ' /Shading <<'.$gs.' >>';
9406			}
9407		}
9408		// spot colors
9409		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9410			$out .= ' /ColorSpace <<';
9411			foreach ($this->spot_colors as $color) {
9412				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9413			}
9414			$out .= ' >>';
9415		}
9416		$out .= ' >>';
9417		$out .= "\n".'endobj';
9418		$this->_out($out);
9419	}
9420
9421	/**
9422	 * Output Resources.
9423	 * @protected
9424	 */
9425	protected function _putresources() {
9426		$this->_putextgstates();
9427		$this->_putocg();
9428		$this->_putfonts();
9429		$this->_putimages();
9430		$this->_putspotcolors();
9431		$this->_putshaders();
9432		$this->_putxobjects();
9433		$this->_putresourcedict();
9434		$this->_putdests();
9435		$this->_putEmbeddedFiles();
9436		$this->_putannotsobjs();
9437		$this->_putjavascript();
9438		$this->_putbookmarks();
9439		$this->_putencryption();
9440	}
9441
9442	/**
9443	 * Adds some Metadata information (Document Information Dictionary)
9444	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9445	 * @return int object id
9446	 * @protected
9447	 */
9448	protected function _putinfo() {
9449		$oid = $this->_newobj();
9450		$out = '<<';
9451		// store current isunicode value
9452		$prev_isunicode = $this->isunicode;
9453		if ($this->docinfounicode) {
9454			$this->isunicode = true;
9455		}
9456		if (!TCPDF_STATIC::empty_string($this->title)) {
9457			// The document's title.
9458			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9459		}
9460		if (!TCPDF_STATIC::empty_string($this->author)) {
9461			// The name of the person who created the document.
9462			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9463		}
9464		if (!TCPDF_STATIC::empty_string($this->subject)) {
9465			// The subject of the document.
9466			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9467		}
9468		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9469			// Keywords associated with the document.
9470			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9471		}
9472		if (!TCPDF_STATIC::empty_string($this->creator)) {
9473			// 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.
9474			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9475		}
9476		// restore previous isunicode value
9477		$this->isunicode = $prev_isunicode;
9478		// default producer
9479		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9480		// The date and time the document was created, in human-readable form
9481		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9482		// The date and time the document was most recently modified, in human-readable form
9483		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9484		// A name object indicating whether the document has been modified to include trapping information
9485		$out .= ' /Trapped /False';
9486		$out .= ' >>';
9487		$out .= "\n".'endobj';
9488		$this->_out($out);
9489		return $oid;
9490	}
9491
9492	/**
9493	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9494	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9495	 * @param $xmp (string) Custom XMP data.
9496	 * @since 5.9.128 (2011-10-06)
9497	 * @public
9498	 */
9499	public function setExtraXMP($xmp) {
9500		$this->custom_xmp = $xmp;
9501	}
9502
9503	/**
9504	 * Put XMP data object and return ID.
9505	 * @return (int) The object ID.
9506	 * @since 5.9.121 (2011-09-28)
9507	 * @protected
9508	 */
9509	protected function _putXMP() {
9510		$oid = $this->_newobj();
9511		// store current isunicode value
9512		$prev_isunicode = $this->isunicode;
9513		$this->isunicode = true;
9514		$prev_encrypted = $this->encrypted;
9515		$this->encrypted = false;
9516		// set XMP data
9517		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9518		$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";
9519		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9520		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9521		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9522		$xmp .= "\t\t\t".'<dc:title>'."\n";
9523		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9524		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9525		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9526		$xmp .= "\t\t\t".'</dc:title>'."\n";
9527		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9528		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9529		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9530		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9531		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9532		$xmp .= "\t\t\t".'<dc:description>'."\n";
9533		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9534		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9535		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9536		$xmp .= "\t\t\t".'</dc:description>'."\n";
9537		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9538		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9539		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9540		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9541		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9542		$xmp .= "\t\t".'</rdf:Description>'."\n";
9543		// convert doc creation date format
9544		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9545		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9546		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9547		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9548		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9549		// convert doc modification date format
9550		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9551		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9552		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9553		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9554		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9555		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9556		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9557		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9558		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9559		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9560		$xmp .= "\t\t".'</rdf:Description>'."\n";
9561		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9562		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9563		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9564		$xmp .= "\t\t".'</rdf:Description>'."\n";
9565		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9566		$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);
9567		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9568		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9569		$xmp .= "\t\t".'</rdf:Description>'."\n";
9570		if ($this->pdfa_mode) {
9571			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9572			$xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9573			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9574			$xmp .= "\t\t".'</rdf:Description>'."\n";
9575		}
9576		// XMP extension schemas
9577		$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";
9578		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9579		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9580		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9581		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9582		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9583		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9584		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9585		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9586		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9587		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9588		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9589		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9590		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9591		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9592		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9593		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9594		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9595		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9596		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9597		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9598		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9599		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9600		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9601		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9602		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9603		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9604		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9605		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9606		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9607		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9608		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9609		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9610		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9611		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9612		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9613		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9614		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9615		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9616		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9617		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9618		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9619		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9620		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9621		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9622		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9623		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9624		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9625		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9626		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9627		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9628		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9629		$xmp .= "\t\t".'</rdf:Description>'."\n";
9630		$xmp .= "\t".'</rdf:RDF>'."\n";
9631		$xmp .= $this->custom_xmp;
9632		$xmp .= '</x:xmpmeta>'."\n";
9633		$xmp .= '<?xpacket end="w"?>';
9634		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9635		// restore previous isunicode value
9636		$this->isunicode = $prev_isunicode;
9637		$this->encrypted = $prev_encrypted;
9638		$this->_out($out);
9639		return $oid;
9640	}
9641
9642	/**
9643	 * Output Catalog.
9644	 * @return int object id
9645	 * @protected
9646	 */
9647	protected function _putcatalog() {
9648		// put XMP
9649		$xmpobj = $this->_putXMP();
9650		// if required, add standard sRGB ICC colour profile
9651		if ($this->pdfa_mode OR $this->force_srgb) {
9652			$iccobj = $this->_newobj();
9653			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9654			$filter = '';
9655			if ($this->compress) {
9656				$filter = ' /Filter /FlateDecode';
9657				$icc = gzcompress($icc);
9658			}
9659			$icc = $this->_getrawstream($icc);
9660			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9661		}
9662		// start catalog
9663		$oid = $this->_newobj();
9664		$out = '<< /Type /Catalog';
9665		$out .= ' /Version /'.$this->PDFVersion;
9666		//$out .= ' /Extensions <<>>';
9667		$out .= ' /Pages 1 0 R';
9668		//$out .= ' /PageLabels ' //...;
9669		$out .= ' /Names <<';
9670		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9671			$out .= ' /JavaScript '.$this->n_js;
9672		}
9673		if (!empty($this->efnames)) {
9674			$out .= ' /EmbeddedFiles <</Names [';
9675			foreach ($this->efnames AS $fn => $fref) {
9676				$out .= ' '.$this->_datastring($fn).' '.$fref;
9677			}
9678			$out .= ' ]>>';
9679		}
9680		$out .= ' >>';
9681		if (!empty($this->dests)) {
9682			$out .= ' /Dests '.($this->n_dests).' 0 R';
9683		}
9684		$out .= $this->_putviewerpreferences();
9685		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9686			$out .= ' /PageLayout /'.$this->LayoutMode;
9687		}
9688		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9689			$out .= ' /PageMode /'.$this->PageMode;
9690		}
9691		if (count($this->outlines) > 0) {
9692			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9693			$out .= ' /PageMode /UseOutlines';
9694		}
9695		//$out .= ' /Threads []';
9696		if ($this->ZoomMode == 'fullpage') {
9697			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9698		} elseif ($this->ZoomMode == 'fullwidth') {
9699			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9700		} elseif ($this->ZoomMode == 'real') {
9701			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9702		} elseif (!is_string($this->ZoomMode)) {
9703			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9704		}
9705		//$out .= ' /AA <<>>';
9706		//$out .= ' /URI <<>>';
9707		$out .= ' /Metadata '.$xmpobj.' 0 R';
9708		//$out .= ' /StructTreeRoot <<>>';
9709		//$out .= ' /MarkInfo <<>>';
9710		if (isset($this->l['a_meta_language'])) {
9711			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9712		}
9713		//$out .= ' /SpiderInfo <<>>';
9714		// set OutputIntent to sRGB IEC61966-2.1 if required
9715		if ($this->pdfa_mode OR $this->force_srgb) {
9716			$out .= ' /OutputIntents [<<';
9717			$out .= ' /Type /OutputIntent';
9718			$out .= ' /S /GTS_PDFA1';
9719			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9720			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9721			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9722			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9723			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9724			$out .= ' >>]';
9725		}
9726		//$out .= ' /PieceInfo <<>>';
9727		if (!empty($this->pdflayers)) {
9728			$lyrobjs = '';
9729			$lyrobjs_off = '';
9730			$lyrobjs_lock = '';
9731			foreach ($this->pdflayers as $layer) {
9732				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9733				$lyrobjs .= $layer_obj_ref;
9734				if ($layer['view'] === false) {
9735					$lyrobjs_off .= $layer_obj_ref;
9736				}
9737				if ($layer['lock']) {
9738					$lyrobjs_lock .= $layer_obj_ref;
9739				}
9740			}
9741			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9742			$out .= ' /D <<';
9743			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9744			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9745			$out .= ' /BaseState /ON';
9746			$out .= ' /OFF ['.$lyrobjs_off.']';
9747			$out .= ' /Locked ['.$lyrobjs_lock.']';
9748			$out .= ' /Intent /View';
9749			$out .= ' /AS [';
9750			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9751			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9752			$out .= ' ]';
9753			$out .= ' /Order ['.$lyrobjs.']';
9754			$out .= ' /ListMode /AllPages';
9755			//$out .= ' /RBGroups ['..']';
9756			//$out .= ' /Locked ['..']';
9757			$out .= ' >>';
9758			$out .= ' >>';
9759		}
9760		// AcroForm
9761		if (!empty($this->form_obj_id)
9762			OR ($this->sign AND isset($this->signature_data['cert_type']))
9763			OR !empty($this->empty_signature_appearance)) {
9764			$out .= ' /AcroForm <<';
9765			$objrefs = '';
9766			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9767				// set reference for signature object
9768				$objrefs .= $this->sig_obj_id.' 0 R';
9769			}
9770			if (!empty($this->empty_signature_appearance)) {
9771				foreach ($this->empty_signature_appearance as $esa) {
9772					// set reference for empty signature objects
9773					$objrefs .= ' '.$esa['objid'].' 0 R';
9774				}
9775			}
9776			if (!empty($this->form_obj_id)) {
9777				foreach($this->form_obj_id as $objid) {
9778					$objrefs .= ' '.$objid.' 0 R';
9779				}
9780			}
9781			$out .= ' /Fields ['.$objrefs.']';
9782			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9783			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9784				$out .= ' /NeedAppearances false';
9785			}
9786			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9787				if ($this->signature_data['cert_type'] > 0) {
9788					$out .= ' /SigFlags 3';
9789				} else {
9790					$out .= ' /SigFlags 1';
9791				}
9792			}
9793			//$out .= ' /CO ';
9794			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9795				$out .= ' /DR <<';
9796				$out .= ' /Font <<';
9797				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9798					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9799				}
9800				$out .= ' >> >>';
9801			}
9802			$font = $this->getFontBuffer('helvetica');
9803			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9804			$out .= ' /Q '.(($this->rtl)?'2':'0');
9805			//$out .= ' /XFA ';
9806			$out .= ' >>';
9807			// signatures
9808			if ($this->sign AND isset($this->signature_data['cert_type'])
9809				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9810				if ($this->signature_data['cert_type'] > 0) {
9811					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9812				} else {
9813					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9814				}
9815			}
9816		}
9817		//$out .= ' /Legal <<>>';
9818		//$out .= ' /Requirements []';
9819		//$out .= ' /Collection <<>>';
9820		//$out .= ' /NeedsRendering true';
9821		$out .= ' >>';
9822		$out .= "\n".'endobj';
9823		$this->_out($out);
9824		return $oid;
9825	}
9826
9827	/**
9828	 * Output viewer preferences.
9829	 * @return string for viewer preferences
9830	 * @author Nicola asuni
9831	 * @since 3.1.000 (2008-06-09)
9832	 * @protected
9833	 */
9834	protected function _putviewerpreferences() {
9835		$vp = $this->viewer_preferences;
9836		$out = ' /ViewerPreferences <<';
9837		if ($this->rtl) {
9838			$out .= ' /Direction /R2L';
9839		} else {
9840			$out .= ' /Direction /L2R';
9841		}
9842		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9843			$out .= ' /HideToolbar true';
9844		}
9845		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9846			$out .= ' /HideMenubar true';
9847		}
9848		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9849			$out .= ' /HideWindowUI true';
9850		}
9851		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9852			$out .= ' /FitWindow true';
9853		}
9854		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9855			$out .= ' /CenterWindow true';
9856		}
9857		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9858			$out .= ' /DisplayDocTitle true';
9859		}
9860		if (isset($vp['NonFullScreenPageMode'])) {
9861			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9862		}
9863		if (isset($vp['ViewArea'])) {
9864			$out .= ' /ViewArea /'.$vp['ViewArea'];
9865		}
9866		if (isset($vp['ViewClip'])) {
9867			$out .= ' /ViewClip /'.$vp['ViewClip'];
9868		}
9869		if (isset($vp['PrintArea'])) {
9870			$out .= ' /PrintArea /'.$vp['PrintArea'];
9871		}
9872		if (isset($vp['PrintClip'])) {
9873			$out .= ' /PrintClip /'.$vp['PrintClip'];
9874		}
9875		if (isset($vp['PrintScaling'])) {
9876			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
9877		}
9878		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9879			$out .= ' /Duplex /'.$vp['Duplex'];
9880		}
9881		if (isset($vp['PickTrayByPDFSize'])) {
9882			if ($vp['PickTrayByPDFSize']) {
9883				$out .= ' /PickTrayByPDFSize true';
9884			} else {
9885				$out .= ' /PickTrayByPDFSize false';
9886			}
9887		}
9888		if (isset($vp['PrintPageRange'])) {
9889			$PrintPageRangeNum = '';
9890			foreach ($vp['PrintPageRange'] as $k => $v) {
9891				$PrintPageRangeNum .= ' '.($v - 1).'';
9892			}
9893			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9894		}
9895		if (isset($vp['NumCopies'])) {
9896			$out .= ' /NumCopies '.intval($vp['NumCopies']);
9897		}
9898		$out .= ' >>';
9899		return $out;
9900	}
9901
9902	/**
9903	 * Output PDF File Header (7.5.2).
9904	 * @protected
9905	 */
9906	protected function _putheader() {
9907		$this->_out('%PDF-'.$this->PDFVersion);
9908		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9909	}
9910
9911	/**
9912	 * Output end of document (EOF).
9913	 * @protected
9914	 */
9915	protected function _enddoc() {
9916		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9917			// save subset chars of the previous font
9918			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9919		}
9920		$this->state = 1;
9921		$this->_putheader();
9922		$this->_putpages();
9923		$this->_putresources();
9924		// empty signature fields
9925		if (!empty($this->empty_signature_appearance)) {
9926			foreach ($this->empty_signature_appearance as $key => $esa) {
9927				// widget annotation for empty signature
9928				$out = $this->_getobj($esa['objid'])."\n";
9929				$out .= '<< /Type /Annot';
9930				$out .= ' /Subtype /Widget';
9931				$out .= ' /Rect ['.$esa['rect'].']';
9932				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
9933				$out .= ' /F 4';
9934				$out .= ' /FT /Sig';
9935				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
9936				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
9937				$out .= ' /Ff 0';
9938				$out .= ' >>';
9939				$out .= "\n".'endobj';
9940				$this->_out($out);
9941			}
9942		}
9943		// Signature
9944		if ($this->sign AND isset($this->signature_data['cert_type'])) {
9945			// widget annotation for signature
9946			$out = $this->_getobj($this->sig_obj_id)."\n";
9947			$out .= '<< /Type /Annot';
9948			$out .= ' /Subtype /Widget';
9949			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
9950			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
9951			$out .= ' /F 4';
9952			$out .= ' /FT /Sig';
9953			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
9954			$out .= ' /Ff 0';
9955			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
9956			$out .= ' >>';
9957			$out .= "\n".'endobj';
9958			$this->_out($out);
9959			// signature
9960			$this->_putsignature();
9961		}
9962		// Info
9963		$objid_info = $this->_putinfo();
9964		// Catalog
9965		$objid_catalog = $this->_putcatalog();
9966		// Cross-ref
9967		$o = $this->bufferlen;
9968		// XREF section
9969		$this->_out('xref');
9970		$this->_out('0 '.($this->n + 1));
9971		$this->_out('0000000000 65535 f ');
9972		$freegen = ($this->n + 2);
9973		for ($i=1; $i <= $this->n; ++$i) {
9974			if (!isset($this->offsets[$i]) AND ($i > 1)) {
9975				$this->_out(sprintf('0000000000 %05d f ', $freegen));
9976				++$freegen;
9977			} else {
9978				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
9979			}
9980		}
9981		// TRAILER
9982		$out = 'trailer'."\n";
9983		$out .= '<<';
9984		$out .= ' /Size '.($this->n + 1);
9985		$out .= ' /Root '.$objid_catalog.' 0 R';
9986		$out .= ' /Info '.$objid_info.' 0 R';
9987		if ($this->encrypted) {
9988			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
9989		}
9990		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
9991		$out .= ' >>';
9992		$this->_out($out);
9993		$this->_out('startxref');
9994		$this->_out($o);
9995		$this->_out('%%EOF');
9996		$this->state = 3; // end-of-doc
9997	}
9998
9999	/**
10000	 * Initialize a new page.
10001	 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10002	 * @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().
10003	 * @protected
10004	 * @see getPageSizeFromFormat(), setPageFormat()
10005	 */
10006	protected function _beginpage($orientation='', $format='') {
10007		++$this->page;
10008		$this->pageobjects[$this->page] = array();
10009		$this->setPageBuffer($this->page, '');
10010		// initialize array for graphics tranformation positions inside a page buffer
10011		$this->transfmrk[$this->page] = array();
10012		$this->state = 2;
10013		if (TCPDF_STATIC::empty_string($orientation)) {
10014			if (isset($this->CurOrientation)) {
10015				$orientation = $this->CurOrientation;
10016			} elseif ($this->fwPt > $this->fhPt) {
10017				// landscape
10018				$orientation = 'L';
10019			} else {
10020				// portrait
10021				$orientation = 'P';
10022			}
10023		}
10024		if (TCPDF_STATIC::empty_string($format)) {
10025			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10026			$this->setPageOrientation($orientation);
10027		} else {
10028			$this->setPageFormat($format, $orientation);
10029		}
10030		if ($this->rtl) {
10031			$this->x = $this->w - $this->rMargin;
10032		} else {
10033			$this->x = $this->lMargin;
10034		}
10035		$this->y = $this->tMargin;
10036		if (isset($this->newpagegroup[$this->page])) {
10037			// start a new group
10038			$this->currpagegroup = $this->newpagegroup[$this->page];
10039			$this->pagegroups[$this->currpagegroup] = 1;
10040		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10041			++$this->pagegroups[$this->currpagegroup];
10042		}
10043	}
10044
10045	/**
10046	 * Mark end of page.
10047	 * @protected
10048	 */
10049	protected function _endpage() {
10050		$this->setVisibility('all');
10051		$this->state = 1;
10052	}
10053
10054	/**
10055	 * Begin a new object and return the object number.
10056	 * @return int object number
10057	 * @protected
10058	 */
10059	protected function _newobj() {
10060		$this->_out($this->_getobj());
10061		return $this->n;
10062	}
10063
10064	/**
10065	 * Return the starting object string for the selected object ID.
10066	 * @param $objid (int) Object ID (leave empty to get a new ID).
10067	 * @return string the starting object string
10068	 * @protected
10069	 * @since 5.8.009 (2010-08-20)
10070	 */
10071	protected function _getobj($objid='') {
10072		if ($objid === '') {
10073			++$this->n;
10074			$objid = $this->n;
10075		}
10076		$this->offsets[$objid] = $this->bufferlen;
10077		$this->pageobjects[$this->page][] = $objid;
10078		return $objid.' 0 obj';
10079	}
10080
10081	/**
10082	 * Underline text.
10083	 * @param $x (int) X coordinate
10084	 * @param $y (int) Y coordinate
10085	 * @param $txt (string) text to underline
10086	 * @protected
10087	 */
10088	protected function _dounderline($x, $y, $txt) {
10089		$w = $this->GetStringWidth($txt);
10090		return $this->_dounderlinew($x, $y, $w);
10091	}
10092
10093	/**
10094	 * Underline for rectangular text area.
10095	 * @param $x (int) X coordinate
10096	 * @param $y (int) Y coordinate
10097	 * @param $w (int) width to underline
10098	 * @protected
10099	 * @since 4.8.008 (2009-09-29)
10100	 */
10101	protected function _dounderlinew($x, $y, $w) {
10102		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10103		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10104	}
10105
10106	/**
10107	 * Line through text.
10108	 * @param $x (int) X coordinate
10109	 * @param $y (int) Y coordinate
10110	 * @param $txt (string) text to linethrough
10111	 * @protected
10112	 */
10113	protected function _dolinethrough($x, $y, $txt) {
10114		$w = $this->GetStringWidth($txt);
10115		return $this->_dolinethroughw($x, $y, $w);
10116	}
10117
10118	/**
10119	 * Line through for rectangular text area.
10120	 * @param $x (int) X coordinate
10121	 * @param $y (int) Y coordinate
10122	 * @param $w (int) line length (width)
10123	 * @protected
10124	 * @since 4.9.008 (2009-09-29)
10125	 */
10126	protected function _dolinethroughw($x, $y, $w) {
10127		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10128		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10129	}
10130
10131	/**
10132	 * Overline text.
10133	 * @param $x (int) X coordinate
10134	 * @param $y (int) Y coordinate
10135	 * @param $txt (string) text to overline
10136	 * @protected
10137	 * @since 4.9.015 (2010-04-19)
10138	 */
10139	protected function _dooverline($x, $y, $txt) {
10140		$w = $this->GetStringWidth($txt);
10141		return $this->_dooverlinew($x, $y, $w);
10142	}
10143
10144	/**
10145	 * Overline for rectangular text area.
10146	 * @param $x (int) X coordinate
10147	 * @param $y (int) Y coordinate
10148	 * @param $w (int) width to overline
10149	 * @protected
10150	 * @since 4.9.015 (2010-04-19)
10151	 */
10152	protected function _dooverlinew($x, $y, $w) {
10153		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10154		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10155
10156	}
10157
10158	/**
10159	 * Format a data string for meta information
10160	 * @param $s (string) data string to escape.
10161	 * @param $n (int) object ID
10162	 * @return string escaped string.
10163	 * @protected
10164	 */
10165	protected function _datastring($s, $n=0) {
10166		if ($n == 0) {
10167			$n = $this->n;
10168		}
10169		$s = $this->_encrypt_data($n, $s);
10170		return '('. TCPDF_STATIC::_escape($s).')';
10171	}
10172
10173	/**
10174	 * Set the document creation timestamp
10175	 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10176	 * @public
10177	 * @since 5.9.152 (2012-03-23)
10178	 */
10179	public function setDocCreationTimestamp($time) {
10180		if (is_string($time)) {
10181			$time = TCPDF_STATIC::getTimestamp($time);
10182		}
10183		$this->doc_creation_timestamp = intval($time);
10184	}
10185
10186	/**
10187	 * Set the document modification timestamp
10188	 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10189	 * @public
10190	 * @since 5.9.152 (2012-03-23)
10191	 */
10192	public function setDocModificationTimestamp($time) {
10193		if (is_string($time)) {
10194			$time = TCPDF_STATIC::getTimestamp($time);
10195		}
10196		$this->doc_modification_timestamp = intval($time);
10197	}
10198
10199	/**
10200	 * Returns document creation timestamp in seconds.
10201	 * @return (int) Creation timestamp in seconds.
10202	 * @public
10203	 * @since 5.9.152 (2012-03-23)
10204	 */
10205	public function getDocCreationTimestamp() {
10206		return $this->doc_creation_timestamp;
10207	}
10208
10209	/**
10210	 * Returns document modification timestamp in seconds.
10211	 * @return (int) Modfication timestamp in seconds.
10212	 * @public
10213	 * @since 5.9.152 (2012-03-23)
10214	 */
10215	public function getDocModificationTimestamp() {
10216		return $this->doc_modification_timestamp;
10217	}
10218
10219	/**
10220	 * Returns a formatted date for meta information
10221	 * @param $n (int) Object ID.
10222	 * @param $timestamp (int) Timestamp to convert.
10223	 * @return string escaped date string.
10224	 * @protected
10225	 * @since 4.6.028 (2009-08-25)
10226	 */
10227	protected function _datestring($n=0, $timestamp=0) {
10228		if ((empty($timestamp)) OR ($timestamp < 0)) {
10229			$timestamp = $this->doc_creation_timestamp;
10230		}
10231		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10232	}
10233
10234	/**
10235	 * Format a text string for meta information
10236	 * @param $s (string) string to escape.
10237	 * @param $n (int) object ID
10238	 * @return string escaped string.
10239	 * @protected
10240	 */
10241	protected function _textstring($s, $n=0) {
10242		if ($this->isunicode) {
10243			//Convert string to UTF-16BE
10244			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10245		}
10246		return $this->_datastring($s, $n);
10247	}
10248
10249	/**
10250	 * get raw output stream.
10251	 * @param $s (string) string to output.
10252	 * @param $n (int) object reference for encryption mode
10253	 * @protected
10254	 * @author Nicola Asuni
10255	 * @since 5.5.000 (2010-06-22)
10256	 */
10257	protected function _getrawstream($s, $n=0) {
10258		if ($n <= 0) {
10259			// default to current object
10260			$n = $this->n;
10261		}
10262		return $this->_encrypt_data($n, $s);
10263	}
10264
10265	/**
10266	 * Output a string to the document.
10267	 * @param $s (string) string to output.
10268	 * @protected
10269	 */
10270	protected function _out($s) {
10271		if ($this->state == 2) {
10272			if ($this->inxobj) {
10273				// we are inside an XObject template
10274				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10275			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10276				// puts data before page footer
10277				$pagebuff = $this->getPageBuffer($this->page);
10278				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10279				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10280				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10281				// update footer position
10282				$this->footerpos[$this->page] += strlen($s."\n");
10283			} else {
10284				// set page data
10285				$this->setPageBuffer($this->page, $s."\n", true);
10286			}
10287		} elseif ($this->state > 0) {
10288			// set general data
10289			$this->setBuffer($s."\n");
10290		}
10291	}
10292
10293	/**
10294	 * Set header font.
10295	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10296	 * @public
10297	 * @since 1.1
10298	 */
10299	public function setHeaderFont($font) {
10300		$this->header_font = $font;
10301	}
10302
10303	/**
10304	 * Get header font.
10305	 * @return array() Array describing the basic font parameters: (family, style, size).
10306	 * @public
10307	 * @since 4.0.012 (2008-07-24)
10308	 */
10309	public function getHeaderFont() {
10310		return $this->header_font;
10311	}
10312
10313	/**
10314	 * Set footer font.
10315	 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10316	 * @public
10317	 * @since 1.1
10318	 */
10319	public function setFooterFont($font) {
10320		$this->footer_font = $font;
10321	}
10322
10323	/**
10324	 * Get Footer font.
10325	 * @return array() Array describing the basic font parameters: (family, style, size).
10326	 * @public
10327	 * @since 4.0.012 (2008-07-24)
10328	 */
10329	public function getFooterFont() {
10330		return $this->footer_font;
10331	}
10332
10333	/**
10334	 * Set language array.
10335	 * @param $language (array)
10336	 * @public
10337	 * @since 1.1
10338	 */
10339	public function setLanguageArray($language) {
10340		$this->l = $language;
10341		if (isset($this->l['a_meta_dir'])) {
10342			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10343		} else {
10344			$this->rtl = false;
10345		}
10346	}
10347
10348	/**
10349	 * Returns the PDF data.
10350	 * @public
10351	 */
10352	public function getPDFData() {
10353		if ($this->state < 3) {
10354			$this->Close();
10355		}
10356		return $this->buffer;
10357	}
10358
10359	/**
10360	 * Output anchor link.
10361	 * @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;)
10362	 * @param $name (string) link name
10363	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10364	 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10365	 * @param $color (array) array of RGB text color
10366	 * @param $style (string) font style (U, D, B, I)
10367	 * @param $firstblock (boolean) if true the string is the starting of a line.
10368	 * @return the number of cells used or the remaining text if $firstline = true;
10369	 * @public
10370	 */
10371	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10372		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10373			// convert url to internal link
10374			$lnkdata = explode(',', $url);
10375			if (isset($lnkdata[0]) ) {
10376				$page = substr($lnkdata[0], 1);
10377				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10378					$lnky = floatval($lnkdata[1]);
10379				} else {
10380					$lnky = 0;
10381				}
10382				$url = $this->AddLink();
10383				$this->SetLink($url, $lnky, $page);
10384			}
10385		}
10386		// store current settings
10387		$prevcolor = $this->fgcolor;
10388		$prevstyle = $this->FontStyle;
10389		if (empty($color)) {
10390			$this->SetTextColorArray($this->htmlLinkColorArray);
10391		} else {
10392			$this->SetTextColorArray($color);
10393		}
10394		if ($style == -1) {
10395			$this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10396		} else {
10397			$this->SetFont('', $this->FontStyle.$style);
10398		}
10399		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10400		// restore settings
10401		$this->SetFont('', $prevstyle);
10402		$this->SetTextColorArray($prevcolor);
10403		return $ret;
10404	}
10405
10406	/**
10407	 * Converts pixels to User's Units.
10408	 * @param $px (int) pixels
10409	 * @return float value in user's unit
10410	 * @public
10411	 * @see setImageScale(), getImageScale()
10412	 */
10413	public function pixelsToUnits($px) {
10414		return ($px / ($this->imgscale * $this->k));
10415	}
10416
10417	/**
10418	 * Reverse function for htmlentities.
10419	 * Convert entities in UTF-8.
10420	 * @param $text_to_convert (string) Text to convert.
10421	 * @return string converted text string
10422	 * @public
10423	 */
10424	public function unhtmlentities($text_to_convert) {
10425		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10426	}
10427
10428	// ENCRYPTION METHODS ----------------------------------
10429
10430	/**
10431	 * Compute encryption key depending on object number where the encrypted data is stored.
10432	 * This is used for all strings and streams without crypt filter specifier.
10433	 * @param $n (int) object number
10434	 * @return int object key
10435	 * @protected
10436	 * @author Nicola Asuni
10437	 * @since 2.0.000 (2008-01-02)
10438	 */
10439	protected function _objectkey($n) {
10440		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10441		if ($this->encryptdata['mode'] == 2) { // AES-128
10442			// AES padding
10443			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10444		}
10445		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10446		$objkey = substr($objkey, 0, 16);
10447		return $objkey;
10448	}
10449
10450	/**
10451	 * Encrypt the input string.
10452	 * @param $n (int) object number
10453	 * @param $s (string) data string to encrypt
10454	 * @return encrypted string
10455	 * @protected
10456	 * @author Nicola Asuni
10457	 * @since 5.0.005 (2010-05-11)
10458	 */
10459	protected function _encrypt_data($n, $s) {
10460		if (!$this->encrypted) {
10461			return $s;
10462		}
10463		switch ($this->encryptdata['mode']) {
10464			case 0:   // RC4-40
10465			case 1: { // RC4-128
10466				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10467				break;
10468			}
10469			case 2: { // AES-128
10470				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10471				break;
10472			}
10473			case 3: { // AES-256
10474				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10475				break;
10476			}
10477		}
10478		return $s;
10479	}
10480
10481	/**
10482	 * Put encryption on PDF document.
10483	 * @protected
10484	 * @author Nicola Asuni
10485	 * @since 2.0.000 (2008-01-02)
10486	 */
10487	protected function _putencryption() {
10488		if (!$this->encrypted) {
10489			return;
10490		}
10491		$this->encryptdata['objid'] = $this->_newobj();
10492		$out = '<<';
10493		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10494			$this->encryptdata['Filter'] = 'Standard';
10495		}
10496		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10497		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10498			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10499		}
10500		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10501			$this->encryptdata['V'] = 1;
10502		}
10503		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10504		$out .= ' /V '.$this->encryptdata['V'];
10505		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10506			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10507			$out .= ' /Length '.$this->encryptdata['Length'];
10508		} else {
10509			$out .= ' /Length 40';
10510		}
10511		if ($this->encryptdata['V'] >= 4) {
10512			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10513				$this->encryptdata['StmF'] = 'Identity';
10514			}
10515			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10516				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10517				$this->encryptdata['StrF'] = 'Identity';
10518			}
10519			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10520			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10521				$out .= ' /CF <<';
10522				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10523				$out .= ' /Type /CryptFilter';
10524				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10525					// The method used
10526					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10527					if ($this->encryptdata['pubkey']) {
10528						$out .= ' /Recipients [';
10529						foreach ($this->encryptdata['Recipients'] as $rec) {
10530							$out .= ' <'.$rec.'>';
10531						}
10532						$out .= ' ]';
10533						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10534							$out .= ' /EncryptMetadata false';
10535						} else {
10536							$out .= ' /EncryptMetadata true';
10537						}
10538					}
10539				} else {
10540					$out .= ' /CFM /None';
10541				}
10542				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10543					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10544					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10545				} else {
10546					$out .= ' /AuthEvent /DocOpen';
10547				}
10548				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10549					// The bit length of the encryption key.
10550					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10551				}
10552				$out .= ' >> >>';
10553			}
10554			// The name of the crypt filter that shall be used by default when decrypting streams.
10555			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10556			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10557			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10558			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10559				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10560				$out .= ' /EFF /'.$this->encryptdata[''];
10561			}
10562		}
10563		// Additional encryption dictionary entries for the standard security handler
10564		if ($this->encryptdata['pubkey']) {
10565			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10566				$out .= ' /Recipients [';
10567				foreach ($this->encryptdata['Recipients'] as $rec) {
10568					$out .= ' <'.$rec.'>';
10569				}
10570				$out .= ' ]';
10571			}
10572		} else {
10573			$out .= ' /R';
10574			if ($this->encryptdata['V'] == 5) { // AES-256
10575				$out .= ' 5';
10576				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10577				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10578				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10579			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10580				$out .= ' 4';
10581			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10582				$out .= ' 2';
10583			} else { // RC-128
10584				$out .= ' 3';
10585			}
10586			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10587			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10588			$out .= ' /P '.$this->encryptdata['P'];
10589			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10590				$out .= ' /EncryptMetadata false';
10591			} else {
10592				$out .= ' /EncryptMetadata true';
10593			}
10594		}
10595		$out .= ' >>';
10596		$out .= "\n".'endobj';
10597		$this->_out($out);
10598	}
10599
10600	/**
10601	 * Compute U value (used for encryption)
10602	 * @return string U value
10603	 * @protected
10604	 * @since 2.0.000 (2008-01-02)
10605	 * @author Nicola Asuni
10606	 */
10607	protected function _Uvalue() {
10608		if ($this->encryptdata['mode'] == 0) { // RC4-40
10609			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10610		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10611			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10612			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10613			$len = strlen($tmp);
10614			for ($i = 1; $i <= 19; ++$i) {
10615				$ek = '';
10616				for ($j = 0; $j < $len; ++$j) {
10617					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10618				}
10619				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10620			}
10621			$enc .= str_repeat("\x00", 16);
10622			return substr($enc, 0, 32);
10623		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10624			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10625			// User Validation Salt
10626			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10627			// User Key Salt
10628			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10629			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10630		}
10631	}
10632
10633	/**
10634	 * Compute UE value (used for encryption)
10635	 * @return string UE value
10636	 * @protected
10637	 * @since 5.9.006 (2010-10-19)
10638	 * @author Nicola Asuni
10639	 */
10640	protected function _UEvalue() {
10641		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10642		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10643	}
10644
10645	/**
10646	 * Compute O value (used for encryption)
10647	 * @return string O value
10648	 * @protected
10649	 * @since 2.0.000 (2008-01-02)
10650	 * @author Nicola Asuni
10651	 */
10652	protected function _Ovalue() {
10653		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10654			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10655			if ($this->encryptdata['mode'] > 0) {
10656				for ($i = 0; $i < 50; ++$i) {
10657					$tmp = TCPDF_STATIC::_md5_16($tmp);
10658				}
10659			}
10660			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10661			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10662			if ($this->encryptdata['mode'] > 0) {
10663				$len = strlen($owner_key);
10664				for ($i = 1; $i <= 19; ++$i) {
10665					$ek = '';
10666					for ($j = 0; $j < $len; ++$j) {
10667						$ek .= chr(ord($owner_key[$j]) ^ $i);
10668					}
10669					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10670				}
10671			}
10672			return $enc;
10673		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10674			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10675			// Owner Validation Salt
10676			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10677			// Owner Key Salt
10678			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10679			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10680		}
10681	}
10682
10683	/**
10684	 * Compute OE value (used for encryption)
10685	 * @return string OE value
10686	 * @protected
10687	 * @since 5.9.006 (2010-10-19)
10688	 * @author Nicola Asuni
10689	 */
10690	protected function _OEvalue() {
10691		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10692		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10693	}
10694
10695	/**
10696	 * Convert password for AES-256 encryption mode
10697	 * @param $password (string) password
10698	 * @return string password
10699	 * @protected
10700	 * @since 5.9.006 (2010-10-19)
10701	 * @author Nicola Asuni
10702	 */
10703	protected function _fixAES256Password($password) {
10704		$psw = ''; // password to be returned
10705		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10706		foreach ($psw_array as $c) {
10707			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10708		}
10709		return substr($psw, 0, 127);
10710	}
10711
10712	/**
10713	 * Compute encryption key
10714	 * @protected
10715	 * @since 2.0.000 (2008-01-02)
10716	 * @author Nicola Asuni
10717	 */
10718	protected function _generateencryptionkey() {
10719		$keybytelen = ($this->encryptdata['Length'] / 8);
10720		if (!$this->encryptdata['pubkey']) { // standard mode
10721			if ($this->encryptdata['mode'] == 3) { // AES-256
10722				// generate 256 bit random key
10723				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10724				// truncate passwords
10725				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10726				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10727				// Compute U value
10728				$this->encryptdata['U'] = $this->_Uvalue();
10729				// Compute UE value
10730				$this->encryptdata['UE'] = $this->_UEvalue();
10731				// Compute O value
10732				$this->encryptdata['O'] = $this->_Ovalue();
10733				// Compute OE value
10734				$this->encryptdata['OE'] = $this->_OEvalue();
10735				// Compute P value
10736				$this->encryptdata['P'] = $this->encryptdata['protection'];
10737				// Computing the encryption dictionary's Perms (permissions) value
10738				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10739				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10740				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10741					$perms .= 'F';
10742				} else {
10743					$perms .= 'T';
10744				}
10745				$perms .= 'adb'; // bytes 9-11
10746				$perms .= 'nick'; // bytes 12-15
10747				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10748			} else { // RC4-40, RC4-128, AES-128
10749				// Pad passwords
10750				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10751				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10752				// Compute O value
10753				$this->encryptdata['O'] = $this->_Ovalue();
10754				// get default permissions (reverse byte order)
10755				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10756				// Compute encryption key
10757				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10758				if ($this->encryptdata['mode'] > 0) {
10759					for ($i = 0; $i < 50; ++$i) {
10760						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10761					}
10762				}
10763				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10764				// Compute U value
10765				$this->encryptdata['U'] = $this->_Uvalue();
10766				// Compute P value
10767				$this->encryptdata['P'] = $this->encryptdata['protection'];
10768			}
10769		} else { // Public-Key mode
10770			// random 20-byte seed
10771			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10772			$recipient_bytes = '';
10773			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10774				// for each public certificate
10775				if (isset($pubkey['p'])) {
10776					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10777				} else {
10778					$pkprotection = $this->encryptdata['protection'];
10779				}
10780				// get default permissions (reverse byte order)
10781				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10782				// envelope data
10783				$envelope = $seed.$pkpermissions;
10784				// write the envelope data to a temporary file
10785				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10786				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10787				if (!$f) {
10788					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10789				}
10790				$envelope_length = strlen($envelope);
10791				fwrite($f, $envelope, $envelope_length);
10792				fclose($f);
10793				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10794				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10795					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10796				}
10797				// read encryption signature
10798				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10799				// extract signature
10800				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10801				$tmparr = explode("\n\n", $signature);
10802				$signature = trim($tmparr[1]);
10803				unset($tmparr);
10804				// decode signature
10805				$signature = base64_decode($signature);
10806				// convert signature to hex
10807				$hexsignature = current(unpack('H*', $signature));
10808				// store signature on recipients array
10809				$this->encryptdata['Recipients'][] = $hexsignature;
10810				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10811				$recipient_bytes .= $signature;
10812			}
10813			// calculate encryption key
10814			if ($this->encryptdata['mode'] == 3) { // AES-256
10815				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10816			} else { // RC4-40, RC4-128, AES-128
10817				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10818			}
10819		}
10820	}
10821
10822	/**
10823	 * Set document protection
10824	 * Remark: the protection against modification is for people who have the full Acrobat product.
10825	 * 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.
10826	 * 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.
10827	 * @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>
10828	 * @param $user_pass (String) user password. Empty by default.
10829	 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10830	 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10831	 * @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')))
10832	 * @public
10833	 * @since 2.0.000 (2008-01-02)
10834	 * @author Nicola Asuni
10835	 */
10836	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) {
10837		if ($this->pdfa_mode) {
10838			// encryption is not allowed in PDF/A mode
10839			return;
10840		}
10841		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10842		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10843			// public-key mode
10844			$this->encryptdata['pubkeys'] = $pubkeys;
10845			if ($mode == 0) {
10846				// public-Key Security requires at least 128 bit
10847				$mode = 1;
10848			}
10849			if (!function_exists('openssl_pkcs7_encrypt')) {
10850				$this->Error('Public-Key Security requires openssl library.');
10851			}
10852			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10853			$this->encryptdata['pubkey'] = true;
10854			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10855			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
10856			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
10857		} else {
10858			// standard mode (password mode)
10859			$this->encryptdata['pubkey'] = false;
10860			$this->encryptdata['Filter'] = 'Standard';
10861			$this->encryptdata['StmF'] = 'StdCF';
10862			$this->encryptdata['StrF'] = 'StdCF';
10863		}
10864		if ($mode > 1) { // AES
10865			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10866				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10867			}
10868			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10869				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10870			}
10871			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10872				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10873			}
10874			if (($mode == 3) AND !function_exists('hash')) {
10875				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10876				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10877			}
10878		}
10879		if ($owner_pass === null) {
10880			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10881		}
10882		$this->encryptdata['user_password'] = $user_pass;
10883		$this->encryptdata['owner_password'] = $owner_pass;
10884		$this->encryptdata['mode'] = $mode;
10885		switch ($mode) {
10886			case 0: { // RC4 40 bit
10887				$this->encryptdata['V'] = 1;
10888				$this->encryptdata['Length'] = 40;
10889				$this->encryptdata['CF']['CFM'] = 'V2';
10890				break;
10891			}
10892			case 1: { // RC4 128 bit
10893				$this->encryptdata['V'] = 2;
10894				$this->encryptdata['Length'] = 128;
10895				$this->encryptdata['CF']['CFM'] = 'V2';
10896				if ($this->encryptdata['pubkey']) {
10897					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10898					$this->encryptdata['Recipients'] = array();
10899				}
10900				break;
10901			}
10902			case 2: { // AES 128 bit
10903				$this->encryptdata['V'] = 4;
10904				$this->encryptdata['Length'] = 128;
10905				$this->encryptdata['CF']['CFM'] = 'AESV2';
10906				$this->encryptdata['CF']['Length'] = 128;
10907				if ($this->encryptdata['pubkey']) {
10908					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10909					$this->encryptdata['Recipients'] = array();
10910				}
10911				break;
10912			}
10913			case 3: { // AES 256 bit
10914				$this->encryptdata['V'] = 5;
10915				$this->encryptdata['Length'] = 256;
10916				$this->encryptdata['CF']['CFM'] = 'AESV3';
10917				$this->encryptdata['CF']['Length'] = 256;
10918				if ($this->encryptdata['pubkey']) {
10919					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10920					$this->encryptdata['Recipients'] = array();
10921				}
10922				break;
10923			}
10924		}
10925		$this->encrypted = true;
10926		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10927		$this->_generateencryptionkey();
10928	}
10929
10930	// END OF ENCRYPTION FUNCTIONS -------------------------
10931
10932	// START TRANSFORMATIONS SECTION -----------------------
10933
10934	/**
10935	 * Starts a 2D tranformation saving current graphic state.
10936	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
10937	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10938	 * @public
10939	 * @since 2.1.000 (2008-01-07)
10940	 * @see StartTransform(), StopTransform()
10941	 */
10942	public function StartTransform() {
10943		if ($this->state != 2) {
10944			return;
10945		}
10946		$this->_outSaveGraphicsState();
10947		if ($this->inxobj) {
10948			// we are inside an XObject template
10949			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
10950		} else {
10951			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
10952		}
10953		++$this->transfmatrix_key;
10954		$this->transfmatrix[$this->transfmatrix_key] = array();
10955	}
10956
10957	/**
10958	 * Stops a 2D tranformation restoring previous graphic state.
10959	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
10960	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10961	 * @public
10962	 * @since 2.1.000 (2008-01-07)
10963	 * @see StartTransform(), StopTransform()
10964	 */
10965	public function StopTransform() {
10966		if ($this->state != 2) {
10967			return;
10968		}
10969		$this->_outRestoreGraphicsState();
10970		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
10971			array_pop($this->transfmatrix[$this->transfmatrix_key]);
10972			--$this->transfmatrix_key;
10973		}
10974		if ($this->inxobj) {
10975			// we are inside an XObject template
10976			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
10977		} else {
10978			array_pop($this->transfmrk[$this->page]);
10979		}
10980	}
10981	/**
10982	 * Horizontal Scaling.
10983	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
10984	 * @param $x (int) abscissa of the scaling center. Default is current x position
10985	 * @param $y (int) ordinate of the scaling center. Default is current y position
10986	 * @public
10987	 * @since 2.1.000 (2008-01-07)
10988	 * @see StartTransform(), StopTransform()
10989	 */
10990	public function ScaleX($s_x, $x='', $y='') {
10991		$this->Scale($s_x, 100, $x, $y);
10992	}
10993
10994	/**
10995	 * Vertical Scaling.
10996	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
10997	 * @param $x (int) abscissa of the scaling center. Default is current x position
10998	 * @param $y (int) ordinate of the scaling center. Default is current y position
10999	 * @public
11000	 * @since 2.1.000 (2008-01-07)
11001	 * @see StartTransform(), StopTransform()
11002	 */
11003	public function ScaleY($s_y, $x='', $y='') {
11004		$this->Scale(100, $s_y, $x, $y);
11005	}
11006
11007	/**
11008	 * Vertical and horizontal proportional Scaling.
11009	 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11010	 * @param $x (int) abscissa of the scaling center. Default is current x position
11011	 * @param $y (int) ordinate of the scaling center. Default is current y position
11012	 * @public
11013	 * @since 2.1.000 (2008-01-07)
11014	 * @see StartTransform(), StopTransform()
11015	 */
11016	public function ScaleXY($s, $x='', $y='') {
11017		$this->Scale($s, $s, $x, $y);
11018	}
11019
11020	/**
11021	 * Vertical and horizontal non-proportional Scaling.
11022	 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11023	 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11024	 * @param $x (int) abscissa of the scaling center. Default is current x position
11025	 * @param $y (int) ordinate of the scaling center. Default is current y position
11026	 * @public
11027	 * @since 2.1.000 (2008-01-07)
11028	 * @see StartTransform(), StopTransform()
11029	 */
11030	public function Scale($s_x, $s_y, $x='', $y='') {
11031		if ($x === '') {
11032			$x = $this->x;
11033		}
11034		if ($y === '') {
11035			$y = $this->y;
11036		}
11037		if (($s_x == 0) OR ($s_y == 0)) {
11038			$this->Error('Please do not use values equal to zero for scaling');
11039		}
11040		$y = ($this->h - $y) * $this->k;
11041		$x *= $this->k;
11042		//calculate elements of transformation matrix
11043		$s_x /= 100;
11044		$s_y /= 100;
11045		$tm = array();
11046		$tm[0] = $s_x;
11047		$tm[1] = 0;
11048		$tm[2] = 0;
11049		$tm[3] = $s_y;
11050		$tm[4] = $x * (1 - $s_x);
11051		$tm[5] = $y * (1 - $s_y);
11052		//scale the coordinate system
11053		$this->Transform($tm);
11054	}
11055
11056	/**
11057	 * Horizontal Mirroring.
11058	 * @param $x (int) abscissa of the point. Default is current x position
11059	 * @public
11060	 * @since 2.1.000 (2008-01-07)
11061	 * @see StartTransform(), StopTransform()
11062	 */
11063	public function MirrorH($x='') {
11064		$this->Scale(-100, 100, $x);
11065	}
11066
11067	/**
11068	 * Verical Mirroring.
11069	 * @param $y (int) ordinate of the point. Default is current y position
11070	 * @public
11071	 * @since 2.1.000 (2008-01-07)
11072	 * @see StartTransform(), StopTransform()
11073	 */
11074	public function MirrorV($y='') {
11075		$this->Scale(100, -100, '', $y);
11076	}
11077
11078	/**
11079	 * Point reflection mirroring.
11080	 * @param $x (int) abscissa of the point. Default is current x position
11081	 * @param $y (int) ordinate of the point. Default is current y position
11082	 * @public
11083	 * @since 2.1.000 (2008-01-07)
11084	 * @see StartTransform(), StopTransform()
11085	 */
11086	public function MirrorP($x='',$y='') {
11087		$this->Scale(-100, -100, $x, $y);
11088	}
11089
11090	/**
11091	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11092	 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11093	 * @param $x (int) abscissa of the point. Default is current x position
11094	 * @param $y (int) ordinate of the point. Default is current y position
11095	 * @public
11096	 * @since 2.1.000 (2008-01-07)
11097	 * @see StartTransform(), StopTransform()
11098	 */
11099	public function MirrorL($angle=0, $x='',$y='') {
11100		$this->Scale(-100, 100, $x, $y);
11101		$this->Rotate(-2*($angle-90), $x, $y);
11102	}
11103
11104	/**
11105	 * Translate graphic object horizontally.
11106	 * @param $t_x (int) movement to the right (or left for RTL)
11107	 * @public
11108	 * @since 2.1.000 (2008-01-07)
11109	 * @see StartTransform(), StopTransform()
11110	 */
11111	public function TranslateX($t_x) {
11112		$this->Translate($t_x, 0);
11113	}
11114
11115	/**
11116	 * Translate graphic object vertically.
11117	 * @param $t_y (int) movement to the bottom
11118	 * @public
11119	 * @since 2.1.000 (2008-01-07)
11120	 * @see StartTransform(), StopTransform()
11121	 */
11122	public function TranslateY($t_y) {
11123		$this->Translate(0, $t_y);
11124	}
11125
11126	/**
11127	 * Translate graphic object horizontally and vertically.
11128	 * @param $t_x (int) movement to the right
11129	 * @param $t_y (int) movement to the bottom
11130	 * @public
11131	 * @since 2.1.000 (2008-01-07)
11132	 * @see StartTransform(), StopTransform()
11133	 */
11134	public function Translate($t_x, $t_y) {
11135		//calculate elements of transformation matrix
11136		$tm = array();
11137		$tm[0] = 1;
11138		$tm[1] = 0;
11139		$tm[2] = 0;
11140		$tm[3] = 1;
11141		$tm[4] = $t_x * $this->k;
11142		$tm[5] = -$t_y * $this->k;
11143		//translate the coordinate system
11144		$this->Transform($tm);
11145	}
11146
11147	/**
11148	 * Rotate object.
11149	 * @param $angle (float) angle in degrees for counter-clockwise rotation
11150	 * @param $x (int) abscissa of the rotation center. Default is current x position
11151	 * @param $y (int) ordinate of the rotation center. Default is current y position
11152	 * @public
11153	 * @since 2.1.000 (2008-01-07)
11154	 * @see StartTransform(), StopTransform()
11155	 */
11156	public function Rotate($angle, $x='', $y='') {
11157		if ($x === '') {
11158			$x = $this->x;
11159		}
11160		if ($y === '') {
11161			$y = $this->y;
11162		}
11163		$y = ($this->h - $y) * $this->k;
11164		$x *= $this->k;
11165		//calculate elements of transformation matrix
11166		$tm = array();
11167		$tm[0] = cos(deg2rad($angle));
11168		$tm[1] = sin(deg2rad($angle));
11169		$tm[2] = -$tm[1];
11170		$tm[3] = $tm[0];
11171		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11172		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11173		//rotate the coordinate system around ($x,$y)
11174		$this->Transform($tm);
11175	}
11176
11177	/**
11178	 * Skew horizontally.
11179	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11180	 * @param $x (int) abscissa of the skewing center. default is current x position
11181	 * @param $y (int) ordinate of the skewing center. default is current y position
11182	 * @public
11183	 * @since 2.1.000 (2008-01-07)
11184	 * @see StartTransform(), StopTransform()
11185	 */
11186	public function SkewX($angle_x, $x='', $y='') {
11187		$this->Skew($angle_x, 0, $x, $y);
11188	}
11189
11190	/**
11191	 * Skew vertically.
11192	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11193	 * @param $x (int) abscissa of the skewing center. default is current x position
11194	 * @param $y (int) ordinate of the skewing center. default is current y position
11195	 * @public
11196	 * @since 2.1.000 (2008-01-07)
11197	 * @see StartTransform(), StopTransform()
11198	 */
11199	public function SkewY($angle_y, $x='', $y='') {
11200		$this->Skew(0, $angle_y, $x, $y);
11201	}
11202
11203	/**
11204	 * Skew.
11205	 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11206	 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11207	 * @param $x (int) abscissa of the skewing center. default is current x position
11208	 * @param $y (int) ordinate of the skewing center. default is current y position
11209	 * @public
11210	 * @since 2.1.000 (2008-01-07)
11211	 * @see StartTransform(), StopTransform()
11212	 */
11213	public function Skew($angle_x, $angle_y, $x='', $y='') {
11214		if ($x === '') {
11215			$x = $this->x;
11216		}
11217		if ($y === '') {
11218			$y = $this->y;
11219		}
11220		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11221			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11222		}
11223		$x *= $this->k;
11224		$y = ($this->h - $y) * $this->k;
11225		//calculate elements of transformation matrix
11226		$tm = array();
11227		$tm[0] = 1;
11228		$tm[1] = tan(deg2rad($angle_y));
11229		$tm[2] = tan(deg2rad($angle_x));
11230		$tm[3] = 1;
11231		$tm[4] = -$tm[2] * $y;
11232		$tm[5] = -$tm[1] * $x;
11233		//skew the coordinate system
11234		$this->Transform($tm);
11235	}
11236
11237	/**
11238	 * Apply graphic transformations.
11239	 * @param $tm (array) transformation matrix
11240	 * @protected
11241	 * @since 2.1.000 (2008-01-07)
11242	 * @see StartTransform(), StopTransform()
11243	 */
11244	protected function Transform($tm) {
11245		if ($this->state != 2) {
11246			return;
11247		}
11248		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11249		// add tranformation matrix
11250		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11251		// update transformation mark
11252		if ($this->inxobj) {
11253			// we are inside an XObject template
11254			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11255				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11256				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11257			}
11258		} elseif (end($this->transfmrk[$this->page]) !== false) {
11259			$key = key($this->transfmrk[$this->page]);
11260			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11261		}
11262	}
11263
11264	// END TRANSFORMATIONS SECTION -------------------------
11265
11266	// START GRAPHIC FUNCTIONS SECTION ---------------------
11267	// The following section is based on the code provided by David Hernandez Sanz
11268
11269	/**
11270	 * 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.
11271	 * @param $width (float) The width.
11272	 * @public
11273	 * @since 1.0
11274	 * @see Line(), Rect(), Cell(), MultiCell()
11275	 */
11276	public function SetLineWidth($width) {
11277		//Set line width
11278		$this->LineWidth = $width;
11279		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11280		if ($this->state == 2) {
11281			$this->_out($this->linestyleWidth);
11282		}
11283	}
11284
11285	/**
11286	 * Returns the current the line width.
11287	 * @return int Line width
11288	 * @public
11289	 * @since 2.1.000 (2008-01-07)
11290	 * @see Line(), SetLineWidth()
11291	 */
11292	public function GetLineWidth() {
11293		return $this->LineWidth;
11294	}
11295
11296	/**
11297	 * Set line style.
11298	 * @param $style (array) Line style. Array with keys among the following:
11299	 * <ul>
11300	 *	 <li>width (float): Width of the line in user units.</li>
11301	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11302	 * butt, round, square. The difference between "square" and "butt" is that
11303	 * "square" projects a flat end past the end of the line.</li>
11304	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11305	 * bevel.</li>
11306	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11307	 * series of length values, which are the lengths of the on and off dashes.
11308	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11309	 * 1 off, 2 on, 1 off, ...</li>
11310	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11311	 * the point at which the pattern starts.</li>
11312	 *	 <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>
11313	 * </ul>
11314	 * @param $ret (boolean) if true do not send the command.
11315	 * @return string the PDF command
11316	 * @public
11317	 * @since 2.1.000 (2008-01-08)
11318	 */
11319	public function SetLineStyle($style, $ret=false) {
11320		$s = ''; // string to be returned
11321		if (!is_array($style)) {
11322			return;
11323		}
11324		if (isset($style['width'])) {
11325			$this->LineWidth = $style['width'];
11326			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11327			$s .= $this->linestyleWidth.' ';
11328		}
11329		if (isset($style['cap'])) {
11330			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11331			if (isset($ca[$style['cap']])) {
11332				$this->linestyleCap = $ca[$style['cap']].' J';
11333				$s .= $this->linestyleCap.' ';
11334			}
11335		}
11336		if (isset($style['join'])) {
11337			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11338			if (isset($ja[$style['join']])) {
11339				$this->linestyleJoin = $ja[$style['join']].' j';
11340				$s .= $this->linestyleJoin.' ';
11341			}
11342		}
11343		if (isset($style['dash'])) {
11344			$dash_string = '';
11345			if ($style['dash']) {
11346				if (preg_match('/^.+,/', $style['dash']) > 0) {
11347					$tab = explode(',', $style['dash']);
11348				} else {
11349					$tab = array($style['dash']);
11350				}
11351				$dash_string = '';
11352				foreach ($tab as $i => $v) {
11353					if ($i) {
11354						$dash_string .= ' ';
11355					}
11356					$dash_string .= sprintf('%F', $v);
11357				}
11358			}
11359			if (!isset($style['phase']) OR !$style['dash']) {
11360				$style['phase'] = 0;
11361			}
11362			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11363			$s .= $this->linestyleDash.' ';
11364		}
11365		if (isset($style['color'])) {
11366			$s .= $this->SetDrawColorArray($style['color'], true).' ';
11367		}
11368		if (!$ret AND ($this->state == 2)) {
11369			$this->_out($s);
11370		}
11371		return $s;
11372	}
11373
11374	/**
11375	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11376	 * @param $x (float) Abscissa of point.
11377	 * @param $y (float) Ordinate of point.
11378	 * @protected
11379	 * @since 2.1.000 (2008-01-08)
11380	 */
11381	protected function _outPoint($x, $y) {
11382		if ($this->state == 2) {
11383			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11384		}
11385	}
11386
11387	/**
11388	 * Append a straight line segment from the current point to the point (x, y).
11389	 * The new current point shall be (x, y).
11390	 * @param $x (float) Abscissa of end point.
11391	 * @param $y (float) Ordinate of end point.
11392	 * @protected
11393	 * @since 2.1.000 (2008-01-08)
11394	 */
11395	protected function _outLine($x, $y) {
11396		if ($this->state == 2) {
11397			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11398		}
11399	}
11400
11401	/**
11402	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11403	 * @param $x (float) Abscissa of upper-left corner.
11404	 * @param $y (float) Ordinate of upper-left corner.
11405	 * @param $w (float) Width.
11406	 * @param $h (float) Height.
11407	 * @param $op (string) options
11408	 * @protected
11409	 * @since 2.1.000 (2008-01-08)
11410	 */
11411	protected function _outRect($x, $y, $w, $h, $op) {
11412		if ($this->state == 2) {
11413			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11414		}
11415	}
11416
11417	/**
11418	 * 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.
11419	 * The new current point shall be (x3, y3).
11420	 * @param $x1 (float) Abscissa of control point 1.
11421	 * @param $y1 (float) Ordinate of control point 1.
11422	 * @param $x2 (float) Abscissa of control point 2.
11423	 * @param $y2 (float) Ordinate of control point 2.
11424	 * @param $x3 (float) Abscissa of end point.
11425	 * @param $y3 (float) Ordinate of end point.
11426	 * @protected
11427	 * @since 2.1.000 (2008-01-08)
11428	 */
11429	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11430		if ($this->state == 2) {
11431			$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)));
11432		}
11433	}
11434
11435	/**
11436	 * 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.
11437	 * The new current point shall be (x3, y3).
11438	 * @param $x2 (float) Abscissa of control point 2.
11439	 * @param $y2 (float) Ordinate of control point 2.
11440	 * @param $x3 (float) Abscissa of end point.
11441	 * @param $y3 (float) Ordinate of end point.
11442	 * @protected
11443	 * @since 4.9.019 (2010-04-26)
11444	 */
11445	protected function _outCurveV($x2, $y2, $x3, $y3) {
11446		if ($this->state == 2) {
11447			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11448		}
11449	}
11450
11451	/**
11452	 * 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.
11453	 * The new current point shall be (x3, y3).
11454	 * @param $x1 (float) Abscissa of control point 1.
11455	 * @param $y1 (float) Ordinate of control point 1.
11456	 * @param $x3 (float) Abscissa of end point.
11457	 * @param $y3 (float) Ordinate of end point.
11458	 * @protected
11459	 * @since 2.1.000 (2008-01-08)
11460	 */
11461	protected function _outCurveY($x1, $y1, $x3, $y3) {
11462		if ($this->state == 2) {
11463			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11464		}
11465	}
11466
11467	/**
11468	 * Draws a line between two points.
11469	 * @param $x1 (float) Abscissa of first point.
11470	 * @param $y1 (float) Ordinate of first point.
11471	 * @param $x2 (float) Abscissa of second point.
11472	 * @param $y2 (float) Ordinate of second point.
11473	 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11474	 * @public
11475	 * @since 1.0
11476	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11477	 */
11478	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11479		if ($this->state != 2) {
11480			return;
11481		}
11482		if (is_array($style)) {
11483			$this->SetLineStyle($style);
11484		}
11485		$this->_outPoint($x1, $y1);
11486		$this->_outLine($x2, $y2);
11487		$this->_out('S');
11488	}
11489
11490	/**
11491	 * Draws a rectangle.
11492	 * @param $x (float) Abscissa of upper-left corner.
11493	 * @param $y (float) Ordinate of upper-left corner.
11494	 * @param $w (float) Width.
11495	 * @param $h (float) Height.
11496	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11497	 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11498	 * <ul>
11499	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11500	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11501	 * </ul>
11502	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11503	 * @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).
11504	 * @public
11505	 * @since 1.0
11506	 * @see SetLineStyle()
11507	 */
11508	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11509		if ($this->state != 2) {
11510			return;
11511		}
11512		if (empty($style)) {
11513			$style = 'S';
11514		}
11515		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11516			// set background color
11517			$this->SetFillColorArray($fill_color);
11518		}
11519		if (!empty($border_style)) {
11520			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11521				//set global style for border
11522				$this->SetLineStyle($border_style['all']);
11523				$border_style = array();
11524			} else {
11525				// remove stroke operator from style
11526				$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*' );
11527				if (isset($opnostroke[$style])) {
11528					$style = $opnostroke[$style];
11529				}
11530			}
11531		}
11532		if (!empty($style)) {
11533			$op = TCPDF_STATIC::getPathPaintOperator($style);
11534			$this->_outRect($x, $y, $w, $h, $op);
11535		}
11536		if (!empty($border_style)) {
11537			$border_style2 = array();
11538			foreach ($border_style as $line => $value) {
11539				$length = strlen($line);
11540				for ($i = 0; $i < $length; ++$i) {
11541					$border_style2[$line[$i]] = $value;
11542				}
11543			}
11544			$border_style = $border_style2;
11545			if (isset($border_style['L']) AND $border_style['L']) {
11546				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11547			}
11548			if (isset($border_style['T']) AND $border_style['T']) {
11549				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11550			}
11551			if (isset($border_style['R']) AND $border_style['R']) {
11552				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11553			}
11554			if (isset($border_style['B']) AND $border_style['B']) {
11555				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11556			}
11557		}
11558	}
11559
11560	/**
11561	 * Draws a Bezier curve.
11562	 * The Bezier curve is a tangent to the line between the control points at
11563	 * either end of the curve.
11564	 * @param $x0 (float) Abscissa of start point.
11565	 * @param $y0 (float) Ordinate of start point.
11566	 * @param $x1 (float) Abscissa of control point 1.
11567	 * @param $y1 (float) Ordinate of control point 1.
11568	 * @param $x2 (float) Abscissa of control point 2.
11569	 * @param $y2 (float) Ordinate of control point 2.
11570	 * @param $x3 (float) Abscissa of end point.
11571	 * @param $y3 (float) Ordinate of end point.
11572	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11573	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11574	 * @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).
11575	 * @public
11576	 * @see SetLineStyle()
11577	 * @since 2.1.000 (2008-01-08)
11578	 */
11579	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11580		if ($this->state != 2) {
11581			return;
11582		}
11583		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11584			$this->SetFillColorArray($fill_color);
11585		}
11586		$op = TCPDF_STATIC::getPathPaintOperator($style);
11587		if ($line_style) {
11588			$this->SetLineStyle($line_style);
11589		}
11590		$this->_outPoint($x0, $y0);
11591		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11592		$this->_out($op);
11593	}
11594
11595	/**
11596	 * Draws a poly-Bezier curve.
11597	 * Each Bezier curve segment is a tangent to the line between the control points at
11598	 * either end of the curve.
11599	 * @param $x0 (float) Abscissa of start point.
11600	 * @param $y0 (float) Ordinate of start point.
11601	 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11602	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11603	 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11604	 * @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).
11605	 * @public
11606	 * @see SetLineStyle()
11607	 * @since 3.0008 (2008-05-12)
11608	 */
11609	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11610		if ($this->state != 2) {
11611			return;
11612		}
11613		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11614			$this->SetFillColorArray($fill_color);
11615		}
11616		$op = TCPDF_STATIC::getPathPaintOperator($style);
11617		if ($op == 'f') {
11618			$line_style = array();
11619		}
11620		if ($line_style) {
11621			$this->SetLineStyle($line_style);
11622		}
11623		$this->_outPoint($x0, $y0);
11624		foreach ($segments as $segment) {
11625			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11626			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11627		}
11628		$this->_out($op);
11629	}
11630
11631	/**
11632	 * Draws an ellipse.
11633	 * An ellipse is formed from n Bezier curves.
11634	 * @param $x0 (float) Abscissa of center point.
11635	 * @param $y0 (float) Ordinate of center point.
11636	 * @param $rx (float) Horizontal radius.
11637	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11638	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11639	 * @param $astart: (float) Angle start of draw line. Default value: 0.
11640	 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11641	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11642	 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11643	 * @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).
11644	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11645	 * @author Nicola Asuni
11646	 * @public
11647	 * @since 2.1.000 (2008-01-08)
11648	 */
11649	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11650		if ($this->state != 2) {
11651			return;
11652		}
11653		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11654			$ry = $rx;
11655		}
11656		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11657			$this->SetFillColorArray($fill_color);
11658		}
11659		$op = TCPDF_STATIC::getPathPaintOperator($style);
11660		if ($op == 'f') {
11661			$line_style = array();
11662		}
11663		if ($line_style) {
11664			$this->SetLineStyle($line_style);
11665		}
11666		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11667		$this->_out($op);
11668	}
11669
11670	/**
11671	 * Append an elliptical arc to the current path.
11672	 * An ellipse is formed from n Bezier curves.
11673	 * @param $xc (float) Abscissa of center point.
11674	 * @param $yc (float) Ordinate of center point.
11675	 * @param $rx (float) Horizontal radius.
11676	 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11677	 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11678	 * @param $angs: (float) Angle start of draw line. Default value: 0.
11679	 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11680	 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11681	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11682	 * @param $startpoint (boolean) if true output a starting point.
11683	 * @param $ccw (boolean) if true draws in counter-clockwise.
11684	 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11685	 * @return array bounding box coordinates (x min, y min, x max, y max)
11686	 * @author Nicola Asuni
11687	 * @protected
11688	 * @since 4.9.019 (2010-04-26)
11689	 */
11690	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11691		if (($rx <= 0) OR ($ry < 0)) {
11692			return;
11693		}
11694		$k = $this->k;
11695		if ($nc < 2) {
11696			$nc = 2;
11697		}
11698		$xmin = 2147483647;
11699		$ymin = 2147483647;
11700		$xmax = 0;
11701		$ymax = 0;
11702		if ($pie) {
11703			// center of the arc
11704			$this->_outPoint($xc, $yc);
11705		}
11706		$xang = deg2rad((float) $xang);
11707		$angs = deg2rad((float) $angs);
11708		$angf = deg2rad((float) $angf);
11709		if ($svg) {
11710			$as = $angs;
11711			$af = $angf;
11712		} else {
11713			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11714			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11715		}
11716		if ($as < 0) {
11717			$as += (2 * M_PI);
11718		}
11719		if ($af < 0) {
11720			$af += (2 * M_PI);
11721		}
11722		if ($ccw AND ($as > $af)) {
11723			// reverse rotation
11724			$as -= (2 * M_PI);
11725		} elseif (!$ccw AND ($as < $af)) {
11726			// reverse rotation
11727			$af -= (2 * M_PI);
11728		}
11729		$total_angle = ($af - $as);
11730		if ($nc < 2) {
11731			$nc = 2;
11732		}
11733		// total arcs to draw
11734		$nc *= (2 * abs($total_angle) / M_PI);
11735		$nc = round($nc) + 1;
11736		// angle of each arc
11737		$arcang = ($total_angle / $nc);
11738		// center point in PDF coordinates
11739		$x0 = $xc;
11740		$y0 = ($this->h - $yc);
11741		// starting angle
11742		$ang = $as;
11743		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11744		$cos_xang = cos($xang);
11745		$sin_xang = sin($xang);
11746		$cos_ang = cos($ang);
11747		$sin_ang = sin($ang);
11748		// first arc point
11749		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11750		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11751		// first Bezier control point
11752		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11753		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11754		if ($pie) {
11755			// line from center to arc starting point
11756			$this->_outLine($px1, $this->h - $py1);
11757		} elseif ($startpoint) {
11758			// arc starting point
11759			$this->_outPoint($px1, $this->h - $py1);
11760		}
11761		// draw arcs
11762		for ($i = 1; $i <= $nc; ++$i) {
11763			// starting angle
11764			$ang = $as + ($i * $arcang);
11765			if ($i == $nc) {
11766				$ang = $af;
11767			}
11768			$cos_ang = cos($ang);
11769			$sin_ang = sin($ang);
11770			// second arc point
11771			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11772			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11773			// second Bezier control point
11774			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11775			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11776			// draw arc
11777			$cx1 = ($px1 + $qx1);
11778			$cy1 = ($this->h - ($py1 + $qy1));
11779			$cx2 = ($px2 - $qx2);
11780			$cy2 = ($this->h - ($py2 - $qy2));
11781			$cx3 = $px2;
11782			$cy3 = ($this->h - $py2);
11783			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11784			// get bounding box coordinates
11785			$xmin = min($xmin, $cx1, $cx2, $cx3);
11786			$ymin = min($ymin, $cy1, $cy2, $cy3);
11787			$xmax = max($xmax, $cx1, $cx2, $cx3);
11788			$ymax = max($ymax, $cy1, $cy2, $cy3);
11789			// move to next point
11790			$px1 = $px2;
11791			$py1 = $py2;
11792			$qx1 = $qx2;
11793			$qy1 = $qy2;
11794		}
11795		if ($pie) {
11796			$this->_outLine($xc, $yc);
11797			// get bounding box coordinates
11798			$xmin = min($xmin, $xc);
11799			$ymin = min($ymin, $yc);
11800			$xmax = max($xmax, $xc);
11801			$ymax = max($ymax, $yc);
11802		}
11803		return array($xmin, $ymin, $xmax, $ymax);
11804	}
11805
11806	/**
11807	 * Draws a circle.
11808	 * A circle is formed from n Bezier curves.
11809	 * @param $x0 (float) Abscissa of center point.
11810	 * @param $y0 (float) Ordinate of center point.
11811	 * @param $r (float) Radius.
11812	 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11813	 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11814	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11815	 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11816	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11817	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11818	 * @public
11819	 * @since 2.1.000 (2008-01-08)
11820	 */
11821	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11822		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11823	}
11824
11825	/**
11826	 * Draws a polygonal line
11827	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11828	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11829	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11830	 * <ul>
11831	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11832	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11833	 * </ul>
11834	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11835	 * @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).
11836	 * @since 4.8.003 (2009-09-15)
11837	 * @public
11838	 */
11839	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11840		$this->Polygon($p, $style, $line_style, $fill_color, false);
11841	}
11842
11843	/**
11844	 * Draws a polygon.
11845	 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11846	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11847	 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11848	 * <ul>
11849	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11850	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11851	 * </ul>
11852	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11853	 * @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).
11854	 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11855	 * @public
11856	 * @since 2.1.000 (2008-01-08)
11857	 */
11858	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11859		if ($this->state != 2) {
11860			return;
11861		}
11862		$nc = count($p); // number of coordinates
11863		$np = $nc / 2; // number of points
11864		if ($closed) {
11865			// close polygon by adding the first 2 points at the end (one line)
11866			for ($i = 0; $i < 4; ++$i) {
11867				$p[$nc + $i] = $p[$i];
11868			}
11869			// copy style for the last added line
11870			if (isset($line_style[0])) {
11871				$line_style[$np] = $line_style[0];
11872			}
11873			$nc += 4;
11874		}
11875		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11876			$this->SetFillColorArray($fill_color);
11877		}
11878		$op = TCPDF_STATIC::getPathPaintOperator($style);
11879		if ($op == 'f') {
11880			$line_style = array();
11881		}
11882		$draw = true;
11883		if ($line_style) {
11884			if (isset($line_style['all'])) {
11885				$this->SetLineStyle($line_style['all']);
11886			} else {
11887				$draw = false;
11888				if ($op == 'B') {
11889					// draw fill
11890					$op = 'f';
11891					$this->_outPoint($p[0], $p[1]);
11892					for ($i = 2; $i < $nc; $i = $i + 2) {
11893						$this->_outLine($p[$i], $p[$i + 1]);
11894					}
11895					$this->_out($op);
11896				}
11897				// draw outline
11898				$this->_outPoint($p[0], $p[1]);
11899				for ($i = 2; $i < $nc; $i = $i + 2) {
11900					$line_num = ($i / 2) - 1;
11901					if (isset($line_style[$line_num])) {
11902						if ($line_style[$line_num] != 0) {
11903							if (is_array($line_style[$line_num])) {
11904								$this->_out('S');
11905								$this->SetLineStyle($line_style[$line_num]);
11906								$this->_outPoint($p[$i - 2], $p[$i - 1]);
11907								$this->_outLine($p[$i], $p[$i + 1]);
11908								$this->_out('S');
11909								$this->_outPoint($p[$i], $p[$i + 1]);
11910							} else {
11911								$this->_outLine($p[$i], $p[$i + 1]);
11912							}
11913						}
11914					} else {
11915						$this->_outLine($p[$i], $p[$i + 1]);
11916					}
11917				}
11918				$this->_out($op);
11919			}
11920		}
11921		if ($draw) {
11922			$this->_outPoint($p[0], $p[1]);
11923			for ($i = 2; $i < $nc; $i = $i + 2) {
11924				$this->_outLine($p[$i], $p[$i + 1]);
11925			}
11926			$this->_out($op);
11927		}
11928	}
11929
11930	/**
11931	 * Draws a regular polygon.
11932	 * @param $x0 (float) Abscissa of center point.
11933	 * @param $y0 (float) Ordinate of center point.
11934	 * @param $r: (float) Radius of inscribed circle.
11935	 * @param $ns (integer) Number of sides.
11936	 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
11937	 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
11938	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11939	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11940	 * <ul>
11941	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
11942	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
11943	 * </ul>
11944	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11945	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11946	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11947	 * <ul>
11948	 *	 <li>D or empty string: Draw (default).</li>
11949	 *	 <li>F: Fill.</li>
11950	 *	 <li>DF or FD: Draw and fill.</li>
11951	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11952	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11953	 * </ul>
11954	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11955	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
11956	 * @public
11957	 * @since 2.1.000 (2008-01-08)
11958	 */
11959	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()) {
11960		if (3 > $ns) {
11961			$ns = 3;
11962		}
11963		if ($draw_circle) {
11964			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
11965		}
11966		$p = array();
11967		for ($i = 0; $i < $ns; ++$i) {
11968			$a = $angle + ($i * 360 / $ns);
11969			$a_rad = deg2rad((float) $a);
11970			$p[] = $x0 + ($r * sin($a_rad));
11971			$p[] = $y0 + ($r * cos($a_rad));
11972		}
11973		$this->Polygon($p, $style, $line_style, $fill_color);
11974	}
11975
11976	/**
11977	 * Draws a star polygon
11978	 * @param $x0 (float) Abscissa of center point.
11979	 * @param $y0 (float) Ordinate of center point.
11980	 * @param $r (float) Radius of inscribed circle.
11981	 * @param $nv (integer) Number of vertices.
11982	 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
11983	 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11984	 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
11985	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11986	 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11987	 * <ul>
11988	 *	 <li>all: Line style of all sides. Array like for
11989	 * SetLineStyle().</li>
11990	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
11991	 * </ul>
11992	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11993	 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11994	 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11995	 * <ul>
11996	 *	 <li>D or empty string: Draw (default).</li>
11997	 *	 <li>F: Fill.</li>
11998	 *	 <li>DF or FD: Draw and fill.</li>
11999	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12000	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12001	 * </ul>
12002	 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12003	 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12004	 * @public
12005	 * @since 2.1.000 (2008-01-08)
12006	 */
12007	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()) {
12008		if ($nv < 2) {
12009			$nv = 2;
12010		}
12011		if ($draw_circle) {
12012			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12013		}
12014		$p2 = array();
12015		$visited = array();
12016		for ($i = 0; $i < $nv; ++$i) {
12017			$a = $angle + ($i * 360 / $nv);
12018			$a_rad = deg2rad((float) $a);
12019			$p2[] = $x0 + ($r * sin($a_rad));
12020			$p2[] = $y0 + ($r * cos($a_rad));
12021			$visited[] = false;
12022		}
12023		$p = array();
12024		$i = 0;
12025		do {
12026			$p[] = $p2[$i * 2];
12027			$p[] = $p2[($i * 2) + 1];
12028			$visited[$i] = true;
12029			$i += $ng;
12030			$i %= $nv;
12031		} while (!$visited[$i]);
12032		$this->Polygon($p, $style, $line_style, $fill_color);
12033	}
12034
12035	/**
12036	 * Draws a rounded rectangle.
12037	 * @param $x (float) Abscissa of upper-left corner.
12038	 * @param $y (float) Ordinate of upper-left corner.
12039	 * @param $w (float) Width.
12040	 * @param $h (float) Height.
12041	 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12042	 * @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").
12043	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12044	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12045	 * @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).
12046	 * @public
12047	 * @since 2.1.000 (2008-01-08)
12048	 */
12049	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12050		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12051	}
12052
12053	/**
12054	 * Draws a rounded rectangle.
12055	 * @param $x (float) Abscissa of upper-left corner.
12056	 * @param $y (float) Ordinate of upper-left corner.
12057	 * @param $w (float) Width.
12058	 * @param $h (float) Height.
12059	 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12060	 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12061	 * @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").
12062	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12063	 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12064	 * @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).
12065	 * @public
12066	 * @since 4.9.019 (2010-04-22)
12067	 */
12068	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12069		if ($this->state != 2) {
12070			return;
12071		}
12072		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12073			// Not rounded
12074			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12075			return;
12076		}
12077		// Rounded
12078		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12079			$this->SetFillColorArray($fill_color);
12080		}
12081		$op = TCPDF_STATIC::getPathPaintOperator($style);
12082		if ($op == 'f') {
12083			$border_style = array();
12084		}
12085		if ($border_style) {
12086			$this->SetLineStyle($border_style);
12087		}
12088		$MyArc = 4 / 3 * (sqrt(2) - 1);
12089		$this->_outPoint($x + $rx, $y);
12090		$xc = $x + $w - $rx;
12091		$yc = $y + $ry;
12092		$this->_outLine($xc, $y);
12093		if ($round_corner[0]) {
12094			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12095		} else {
12096			$this->_outLine($x + $w, $y);
12097		}
12098		$xc = $x + $w - $rx;
12099		$yc = $y + $h - $ry;
12100		$this->_outLine($x + $w, $yc);
12101		if ($round_corner[1]) {
12102			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12103		} else {
12104			$this->_outLine($x + $w, $y + $h);
12105		}
12106		$xc = $x + $rx;
12107		$yc = $y + $h - $ry;
12108		$this->_outLine($xc, $y + $h);
12109		if ($round_corner[2]) {
12110			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12111		} else {
12112			$this->_outLine($x, $y + $h);
12113		}
12114		$xc = $x + $rx;
12115		$yc = $y + $ry;
12116		$this->_outLine($x, $yc);
12117		if ($round_corner[3]) {
12118			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12119		} else {
12120			$this->_outLine($x, $y);
12121			$this->_outLine($x + $rx, $y);
12122		}
12123		$this->_out($op);
12124	}
12125
12126	/**
12127	 * Draws a grahic arrow.
12128	 * @param $x0 (float) Abscissa of first point.
12129	 * @param $y0 (float) Ordinate of first point.
12130	 * @param $x1 (float) Abscissa of second point.
12131	 * @param $y1 (float) Ordinate of second point.
12132	 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12133	 * @param $arm_size (float) length of arrowhead arms
12134	 * @param $arm_angle (int) angle between an arm and the shaft
12135	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12136	 * @since 4.6.018 (2009-07-10)
12137	 */
12138	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12139		// getting arrow direction angle
12140		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12141		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12142		if ($dir_angle < 0) {
12143			$dir_angle += (2 * M_PI);
12144		}
12145		$arm_angle = deg2rad($arm_angle);
12146		$sx1 = $x1;
12147		$sy1 = $y1;
12148		if ($head_style > 0) {
12149			// calculate the stopping point for the arrow shaft
12150			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12151			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12152		}
12153		// main arrow line / shaft
12154		$this->Line($x0, $y0, $sx1, $sy1);
12155		// left arrowhead arm tip
12156		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12157		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12158		// right arrowhead arm tip
12159		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12160		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12161		$mode = 'D';
12162		$style = array();
12163		switch ($head_style) {
12164			case 0: {
12165				// draw only arrowhead arms
12166				$mode = 'D';
12167				$style = array(1, 1, 0);
12168				break;
12169			}
12170			case 1: {
12171				// draw closed arrowhead, but no fill
12172				$mode = 'D';
12173				break;
12174			}
12175			case 2: {
12176				// closed and filled arrowhead
12177				$mode = 'DF';
12178				break;
12179			}
12180			case 3: {
12181				// filled arrowhead
12182				$mode = 'F';
12183				break;
12184			}
12185		}
12186		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12187	}
12188
12189	// END GRAPHIC FUNCTIONS SECTION -----------------------
12190
12191	/**
12192	 * Add a Named Destination.
12193	 * NOTE: destination names are unique, so only last entry will be saved.
12194	 * @param $name (string) Destination name.
12195	 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12196	 * @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.
12197	 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12198	 * @return (string) Stripped named destination identifier or false in case of error.
12199	 * @public
12200	 * @author Christian Deligant, Nicola Asuni
12201	 * @since 5.9.097 (2011-06-23)
12202	 */
12203	public function setDestination($name, $y=-1, $page='', $x=-1) {
12204		// remove unsupported characters
12205		$name = TCPDF_STATIC::encodeNameObject($name);
12206		if (TCPDF_STATIC::empty_string($name)) {
12207			return false;
12208		}
12209		if ($y == -1) {
12210			$y = $this->GetY();
12211		} elseif ($y < 0) {
12212			$y = 0;
12213		} elseif ($y > $this->h) {
12214			$y = $this->h;
12215		}
12216		if ($x == -1) {
12217			$x = $this->GetX();
12218		} elseif ($x < 0) {
12219			$x = 0;
12220		} elseif ($x > $this->w) {
12221			$x = $this->w;
12222		}
12223		$fixed = false;
12224		if (!empty($page) AND ($page[0] == '*')) {
12225			$page = intval(substr($page, 1));
12226			// this page number will not be changed when moving/add/deleting pages
12227			$fixed = true;
12228		}
12229		if (empty($page)) {
12230			$page = $this->PageNo();
12231			if (empty($page)) {
12232				return;
12233			}
12234		}
12235		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12236		return $name;
12237	}
12238
12239	/**
12240	 * Return the Named Destination array.
12241	 * @return (array) Named Destination array.
12242	 * @public
12243	 * @author Nicola Asuni
12244	 * @since 5.9.097 (2011-06-23)
12245	 */
12246	public function getDestination() {
12247		return $this->dests;
12248	}
12249
12250	/**
12251	 * Insert Named Destinations.
12252	 * @protected
12253	 * @author Johannes G\FCntert, Nicola Asuni
12254	 * @since 5.9.098 (2011-06-23)
12255	 */
12256	protected function _putdests() {
12257		if (empty($this->dests)) {
12258			return;
12259		}
12260		$this->n_dests = $this->_newobj();
12261		$out = ' <<';
12262		foreach($this->dests as $name => $o) {
12263			$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)));
12264		}
12265		$out .= ' >>';
12266		$out .= "\n".'endobj';
12267		$this->_out($out);
12268	}
12269
12270	/**
12271	 * Adds a bookmark - alias for Bookmark().
12272	 * @param $txt (string) Bookmark description.
12273	 * @param $level (int) Bookmark level (minimum value is 0).
12274	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12275	 * @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.
12276	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12277	 * @param $color (array) RGB color array (values from 0 to 255).
12278	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12279	 * @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).
12280	 * @public
12281	 */
12282	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12283		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12284	}
12285
12286	/**
12287	 * Adds a bookmark.
12288	 * @param $txt (string) Bookmark description.
12289	 * @param $level (int) Bookmark level (minimum value is 0).
12290	 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12291	 * @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.
12292	 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12293	 * @param $color (array) RGB color array (values from 0 to 255).
12294	 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12295	 * @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).
12296	 * @public
12297	 * @since 2.1.002 (2008-02-12)
12298	 */
12299	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12300		if ($level < 0) {
12301			$level = 0;
12302		}
12303		if (isset($this->outlines[0])) {
12304			$lastoutline = end($this->outlines);
12305			$maxlevel = $lastoutline['l'] + 1;
12306		} else {
12307			$maxlevel = 0;
12308		}
12309		if ($level > $maxlevel) {
12310			$level = $maxlevel;
12311		}
12312		if ($y == -1) {
12313			$y = $this->GetY();
12314		} elseif ($y < 0) {
12315			$y = 0;
12316		} elseif ($y > $this->h) {
12317			$y = $this->h;
12318		}
12319		if ($x == -1) {
12320			$x = $this->GetX();
12321		} elseif ($x < 0) {
12322			$x = 0;
12323		} elseif ($x > $this->w) {
12324			$x = $this->w;
12325		}
12326		$fixed = false;
12327		if (!empty($page) AND ($page[0] == '*')) {
12328			$page = intval(substr($page, 1));
12329			// this page number will not be changed when moving/add/deleting pages
12330			$fixed = true;
12331		}
12332		if (empty($page)) {
12333			$page = $this->PageNo();
12334			if (empty($page)) {
12335				return;
12336			}
12337		}
12338		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12339	}
12340
12341	/**
12342	 * Sort bookmarks for page and key.
12343	 * @protected
12344	 * @since 5.9.119 (2011-09-19)
12345	 */
12346	protected function sortBookmarks() {
12347		// get sorting columns
12348		$outline_p = array();
12349		$outline_y = array();
12350		foreach ($this->outlines as $key => $row) {
12351			$outline_p[$key] = $row['p'];
12352			$outline_k[$key] = $key;
12353		}
12354		// sort outlines by page and original position
12355		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12356	}
12357
12358	/**
12359	 * Create a bookmark PDF string.
12360	 * @protected
12361	 * @author Olivier Plathey, Nicola Asuni
12362	 * @since 2.1.002 (2008-02-12)
12363	 */
12364	protected function _putbookmarks() {
12365		$nb = count($this->outlines);
12366		if ($nb == 0) {
12367			return;
12368		}
12369		// sort bookmarks
12370		$this->sortBookmarks();
12371		$lru = array();
12372		$level = 0;
12373		foreach ($this->outlines as $i => $o) {
12374			if ($o['l'] > 0) {
12375				$parent = $lru[($o['l'] - 1)];
12376				//Set parent and last pointers
12377				$this->outlines[$i]['parent'] = $parent;
12378				$this->outlines[$parent]['last'] = $i;
12379				if ($o['l'] > $level) {
12380					//Level increasing: set first pointer
12381					$this->outlines[$parent]['first'] = $i;
12382				}
12383			} else {
12384				$this->outlines[$i]['parent'] = $nb;
12385			}
12386			if (($o['l'] <= $level) AND ($i > 0)) {
12387				//Set prev and next pointers
12388				$prev = $lru[$o['l']];
12389				$this->outlines[$prev]['next'] = $i;
12390				$this->outlines[$i]['prev'] = $prev;
12391			}
12392			$lru[$o['l']] = $i;
12393			$level = $o['l'];
12394		}
12395		//Outline items
12396		$n = $this->n + 1;
12397		$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';
12398		foreach ($this->outlines as $i => $o) {
12399			$oid = $this->_newobj();
12400			// covert HTML title to string
12401			$title = preg_replace($nltags, "\n", $o['t']);
12402			$title = preg_replace("/[\r]+/si", '', $title);
12403			$title = preg_replace("/[\n]+/si", "\n", $title);
12404			$title = strip_tags($title);
12405			$title = $this->stringTrim($title);
12406			$out = '<</Title '.$this->_textstring($title, $oid);
12407			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12408			if (isset($o['prev'])) {
12409				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12410			}
12411			if (isset($o['next'])) {
12412				$out .= ' /Next '.($n + $o['next']).' 0 R';
12413			}
12414			if (isset($o['first'])) {
12415				$out .= ' /First '.($n + $o['first']).' 0 R';
12416			}
12417			if (isset($o['last'])) {
12418				$out .= ' /Last '.($n + $o['last']).' 0 R';
12419			}
12420			if (isset($o['u']) AND !empty($o['u'])) {
12421				// link
12422				if (is_string($o['u'])) {
12423					if ($o['u'][0] == '#') {
12424						// internal destination
12425						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12426					} elseif ($o['u'][0] == '%') {
12427						// embedded PDF file
12428						$filename = basename(substr($o['u'], 1));
12429						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12430					} elseif ($o['u'][0] == '*') {
12431						// embedded generic file
12432						$filename = basename(substr($o['u'], 1));
12433						$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});';
12434						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12435					} else {
12436						// external URI link
12437						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12438					}
12439				} elseif (isset($this->links[$o['u']])) {
12440					// internal link ID
12441					$l = $this->links[$o['u']];
12442					if (isset($this->page_obj_id[($l['p'])])) {
12443						$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)));
12444					}
12445				}
12446			} elseif (isset($this->page_obj_id[($o['p'])])) {
12447				// link to a page
12448				$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)));
12449			}
12450			// set font style
12451			$style = 0;
12452			if (!empty($o['s'])) {
12453				// bold
12454				if (strpos($o['s'], 'B') !== false) {
12455					$style |= 2;
12456				}
12457				// oblique
12458				if (strpos($o['s'], 'I') !== false) {
12459					$style |= 1;
12460				}
12461			}
12462			$out .= sprintf(' /F %d', $style);
12463			// set bookmark color
12464			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12465				$color = array_values($o['c']);
12466				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12467			} else {
12468				// black
12469				$out .= ' /C [0.0 0.0 0.0]';
12470			}
12471			$out .= ' /Count 0'; // normally closed item
12472			$out .= ' >>';
12473			$out .= "\n".'endobj';
12474			$this->_out($out);
12475		}
12476		//Outline root
12477		$this->OutlineRoot = $this->_newobj();
12478		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12479	}
12480
12481	// --- JAVASCRIPT ------------------------------------------------------
12482
12483	/**
12484	 * Adds a javascript
12485	 * @param $script (string) Javascript code
12486	 * @public
12487	 * @author Johannes G\FCntert, Nicola Asuni
12488	 * @since 2.1.002 (2008-02-12)
12489	 */
12490	public function IncludeJS($script) {
12491		$this->javascript .= $script;
12492	}
12493
12494	/**
12495	 * Adds a javascript object and return object ID
12496	 * @param $script (string) Javascript code
12497	 * @param $onload (boolean) if true executes this object when opening the document
12498	 * @return int internal object ID
12499	 * @public
12500	 * @author Nicola Asuni
12501	 * @since 4.8.000 (2009-09-07)
12502	 */
12503	public function addJavascriptObject($script, $onload=false) {
12504		if ($this->pdfa_mode) {
12505			// javascript is not allowed in PDF/A mode
12506			return false;
12507		}
12508		++$this->n;
12509		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12510		return $this->n;
12511	}
12512
12513	/**
12514	 * Create a javascript PDF string.
12515	 * @protected
12516	 * @author Johannes G\FCntert, Nicola Asuni
12517	 * @since 2.1.002 (2008-02-12)
12518	 */
12519	protected function _putjavascript() {
12520		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12521			return;
12522		}
12523		if (strpos($this->javascript, 'this.addField') > 0) {
12524			if (!$this->ur['enabled']) {
12525				//$this->setUserRights();
12526			}
12527			// the following two lines are used to avoid form fields duplication after saving
12528			// The addField method only works when releasing user rights (UR3)
12529			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12530			$jsb = "getField('tcpdfdocsaved').value='saved';";
12531			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12532		}
12533		// name tree for javascript
12534		$this->n_js = '<< /Names [';
12535		if (!empty($this->javascript)) {
12536			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12537		}
12538		if (!empty($this->js_objects)) {
12539			foreach ($this->js_objects as $key => $val) {
12540				if ($val['onload']) {
12541					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12542				}
12543			}
12544		}
12545		$this->n_js .= ' ] >>';
12546		// default Javascript object
12547		if (!empty($this->javascript)) {
12548			$obj_id = $this->_newobj();
12549			$out = '<< /S /JavaScript';
12550			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12551			$out .= ' >>';
12552			$out .= "\n".'endobj';
12553			$this->_out($out);
12554		}
12555		// additional Javascript objects
12556		if (!empty($this->js_objects)) {
12557			foreach ($this->js_objects as $key => $val) {
12558				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12559				$this->_out($out);
12560			}
12561		}
12562	}
12563
12564	/**
12565	 * Adds a javascript form field.
12566	 * @param $type (string) field type
12567	 * @param $name (string) field name
12568	 * @param $x (int) horizontal position
12569	 * @param $y (int) vertical position
12570	 * @param $w (int) width
12571	 * @param $h (int) height
12572	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12573	 * @protected
12574	 * @author Denis Van Nuffelen, Nicola Asuni
12575	 * @since 2.1.002 (2008-02-12)
12576	 */
12577	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12578		if ($this->rtl) {
12579			$x = $x - $w;
12580		}
12581		// the followind avoid fields duplication after saving the document
12582		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12583		$k = $this->k;
12584		$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";
12585		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12586		foreach($prop as $key => $val) {
12587			if (strcmp(substr($key, -5), 'Color') == 0) {
12588				$val = TCPDF_COLORS::_JScolor($val);
12589			} else {
12590				$val = "'".$val."'";
12591			}
12592			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12593		}
12594		if ($this->rtl) {
12595			$this->x -= $w;
12596		} else {
12597			$this->x += $w;
12598		}
12599		$this->javascript .= '}';
12600	}
12601
12602	// --- FORM FIELDS -----------------------------------------------------
12603
12604
12605
12606	/**
12607	 * Set default properties for form fields.
12608	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12609	 * @public
12610	 * @author Nicola Asuni
12611	 * @since 4.8.000 (2009-09-06)
12612	 */
12613	public function setFormDefaultProp($prop=array()) {
12614		$this->default_form_prop = $prop;
12615	}
12616
12617	/**
12618	 * Return the default properties for form fields.
12619	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12620	 * @public
12621	 * @author Nicola Asuni
12622	 * @since 4.8.000 (2009-09-06)
12623	 */
12624	public function getFormDefaultProp() {
12625		return $this->default_form_prop;
12626	}
12627
12628	/**
12629	 * Creates a text field
12630	 * @param $name (string) field name
12631	 * @param $w (float) Width of the rectangle
12632	 * @param $h (float) Height of the rectangle
12633	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12634	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12635	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12636	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12637	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12638	 * @public
12639	 * @author Nicola Asuni
12640	 * @since 4.8.000 (2009-09-07)
12641	 */
12642	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12643		if ($x === '') {
12644			$x = $this->x;
12645		}
12646		if ($y === '') {
12647			$y = $this->y;
12648		}
12649		// check page for no-write regions and adapt page margins if necessary
12650		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12651		if ($js) {
12652			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12653			return;
12654		}
12655		// get default style
12656		$prop = array_merge($this->getFormDefaultProp(), $prop);
12657		// get annotation data
12658		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12659		// set default appearance stream
12660		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12661		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12662		$popt['da'] = $fontstyle;
12663		// build appearance stream
12664		$popt['ap'] = array();
12665		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12666		$text = '';
12667		if (isset($prop['value']) AND !empty($prop['value'])) {
12668			$text = $prop['value'];
12669		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12670			$text = $opt['v'];
12671		}
12672		$tmpid = $this->startTemplate($w, $h, false);
12673		$align = '';
12674		if (isset($popt['q'])) {
12675			switch ($popt['q']) {
12676				case 0: {
12677					$align = 'L';
12678					break;
12679				}
12680				case 1: {
12681					$align = 'C';
12682					break;
12683				}
12684				case 2: {
12685					$align = 'R';
12686					break;
12687				}
12688				default: {
12689					$align = '';
12690					break;
12691				}
12692			}
12693		}
12694		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12695		$this->endTemplate();
12696		--$this->n;
12697		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12698		unset($this->xobjects[$tmpid]);
12699		$popt['ap']['n'] .= 'Q EMC';
12700		// merge options
12701		$opt = array_merge($popt, $opt);
12702		// remove some conflicting options
12703		unset($opt['bs']);
12704		// set remaining annotation data
12705		$opt['Subtype'] = 'Widget';
12706		$opt['ft'] = 'Tx';
12707		$opt['t'] = $name;
12708		// Additional annotation's parameters (check _putannotsobj() method):
12709		//$opt['f']
12710		//$opt['as']
12711		//$opt['bs']
12712		//$opt['be']
12713		//$opt['c']
12714		//$opt['border']
12715		//$opt['h']
12716		//$opt['mk'];
12717		//$opt['mk']['r']
12718		//$opt['mk']['bc'];
12719		//$opt['mk']['bg'];
12720		unset($opt['mk']['ca']);
12721		unset($opt['mk']['rc']);
12722		unset($opt['mk']['ac']);
12723		unset($opt['mk']['i']);
12724		unset($opt['mk']['ri']);
12725		unset($opt['mk']['ix']);
12726		unset($opt['mk']['if']);
12727		//$opt['mk']['if']['sw'];
12728		//$opt['mk']['if']['s'];
12729		//$opt['mk']['if']['a'];
12730		//$opt['mk']['if']['fb'];
12731		unset($opt['mk']['tp']);
12732		//$opt['tu']
12733		//$opt['tm']
12734		//$opt['ff']
12735		//$opt['v']
12736		//$opt['dv']
12737		//$opt['a']
12738		//$opt['aa']
12739		//$opt['q']
12740		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12741		if ($this->rtl) {
12742			$this->x -= $w;
12743		} else {
12744			$this->x += $w;
12745		}
12746	}
12747
12748	/**
12749	 * Creates a RadioButton field.
12750	 * @param $name (string) Field name.
12751	 * @param $w (int) Width of the radio button.
12752	 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12753	 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12754	 * @param $onvalue (string) Value to be returned if selected.
12755	 * @param $checked (boolean) Define the initial state.
12756	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12757	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12758	 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12759	 * @public
12760	 * @author Nicola Asuni
12761	 * @since 4.8.000 (2009-09-07)
12762	 */
12763	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12764		if ($x === '') {
12765			$x = $this->x;
12766		}
12767		if ($y === '') {
12768			$y = $this->y;
12769		}
12770		// check page for no-write regions and adapt page margins if necessary
12771		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12772		if ($js) {
12773			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12774			return;
12775		}
12776		if (TCPDF_STATIC::empty_string($onvalue)) {
12777			$onvalue = 'On';
12778		}
12779		if ($checked) {
12780			$defval = $onvalue;
12781		} else {
12782			$defval = 'Off';
12783		}
12784		// set font
12785		$font = 'zapfdingbats';
12786		if ($this->pdfa_mode) {
12787			// all fonts must be embedded
12788			$font = 'pdfa'.$font;
12789		}
12790		$this->AddFont($font);
12791		$tmpfont = $this->getFontBuffer($font);
12792		// set data for parent group
12793		if (!isset($this->radiobutton_groups[$this->page])) {
12794			$this->radiobutton_groups[$this->page] = array();
12795		}
12796		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12797			$this->radiobutton_groups[$this->page][$name] = array();
12798			++$this->n;
12799			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12800			$this->radio_groups[] = $this->n;
12801		}
12802		$kid = ($this->n + 1);
12803		// save object ID to be added on Kids entry on parent object
12804		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12805		// get default style
12806		$prop = array_merge($this->getFormDefaultProp(), $prop);
12807		$prop['NoToggleToOff'] = 'true';
12808		$prop['Radio'] = 'true';
12809		$prop['borderStyle'] = 'inset';
12810		// get annotation data
12811		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12812		// set additional default options
12813		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12814		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12815		$popt['da'] = $fontstyle;
12816		// build appearance stream
12817		$popt['ap'] = array();
12818		$popt['ap']['n'] = array();
12819		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12820		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12821		$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);
12822		$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);
12823		if (!isset($popt['mk'])) {
12824			$popt['mk'] = array();
12825		}
12826		$popt['mk']['ca'] = '(l)';
12827		// merge options
12828		$opt = array_merge($popt, $opt);
12829		// set remaining annotation data
12830		$opt['Subtype'] = 'Widget';
12831		$opt['ft'] = 'Btn';
12832		if ($checked) {
12833			$opt['v'] = array('/'.$onvalue);
12834			$opt['as'] = $onvalue;
12835		} else {
12836			$opt['as'] = 'Off';
12837		}
12838		// store readonly flag
12839		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12840			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12841		}
12842		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12843		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12844		if ($this->rtl) {
12845			$this->x -= $w;
12846		} else {
12847			$this->x += $w;
12848		}
12849	}
12850
12851	/**
12852	 * Creates a List-box field
12853	 * @param $name (string) field name
12854	 * @param $w (int) width
12855	 * @param $h (int) height
12856	 * @param $values (array) array containing the list of values.
12857	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12858	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12859	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12860	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12861	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12862	 * @public
12863	 * @author Nicola Asuni
12864	 * @since 4.8.000 (2009-09-07)
12865	 */
12866	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12867		if ($x === '') {
12868			$x = $this->x;
12869		}
12870		if ($y === '') {
12871			$y = $this->y;
12872		}
12873		// check page for no-write regions and adapt page margins if necessary
12874		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12875		if ($js) {
12876			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12877			$s = '';
12878			foreach ($values as $value) {
12879				if (is_array($value)) {
12880					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12881				} else {
12882					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12883				}
12884			}
12885			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12886			return;
12887		}
12888		// get default style
12889		$prop = array_merge($this->getFormDefaultProp(), $prop);
12890		// get annotation data
12891		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12892		// set additional default values
12893		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12894		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12895		$popt['da'] = $fontstyle;
12896		// build appearance stream
12897		$popt['ap'] = array();
12898		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12899		$text = '';
12900		foreach($values as $item) {
12901			if (is_array($item)) {
12902				$text .= $item[1]."\n";
12903			} else {
12904				$text .= $item."\n";
12905			}
12906		}
12907		$tmpid = $this->startTemplate($w, $h, false);
12908		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12909		$this->endTemplate();
12910		--$this->n;
12911		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12912		unset($this->xobjects[$tmpid]);
12913		$popt['ap']['n'] .= 'Q EMC';
12914		// merge options
12915		$opt = array_merge($popt, $opt);
12916		// set remaining annotation data
12917		$opt['Subtype'] = 'Widget';
12918		$opt['ft'] = 'Ch';
12919		$opt['t'] = $name;
12920		$opt['opt'] = $values;
12921		unset($opt['mk']['ca']);
12922		unset($opt['mk']['rc']);
12923		unset($opt['mk']['ac']);
12924		unset($opt['mk']['i']);
12925		unset($opt['mk']['ri']);
12926		unset($opt['mk']['ix']);
12927		unset($opt['mk']['if']);
12928		unset($opt['mk']['tp']);
12929		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12930		if ($this->rtl) {
12931			$this->x -= $w;
12932		} else {
12933			$this->x += $w;
12934		}
12935	}
12936
12937	/**
12938	 * Creates a Combo-box field
12939	 * @param $name (string) field name
12940	 * @param $w (int) width
12941	 * @param $h (int) height
12942	 * @param $values (array) array containing the list of values.
12943	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12944	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12945	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12946	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12947	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12948	 * @public
12949	 * @author Nicola Asuni
12950	 * @since 4.8.000 (2009-09-07)
12951	 */
12952	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12953		if ($x === '') {
12954			$x = $this->x;
12955		}
12956		if ($y === '') {
12957			$y = $this->y;
12958		}
12959		// check page for no-write regions and adapt page margins if necessary
12960		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12961		if ($js) {
12962			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
12963			$s = '';
12964			foreach ($values as $value) {
12965				if (is_array($value)) {
12966					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12967				} else {
12968					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12969				}
12970			}
12971			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12972			return;
12973		}
12974		// get default style
12975		$prop = array_merge($this->getFormDefaultProp(), $prop);
12976		$prop['Combo'] = true;
12977		// get annotation data
12978		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12979		// set additional default options
12980		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12981		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12982		$popt['da'] = $fontstyle;
12983		// build appearance stream
12984		$popt['ap'] = array();
12985		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12986		$text = '';
12987		foreach($values as $item) {
12988			if (is_array($item)) {
12989				$text .= $item[1]."\n";
12990			} else {
12991				$text .= $item."\n";
12992			}
12993		}
12994		$tmpid = $this->startTemplate($w, $h, false);
12995		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12996		$this->endTemplate();
12997		--$this->n;
12998		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12999		unset($this->xobjects[$tmpid]);
13000		$popt['ap']['n'] .= 'Q EMC';
13001		// merge options
13002		$opt = array_merge($popt, $opt);
13003		// set remaining annotation data
13004		$opt['Subtype'] = 'Widget';
13005		$opt['ft'] = 'Ch';
13006		$opt['t'] = $name;
13007		$opt['opt'] = $values;
13008		unset($opt['mk']['ca']);
13009		unset($opt['mk']['rc']);
13010		unset($opt['mk']['ac']);
13011		unset($opt['mk']['i']);
13012		unset($opt['mk']['ri']);
13013		unset($opt['mk']['ix']);
13014		unset($opt['mk']['if']);
13015		unset($opt['mk']['tp']);
13016		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13017		if ($this->rtl) {
13018			$this->x -= $w;
13019		} else {
13020			$this->x += $w;
13021		}
13022	}
13023
13024	/**
13025	 * Creates a CheckBox field
13026	 * @param $name (string) field name
13027	 * @param $w (int) width
13028	 * @param $checked (boolean) define the initial state.
13029	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13030	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13031	 * @param $onvalue (string) value to be returned if selected.
13032	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13033	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13034	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13035	 * @public
13036	 * @author Nicola Asuni
13037	 * @since 4.8.000 (2009-09-07)
13038	 */
13039	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13040		if ($x === '') {
13041			$x = $this->x;
13042		}
13043		if ($y === '') {
13044			$y = $this->y;
13045		}
13046		// check page for no-write regions and adapt page margins if necessary
13047		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13048		if ($js) {
13049			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13050			return;
13051		}
13052		if (!isset($prop['value'])) {
13053			$prop['value'] = array('Yes');
13054		}
13055		// get default style
13056		$prop = array_merge($this->getFormDefaultProp(), $prop);
13057		$prop['borderStyle'] = 'inset';
13058		// get annotation data
13059		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13060		// set additional default options
13061		$font = 'zapfdingbats';
13062		if ($this->pdfa_mode) {
13063			// all fonts must be embedded
13064			$font = 'pdfa'.$font;
13065		}
13066		$this->AddFont($font);
13067		$tmpfont = $this->getFontBuffer($font);
13068		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13069		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13070		$popt['da'] = $fontstyle;
13071		// build appearance stream
13072		$popt['ap'] = array();
13073		$popt['ap']['n'] = array();
13074		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13075		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13076		$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);
13077		$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);
13078		// merge options
13079		$opt = array_merge($popt, $opt);
13080		// set remaining annotation data
13081		$opt['Subtype'] = 'Widget';
13082		$opt['ft'] = 'Btn';
13083		$opt['t'] = $name;
13084		if (TCPDF_STATIC::empty_string($onvalue)) {
13085			$onvalue = 'Yes';
13086		}
13087		$opt['opt'] = array($onvalue);
13088		if ($checked) {
13089			$opt['v'] = array('/Yes');
13090			$opt['as'] = 'Yes';
13091		} else {
13092			$opt['v'] = array('/Off');
13093			$opt['as'] = 'Off';
13094		}
13095		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13096		if ($this->rtl) {
13097			$this->x -= $w;
13098		} else {
13099			$this->x += $w;
13100		}
13101	}
13102
13103	/**
13104	 * Creates a button field
13105	 * @param $name (string) field name
13106	 * @param $w (int) width
13107	 * @param $h (int) height
13108	 * @param $caption (string) caption.
13109	 * @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.
13110	 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13111	 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13112	 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13113	 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13114	 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13115	 * @public
13116	 * @author Nicola Asuni
13117	 * @since 4.8.000 (2009-09-07)
13118	 */
13119	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13120		if ($x === '') {
13121			$x = $this->x;
13122		}
13123		if ($y === '') {
13124			$y = $this->y;
13125		}
13126		// check page for no-write regions and adapt page margins if necessary
13127		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13128		if ($js) {
13129			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13130			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13131			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13132			$this->javascript .= 'f'.$name.".highlight='push';\n";
13133			$this->javascript .= 'f'.$name.".print=false;\n";
13134			return;
13135		}
13136		// get default style
13137		$prop = array_merge($this->getFormDefaultProp(), $prop);
13138		$prop['Pushbutton'] = 'true';
13139		$prop['highlight'] = 'push';
13140		$prop['display'] = 'display.noPrint';
13141		// get annotation data
13142		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13143		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13144		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13145		$popt['da'] = $fontstyle;
13146		// build appearance stream
13147		$popt['ap'] = array();
13148		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13149		$tmpid = $this->startTemplate($w, $h, false);
13150		$bw = (2 / $this->k); // border width
13151		$border = array(
13152			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13153			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13154			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13155			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13156		$this->SetFillColor(204);
13157		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13158		$this->endTemplate();
13159		--$this->n;
13160		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13161		unset($this->xobjects[$tmpid]);
13162		$popt['ap']['n'] .= 'Q EMC';
13163		// set additional default options
13164		if (!isset($popt['mk'])) {
13165			$popt['mk'] = array();
13166		}
13167		$ann_obj_id = ($this->n + 1);
13168		if (!empty($action) AND !is_array($action)) {
13169			$ann_obj_id = ($this->n + 2);
13170		}
13171		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13172		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13173		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13174		// merge options
13175		$opt = array_merge($popt, $opt);
13176		// set remaining annotation data
13177		$opt['Subtype'] = 'Widget';
13178		$opt['ft'] = 'Btn';
13179		$opt['t'] = $caption;
13180		$opt['v'] = $name;
13181		if (!empty($action)) {
13182			if (is_array($action)) {
13183				// form action options as on section 12.7.5 of PDF32000_2008.
13184				$opt['aa'] = '/D <<';
13185				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13186				foreach ($action AS $key => $val) {
13187					if (($key == 'S') AND in_array($val, $bmode)) {
13188						$opt['aa'] .= ' /S /'.$val;
13189					} elseif (($key == 'F') AND (!empty($val))) {
13190						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13191					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13192						$opt['aa'] .= ' /Fields [';
13193						foreach ($val AS $field) {
13194							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13195						}
13196						$opt['aa'] .= ']';
13197					} elseif (($key == 'Flags')) {
13198						$ff = 0;
13199						if (is_array($val)) {
13200							foreach ($val AS $flag) {
13201								switch ($flag) {
13202									case 'Include/Exclude': {
13203										$ff += 1 << 0;
13204										break;
13205									}
13206									case 'IncludeNoValueFields': {
13207										$ff += 1 << 1;
13208										break;
13209									}
13210									case 'ExportFormat': {
13211										$ff += 1 << 2;
13212										break;
13213									}
13214									case 'GetMethod': {
13215										$ff += 1 << 3;
13216										break;
13217									}
13218									case 'SubmitCoordinates': {
13219										$ff += 1 << 4;
13220										break;
13221									}
13222									case 'XFDF': {
13223										$ff += 1 << 5;
13224										break;
13225									}
13226									case 'IncludeAppendSaves': {
13227										$ff += 1 << 6;
13228										break;
13229									}
13230									case 'IncludeAnnotations': {
13231										$ff += 1 << 7;
13232										break;
13233									}
13234									case 'SubmitPDF': {
13235										$ff += 1 << 8;
13236										break;
13237									}
13238									case 'CanonicalFormat': {
13239										$ff += 1 << 9;
13240										break;
13241									}
13242									case 'ExclNonUserAnnots': {
13243										$ff += 1 << 10;
13244										break;
13245									}
13246									case 'ExclFKey': {
13247										$ff += 1 << 11;
13248										break;
13249									}
13250									case 'EmbedForm': {
13251										$ff += 1 << 13;
13252										break;
13253									}
13254								}
13255							}
13256						} else {
13257							$ff = intval($val);
13258						}
13259						$opt['aa'] .= ' /Flags '.$ff;
13260					}
13261				}
13262				$opt['aa'] .= ' >>';
13263			} else {
13264				// Javascript action or raw action command
13265				$js_obj_id = $this->addJavascriptObject($action);
13266				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13267			}
13268		}
13269		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13270		if ($this->rtl) {
13271			$this->x -= $w;
13272		} else {
13273			$this->x += $w;
13274		}
13275	}
13276
13277	// --- END FORMS FIELDS ------------------------------------------------
13278
13279	/**
13280	 * Add certification signature (DocMDP or UR3)
13281	 * You can set only one signature type
13282	 * @protected
13283	 * @author Nicola Asuni
13284	 * @since 4.6.008 (2009-05-07)
13285	 */
13286	protected function _putsignature() {
13287		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13288			return;
13289		}
13290		$sigobjid = ($this->sig_obj_id + 1);
13291		$out = $this->_getobj($sigobjid)."\n";
13292		$out .= '<< /Type /Sig';
13293		$out .= ' /Filter /Adobe.PPKLite';
13294		$out .= ' /SubFilter /adbe.pkcs7.detached';
13295		$out .= ' '.TCPDF_STATIC::$byterange_string;
13296		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13297		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13298			$out .= ' /Reference ['; // array of signature reference dictionaries
13299			$out .= ' << /Type /SigRef';
13300			if ($this->signature_data['cert_type'] > 0) {
13301				$out .= ' /TransformMethod /DocMDP';
13302				$out .= ' /TransformParams <<';
13303				$out .= ' /Type /TransformParams';
13304				$out .= ' /P '.$this->signature_data['cert_type'];
13305				$out .= ' /V /1.2';
13306			} else {
13307				$out .= ' /TransformMethod /UR3';
13308				$out .= ' /TransformParams <<';
13309				$out .= ' /Type /TransformParams';
13310				$out .= ' /V /2.2';
13311				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13312					$out .= ' /Document['.$this->ur['document'].']';
13313				}
13314				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13315					$out .= ' /Form['.$this->ur['form'].']';
13316				}
13317				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13318					$out .= ' /Signature['.$this->ur['signature'].']';
13319				}
13320				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13321					$out .= ' /Annots['.$this->ur['annots'].']';
13322				}
13323				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13324					$out .= ' /EF['.$this->ur['ef'].']';
13325				}
13326				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13327					$out .= ' /FormEX['.$this->ur['formex'].']';
13328				}
13329			}
13330			$out .= ' >>'; // close TransformParams
13331			// optional digest data (values must be calculated and replaced later)
13332			//$out .= ' /Data ********** 0 R';
13333			//$out .= ' /DigestMethod/MD5';
13334			//$out .= ' /DigestLocation[********** 34]';
13335			//$out .= ' /DigestValue<********************************>';
13336			$out .= ' >>';
13337			$out .= ' ]'; // end of reference
13338		}
13339		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13340			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13341		}
13342		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13343			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13344		}
13345		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13346			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13347		}
13348		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13349			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13350		}
13351		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13352		$out .= ' >>';
13353		$out .= "\n".'endobj';
13354		$this->_out($out);
13355	}
13356
13357	/**
13358	 * Set User's Rights for PDF Reader
13359	 * WARNING: This is experimental and currently do not work.
13360	 * Check the PDF Reference 8.7.1 Transform Methods,
13361	 * Table 8.105 Entries in the UR transform parameters dictionary
13362	 * @param $enable (boolean) if true enable user's rights on PDF reader
13363	 * @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.
13364	 * @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.
13365	 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13366	 * @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.
13367	 * @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
13368	 Names specifying additional embedded-files-related usage rights for the document.
13369	 * @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.
13370	 * @public
13371	 * @author Nicola Asuni
13372	 * @since 2.9.000 (2008-03-26)
13373	 */
13374	public function setUserRights(
13375			$enable=true,
13376			$document='/FullSave',
13377			$annots='/Create/Delete/Modify/Copy/Import/Export',
13378			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13379			$signature='/Modify',
13380			$ef='/Create/Delete/Modify/Import',
13381			$formex='') {
13382		$this->ur['enabled'] = $enable;
13383		$this->ur['document'] = $document;
13384		$this->ur['annots'] = $annots;
13385		$this->ur['form'] = $form;
13386		$this->ur['signature'] = $signature;
13387		$this->ur['ef'] = $ef;
13388		$this->ur['formex'] = $formex;
13389		if (!$this->sign) {
13390			$this->setSignature('', '', '', '', 0, array());
13391		}
13392	}
13393
13394	/**
13395	 * Enable document signature (requires the OpenSSL Library).
13396	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13397	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13398	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13399	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13400	 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13401	 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13402	 * @param $private_key_password (string) password
13403	 * @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.
13404	 * @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.
13405	 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13406	 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13407	 * @public
13408	 * @author Nicola Asuni
13409	 * @since 4.6.005 (2009-04-24)
13410	 */
13411	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13412		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13413		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13414		// to convert pfx certificate to pem: openssl
13415		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13416		$this->sign = true;
13417		++$this->n;
13418		$this->sig_obj_id = $this->n; // signature widget
13419		++$this->n; // signature object ($this->sig_obj_id + 1)
13420		$this->signature_data = array();
13421		if (strlen($signing_cert) == 0) {
13422			$this->Error('Please provide a certificate file and password!');
13423		}
13424		if (strlen($private_key) == 0) {
13425			$private_key = $signing_cert;
13426		}
13427		$this->signature_data['signcert'] = $signing_cert;
13428		$this->signature_data['privkey'] = $private_key;
13429		$this->signature_data['password'] = $private_key_password;
13430		$this->signature_data['extracerts'] = $extracerts;
13431		$this->signature_data['cert_type'] = $cert_type;
13432		$this->signature_data['info'] = $info;
13433		$this->signature_data['approval'] = $approval;
13434	}
13435
13436	/**
13437	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13438	 * @param $x (float) Abscissa of the upper-left corner.
13439	 * @param $y (float) Ordinate of the upper-left corner.
13440	 * @param $w (float) Width of the signature area.
13441	 * @param $h (float) Height of the signature area.
13442	 * @param $page (int) option page number (if < 0 the current page is used).
13443	 * @param $name (string) Name of the signature.
13444	 * @public
13445	 * @author Nicola Asuni
13446	 * @since 5.3.011 (2010-06-17)
13447	 */
13448	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13449		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13450	}
13451
13452	/**
13453	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13454	 * @param $x (float) Abscissa of the upper-left corner.
13455	 * @param $y (float) Ordinate of the upper-left corner.
13456	 * @param $w (float) Width of the signature area.
13457	 * @param $h (float) Height of the signature area.
13458	 * @param $page (int) option page number (if < 0 the current page is used).
13459	 * @param $name (string) Name of the signature.
13460	 * @public
13461	 * @author Nicola Asuni
13462	 * @since 5.9.101 (2011-07-06)
13463	 */
13464	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13465		++$this->n;
13466		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13467	}
13468
13469	/**
13470	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13471	 * @param $x (float) Abscissa of the upper-left corner.
13472	 * @param $y (float) Ordinate of the upper-left corner.
13473	 * @param $w (float) Width of the signature area.
13474	 * @param $h (float) Height of the signature area.
13475	 * @param $page (int) option page number (if < 0 the current page is used).
13476	 * @param $name (string) Name of the signature.
13477	 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13478	 * @protected
13479	 * @author Nicola Asuni
13480	 * @since 5.9.101 (2011-07-06)
13481	 */
13482	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13483		$sigapp = array();
13484		if (($page < 1) OR ($page > $this->numpages)) {
13485			$sigapp['page'] = $this->page;
13486		} else {
13487			$sigapp['page'] = intval($page);
13488		}
13489		if (empty($name)) {
13490			$sigapp['name'] = 'Signature';
13491		} else {
13492			$sigapp['name'] = $name;
13493		}
13494		$a = $x * $this->k;
13495		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13496		$c = $w * $this->k;
13497		$d = $h * $this->k;
13498		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13499		return $sigapp;
13500	}
13501
13502	/**
13503	 * Enable document timestamping (requires the OpenSSL Library).
13504	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13505	 * Use with digital signature only!
13506	 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13507	 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13508	 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13509	 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13510	 * @public
13511	 * @author Richard Stockinger
13512	 * @since 6.0.090 (2014-06-16)
13513	 */
13514	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13515		$this->tsa_data = array();
13516		if (!function_exists('curl_init')) {
13517			$this->Error('Please enable cURL PHP extension!');
13518		}
13519		if (strlen($tsa_host) == 0) {
13520			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13521		}
13522		$this->tsa_data['tsa_host'] = $tsa_host;
13523		if (is_file($tsa_username)) {
13524			$this->tsa_data['tsa_auth'] = $tsa_username;
13525		} else {
13526			$this->tsa_data['tsa_username'] = $tsa_username;
13527		}
13528		$this->tsa_data['tsa_password'] = $tsa_password;
13529		$this->tsa_data['tsa_cert'] = $tsa_cert;
13530		$this->tsa_timestamp = true;
13531	}
13532
13533	/**
13534	 * NOT YET IMPLEMENTED
13535	 * Request TSA for a timestamp
13536	 * @param $signature (string) Digital signature as binary string
13537	 * @return (string) Timestamped digital signature
13538	 * @protected
13539	 * @author Richard Stockinger
13540	 * @since 6.0.090 (2014-06-16)
13541	 */
13542	protected function applyTSA($signature) {
13543		if (!$this->tsa_timestamp) {
13544			return $signature;
13545		}
13546		//@TODO: implement this feature
13547		return $signature;
13548	}
13549
13550	/**
13551	 * Create a new page group.
13552	 * NOTE: call this function before calling AddPage()
13553	 * @param $page (int) starting group page (leave empty for next page).
13554	 * @public
13555	 * @since 3.0.000 (2008-03-27)
13556	 */
13557	public function startPageGroup($page='') {
13558		if (empty($page)) {
13559			$page = $this->page + 1;
13560		}
13561		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13562	}
13563
13564	/**
13565	 * Set the starting page number.
13566	 * @param $num (int) Starting page number.
13567	 * @since 5.9.093 (2011-06-16)
13568	 * @public
13569	 */
13570	public function setStartingPageNumber($num=1) {
13571		$this->starting_page_number = max(0, intval($num));
13572	}
13573
13574	/**
13575	 * Returns the string alias used right align page numbers.
13576	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13577	 * @return string
13578	 * @since 5.9.099 (2011-06-27)
13579	 * @public
13580	 */
13581	public function getAliasRightShift() {
13582		// calculate aproximatively the ratio between widths of aliases and replacements.
13583		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13584		$rep = str_repeat(' ', $this->GetNumChars($ref));
13585		$wrep = $this->GetStringWidth($rep);
13586		if ($wrep > 0) {
13587			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13588		} else {
13589			$wdiff = 1;
13590		}
13591		$sdiff = sprintf('%F', $wdiff);
13592		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13593		if ($this->isUnicodeFont()) {
13594			$alias = '{'.$alias;
13595		}
13596		return $alias;
13597	}
13598
13599	/**
13600	 * Returns the string alias used for the total number of pages.
13601	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13602	 * This alias will be replaced by the total number of pages in the document.
13603	 * @return string
13604	 * @since 4.0.018 (2008-08-08)
13605	 * @public
13606	 */
13607	public function getAliasNbPages() {
13608		if ($this->isUnicodeFont()) {
13609			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13610		}
13611		return TCPDF_STATIC::$alias_tot_pages;
13612	}
13613
13614	/**
13615	 * Returns the string alias used for the page number.
13616	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13617	 * This alias will be replaced by the page number.
13618	 * @return string
13619	 * @since 4.5.000 (2009-01-02)
13620	 * @public
13621	 */
13622	public function getAliasNumPage() {
13623		if ($this->isUnicodeFont()) {
13624			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13625		}
13626		return TCPDF_STATIC::$alias_num_page;
13627	}
13628
13629	/**
13630	 * Return the alias for the total number of pages in the current page group.
13631	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13632	 * This alias will be replaced by the total number of pages in this group.
13633	 * @return alias of the current page group
13634	 * @public
13635	 * @since 3.0.000 (2008-03-27)
13636	 */
13637	public function getPageGroupAlias() {
13638		if ($this->isUnicodeFont()) {
13639			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13640		}
13641		return TCPDF_STATIC::$alias_group_tot_pages;
13642	}
13643
13644	/**
13645	 * Return the alias for the page number on the current page group.
13646	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13647	 * This alias will be replaced by the page number (relative to the belonging group).
13648	 * @return alias of the current page group
13649	 * @public
13650	 * @since 4.5.000 (2009-01-02)
13651	 */
13652	public function getPageNumGroupAlias() {
13653		if ($this->isUnicodeFont()) {
13654			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13655		}
13656		return TCPDF_STATIC::$alias_group_num_page;
13657	}
13658
13659	/**
13660	 * Return the current page in the group.
13661	 * @return current page in the group
13662	 * @public
13663	 * @since 3.0.000 (2008-03-27)
13664	 */
13665	public function getGroupPageNo() {
13666		return $this->pagegroups[$this->currpagegroup];
13667	}
13668
13669	/**
13670	 * Returns the current group page number formatted as a string.
13671	 * @public
13672	 * @since 4.3.003 (2008-11-18)
13673	 * @see PaneNo(), formatPageNumber()
13674	 */
13675	public function getGroupPageNoFormatted() {
13676		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13677	}
13678
13679	/**
13680	 * Returns the current page number formatted as a string.
13681	 * @public
13682	 * @since 4.2.005 (2008-11-06)
13683	 * @see PaneNo(), formatPageNumber()
13684	 */
13685	public function PageNoFormatted() {
13686		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13687	}
13688
13689	/**
13690	 * Put pdf layers.
13691	 * @protected
13692	 * @since 3.0.000 (2008-03-27)
13693	 */
13694	protected function _putocg() {
13695		if (empty($this->pdflayers)) {
13696			return;
13697		}
13698		foreach ($this->pdflayers as $key => $layer) {
13699			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13700			 $out = '<< /Type /OCG';
13701			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13702			 $out .= ' /Usage <<';
13703			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13704				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13705			 }
13706			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13707			 $out .= ' >> >>';
13708			 $out .= "\n".'endobj';
13709			 $this->_out($out);
13710		}
13711	}
13712
13713	/**
13714	 * Start a new pdf layer.
13715	 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13716	 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13717	 * @param $view (boolean) Set to true to view this layer.
13718	 * @param $lock (boolean) If true lock the layer
13719	 * @public
13720	 * @since 5.9.102 (2011-07-13)
13721	 */
13722	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13723		if ($this->state != 2) {
13724			return;
13725		}
13726		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13727		if (empty($name)) {
13728			$name = $layer;
13729		} else {
13730			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13731		}
13732		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13733		$this->openMarkedContent = true;
13734		$this->_out('/OC /'.$layer.' BDC');
13735	}
13736
13737	/**
13738	 * End the current PDF layer.
13739	 * @public
13740	 * @since 5.9.102 (2011-07-13)
13741	 */
13742	public function endLayer() {
13743		if ($this->state != 2) {
13744			return;
13745		}
13746		if ($this->openMarkedContent) {
13747			// close existing open marked-content layer
13748			$this->_out('EMC');
13749			$this->openMarkedContent = false;
13750		}
13751	}
13752
13753	/**
13754	 * Set the visibility of the successive elements.
13755	 * This can be useful, for instance, to put a background
13756	 * image or color that will show on screen but won't print.
13757	 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13758	 * @public
13759	 * @since 3.0.000 (2008-03-27)
13760	 */
13761	public function setVisibility($v) {
13762		if ($this->state != 2) {
13763			return;
13764		}
13765		$this->endLayer();
13766		switch($v) {
13767			case 'print': {
13768				$this->startLayer('Print', true, false);
13769				break;
13770			}
13771			case 'view':
13772			case 'screen': {
13773				$this->startLayer('View', false, true);
13774				break;
13775			}
13776			case 'all': {
13777				$this->_out('');
13778				break;
13779			}
13780			default: {
13781				$this->Error('Incorrect visibility: '.$v);
13782				break;
13783			}
13784		}
13785	}
13786
13787	/**
13788	 * Add transparency parameters to the current extgstate
13789	 * @param $parms (array) parameters
13790	 * @return the number of extgstates
13791	 * @protected
13792	 * @since 3.0.000 (2008-03-27)
13793	 */
13794	protected function addExtGState($parms) {
13795		if ($this->pdfa_mode) {
13796			// transparencies are not allowed in PDF/A mode
13797			return;
13798		}
13799		// check if this ExtGState already exist
13800		foreach ($this->extgstates as $i => $ext) {
13801			if ($ext['parms'] == $parms) {
13802				if ($this->inxobj) {
13803					// we are inside an XObject template
13804					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13805				}
13806				// return reference to existing ExtGState
13807				return $i;
13808			}
13809		}
13810		$n = (count($this->extgstates) + 1);
13811		$this->extgstates[$n] = array('parms' => $parms);
13812		if ($this->inxobj) {
13813			// we are inside an XObject template
13814			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13815		}
13816		return $n;
13817	}
13818
13819	/**
13820	 * Add an extgstate
13821	 * @param $gs (array) extgstate
13822	 * @protected
13823	 * @since 3.0.000 (2008-03-27)
13824	 */
13825	protected function setExtGState($gs) {
13826		if ($this->pdfa_mode OR ($this->state != 2)) {
13827			// transparency is not allowed in PDF/A mode
13828			return;
13829		}
13830		$this->_out(sprintf('/GS%d gs', $gs));
13831	}
13832
13833	/**
13834	 * Put extgstates for object transparency
13835	 * @protected
13836	 * @since 3.0.000 (2008-03-27)
13837	 */
13838	protected function _putextgstates() {
13839		foreach ($this->extgstates as $i => $ext) {
13840			$this->extgstates[$i]['n'] = $this->_newobj();
13841			$out = '<< /Type /ExtGState';
13842			foreach ($ext['parms'] as $k => $v) {
13843				if (is_float($v)) {
13844					$v = sprintf('%F', $v);
13845				} elseif ($v === true) {
13846					$v = 'true';
13847				} elseif ($v === false) {
13848					$v = 'false';
13849				}
13850				$out .= ' /'.$k.' '.$v;
13851			}
13852			$out .= ' >>';
13853			$out .= "\n".'endobj';
13854			$this->_out($out);
13855		}
13856	}
13857
13858	/**
13859	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13860	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13861	 * @param $stroking (boolean) If true apply overprint for stroking operations.
13862	 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13863	 * @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).
13864	 * @public
13865	 * @since 5.9.152 (2012-03-23)
13866	 */
13867	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13868		if ($this->state != 2) {
13869			return;
13870		}
13871		$stroking = $stroking ? true : false;
13872		if (TCPDF_STATIC::empty_string($nonstroking)) {
13873			// default value if not set
13874			$nonstroking = $stroking;
13875		} else {
13876			$nonstroking = $nonstroking ? true : false;
13877		}
13878		if (($mode != 0) AND ($mode != 1)) {
13879			$mode = 0;
13880		}
13881		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13882		$gs = $this->addExtGState($this->overprint);
13883		$this->setExtGState($gs);
13884	}
13885
13886	/**
13887	 * Get the overprint mode array (OP, op, OPM).
13888	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13889	 * @return array.
13890	 * @public
13891	 * @since 5.9.152 (2012-03-23)
13892	 */
13893	public function getOverprint() {
13894		return $this->overprint;
13895	}
13896
13897	/**
13898	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13899	 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13900	 * @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
13901	 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13902	 * @param $ais (boolean)
13903	 * @public
13904	 * @since 3.0.000 (2008-03-27)
13905	 */
13906	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13907		if ($this->pdfa_mode) {
13908			// transparency is not allowed in PDF/A mode
13909			return;
13910		}
13911		$stroking = floatval($stroking);
13912		if (TCPDF_STATIC::empty_string($nonstroking)) {
13913			// default value if not set
13914			$nonstroking = $stroking;
13915		} else {
13916			$nonstroking = floatval($nonstroking);
13917		}
13918		if ($bm[0] == '/') {
13919			// remove trailing slash
13920			$bm = substr($bm, 1);
13921		}
13922		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13923			$bm = 'Normal';
13924		}
13925		$ais = $ais ? true : false;
13926		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13927		$gs = $this->addExtGState($this->alpha);
13928		$this->setExtGState($gs);
13929	}
13930
13931	/**
13932	 * Get the alpha mode array (CA, ca, BM, AIS).
13933	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13934	 * @return array.
13935	 * @public
13936	 * @since 5.9.152 (2012-03-23)
13937	 */
13938	public function getAlpha() {
13939		return $this->alpha;
13940	}
13941
13942	/**
13943	 * Set the default JPEG compression quality (1-100)
13944	 * @param $quality (int) JPEG quality, integer between 1 and 100
13945	 * @public
13946	 * @since 3.0.000 (2008-03-27)
13947	 */
13948	public function setJPEGQuality($quality) {
13949		if (($quality < 1) OR ($quality > 100)) {
13950			$quality = 75;
13951		}
13952		$this->jpeg_quality = intval($quality);
13953	}
13954
13955	/**
13956	 * Set the default number of columns in a row for HTML tables.
13957	 * @param $cols (int) number of columns
13958	 * @public
13959	 * @since 3.0.014 (2008-06-04)
13960	 */
13961	public function setDefaultTableColumns($cols=4) {
13962		$this->default_table_columns = intval($cols);
13963	}
13964
13965	/**
13966	 * Set the height of the cell (line height) respect the font height.
13967	 * @param $h (int) cell proportion respect font height (typical value = 1.25).
13968	 * @public
13969	 * @since 3.0.014 (2008-06-04)
13970	 */
13971	public function setCellHeightRatio($h) {
13972		$this->cell_height_ratio = $h;
13973	}
13974
13975	/**
13976	 * return the height of cell repect font height.
13977	 * @public
13978	 * @since 4.0.012 (2008-07-24)
13979	 */
13980	public function getCellHeightRatio() {
13981		return $this->cell_height_ratio;
13982	}
13983
13984	/**
13985	 * Set the PDF version (check PDF reference for valid values).
13986	 * @param $version (string) PDF document version.
13987	 * @public
13988	 * @since 3.1.000 (2008-06-09)
13989	 */
13990	public function setPDFVersion($version='1.7') {
13991		if ($this->pdfa_mode) {
13992			// PDF/A mode
13993			$this->PDFVersion = '1.4';
13994		} else {
13995			$this->PDFVersion = $version;
13996		}
13997	}
13998
13999	/**
14000	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14001	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14002	 * <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>
14003	 * @param $preferences (array) array of options.
14004	 * @author Nicola Asuni
14005	 * @public
14006	 * @since 3.1.000 (2008-06-09)
14007	 */
14008	public function setViewerPreferences($preferences) {
14009		$this->viewer_preferences = $preferences;
14010	}
14011
14012	/**
14013	 * Paints color transition registration bars
14014	 * @param $x (float) abscissa of the top left corner of the rectangle.
14015	 * @param $y (float) ordinate of the top left corner of the rectangle.
14016	 * @param $w (float) width of the rectangle.
14017	 * @param $h (float) height of the rectangle.
14018	 * @param $transition (boolean) if true prints tcolor transitions to white.
14019	 * @param $vertical (boolean) if true prints bar vertically.
14020	 * @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.
14021	 * @author Nicola Asuni
14022	 * @since 4.9.000 (2010-03-26)
14023	 * @public
14024	 */
14025	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14026		if (strpos($colors, 'ALLSPOT') !== false) {
14027			// expand spot colors
14028			$spot_colors = '';
14029			foreach ($this->spot_colors as $spot_color_name => $v) {
14030				$spot_colors .= ','.$spot_color_name;
14031			}
14032			if (!empty($spot_colors)) {
14033				$spot_colors = substr($spot_colors, 1);
14034				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14035			} else {
14036				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14037			}
14038		}
14039		$bars = explode(',', $colors);
14040		$numbars = count($bars); // number of bars to print
14041		if ($numbars <= 0) {
14042			return;
14043		}
14044		// set bar measures
14045		if ($vertical) {
14046			$coords = array(0, 0, 0, 1);
14047			$wb = $w / $numbars; // bar width
14048			$hb = $h; // bar height
14049			$xd = $wb; // delta x
14050			$yd = 0; // delta y
14051		} else {
14052			$coords = array(1, 0, 0, 0);
14053			$wb = $w; // bar width
14054			$hb = $h / $numbars; // bar height
14055			$xd = 0; // delta x
14056			$yd = $hb; // delta y
14057		}
14058		$xb = $x;
14059		$yb = $y;
14060		foreach ($bars as $col) {
14061			switch ($col) {
14062				// set transition colors
14063				case 'A': { // BLACK (GRAYSCALE)
14064					$col_a = array(255);
14065					$col_b = array(0);
14066					break;
14067				}
14068				case 'W': { // WHITE (GRAYSCALE)
14069					$col_a = array(0);
14070					$col_b = array(255);
14071					break;
14072				}
14073				case 'R': { // RED (RGB)
14074					$col_a = array(255,255,255);
14075					$col_b = array(255,0,0);
14076					break;
14077				}
14078				case 'G': { // GREEN (RGB)
14079					$col_a = array(255,255,255);
14080					$col_b = array(0,255,0);
14081					break;
14082				}
14083				case 'B': { // BLUE (RGB)
14084					$col_a = array(255,255,255);
14085					$col_b = array(0,0,255);
14086					break;
14087				}
14088				case 'C': { // CYAN (CMYK)
14089					$col_a = array(0,0,0,0);
14090					$col_b = array(100,0,0,0);
14091					break;
14092				}
14093				case 'M': { // MAGENTA (CMYK)
14094					$col_a = array(0,0,0,0);
14095					$col_b = array(0,100,0,0);
14096					break;
14097				}
14098				case 'Y': { // YELLOW (CMYK)
14099					$col_a = array(0,0,0,0);
14100					$col_b = array(0,0,100,0);
14101					break;
14102				}
14103				case 'K': { // KEY - BLACK (CMYK)
14104					$col_a = array(0,0,0,0);
14105					$col_b = array(0,0,0,100);
14106					break;
14107				}
14108				case 'RGB': { // BLACK REGISTRATION (RGB)
14109					$col_a = array(255,255,255);
14110					$col_b = array(0,0,0);
14111					break;
14112				}
14113				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14114					$col_a = array(0,0,0,0);
14115					$col_b = array(100,100,100,100);
14116					break;
14117				}
14118				case 'ALL': { // SPOT COLOR REGISTRATION
14119					$col_a = array(0,0,0,0,'None');
14120					$col_b = array(100,100,100,100,'All');
14121					break;
14122				}
14123				case 'NONE': { // SKIP THIS COLOR
14124					$col_a = array(0,0,0,0,'None');
14125					$col_b = array(0,0,0,0,'None');
14126					break;
14127				}
14128				default: { // SPECIFIC SPOT COLOR NAME
14129					$col_a = array(0,0,0,0,'None');
14130					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14131					if ($col_b === false) {
14132						// in case of error defaults to the registration color
14133						$col_b = array(100,100,100,100,'All');
14134					}
14135					break;
14136				}
14137			}
14138			if ($col != 'NONE') {
14139				if ($transition) {
14140					// color gradient
14141					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14142				} else {
14143					$this->SetFillColorArray($col_b);
14144					// colored rectangle
14145					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14146				}
14147				$xb += $xd;
14148				$yb += $yd;
14149			}
14150		}
14151	}
14152
14153	/**
14154	 * Paints crop marks.
14155	 * @param $x (float) abscissa of the crop mark center.
14156	 * @param $y (float) ordinate of the crop mark center.
14157	 * @param $w (float) width of the crop mark.
14158	 * @param $h (float) height of the crop mark.
14159	 * @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.
14160	 * @param $color (array) crop mark color (default spot registration color).
14161	 * @author Nicola Asuni
14162	 * @since 4.9.000 (2010-03-26)
14163	 * @public
14164	 */
14165	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14166		$this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14167		$type = strtoupper($type);
14168		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14169		// split type in single components
14170		$type = str_replace('-', ',', $type);
14171		$type = str_replace('TL', 'T,L', $type);
14172		$type = str_replace('TR', 'T,R', $type);
14173		$type = str_replace('BL', 'F,L', $type);
14174		$type = str_replace('BR', 'F,R', $type);
14175		$type = str_replace('A', 'T,L', $type);
14176		$type = str_replace('B', 'T,R', $type);
14177		$type = str_replace('T,RO', 'BO', $type);
14178		$type = str_replace('C', 'F,L', $type);
14179		$type = str_replace('D', 'F,R', $type);
14180		$crops = explode(',', strtoupper($type));
14181		// remove duplicates
14182		$crops = array_unique($crops);
14183		$dw = ($w / 4); // horizontal space to leave before the intersection point
14184		$dh = ($h / 4); // vertical space to leave before the intersection point
14185		foreach ($crops as $crop) {
14186			switch ($crop) {
14187				case 'T':
14188				case 'TOP': {
14189					$x1 = $x;
14190					$y1 = ($y - $h);
14191					$x2 = $x;
14192					$y2 = ($y - $dh);
14193					break;
14194				}
14195				case 'F':
14196				case 'BOTTOM': {
14197					$x1 = $x;
14198					$y1 = ($y + $dh);
14199					$x2 = $x;
14200					$y2 = ($y + $h);
14201					break;
14202				}
14203				case 'L':
14204				case 'LEFT': {
14205					$x1 = ($x - $w);
14206					$y1 = $y;
14207					$x2 = ($x - $dw);
14208					$y2 = $y;
14209					break;
14210				}
14211				case 'R':
14212				case 'RIGHT': {
14213					$x1 = ($x + $dw);
14214					$y1 = $y;
14215					$x2 = ($x + $w);
14216					$y2 = $y;
14217					break;
14218				}
14219			}
14220			$this->Line($x1, $y1, $x2, $y2);
14221		}
14222	}
14223
14224	/**
14225	 * Paints a registration mark
14226	 * @param $x (float) abscissa of the registration mark center.
14227	 * @param $y (float) ordinate of the registration mark center.
14228	 * @param $r (float) radius of the crop mark.
14229	 * @param $double (boolean) if true print two concentric crop marks.
14230	 * @param $cola (array) crop mark color (default spot registration color 'All').
14231	 * @param $colb (array) second crop mark color (default spot registration color 'None').
14232	 * @author Nicola Asuni
14233	 * @since 4.9.000 (2010-03-26)
14234	 * @public
14235	 */
14236	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14237		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14238		$this->SetFillColorArray($cola);
14239		$this->PieSector($x, $y, $r, 90, 180, 'F');
14240		$this->PieSector($x, $y, $r, 270, 360, 'F');
14241		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14242		if ($double) {
14243			$ri = $r * 0.5;
14244			$this->SetFillColorArray($colb);
14245			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14246			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14247			$this->SetFillColorArray($cola);
14248			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14249			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14250			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14251		}
14252	}
14253
14254	/**
14255	 * Paints a CMYK registration mark
14256	 * @param $x (float) abscissa of the registration mark center.
14257	 * @param $y (float) ordinate of the registration mark center.
14258	 * @param $r (float) radius of the crop mark.
14259	 * @author Nicola Asuni
14260	 * @since 6.0.038 (2013-09-30)
14261	 * @public
14262	 */
14263	public function registrationMarkCMYK($x, $y, $r) {
14264		// line width
14265		$lw = max((0.5 / $this->k),($r / 8));
14266		// internal radius
14267		$ri = ($r * 0.6);
14268		// external radius
14269		$re = ($r * 1.3);
14270		// Cyan
14271		$this->SetFillColorArray(array(100,0,0,0));
14272		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14273		// Magenta
14274		$this->SetFillColorArray(array(0,100,0,0));
14275		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14276		// Yellow
14277		$this->SetFillColorArray(array(0,0,100,0));
14278		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14279		// Key - black
14280		$this->SetFillColorArray(array(0,0,0,100));
14281		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14282		// registration color
14283		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14284		$this->SetFillColorArray(array(100,100,100,100,'All'));
14285		// external circle
14286		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14287		// cross lines
14288		$this->Line($x, ($y - $re), $x, ($y - $ri));
14289		$this->Line($x, ($y + $ri), $x, ($y + $re));
14290		$this->Line(($x - $re), $y, ($x - $ri), $y);
14291		$this->Line(($x + $ri), $y, ($x + $re), $y);
14292	}
14293
14294	/**
14295	 * Paints a linear colour gradient.
14296	 * @param $x (float) abscissa of the top left corner of the rectangle.
14297	 * @param $y (float) ordinate of the top left corner of the rectangle.
14298	 * @param $w (float) width of the rectangle.
14299	 * @param $h (float) height of the rectangle.
14300	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14301	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14302	 * @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).
14303	 * @author Andreas W\FCrmser, Nicola Asuni
14304	 * @since 3.1.000 (2008-06-09)
14305	 * @public
14306	 */
14307	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14308		$this->Clip($x, $y, $w, $h);
14309		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14310	}
14311
14312	/**
14313	 * Paints a radial colour gradient.
14314	 * @param $x (float) abscissa of the top left corner of the rectangle.
14315	 * @param $y (float) ordinate of the top left corner of the rectangle.
14316	 * @param $w (float) width of the rectangle.
14317	 * @param $h (float) height of the rectangle.
14318	 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14319	 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14320	 * @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.
14321	 * @author Andreas W\FCrmser, Nicola Asuni
14322	 * @since 3.1.000 (2008-06-09)
14323	 * @public
14324	 */
14325	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14326		$this->Clip($x, $y, $w, $h);
14327		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14328	}
14329
14330	/**
14331	 * Paints a coons patch mesh.
14332	 * @param $x (float) abscissa of the top left corner of the rectangle.
14333	 * @param $y (float) ordinate of the top left corner of the rectangle.
14334	 * @param $w (float) width of the rectangle.
14335	 * @param $h (float) height of the rectangle.
14336	 * @param $col1 (array) first color (lower left corner) (RGB components).
14337	 * @param $col2 (array) second color (lower right corner) (RGB components).
14338	 * @param $col3 (array) third color (upper right corner) (RGB components).
14339	 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14340	 * @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>
14341	 * @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
14342	 * @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
14343	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14344	 * @author Andreas W\FCrmser, Nicola Asuni
14345	 * @since 3.1.000 (2008-06-09)
14346	 * @public
14347	 */
14348	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) {
14349		if ($this->pdfa_mode OR ($this->state != 2)) {
14350			return;
14351		}
14352		$this->Clip($x, $y, $w, $h);
14353		$n = count($this->gradients) + 1;
14354		$this->gradients[$n] = array();
14355		$this->gradients[$n]['type'] = 6; //coons patch mesh
14356		$this->gradients[$n]['coords'] = array();
14357		$this->gradients[$n]['antialias'] = $antialias;
14358		$this->gradients[$n]['colors'] = array();
14359		$this->gradients[$n]['transparency'] = false;
14360		//check the coords array if it is the simple array or the multi patch array
14361		if (!isset($coords[0]['f'])) {
14362			//simple array -> convert to multi patch array
14363			if (!isset($col1[1])) {
14364				$col1[1] = $col1[2] = $col1[0];
14365			}
14366			if (!isset($col2[1])) {
14367				$col2[1] = $col2[2] = $col2[0];
14368			}
14369			if (!isset($col3[1])) {
14370				$col3[1] = $col3[2] = $col3[0];
14371			}
14372			if (!isset($col4[1])) {
14373				$col4[1] = $col4[2] = $col4[0];
14374			}
14375			$patch_array[0]['f'] = 0;
14376			$patch_array[0]['points'] = $coords;
14377			$patch_array[0]['colors'][0]['r'] = $col1[0];
14378			$patch_array[0]['colors'][0]['g'] = $col1[1];
14379			$patch_array[0]['colors'][0]['b'] = $col1[2];
14380			$patch_array[0]['colors'][1]['r'] = $col2[0];
14381			$patch_array[0]['colors'][1]['g'] = $col2[1];
14382			$patch_array[0]['colors'][1]['b'] = $col2[2];
14383			$patch_array[0]['colors'][2]['r'] = $col3[0];
14384			$patch_array[0]['colors'][2]['g'] = $col3[1];
14385			$patch_array[0]['colors'][2]['b'] = $col3[2];
14386			$patch_array[0]['colors'][3]['r'] = $col4[0];
14387			$patch_array[0]['colors'][3]['g'] = $col4[1];
14388			$patch_array[0]['colors'][3]['b'] = $col4[2];
14389		} else {
14390			//multi patch array
14391			$patch_array = $coords;
14392		}
14393		$bpcd = 65535; //16 bits per coordinate
14394		//build the data stream
14395		$this->gradients[$n]['stream'] = '';
14396		$count_patch = count($patch_array);
14397		for ($i=0; $i < $count_patch; ++$i) {
14398			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14399			$count_points = count($patch_array[$i]['points']);
14400			for ($j=0; $j < $count_points; ++$j) {
14401				//each point as 16 bit
14402				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14403				if ($patch_array[$i]['points'][$j] < 0) {
14404					$patch_array[$i]['points'][$j] = 0;
14405				}
14406				if ($patch_array[$i]['points'][$j] > $bpcd) {
14407					$patch_array[$i]['points'][$j] = $bpcd;
14408				}
14409				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14410				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14411			}
14412			$count_cols = count($patch_array[$i]['colors']);
14413			for ($j=0; $j < $count_cols; ++$j) {
14414				//each color component as 8 bit
14415				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14416				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14417				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14418			}
14419		}
14420		//paint the gradient
14421		$this->_out('/Sh'.$n.' sh');
14422		//restore previous Graphic State
14423		$this->_outRestoreGraphicsState();
14424		if ($this->inxobj) {
14425			// we are inside an XObject template
14426			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14427		}
14428	}
14429
14430	/**
14431	 * Set a rectangular clipping area.
14432	 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
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	 * @author Andreas W\FCrmser, Nicola Asuni
14437	 * @since 3.1.000 (2008-06-09)
14438	 * @protected
14439	 */
14440	protected function Clip($x, $y, $w, $h) {
14441		if ($this->state != 2) {
14442			 return;
14443		}
14444		if ($this->rtl) {
14445			$x = $this->w - $x - $w;
14446		}
14447		//save current Graphic State
14448		$s = 'q';
14449		//set clipping area
14450		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14451		//set up transformation matrix for gradient
14452		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14453		$this->_out($s);
14454	}
14455
14456	/**
14457	 * Output gradient.
14458	 * @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)
14459	 * @param $coords (array) array of coordinates.
14460	 * @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).
14461	 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14462	 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14463	 * @author Nicola Asuni
14464	 * @since 3.1.000 (2008-06-09)
14465	 * @public
14466	 */
14467	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14468		if ($this->pdfa_mode OR ($this->state != 2)) {
14469			return;
14470		}
14471		$n = count($this->gradients) + 1;
14472		$this->gradients[$n] = array();
14473		$this->gradients[$n]['type'] = $type;
14474		$this->gradients[$n]['coords'] = $coords;
14475		$this->gradients[$n]['antialias'] = $antialias;
14476		$this->gradients[$n]['colors'] = array();
14477		$this->gradients[$n]['transparency'] = false;
14478		// color space
14479		$numcolspace = count($stops[0]['color']);
14480		$bcolor = array_values($background);
14481		switch($numcolspace) {
14482			case 5:   // SPOT
14483			case 4: { // CMYK
14484				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14485				if (!empty($background)) {
14486					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14487				}
14488				break;
14489			}
14490			case 3: { // RGB
14491				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14492				if (!empty($background)) {
14493					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14494				}
14495				break;
14496			}
14497			case 1: { // GRAY SCALE
14498				$this->gradients[$n]['colspace'] = 'DeviceGray';
14499				if (!empty($background)) {
14500					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14501				}
14502				break;
14503			}
14504		}
14505		$num_stops = count($stops);
14506		$last_stop_id = $num_stops - 1;
14507		foreach ($stops as $key => $stop) {
14508			$this->gradients[$n]['colors'][$key] = array();
14509			// offset represents a location along the gradient vector
14510			if (isset($stop['offset'])) {
14511				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14512			} else {
14513				if ($key == 0) {
14514					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14515				} elseif ($key == $last_stop_id) {
14516					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14517				} else {
14518					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14519					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14520				}
14521			}
14522			if (isset($stop['opacity'])) {
14523				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14524				if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14525					$this->gradients[$n]['transparency'] = true;
14526				}
14527			} else {
14528				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14529			}
14530			// exponent for the exponential interpolation function
14531			if (isset($stop['exponent'])) {
14532				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14533			} else {
14534				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14535			}
14536			// set colors
14537			$color = array_values($stop['color']);
14538			switch($numcolspace) {
14539				case 5:   // SPOT
14540				case 4: { // CMYK
14541					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14542					break;
14543				}
14544				case 3: { // RGB
14545					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14546					break;
14547				}
14548				case 1: { // GRAY SCALE
14549					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14550					break;
14551				}
14552			}
14553		}
14554		if ($this->gradients[$n]['transparency']) {
14555			// paint luminosity gradient
14556			$this->_out('/TGS'.$n.' gs');
14557		}
14558		//paint the gradient
14559		$this->_out('/Sh'.$n.' sh');
14560		//restore previous Graphic State
14561		$this->_outRestoreGraphicsState();
14562		if ($this->inxobj) {
14563			// we are inside an XObject template
14564			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14565		}
14566	}
14567
14568	/**
14569	 * Output gradient shaders.
14570	 * @author Nicola Asuni
14571	 * @since 3.1.000 (2008-06-09)
14572	 * @protected
14573	 */
14574	function _putshaders() {
14575		if ($this->pdfa_mode) {
14576			return;
14577		}
14578		$idt = count($this->gradients); //index for transparency gradients
14579		foreach ($this->gradients as $id => $grad) {
14580			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14581				$fc = $this->_newobj();
14582				$out = '<<';
14583				$out .= ' /FunctionType 3';
14584				$out .= ' /Domain [0 1]';
14585				$functions = '';
14586				$bounds = '';
14587				$encode = '';
14588				$i = 1;
14589				$num_cols = count($grad['colors']);
14590				$lastcols = $num_cols - 1;
14591				for ($i = 1; $i < $num_cols; ++$i) {
14592					$functions .= ($fc + $i).' 0 R ';
14593					if ($i < $lastcols) {
14594						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14595					}
14596					$encode .= '0 1 ';
14597				}
14598				$out .= ' /Functions ['.trim($functions).']';
14599				$out .= ' /Bounds ['.trim($bounds).']';
14600				$out .= ' /Encode ['.trim($encode).']';
14601				$out .= ' >>';
14602				$out .= "\n".'endobj';
14603				$this->_out($out);
14604				for ($i = 1; $i < $num_cols; ++$i) {
14605					$this->_newobj();
14606					$out = '<<';
14607					$out .= ' /FunctionType 2';
14608					$out .= ' /Domain [0 1]';
14609					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14610					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14611					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14612					$out .= ' >>';
14613					$out .= "\n".'endobj';
14614					$this->_out($out);
14615				}
14616				// set transparency functions
14617				if ($grad['transparency']) {
14618					$ft = $this->_newobj();
14619					$out = '<<';
14620					$out .= ' /FunctionType 3';
14621					$out .= ' /Domain [0 1]';
14622					$functions = '';
14623					$i = 1;
14624					$num_cols = count($grad['colors']);
14625					for ($i = 1; $i < $num_cols; ++$i) {
14626						$functions .= ($ft + $i).' 0 R ';
14627					}
14628					$out .= ' /Functions ['.trim($functions).']';
14629					$out .= ' /Bounds ['.trim($bounds).']';
14630					$out .= ' /Encode ['.trim($encode).']';
14631					$out .= ' >>';
14632					$out .= "\n".'endobj';
14633					$this->_out($out);
14634					for ($i = 1; $i < $num_cols; ++$i) {
14635						$this->_newobj();
14636						$out = '<<';
14637						$out .= ' /FunctionType 2';
14638						$out .= ' /Domain [0 1]';
14639						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14640						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14641						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14642						$out .= ' >>';
14643						$out .= "\n".'endobj';
14644						$this->_out($out);
14645					}
14646				}
14647			}
14648			// set shading object
14649			$this->_newobj();
14650			$out = '<< /ShadingType '.$grad['type'];
14651			if (isset($grad['colspace'])) {
14652				$out .= ' /ColorSpace /'.$grad['colspace'];
14653			} else {
14654				$out .= ' /ColorSpace /DeviceRGB';
14655			}
14656			if (isset($grad['background']) AND !empty($grad['background'])) {
14657				$out .= ' /Background ['.$grad['background'].']';
14658			}
14659			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14660				$out .= ' /AntiAlias true';
14661			}
14662			if ($grad['type'] == 2) {
14663				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14664				$out .= ' /Domain [0 1]';
14665				$out .= ' /Function '.$fc.' 0 R';
14666				$out .= ' /Extend [true true]';
14667				$out .= ' >>';
14668			} elseif ($grad['type'] == 3) {
14669				//x0, y0, r0, x1, y1, r1
14670				//at this this time radius of inner circle is 0
14671				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14672				$out .= ' /Domain [0 1]';
14673				$out .= ' /Function '.$fc.' 0 R';
14674				$out .= ' /Extend [true true]';
14675				$out .= ' >>';
14676			} elseif ($grad['type'] == 6) {
14677				$out .= ' /BitsPerCoordinate 16';
14678				$out .= ' /BitsPerComponent 8';
14679				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14680				$out .= ' /BitsPerFlag 8';
14681				$stream = $this->_getrawstream($grad['stream']);
14682				$out .= ' /Length '.strlen($stream);
14683				$out .= ' >>';
14684				$out .= ' stream'."\n".$stream."\n".'endstream';
14685			}
14686			$out .= "\n".'endobj';
14687			$this->_out($out);
14688			if ($grad['transparency']) {
14689				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14690				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14691			}
14692			$this->gradients[$id]['id'] = $this->n;
14693			// set pattern object
14694			$this->_newobj();
14695			$out = '<< /Type /Pattern /PatternType 2';
14696			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14697			$out .= ' >>';
14698			$out .= "\n".'endobj';
14699			$this->_out($out);
14700			$this->gradients[$id]['pattern'] = $this->n;
14701			// set shading and pattern for transparency mask
14702			if ($grad['transparency']) {
14703				// luminosity pattern
14704				$idgs = $id + $idt;
14705				$this->_newobj();
14706				$this->_out($shading_transparency);
14707				$this->gradients[$idgs]['id'] = $this->n;
14708				$this->_newobj();
14709				$out = '<< /Type /Pattern /PatternType 2';
14710				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14711				$out .= ' >>';
14712				$out .= "\n".'endobj';
14713				$this->_out($out);
14714				$this->gradients[$idgs]['pattern'] = $this->n;
14715				// luminosity XObject
14716				$oid = $this->_newobj();
14717				$this->xobjects['LX'.$oid] = array('n' => $oid);
14718				$filter = '';
14719				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14720				if ($this->compress) {
14721					$filter = ' /Filter /FlateDecode';
14722					$stream = gzcompress($stream);
14723				}
14724				$stream = $this->_getrawstream($stream);
14725				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14726				$out .= ' /Length '.strlen($stream);
14727				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14728				$out .= ' /BBox [0 0 '.$rect.']';
14729				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14730				$out .= ' /Resources <<';
14731				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14732				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14733				$out .= ' >>';
14734				$out .= ' >> ';
14735				$out .= ' stream'."\n".$stream."\n".'endstream';
14736				$out .= "\n".'endobj';
14737				$this->_out($out);
14738				// SMask
14739				$this->_newobj();
14740				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14741				$this->_out($out);
14742				// ExtGState
14743				$this->_newobj();
14744				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14745				$this->_out($out);
14746				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14747			}
14748		}
14749	}
14750
14751	/**
14752	 * Draw the sector of a circle.
14753	 * It can be used for instance to render pie charts.
14754	 * @param $xc (float) abscissa of the center.
14755	 * @param $yc (float) ordinate of the center.
14756	 * @param $r (float) radius.
14757	 * @param $a (float) start angle (in degrees).
14758	 * @param $b (float) end angle (in degrees).
14759	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14760	 * @param $cw: (float) indicates whether to go clockwise (default: true).
14761	 * @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.
14762	 * @author Maxime Delorme, Nicola Asuni
14763	 * @since 3.1.000 (2008-06-09)
14764	 * @public
14765	 */
14766	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14767		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14768	}
14769
14770	/**
14771	 * Draw the sector of an ellipse.
14772	 * It can be used for instance to render pie charts.
14773	 * @param $xc (float) abscissa of the center.
14774	 * @param $yc (float) ordinate of the center.
14775	 * @param $rx (float) the x-axis radius.
14776	 * @param $ry (float) the y-axis radius.
14777	 * @param $a (float) start angle (in degrees).
14778	 * @param $b (float) end angle (in degrees).
14779	 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14780	 * @param $cw: (float) indicates whether to go clockwise.
14781	 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14782	 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14783	 * @author Maxime Delorme, Nicola Asuni
14784	 * @since 3.1.000 (2008-06-09)
14785	 * @public
14786	 */
14787	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14788		if ($this->state != 2) {
14789			 return;
14790		}
14791		if ($this->rtl) {
14792			$xc = ($this->w - $xc);
14793		}
14794		$op = TCPDF_STATIC::getPathPaintOperator($style);
14795		if ($op == 'f') {
14796			$line_style = array();
14797		}
14798		if ($cw) {
14799			$d = $b;
14800			$b = (360 - $a + $o);
14801			$a = (360 - $d + $o);
14802		} else {
14803			$b += $o;
14804			$a += $o;
14805		}
14806		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14807		$this->_out($op);
14808	}
14809
14810	/**
14811	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14812	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14813	 * Only vector drawing is supported, not text or bitmap.
14814	 * 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).
14815	 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14816	 * @param $x (float) Abscissa of the upper-left corner.
14817	 * @param $y (float) Ordinate of the upper-left corner.
14818	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14819	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14820	 * @param $link (mixed) URL or identifier returned by AddLink().
14821	 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14822	 * @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>
14823	 * @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>
14824	 * @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)))
14825	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14826	 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14827	 * @author Valentin Schmidt, Nicola Asuni
14828	 * @since 3.1.000 (2008-06-09)
14829	 * @public
14830	 */
14831	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14832		if ($this->state != 2) {
14833			 return;
14834		}
14835		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14836			// convert EPS to raster image using GD or ImageMagick libraries
14837			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14838		}
14839		if ($x === '') {
14840			$x = $this->x;
14841		}
14842		if ($y === '') {
14843			$y = $this->y;
14844		}
14845		// check page for no-write regions and adapt page margins if necessary
14846		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14847		$k = $this->k;
14848		if ($file[0] === '@') { // image from string
14849			$data = substr($file, 1);
14850		} else { // EPS/AI file
14851			$data = TCPDF_STATIC::fileGetContents($file);
14852		}
14853		if ($data === FALSE) {
14854			$this->Error('EPS file not found: '.$file);
14855		}
14856		$regs = array();
14857		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14858		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14859		if (count($regs) > 1) {
14860			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14861			if (strpos($version_str, 'Adobe Illustrator') !== false) {
14862				$versexp = explode(' ', $version_str);
14863				$version = (float)array_pop($versexp);
14864				if ($version >= 9) {
14865					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14866				}
14867			}
14868		}
14869		// strip binary bytes in front of PS-header
14870		$start = strpos($data, '%!PS-Adobe');
14871		if ($start > 0) {
14872			$data = substr($data, $start);
14873		}
14874		// find BoundingBox params
14875		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14876		if (count($regs) > 1) {
14877			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14878		} else {
14879			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
14880		}
14881		$start = strpos($data, '%%EndSetup');
14882		if ($start === false) {
14883			$start = strpos($data, '%%EndProlog');
14884		}
14885		if ($start === false) {
14886			$start = strpos($data, '%%BoundingBox');
14887		}
14888		$data = substr($data, $start);
14889		$end = strpos($data, '%%PageTrailer');
14890		if ($end===false) {
14891			$end = strpos($data, 'showpage');
14892		}
14893		if ($end) {
14894			$data = substr($data, 0, $end);
14895		}
14896		// calculate image width and height on document
14897		if (($w <= 0) AND ($h <= 0)) {
14898			$w = ($x2 - $x1) / $k;
14899			$h = ($y2 - $y1) / $k;
14900		} elseif ($w <= 0) {
14901			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14902		} elseif ($h <= 0) {
14903			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14904		}
14905		// fit the image on available space
14906		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14907		if ($this->rasterize_vector_images) {
14908			// convert EPS to raster image using GD or ImageMagick libraries
14909			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14910		}
14911		// set scaling factors
14912		$scale_x = $w / (($x2 - $x1) / $k);
14913		$scale_y = $h / (($y2 - $y1) / $k);
14914		// set alignment
14915		$this->img_rb_y = $y + $h;
14916		// set alignment
14917		if ($this->rtl) {
14918			if ($palign == 'L') {
14919				$ximg = $this->lMargin;
14920			} elseif ($palign == 'C') {
14921				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14922			} elseif ($palign == 'R') {
14923				$ximg = $this->w - $this->rMargin - $w;
14924			} else {
14925				$ximg = $x - $w;
14926			}
14927			$this->img_rb_x = $ximg;
14928		} else {
14929			if ($palign == 'L') {
14930				$ximg = $this->lMargin;
14931			} elseif ($palign == 'C') {
14932				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14933			} elseif ($palign == 'R') {
14934				$ximg = $this->w - $this->rMargin - $w;
14935			} else {
14936				$ximg = $x;
14937			}
14938			$this->img_rb_x = $ximg + $w;
14939		}
14940		if ($useBoundingBox) {
14941			$dx = $ximg * $k - $x1;
14942			$dy = $y * $k - $y1;
14943		} else {
14944			$dx = $ximg * $k;
14945			$dy = $y * $k;
14946		}
14947		// save the current graphic state
14948		$this->_out('q'.$this->epsmarker);
14949		// translate
14950		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
14951		// scale
14952		if (isset($scale_x)) {
14953			$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
14954		}
14955		// handle pc/unix/mac line endings
14956		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
14957		$u=0;
14958		$cnt = count($lines);
14959		for ($i=0; $i < $cnt; ++$i) {
14960			$line = $lines[$i];
14961			if (($line == '') OR ($line[0] == '%')) {
14962				continue;
14963			}
14964			$len = strlen($line);
14965			// check for spot color names
14966			$color_name = '';
14967			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
14968				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
14969					// extract spot color name
14970					$color_name = $matches[0];
14971					// remove color name from string
14972					$line = str_replace(' '.$color_name, '', $line);
14973					// remove pharentesis from color name
14974					$color_name = substr($color_name, 1, -1);
14975				}
14976			}
14977			$chunks = explode(' ', $line);
14978			$cmd = trim(array_pop($chunks));
14979			// RGB
14980			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
14981				$b = array_pop($chunks);
14982				$g = array_pop($chunks);
14983				$r = array_pop($chunks);
14984				$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!
14985				continue;
14986			}
14987			$skip = false;
14988			if ($fixoutvals) {
14989				// check for values outside the bounding box
14990				switch ($cmd) {
14991					case 'm':
14992					case 'l':
14993					case 'L': {
14994						// skip values outside bounding box
14995						foreach ($chunks as $key => $val) {
14996							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
14997								$skip = true;
14998							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
14999								$skip = true;
15000							}
15001						}
15002					}
15003				}
15004			}
15005			switch ($cmd) {
15006				case 'm':
15007				case 'l':
15008				case 'v':
15009				case 'y':
15010				case 'c':
15011				case 'k':
15012				case 'K':
15013				case 'g':
15014				case 'G':
15015				case 's':
15016				case 'S':
15017				case 'J':
15018				case 'j':
15019				case 'w':
15020				case 'M':
15021				case 'd':
15022				case 'n': {
15023					if ($skip) {
15024						break;
15025					}
15026					$this->_out($line);
15027					break;
15028				}
15029				case 'x': {// custom fill color
15030					if (empty($color_name)) {
15031						// CMYK color
15032						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15033						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15034					} else {
15035						// Spot Color (CMYK + tint)
15036						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15037						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15038						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15039						$this->_out($color_cmd);
15040					}
15041					break;
15042				}
15043				case 'X': { // custom stroke color
15044					if (empty($color_name)) {
15045						// CMYK color
15046						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15047						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15048					} else {
15049						// Spot Color (CMYK + tint)
15050						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15051						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15052						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15053						$this->_out($color_cmd);
15054					}
15055					break;
15056				}
15057				case 'Y':
15058				case 'N':
15059				case 'V':
15060				case 'L':
15061				case 'C': {
15062					if ($skip) {
15063						break;
15064					}
15065					$line[($len - 1)] = strtolower($cmd);
15066					$this->_out($line);
15067					break;
15068				}
15069				case 'b':
15070				case 'B': {
15071					$this->_out($cmd . '*');
15072					break;
15073				}
15074				case 'f':
15075				case 'F': {
15076					if ($u > 0) {
15077						$isU = false;
15078						$max = min(($i + 5), $cnt);
15079						for ($j = ($i + 1); $j < $max; ++$j) {
15080							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15081						}
15082						if ($isU) {
15083							$this->_out('f*');
15084						}
15085					} else {
15086						$this->_out('f*');
15087					}
15088					break;
15089				}
15090				case '*u': {
15091					++$u;
15092					break;
15093				}
15094				case '*U': {
15095					--$u;
15096					break;
15097				}
15098			}
15099		}
15100		// restore previous graphic state
15101		$this->_out($this->epsmarker.'Q');
15102		if (!empty($border)) {
15103			$bx = $this->x;
15104			$by = $this->y;
15105			$this->x = $ximg;
15106			if ($this->rtl) {
15107				$this->x += $w;
15108			}
15109			$this->y = $y;
15110			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15111			$this->x = $bx;
15112			$this->y = $by;
15113		}
15114		if ($link) {
15115			$this->Link($ximg, $y, $w, $h, $link, 0);
15116		}
15117		// set pointer to align the next text/objects
15118		switch($align) {
15119			case 'T':{
15120				$this->y = $y;
15121				$this->x = $this->img_rb_x;
15122				break;
15123			}
15124			case 'M':{
15125				$this->y = $y + round($h/2);
15126				$this->x = $this->img_rb_x;
15127				break;
15128			}
15129			case 'B':{
15130				$this->y = $this->img_rb_y;
15131				$this->x = $this->img_rb_x;
15132				break;
15133			}
15134			case 'N':{
15135				$this->SetY($this->img_rb_y);
15136				break;
15137			}
15138			default:{
15139				break;
15140			}
15141		}
15142		$this->endlinex = $this->img_rb_x;
15143	}
15144
15145	/**
15146	 * Set document barcode.
15147	 * @param $bc (string) barcode
15148	 * @public
15149	 */
15150	public function setBarcode($bc='') {
15151		$this->barcode = $bc;
15152	}
15153
15154	/**
15155	 * Get current barcode.
15156	 * @return string
15157	 * @public
15158	 * @since 4.0.012 (2008-07-24)
15159	 */
15160	public function getBarcode() {
15161		return $this->barcode;
15162	}
15163
15164	/**
15165	 * Print a Linear Barcode.
15166	 * @param $code (string) code to print
15167	 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15168	 * @param $x (int) x position in user units (empty string = current x position)
15169	 * @param $y (int) y position in user units (empty string = current y position)
15170	 * @param $w (int) width in user units (empty string = remaining page width)
15171	 * @param $h (int) height in user units (empty string = remaining page height)
15172	 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15173	 * @param $style (array) array of options:<ul>
15174	 * <li>boolean $style['border'] if true prints a border</li>
15175	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15176	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15177	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15178	 * <li>array $style['fgcolor'] color array for bars and text</li>
15179	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15180	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15181	 * <li>string $style['label'] override default label</li>
15182	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15183	 * <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>
15184	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15185	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15186	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15187	 * <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>
15188	 * <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>
15189	 * @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>
15190	 * @author Nicola Asuni
15191	 * @since 3.1.000 (2008-06-09)
15192	 * @public
15193	 */
15194	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') {
15195		if (TCPDF_STATIC::empty_string(trim($code))) {
15196			return;
15197		}
15198		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15199		// save current graphic settings
15200		$gvars = $this->getGraphicVars();
15201		// create new barcode object
15202		$barcodeobj = new TCPDFBarcode($code, $type);
15203		$arrcode = $barcodeobj->getBarcodeArray();
15204		if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15205			$this->Error('Error in 1D barcode string');
15206		}
15207		if ($arrcode['maxh'] <= 0) {
15208			$arrcode['maxh'] = 1;
15209		}
15210		// set default values
15211		if (!isset($style['position'])) {
15212			$style['position'] = '';
15213		} elseif ($style['position'] == 'S') {
15214			// keep this for backward compatibility
15215			$style['position'] = '';
15216			$style['stretch'] = true;
15217		}
15218		if (!isset($style['fitwidth'])) {
15219			if (!isset($style['stretch'])) {
15220				$style['fitwidth'] = true;
15221			} else {
15222				$style['fitwidth'] = false;
15223			}
15224		}
15225		if ($style['fitwidth']) {
15226			// disable stretch
15227			$style['stretch'] = false;
15228		}
15229		if (!isset($style['stretch'])) {
15230			if (($w === '') OR ($w <= 0)) {
15231				$style['stretch'] = false;
15232			} else {
15233				$style['stretch'] = true;
15234			}
15235		}
15236		if (!isset($style['fgcolor'])) {
15237			$style['fgcolor'] = array(0,0,0); // default black
15238		}
15239		if (!isset($style['bgcolor'])) {
15240			$style['bgcolor'] = false; // default transparent
15241		}
15242		if (!isset($style['border'])) {
15243			$style['border'] = false;
15244		}
15245		$fontsize = 0;
15246		if (!isset($style['text'])) {
15247			$style['text'] = false;
15248		}
15249		if ($style['text'] AND isset($style['font'])) {
15250			if (isset($style['fontsize'])) {
15251				$fontsize = $style['fontsize'];
15252			}
15253			$this->SetFont($style['font'], '', $fontsize);
15254		}
15255		if (!isset($style['stretchtext'])) {
15256			$style['stretchtext'] = 4;
15257		}
15258		if ($x === '') {
15259			$x = $this->x;
15260		}
15261		if ($y === '') {
15262			$y = $this->y;
15263		}
15264		// check page for no-write regions and adapt page margins if necessary
15265		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15266		if (($w === '') OR ($w <= 0)) {
15267			if ($this->rtl) {
15268				$w = $x - $this->lMargin;
15269			} else {
15270				$w = $this->w - $this->rMargin - $x;
15271			}
15272		}
15273		// padding
15274		if (!isset($style['padding'])) {
15275			$padding = 0;
15276		} elseif ($style['padding'] === 'auto') {
15277			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15278		} else {
15279			$padding = floatval($style['padding']);
15280		}
15281		// horizontal padding
15282		if (!isset($style['hpadding'])) {
15283			$hpadding = $padding;
15284		} elseif ($style['hpadding'] === 'auto') {
15285			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15286		} else {
15287			$hpadding = floatval($style['hpadding']);
15288		}
15289		// vertical padding
15290		if (!isset($style['vpadding'])) {
15291			$vpadding = $padding;
15292		} elseif ($style['vpadding'] === 'auto') {
15293			$vpadding = ($hpadding / 2);
15294		} else {
15295			$vpadding = floatval($style['vpadding']);
15296		}
15297		// calculate xres (single bar width)
15298		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15299		if ($style['stretch']) {
15300			$xres = $max_xres;
15301		} else {
15302			if (TCPDF_STATIC::empty_string($xres)) {
15303				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15304			}
15305			if ($xres > $max_xres) {
15306				// correct xres to fit on $w
15307				$xres = $max_xres;
15308			}
15309			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15310				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15311				$hpadding = 10 * $xres;
15312				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15313					$vpadding = ($hpadding / 2);
15314				}
15315			}
15316		}
15317		if ($style['fitwidth']) {
15318			$wold = $w;
15319			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15320			if (isset($style['cellfitalign'])) {
15321				switch ($style['cellfitalign']) {
15322					case 'L': {
15323						if ($this->rtl) {
15324							$x -= ($wold - $w);
15325						}
15326						break;
15327					}
15328					case 'R': {
15329						if (!$this->rtl) {
15330							$x += ($wold - $w);
15331						}
15332						break;
15333					}
15334					case 'C': {
15335						if ($this->rtl) {
15336							$x -= (($wold - $w) / 2);
15337						} else {
15338							$x += (($wold - $w) / 2);
15339						}
15340						break;
15341					}
15342					default : {
15343						break;
15344					}
15345				}
15346			}
15347		}
15348		$text_height = $this->getCellHeight($fontsize / $this->k);
15349		// height
15350		if (($h === '') OR ($h <= 0)) {
15351			// set default height
15352			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15353		}
15354		$barh = $h - $text_height - (2 * $vpadding);
15355		if ($barh <=0) {
15356			// try to reduce font or padding to fit barcode on available height
15357			if ($text_height > $h) {
15358				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15359				$text_height = $this->getCellHeight($fontsize / $this->k);
15360				$this->SetFont($style['font'], '', $fontsize);
15361			}
15362			if ($vpadding > 0) {
15363				$vpadding = (($h - $text_height) / 4);
15364			}
15365			$barh = $h - $text_height - (2 * $vpadding);
15366		}
15367		// fit the barcode on available space
15368		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15369		// set alignment
15370		$this->img_rb_y = $y + $h;
15371		// set alignment
15372		if ($this->rtl) {
15373			if ($style['position'] == 'L') {
15374				$xpos = $this->lMargin;
15375			} elseif ($style['position'] == 'C') {
15376				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15377			} elseif ($style['position'] == 'R') {
15378				$xpos = $this->w - $this->rMargin - $w;
15379			} else {
15380				$xpos = $x - $w;
15381			}
15382			$this->img_rb_x = $xpos;
15383		} else {
15384			if ($style['position'] == 'L') {
15385				$xpos = $this->lMargin;
15386			} elseif ($style['position'] == 'C') {
15387				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15388			} elseif ($style['position'] == 'R') {
15389				$xpos = $this->w - $this->rMargin - $w;
15390			} else {
15391				$xpos = $x;
15392			}
15393			$this->img_rb_x = $xpos + $w;
15394		}
15395		$xpos_rect = $xpos;
15396		if (!isset($style['align'])) {
15397			$style['align'] = 'C';
15398		}
15399		switch ($style['align']) {
15400			case 'L': {
15401				$xpos = $xpos_rect + $hpadding;
15402				break;
15403			}
15404			case 'R': {
15405				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15406				break;
15407			}
15408			case 'C':
15409			default : {
15410				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15411				break;
15412			}
15413		}
15414		$xpos_text = $xpos;
15415		// barcode is always printed in LTR direction
15416		$tempRTL = $this->rtl;
15417		$this->rtl = false;
15418		// print background color
15419		if ($style['bgcolor']) {
15420			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15421		} elseif ($style['border']) {
15422			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15423		}
15424		// set foreground color
15425		$this->SetDrawColorArray($style['fgcolor']);
15426		$this->SetTextColorArray($style['fgcolor']);
15427		// print bars
15428		foreach ($arrcode['bcode'] as $k => $v) {
15429			$bw = ($v['w'] * $xres);
15430			if ($v['t']) {
15431				// draw a vertical bar
15432				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15433				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15434			}
15435			$xpos += $bw;
15436		}
15437		// print text
15438		if ($style['text']) {
15439			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15440				$label = $style['label'];
15441			} else {
15442				$label = $code;
15443			}
15444			$txtwidth = ($arrcode['maxw'] * $xres);
15445			if ($this->GetStringWidth($label) > $txtwidth) {
15446				$style['stretchtext'] = 2;
15447			}
15448			// print text
15449			$this->x = $xpos_text;
15450			$this->y = $y + $vpadding + $barh;
15451			$cellpadding = $this->cell_padding;
15452			$this->SetCellPadding(0);
15453			$this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15454			$this->cell_padding = $cellpadding;
15455		}
15456		// restore original direction
15457		$this->rtl = $tempRTL;
15458		// restore previous settings
15459		$this->setGraphicVars($gvars);
15460		// set pointer to align the next text/objects
15461		switch($align) {
15462			case 'T':{
15463				$this->y = $y;
15464				$this->x = $this->img_rb_x;
15465				break;
15466			}
15467			case 'M':{
15468				$this->y = $y + round($h / 2);
15469				$this->x = $this->img_rb_x;
15470				break;
15471			}
15472			case 'B':{
15473				$this->y = $this->img_rb_y;
15474				$this->x = $this->img_rb_x;
15475				break;
15476			}
15477			case 'N':{
15478				$this->SetY($this->img_rb_y);
15479				break;
15480			}
15481			default:{
15482				break;
15483			}
15484		}
15485		$this->endlinex = $this->img_rb_x;
15486	}
15487
15488	/**
15489	 * Print 2D Barcode.
15490	 * @param $code (string) code to print
15491	 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15492	 * @param $x (int) x position in user units
15493	 * @param $y (int) y position in user units
15494	 * @param $w (int) width in user units
15495	 * @param $h (int) height in user units
15496	 * @param $style (array) array of options:<ul>
15497	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15498	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15499	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15500	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15501	 * <li>int $style['module_width'] width of a single module in points</li>
15502	 * <li>int $style['module_height'] height of a single module in points</li>
15503	 * <li>array $style['fgcolor'] color array for bars and text</li>
15504	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15505	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
15506	 * <li>$style['module_height'] height of a single module in points</li></ul>
15507	 * @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>
15508	 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15509	 * @author Nicola Asuni
15510	 * @since 4.5.037 (2009-04-07)
15511	 * @public
15512	 */
15513	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) {
15514		if (TCPDF_STATIC::empty_string(trim($code))) {
15515			return;
15516		}
15517		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15518		// save current graphic settings
15519		$gvars = $this->getGraphicVars();
15520		// create new barcode object
15521		$barcodeobj = new TCPDF2DBarcode($code, $type);
15522		$arrcode = $barcodeobj->getBarcodeArray();
15523		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)) {
15524			$this->Error('Error in 2D barcode string');
15525		}
15526		// set default values
15527		if (!isset($style['position'])) {
15528			$style['position'] = '';
15529		}
15530		if (!isset($style['fgcolor'])) {
15531			$style['fgcolor'] = array(0,0,0); // default black
15532		}
15533		if (!isset($style['bgcolor'])) {
15534			$style['bgcolor'] = false; // default transparent
15535		}
15536		if (!isset($style['border'])) {
15537			$style['border'] = false;
15538		}
15539		// padding
15540		if (!isset($style['padding'])) {
15541			$style['padding'] = 0;
15542		} elseif ($style['padding'] === 'auto') {
15543			$style['padding'] = 4;
15544		}
15545		if (!isset($style['hpadding'])) {
15546			$style['hpadding'] = $style['padding'];
15547		} elseif ($style['hpadding'] === 'auto') {
15548			$style['hpadding'] = 4;
15549		}
15550		if (!isset($style['vpadding'])) {
15551			$style['vpadding'] = $style['padding'];
15552		} elseif ($style['vpadding'] === 'auto') {
15553			$style['vpadding'] = 4;
15554		}
15555		$hpad = (2 * $style['hpadding']);
15556		$vpad = (2 * $style['vpadding']);
15557		// cell (module) dimension
15558		if (!isset($style['module_width'])) {
15559			$style['module_width'] = 1; // width of a single module in points
15560		}
15561		if (!isset($style['module_height'])) {
15562			$style['module_height'] = 1; // height of a single module in points
15563		}
15564		if ($x === '') {
15565			$x = $this->x;
15566		}
15567		if ($y === '') {
15568			$y = $this->y;
15569		}
15570		// check page for no-write regions and adapt page margins if necessary
15571		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15572		// number of barcode columns and rows
15573		$rows = $arrcode['num_rows'];
15574		$cols = $arrcode['num_cols'];
15575		if (($rows <= 0) || ($cols <= 0)){
15576			$this->Error('Error in 2D barcode string');
15577		}
15578		// module width and height
15579		$mw = $style['module_width'];
15580		$mh = $style['module_height'];
15581		if (($mw <= 0) OR ($mh <= 0)) {
15582			$this->Error('Error in 2D barcode string');
15583		}
15584		// get max dimensions
15585		if ($this->rtl) {
15586			$maxw = $x - $this->lMargin;
15587		} else {
15588			$maxw = $this->w - $this->rMargin - $x;
15589		}
15590		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15591		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15592		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15593		if (!$distort) {
15594			if (($maxw * $ratioHW) > $maxh) {
15595				$maxw = $maxh * $ratioWH;
15596			}
15597			if (($maxh * $ratioWH) > $maxw) {
15598				$maxh = $maxw * $ratioHW;
15599			}
15600		}
15601		// set maximum dimensions
15602		if ($w > $maxw) {
15603			$w = $maxw;
15604		}
15605		if ($h > $maxh) {
15606			$h = $maxh;
15607		}
15608		// set dimensions
15609		if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15610			$w = ($cols + $hpad) * ($mw / $this->k);
15611			$h = ($rows + $vpad) * ($mh / $this->k);
15612		} elseif (($w === '') OR ($w <= 0)) {
15613			$w = $h * $ratioWH;
15614		} elseif (($h === '') OR ($h <= 0)) {
15615			$h = $w * $ratioHW;
15616		}
15617		// barcode size (excluding padding)
15618		$bw = ($w * $cols) / ($cols + $hpad);
15619		$bh = ($h * $rows) / ($rows + $vpad);
15620		// dimension of single barcode cell unit
15621		$cw = $bw / $cols;
15622		$ch = $bh / $rows;
15623		if (!$distort) {
15624			if (($cw / $ch) > ($mw / $mh)) {
15625				// correct horizontal distortion
15626				$cw = $ch * $mw / $mh;
15627				$bw = $cw * $cols;
15628				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15629			} else {
15630				// correct vertical distortion
15631				$ch = $cw * $mh / $mw;
15632				$bh = $ch * $rows;
15633				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15634			}
15635		}
15636		// fit the barcode on available space
15637		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15638		// set alignment
15639		$this->img_rb_y = $y + $h;
15640		// set alignment
15641		if ($this->rtl) {
15642			if ($style['position'] == 'L') {
15643				$xpos = $this->lMargin;
15644			} elseif ($style['position'] == 'C') {
15645				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15646			} elseif ($style['position'] == 'R') {
15647				$xpos = $this->w - $this->rMargin - $w;
15648			} else {
15649				$xpos = $x - $w;
15650			}
15651			$this->img_rb_x = $xpos;
15652		} else {
15653			if ($style['position'] == 'L') {
15654				$xpos = $this->lMargin;
15655			} elseif ($style['position'] == 'C') {
15656				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15657			} elseif ($style['position'] == 'R') {
15658				$xpos = $this->w - $this->rMargin - $w;
15659			} else {
15660				$xpos = $x;
15661			}
15662			$this->img_rb_x = $xpos + $w;
15663		}
15664		$xstart = $xpos + ($style['hpadding'] * $cw);
15665		$ystart = $y + ($style['vpadding'] * $ch);
15666		// barcode is always printed in LTR direction
15667		$tempRTL = $this->rtl;
15668		$this->rtl = false;
15669		// print background color
15670		if ($style['bgcolor']) {
15671			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15672		} elseif ($style['border']) {
15673			$this->Rect($xpos, $y, $w, $h, 'D');
15674		}
15675		// set foreground color
15676		$this->SetDrawColorArray($style['fgcolor']);
15677		// print barcode cells
15678		// for each row
15679		for ($r = 0; $r < $rows; ++$r) {
15680			$xr = $xstart;
15681			// for each column
15682			for ($c = 0; $c < $cols; ++$c) {
15683				if ($arrcode['bcode'][$r][$c] == 1) {
15684					// draw a single barcode cell
15685					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15686				}
15687				$xr += $cw;
15688			}
15689			$ystart += $ch;
15690		}
15691		// restore original direction
15692		$this->rtl = $tempRTL;
15693		// restore previous settings
15694		$this->setGraphicVars($gvars);
15695		// set pointer to align the next text/objects
15696		switch($align) {
15697			case 'T':{
15698				$this->y = $y;
15699				$this->x = $this->img_rb_x;
15700				break;
15701			}
15702			case 'M':{
15703				$this->y = $y + round($h/2);
15704				$this->x = $this->img_rb_x;
15705				break;
15706			}
15707			case 'B':{
15708				$this->y = $this->img_rb_y;
15709				$this->x = $this->img_rb_x;
15710				break;
15711			}
15712			case 'N':{
15713				$this->SetY($this->img_rb_y);
15714				break;
15715			}
15716			default:{
15717				break;
15718			}
15719		}
15720		$this->endlinex = $this->img_rb_x;
15721	}
15722
15723	/**
15724	 * Returns an array containing current margins:
15725	 * <ul>
15726			<li>$ret['left'] = left margin</li>
15727			<li>$ret['right'] = right margin</li>
15728			<li>$ret['top'] = top margin</li>
15729			<li>$ret['bottom'] = bottom margin</li>
15730			<li>$ret['header'] = header margin</li>
15731			<li>$ret['footer'] = footer margin</li>
15732			<li>$ret['cell'] = cell padding array</li>
15733			<li>$ret['padding_left'] = cell left padding</li>
15734			<li>$ret['padding_top'] = cell top padding</li>
15735			<li>$ret['padding_right'] = cell right padding</li>
15736			<li>$ret['padding_bottom'] = cell bottom padding</li>
15737	 * </ul>
15738	 * @return array containing all margins measures
15739	 * @public
15740	 * @since 3.2.000 (2008-06-23)
15741	 */
15742	public function getMargins() {
15743		$ret = array(
15744			'left' => $this->lMargin,
15745			'right' => $this->rMargin,
15746			'top' => $this->tMargin,
15747			'bottom' => $this->bMargin,
15748			'header' => $this->header_margin,
15749			'footer' => $this->footer_margin,
15750			'cell' => $this->cell_padding,
15751			'padding_left' => $this->cell_padding['L'],
15752			'padding_top' => $this->cell_padding['T'],
15753			'padding_right' => $this->cell_padding['R'],
15754			'padding_bottom' => $this->cell_padding['B']
15755		);
15756		return $ret;
15757	}
15758
15759	/**
15760	 * Returns an array containing original margins:
15761	 * <ul>
15762			<li>$ret['left'] = left margin</li>
15763			<li>$ret['right'] = right margin</li>
15764	 * </ul>
15765	 * @return array containing all margins measures
15766	 * @public
15767	 * @since 4.0.012 (2008-07-24)
15768	 */
15769	public function getOriginalMargins() {
15770		$ret = array(
15771			'left' => $this->original_lMargin,
15772			'right' => $this->original_rMargin
15773		);
15774		return $ret;
15775	}
15776
15777	/**
15778	 * Returns the current font size.
15779	 * @return current font size
15780	 * @public
15781	 * @since 3.2.000 (2008-06-23)
15782	 */
15783	public function getFontSize() {
15784		return $this->FontSize;
15785	}
15786
15787	/**
15788	 * Returns the current font size in points unit.
15789	 * @return current font size in points unit
15790	 * @public
15791	 * @since 3.2.000 (2008-06-23)
15792	 */
15793	public function getFontSizePt() {
15794		return $this->FontSizePt;
15795	}
15796
15797	/**
15798	 * Returns the current font family name.
15799	 * @return string current font family name
15800	 * @public
15801	 * @since 4.3.008 (2008-12-05)
15802	 */
15803	public function getFontFamily() {
15804		return $this->FontFamily;
15805	}
15806
15807	/**
15808	 * Returns the current font style.
15809	 * @return string current font style
15810	 * @public
15811	 * @since 4.3.008 (2008-12-05)
15812	 */
15813	public function getFontStyle() {
15814		return $this->FontStyle;
15815	}
15816
15817	/**
15818	 * Cleanup HTML code (requires HTML Tidy library).
15819	 * @param $html (string) htmlcode to fix
15820	 * @param $default_css (string) CSS commands to add
15821	 * @param $tagvs (array) parameters for setHtmlVSpace method
15822	 * @param $tidy_options (array) options for tidy_parse_string function
15823	 * @return string XHTML code cleaned up
15824	 * @author Nicola Asuni
15825	 * @public
15826	 * @since 5.9.017 (2010-11-16)
15827	 * @see setHtmlVSpace()
15828	 */
15829	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15830		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15831	}
15832
15833	/**
15834	 * Returns the border width from CSS property
15835	 * @param $width (string) border width
15836	 * @return int with in user units
15837	 * @protected
15838	 * @since 5.7.000 (2010-08-02)
15839	 */
15840	protected function getCSSBorderWidth($width) {
15841		if ($width == 'thin') {
15842			$width = (2 / $this->k);
15843		} elseif ($width == 'medium') {
15844			$width = (4 / $this->k);
15845		} elseif ($width == 'thick') {
15846			$width = (6 / $this->k);
15847		} else {
15848			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15849		}
15850		return $width;
15851	}
15852
15853	/**
15854	 * Returns the border dash style from CSS property
15855	 * @param $style (string) border style to convert
15856	 * @return int sash style (return -1 in case of none or hidden border)
15857	 * @protected
15858	 * @since 5.7.000 (2010-08-02)
15859	 */
15860	protected function getCSSBorderDashStyle($style) {
15861		switch (strtolower($style)) {
15862			case 'none':
15863			case 'hidden': {
15864				$dash = -1;
15865				break;
15866			}
15867			case 'dotted': {
15868				$dash = 1;
15869				break;
15870			}
15871			case 'dashed': {
15872				$dash = 3;
15873				break;
15874			}
15875			case 'double':
15876			case 'groove':
15877			case 'ridge':
15878			case 'inset':
15879			case 'outset':
15880			case 'solid':
15881			default: {
15882				$dash = 0;
15883				break;
15884			}
15885		}
15886		return $dash;
15887	}
15888
15889	/**
15890	 * Returns the border style array from CSS border properties
15891	 * @param $cssborder (string) border properties
15892	 * @return array containing border properties
15893	 * @protected
15894	 * @since 5.7.000 (2010-08-02)
15895	 */
15896	protected function getCSSBorderStyle($cssborder) {
15897		$bprop = preg_split('/[\s]+/', trim($cssborder));
15898		$border = array(); // value to be returned
15899		switch (count($bprop)) {
15900			case 3: {
15901				$width = $bprop[0];
15902				$style = $bprop[1];
15903				$color = $bprop[2];
15904				break;
15905			}
15906			case 2: {
15907				$width = 'medium';
15908				$style = $bprop[0];
15909				$color = $bprop[1];
15910				break;
15911			}
15912			case 1: {
15913				$width = 'medium';
15914				$style = $bprop[0];
15915				$color = 'black';
15916				break;
15917			}
15918			default: {
15919				$width = 'medium';
15920				$style = 'solid';
15921				$color = 'black';
15922				break;
15923			}
15924		}
15925		if ($style == 'none') {
15926			return array();
15927		}
15928		$border['cap'] = 'square';
15929		$border['join'] = 'miter';
15930		$border['dash'] = $this->getCSSBorderDashStyle($style);
15931		if ($border['dash'] < 0) {
15932			return array();
15933		}
15934		$border['width'] = $this->getCSSBorderWidth($width);
15935		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
15936		return $border;
15937	}
15938
15939	/**
15940	 * Get the internal Cell padding from CSS attribute.
15941	 * @param $csspadding (string) padding properties
15942	 * @param $width (float) width of the containing element
15943	 * @return array of cell paddings
15944	 * @public
15945	 * @since 5.9.000 (2010-10-04)
15946	 */
15947	public function getCSSPadding($csspadding, $width=0) {
15948		$padding = preg_split('/[\s]+/', trim($csspadding));
15949		$cell_padding = array(); // value to be returned
15950		switch (count($padding)) {
15951			case 4: {
15952				$cell_padding['T'] = $padding[0];
15953				$cell_padding['R'] = $padding[1];
15954				$cell_padding['B'] = $padding[2];
15955				$cell_padding['L'] = $padding[3];
15956				break;
15957			}
15958			case 3: {
15959				$cell_padding['T'] = $padding[0];
15960				$cell_padding['R'] = $padding[1];
15961				$cell_padding['B'] = $padding[2];
15962				$cell_padding['L'] = $padding[1];
15963				break;
15964			}
15965			case 2: {
15966				$cell_padding['T'] = $padding[0];
15967				$cell_padding['R'] = $padding[1];
15968				$cell_padding['B'] = $padding[0];
15969				$cell_padding['L'] = $padding[1];
15970				break;
15971			}
15972			case 1: {
15973				$cell_padding['T'] = $padding[0];
15974				$cell_padding['R'] = $padding[0];
15975				$cell_padding['B'] = $padding[0];
15976				$cell_padding['L'] = $padding[0];
15977				break;
15978			}
15979			default: {
15980				return $this->cell_padding;
15981			}
15982		}
15983		if ($width == 0) {
15984			$width = $this->w - $this->lMargin - $this->rMargin;
15985		}
15986		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
15987		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
15988		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
15989		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
15990		return $cell_padding;
15991	}
15992
15993	/**
15994	 * Get the internal Cell margin from CSS attribute.
15995	 * @param $cssmargin (string) margin properties
15996	 * @param $width (float) width of the containing element
15997	 * @return array of cell margins
15998	 * @public
15999	 * @since 5.9.000 (2010-10-04)
16000	 */
16001	public function getCSSMargin($cssmargin, $width=0) {
16002		$margin = preg_split('/[\s]+/', trim($cssmargin));
16003		$cell_margin = array(); // value to be returned
16004		switch (count($margin)) {
16005			case 4: {
16006				$cell_margin['T'] = $margin[0];
16007				$cell_margin['R'] = $margin[1];
16008				$cell_margin['B'] = $margin[2];
16009				$cell_margin['L'] = $margin[3];
16010				break;
16011			}
16012			case 3: {
16013				$cell_margin['T'] = $margin[0];
16014				$cell_margin['R'] = $margin[1];
16015				$cell_margin['B'] = $margin[2];
16016				$cell_margin['L'] = $margin[1];
16017				break;
16018			}
16019			case 2: {
16020				$cell_margin['T'] = $margin[0];
16021				$cell_margin['R'] = $margin[1];
16022				$cell_margin['B'] = $margin[0];
16023				$cell_margin['L'] = $margin[1];
16024				break;
16025			}
16026			case 1: {
16027				$cell_margin['T'] = $margin[0];
16028				$cell_margin['R'] = $margin[0];
16029				$cell_margin['B'] = $margin[0];
16030				$cell_margin['L'] = $margin[0];
16031				break;
16032			}
16033			default: {
16034				return $this->cell_margin;
16035			}
16036		}
16037		if ($width == 0) {
16038			$width = $this->w - $this->lMargin - $this->rMargin;
16039		}
16040		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16041		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16042		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16043		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16044		return $cell_margin;
16045	}
16046
16047	/**
16048	 * Get the border-spacing from CSS attribute.
16049	 * @param $cssbspace (string) border-spacing CSS properties
16050	 * @param $width (float) width of the containing element
16051	 * @return array of border spacings
16052	 * @public
16053	 * @since 5.9.010 (2010-10-27)
16054	 */
16055	public function getCSSBorderMargin($cssbspace, $width=0) {
16056		$space = preg_split('/[\s]+/', trim($cssbspace));
16057		$border_spacing = array(); // value to be returned
16058		switch (count($space)) {
16059			case 2: {
16060				$border_spacing['H'] = $space[0];
16061				$border_spacing['V'] = $space[1];
16062				break;
16063			}
16064			case 1: {
16065				$border_spacing['H'] = $space[0];
16066				$border_spacing['V'] = $space[0];
16067				break;
16068			}
16069			default: {
16070				return array('H' => 0, 'V' => 0);
16071			}
16072		}
16073		if ($width == 0) {
16074			$width = $this->w - $this->lMargin - $this->rMargin;
16075		}
16076		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16077		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16078		return $border_spacing;
16079	}
16080
16081	/**
16082	 * Returns the letter-spacing value from CSS value
16083	 * @param $spacing (string) letter-spacing value
16084	 * @param $parent (float) font spacing (tracking) value of the parent element
16085	 * @return float quantity to increases or decreases the space between characters in a text.
16086	 * @protected
16087	 * @since 5.9.000 (2010-10-02)
16088	 */
16089	protected function getCSSFontSpacing($spacing, $parent=0) {
16090		$val = 0; // value to be returned
16091		$spacing = trim($spacing);
16092		switch ($spacing) {
16093			case 'normal': {
16094				$val = 0;
16095				break;
16096			}
16097			case 'inherit': {
16098				if ($parent == 'normal') {
16099					$val = 0;
16100				} else {
16101					$val = $parent;
16102				}
16103				break;
16104			}
16105			default: {
16106				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16107			}
16108		}
16109		return $val;
16110	}
16111
16112	/**
16113	 * Returns the percentage of font stretching from CSS value
16114	 * @param $stretch (string) stretch mode
16115	 * @param $parent (float) stretch value of the parent element
16116	 * @return float font stretching percentage
16117	 * @protected
16118	 * @since 5.9.000 (2010-10-02)
16119	 */
16120	protected function getCSSFontStretching($stretch, $parent=100) {
16121		$val = 100; // value to be returned
16122		$stretch = trim($stretch);
16123		switch ($stretch) {
16124			case 'ultra-condensed': {
16125				$val = 40;
16126				break;
16127			}
16128			case 'extra-condensed': {
16129				$val = 55;
16130				break;
16131			}
16132			case 'condensed': {
16133				$val = 70;
16134				break;
16135			}
16136			case 'semi-condensed': {
16137				$val = 85;
16138				break;
16139			}
16140			case 'normal': {
16141				$val = 100;
16142				break;
16143			}
16144			case 'semi-expanded': {
16145				$val = 115;
16146				break;
16147			}
16148			case 'expanded': {
16149				$val = 130;
16150				break;
16151			}
16152			case 'extra-expanded': {
16153				$val = 145;
16154				break;
16155			}
16156			case 'ultra-expanded': {
16157				$val = 160;
16158				break;
16159			}
16160			case 'wider': {
16161				$val = ($parent + 10);
16162				break;
16163			}
16164			case 'narrower': {
16165				$val = ($parent - 10);
16166				break;
16167			}
16168			case 'inherit': {
16169				if ($parent == 'normal') {
16170					$val = 100;
16171				} else {
16172					$val = $parent;
16173				}
16174				break;
16175			}
16176			default: {
16177				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16178			}
16179		}
16180		return $val;
16181	}
16182
16183	/**
16184	 * Convert HTML string containing font size value to points
16185	 * @param $val (string) String containing font size value and unit.
16186	 * @param $refsize (float) Reference font size in points.
16187	 * @param $parent_size (float) Parent font size in points.
16188	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16189	 * @return float value in points
16190	 * @public
16191	 */
16192	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16193		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16194		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16195		switch ($val) {
16196			case 'xx-small': {
16197				$size = ($refsize - 4);
16198				break;
16199			}
16200			case 'x-small': {
16201				$size = ($refsize - 3);
16202				break;
16203			}
16204			case 'small': {
16205				$size = ($refsize - 2);
16206				break;
16207			}
16208			case 'medium': {
16209				$size = $refsize;
16210				break;
16211			}
16212			case 'large': {
16213				$size = ($refsize + 2);
16214				break;
16215			}
16216			case 'x-large': {
16217				$size = ($refsize + 4);
16218				break;
16219			}
16220			case 'xx-large': {
16221				$size = ($refsize + 6);
16222				break;
16223			}
16224			case 'smaller': {
16225				$size = ($parent_size - 3);
16226				break;
16227			}
16228			case 'larger': {
16229				$size = ($parent_size + 3);
16230				break;
16231			}
16232			default: {
16233				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16234			}
16235		}
16236		return $size;
16237	}
16238
16239	/**
16240	 * Returns the HTML DOM array.
16241	 * @param $html (string) html code
16242	 * @return array
16243	 * @protected
16244	 * @since 3.2.000 (2008-06-20)
16245	 */
16246	protected function getHtmlDomArray($html) {
16247		// array of CSS styles ( selector => properties).
16248		$css = array();
16249		// get CSS array defined at previous call
16250		$matches = array();
16251		if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16252			if (isset($matches[1][0])) {
16253				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16254			}
16255			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16256		}
16257		// extract external CSS files
16258		$matches = array();
16259		if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16260			foreach ($matches[1] as $key => $link) {
16261				$type = array();
16262				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16263					$type = array();
16264					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16265					// get 'all' and 'print' media, other media types are discarded
16266					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16267					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16268						$type = array();
16269						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16270							// read CSS data file
16271							$cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16272							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16273								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16274							}
16275						}
16276					}
16277				}
16278			}
16279		}
16280		// extract style tags
16281		$matches = array();
16282		if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16283			foreach ($matches[1] as $key => $media) {
16284				$type = array();
16285				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16286				// get 'all' and 'print' media, other media types are discarded
16287				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16288				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16289					$cssdata = $matches[2][$key];
16290					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16291				}
16292			}
16293		}
16294		// create a special tag to contain the CSS array (used for table content)
16295		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16296		// remove head and style blocks
16297		$html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16298		$html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16299		// define block tags
16300		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16301		// define self-closing tags
16302		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16303		// remove all unsupported tags (the line below lists all supported tags)
16304		$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>');
16305		//replace some blank characters
16306		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16307		$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);
16308		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16309		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16310		$html = strtr($html, $repTable);
16311		$offset = 0;
16312		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16313			$html_a = substr($html, 0, $offset);
16314			$html_b = substr($html, $offset, ($pos - $offset + 6));
16315			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16316				// preserve newlines on <pre> tag
16317				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16318			}
16319			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16320				// preserve spaces on <pre> tag
16321				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16322			}
16323			$html = $html_a.$html_b.substr($html, $pos + 6);
16324			$offset = strlen($html_a.$html_b);
16325		}
16326		$offset = 0;
16327		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16328			$html_a = substr($html, 0, $offset);
16329			$html_b = substr($html, $offset, ($pos - $offset + 11));
16330			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16331				// preserve newlines on <textarea> tag
16332				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16333				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16334			}
16335			$html = $html_a.$html_b.substr($html, $pos + 11);
16336			$offset = strlen($html_a.$html_b);
16337		}
16338		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16339		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16340		$offset = 0;
16341		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16342			$html_a = substr($html, 0, $offset);
16343			$html_b = substr($html, $offset, ($pos - $offset + 9));
16344			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16345				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16346				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16347			}
16348			$html = $html_a.$html_b.substr($html, $pos + 9);
16349			$offset = strlen($html_a.$html_b);
16350		}
16351		if (preg_match("'</select'si", $html)) {
16352			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16353			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16354		}
16355		$html = str_replace("\n", ' ', $html);
16356		// restore textarea newlines
16357		$html = str_replace('<TBR>', "\n", $html);
16358		// remove extra spaces from code
16359		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16360		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16361		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16362		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16363		$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);
16364		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16365		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16366		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16367		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16368		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16369		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16370		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16371		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16372		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16373		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16374		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16375		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16376		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16377		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16378		// trim string
16379		$html = $this->stringTrim($html);
16380		// fix br tag after li
16381		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16382		// fix first image tag alignment
16383		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16384		// pattern for generic tag
16385		$tagpattern = '/(<[^>]+>)/';
16386		// explodes the string
16387		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16388		// count elements
16389		$maxel = count($a);
16390		$elkey = 0;
16391		$key = 0;
16392		// create an array of elements
16393		$dom = array();
16394		$dom[$key] = array();
16395		// set inheritable properties fot the first void element
16396		// 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
16397		$dom[$key]['tag'] = false;
16398		$dom[$key]['block'] = false;
16399		$dom[$key]['value'] = '';
16400		$dom[$key]['parent'] = 0;
16401		$dom[$key]['hide'] = false;
16402		$dom[$key]['fontname'] = $this->FontFamily;
16403		$dom[$key]['fontstyle'] = $this->FontStyle;
16404		$dom[$key]['fontsize'] = $this->FontSizePt;
16405		$dom[$key]['font-stretch'] = $this->font_stretching;
16406		$dom[$key]['letter-spacing'] = $this->font_spacing;
16407		$dom[$key]['stroke'] = $this->textstrokewidth;
16408		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16409		$dom[$key]['clip'] = ($this->textrendermode > 3);
16410		$dom[$key]['line-height'] = $this->cell_height_ratio;
16411		$dom[$key]['bgcolor'] = false;
16412		$dom[$key]['fgcolor'] = $this->fgcolor; // color
16413		$dom[$key]['strokecolor'] = $this->strokecolor;
16414		$dom[$key]['align'] = '';
16415		$dom[$key]['listtype'] = '';
16416		$dom[$key]['text-indent'] = 0;
16417		$dom[$key]['text-transform'] = '';
16418		$dom[$key]['border'] = array();
16419		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16420		$thead = false; // true when we are inside the THEAD tag
16421		++$key;
16422		$level = array();
16423		array_push($level, 0); // root
16424		while ($elkey < $maxel) {
16425			$dom[$key] = array();
16426			$element = $a[$elkey];
16427			$dom[$key]['elkey'] = $elkey;
16428			if (preg_match($tagpattern, $element)) {
16429				// html tag
16430				$element = substr($element, 1, -1);
16431				// get tag name
16432				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16433				$tagname = strtolower($tag[1]);
16434				// check if we are inside a table header
16435				if ($tagname == 'thead') {
16436					if ($element[0] == '/') {
16437						$thead = false;
16438					} else {
16439						$thead = true;
16440					}
16441					++$elkey;
16442					continue;
16443				}
16444				$dom[$key]['tag'] = true;
16445				$dom[$key]['value'] = $tagname;
16446				if (in_array($dom[$key]['value'], $blocktags)) {
16447					$dom[$key]['block'] = true;
16448				} else {
16449					$dom[$key]['block'] = false;
16450				}
16451				if ($element[0] == '/') {
16452					// *** closing html tag
16453					$dom[$key]['opening'] = false;
16454					$dom[$key]['parent'] = end($level);
16455					array_pop($level);
16456					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16457					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16458					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16459					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16460					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16461					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16462					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16463					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16464					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16465					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16466					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16467					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16468					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16469					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16470					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16471					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16472					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16473						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16474					}
16475					// set the number of columns in table tag
16476					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16477						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16478					}
16479					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16480						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16481						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16482							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16483						}
16484						$key = $i;
16485						// mark nested tables
16486						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16487						// remove thead sections from nested tables
16488						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16489						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16490					}
16491					// store header rows on a new table
16492					if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16493						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16494							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16495						}
16496						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16497							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16498						}
16499						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16500							$dom[($dom[$key]['parent'])]['attribute'] = array();
16501						}
16502						// header elements must be always contained in a single page
16503						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16504					}
16505					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16506						// remove the nobr attributes from the table header
16507						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16508						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16509					}
16510				} else {
16511					// *** opening or self-closing html tag
16512					$dom[$key]['opening'] = true;
16513					$dom[$key]['parent'] = end($level);
16514					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16515						// self-closing tag
16516						$dom[$key]['self'] = true;
16517					} else {
16518						// opening tag
16519						array_push($level, $key);
16520						$dom[$key]['self'] = false;
16521					}
16522					// copy some values from parent
16523					$parentkey = 0;
16524					if ($key > 0) {
16525						$parentkey = $dom[$key]['parent'];
16526						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16527						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16528						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16529						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16530						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16531						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16532						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16533						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16534						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16535						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16536						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16537						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16538						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16539						$dom[$key]['align'] = $dom[$parentkey]['align'];
16540						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16541						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16542						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16543						$dom[$key]['border'] = array();
16544						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16545					}
16546					// get attributes
16547					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16548					$dom[$key]['attribute'] = array(); // reset attribute array
16549                    foreach($attr_array[1] as $id => $name) {
16550                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16551                    }
16552					if (!empty($css)) {
16553						// merge CSS style to current style
16554						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16555						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16556					}
16557					// split style attributes
16558					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16559						// get style attributes
16560						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16561						$dom[$key]['style'] = array(); // reset style attribute array
16562                        foreach($style_array[1] as $id => $name) {
16563                            // in case of duplicate attribute the last replace the previous
16564                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16565                        }
16566						// --- get some style attributes ---
16567						// text direction
16568						if (isset($dom[$key]['style']['direction'])) {
16569							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16570						}
16571						// display
16572						if (isset($dom[$key]['style']['display'])) {
16573							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16574						}
16575						// font family
16576						if (isset($dom[$key]['style']['font-family'])) {
16577							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16578						}
16579						// list-style-type
16580						if (isset($dom[$key]['style']['list-style-type'])) {
16581							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16582							if ($dom[$key]['listtype'] == 'inherit') {
16583								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16584							}
16585						}
16586						// text-indent
16587						if (isset($dom[$key]['style']['text-indent'])) {
16588							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16589							if ($dom[$key]['text-indent'] == 'inherit') {
16590								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16591							}
16592						}
16593						// text-transform
16594						if (isset($dom[$key]['style']['text-transform'])) {
16595							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16596						}
16597						// font size
16598						if (isset($dom[$key]['style']['font-size'])) {
16599							$fsize = trim($dom[$key]['style']['font-size']);
16600							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16601						}
16602						// font-stretch
16603						if (isset($dom[$key]['style']['font-stretch'])) {
16604							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16605						}
16606						// letter-spacing
16607						if (isset($dom[$key]['style']['letter-spacing'])) {
16608							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16609						}
16610						// line-height (internally is the cell height ratio)
16611						if (isset($dom[$key]['style']['line-height'])) {
16612							$lineheight = trim($dom[$key]['style']['line-height']);
16613							switch ($lineheight) {
16614								// A normal line height. This is default
16615								case 'normal': {
16616									$dom[$key]['line-height'] = $dom[0]['line-height'];
16617									break;
16618								}
16619								case 'inherit': {
16620									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16621								}
16622								default: {
16623									if (is_numeric($lineheight)) {
16624										// convert to percentage of font height
16625										$lineheight = ($lineheight * 100).'%';
16626									}
16627									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16628									if (substr($lineheight, -1) !== '%') {
16629										if ($dom[$key]['fontsize'] <= 0) {
16630											$dom[$key]['line-height'] = 1;
16631										} else {
16632											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16633										}
16634									}
16635								}
16636							}
16637						}
16638						// font style
16639						if (isset($dom[$key]['style']['font-weight'])) {
16640							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16641								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16642									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16643								}
16644							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16645								$dom[$key]['fontstyle'] .= 'B';
16646							}
16647						}
16648						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16649							$dom[$key]['fontstyle'] .= 'I';
16650						}
16651						// font color
16652						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16653							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16654						} elseif ($dom[$key]['value'] == 'a') {
16655							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16656						}
16657						// background color
16658						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16659							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16660						}
16661						// text-decoration
16662						if (isset($dom[$key]['style']['text-decoration'])) {
16663							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16664							foreach ($decors as $dec) {
16665								$dec = trim($dec);
16666								if (!TCPDF_STATIC::empty_string($dec)) {
16667									if ($dec[0] == 'u') {
16668										// underline
16669										$dom[$key]['fontstyle'] .= 'U';
16670									} elseif ($dec[0] == 'l') {
16671										// line-through
16672										$dom[$key]['fontstyle'] .= 'D';
16673									} elseif ($dec[0] == 'o') {
16674										// overline
16675										$dom[$key]['fontstyle'] .= 'O';
16676									}
16677								}
16678							}
16679						} elseif ($dom[$key]['value'] == 'a') {
16680							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16681						}
16682						// check for width attribute
16683						if (isset($dom[$key]['style']['width'])) {
16684							$dom[$key]['width'] = $dom[$key]['style']['width'];
16685						}
16686						// check for height attribute
16687						if (isset($dom[$key]['style']['height'])) {
16688							$dom[$key]['height'] = $dom[$key]['style']['height'];
16689						}
16690						// check for text alignment
16691						if (isset($dom[$key]['style']['text-align'])) {
16692							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16693						}
16694						// check for CSS border properties
16695						if (isset($dom[$key]['style']['border'])) {
16696							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16697							if (!empty($borderstyle)) {
16698								$dom[$key]['border']['LTRB'] = $borderstyle;
16699							}
16700						}
16701						if (isset($dom[$key]['style']['border-color'])) {
16702							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16703							if (isset($brd_colors[3])) {
16704								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16705							}
16706							if (isset($brd_colors[1])) {
16707								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16708							}
16709							if (isset($brd_colors[0])) {
16710								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16711							}
16712							if (isset($brd_colors[2])) {
16713								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16714							}
16715						}
16716						if (isset($dom[$key]['style']['border-width'])) {
16717							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16718							if (isset($brd_widths[3])) {
16719								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16720							}
16721							if (isset($brd_widths[1])) {
16722								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16723							}
16724							if (isset($brd_widths[0])) {
16725								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16726							}
16727							if (isset($brd_widths[2])) {
16728								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16729							}
16730						}
16731						if (isset($dom[$key]['style']['border-style'])) {
16732							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16733							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16734								$dom[$key]['border']['L']['cap'] = 'square';
16735								$dom[$key]['border']['L']['join'] = 'miter';
16736								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16737								if ($dom[$key]['border']['L']['dash'] < 0) {
16738									$dom[$key]['border']['L'] = array();
16739								}
16740							}
16741							if (isset($brd_styles[1])) {
16742								$dom[$key]['border']['R']['cap'] = 'square';
16743								$dom[$key]['border']['R']['join'] = 'miter';
16744								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16745								if ($dom[$key]['border']['R']['dash'] < 0) {
16746									$dom[$key]['border']['R'] = array();
16747								}
16748							}
16749							if (isset($brd_styles[0])) {
16750								$dom[$key]['border']['T']['cap'] = 'square';
16751								$dom[$key]['border']['T']['join'] = 'miter';
16752								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16753								if ($dom[$key]['border']['T']['dash'] < 0) {
16754									$dom[$key]['border']['T'] = array();
16755								}
16756							}
16757							if (isset($brd_styles[2])) {
16758								$dom[$key]['border']['B']['cap'] = 'square';
16759								$dom[$key]['border']['B']['join'] = 'miter';
16760								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16761								if ($dom[$key]['border']['B']['dash'] < 0) {
16762									$dom[$key]['border']['B'] = array();
16763								}
16764							}
16765						}
16766						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16767						foreach ($cellside as $bsk => $bsv) {
16768							if (isset($dom[$key]['style']['border-'.$bsv])) {
16769								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16770								if (!empty($borderstyle)) {
16771									$dom[$key]['border'][$bsk] = $borderstyle;
16772								}
16773							}
16774							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16775								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16776							}
16777							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16778								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16779							}
16780							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16781								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16782								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16783									$dom[$key]['border'][$bsk] = array();
16784								}
16785							}
16786						}
16787						// check for CSS padding properties
16788						if (isset($dom[$key]['style']['padding'])) {
16789							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16790						} else {
16791							$dom[$key]['padding'] = $this->cell_padding;
16792						}
16793						foreach ($cellside as $psk => $psv) {
16794							if (isset($dom[$key]['style']['padding-'.$psv])) {
16795								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16796							}
16797						}
16798						// check for CSS margin properties
16799						if (isset($dom[$key]['style']['margin'])) {
16800							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16801						} else {
16802							$dom[$key]['margin'] = $this->cell_margin;
16803						}
16804						foreach ($cellside as $psk => $psv) {
16805							if (isset($dom[$key]['style']['margin-'.$psv])) {
16806								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16807							}
16808						}
16809						// check for CSS border-spacing properties
16810						if (isset($dom[$key]['style']['border-spacing'])) {
16811							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16812						}
16813						// page-break-inside
16814						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16815							$dom[$key]['attribute']['nobr'] = 'true';
16816						}
16817						// page-break-before
16818						if (isset($dom[$key]['style']['page-break-before'])) {
16819							if ($dom[$key]['style']['page-break-before'] == 'always') {
16820								$dom[$key]['attribute']['pagebreak'] = 'true';
16821							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16822								$dom[$key]['attribute']['pagebreak'] = 'left';
16823							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16824								$dom[$key]['attribute']['pagebreak'] = 'right';
16825							}
16826						}
16827						// page-break-after
16828						if (isset($dom[$key]['style']['page-break-after'])) {
16829							if ($dom[$key]['style']['page-break-after'] == 'always') {
16830								$dom[$key]['attribute']['pagebreakafter'] = 'true';
16831							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16832								$dom[$key]['attribute']['pagebreakafter'] = 'left';
16833							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16834								$dom[$key]['attribute']['pagebreakafter'] = 'right';
16835							}
16836						}
16837					}
16838					if (isset($dom[$key]['attribute']['display'])) {
16839						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16840					}
16841					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16842						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16843						if (!empty($borderstyle)) {
16844							$dom[$key]['border']['LTRB'] = $borderstyle;
16845						}
16846					}
16847					// check for font tag
16848					if ($dom[$key]['value'] == 'font') {
16849						// font family
16850						if (isset($dom[$key]['attribute']['face'])) {
16851							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16852						}
16853						// font size
16854						if (isset($dom[$key]['attribute']['size'])) {
16855							if ($key > 0) {
16856								if ($dom[$key]['attribute']['size'][0] == '+') {
16857									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16858								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
16859									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16860								} else {
16861									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16862								}
16863							} else {
16864								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16865							}
16866						}
16867					}
16868					// force natural alignment for lists
16869					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16870						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16871						if ($this->rtl) {
16872							$dom[$key]['align'] = 'R';
16873						} else {
16874							$dom[$key]['align'] = 'L';
16875						}
16876					}
16877					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16878						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16879							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16880						}
16881					}
16882					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16883						$dom[$key]['fontstyle'] .= 'B';
16884					}
16885					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16886						$dom[$key]['fontstyle'] .= 'I';
16887					}
16888					if ($dom[$key]['value'] == 'u') {
16889						$dom[$key]['fontstyle'] .= 'U';
16890					}
16891					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16892						$dom[$key]['fontstyle'] .= 'D';
16893					}
16894					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16895						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16896					}
16897					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16898						$dom[$key]['fontname'] = $this->default_monospaced_font;
16899					}
16900					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)) {
16901						// headings h1, h2, h3, h4, h5, h6
16902						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16903							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
16904							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16905						}
16906						if (!isset($dom[$key]['style']['font-weight'])) {
16907							$dom[$key]['fontstyle'] .= 'B';
16908						}
16909					}
16910					if (($dom[$key]['value'] == 'table')) {
16911						$dom[$key]['rows'] = 0; // number of rows
16912						$dom[$key]['trids'] = array(); // IDs of TR elements
16913						$dom[$key]['thead'] = ''; // table header rows
16914					}
16915					if (($dom[$key]['value'] == 'tr')) {
16916						$dom[$key]['cols'] = 0;
16917						if ($thead) {
16918							$dom[$key]['thead'] = true;
16919							// rows on thead block are printed as a separate table
16920						} else {
16921							$dom[$key]['thead'] = false;
16922							// store the number of rows on table element
16923							++$dom[($dom[$key]['parent'])]['rows'];
16924							// store the TR elements IDs on table element
16925							array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16926						}
16927					}
16928					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16929						if (isset($dom[$key]['attribute']['colspan'])) {
16930							$colspan = intval($dom[$key]['attribute']['colspan']);
16931						} else {
16932							$colspan = 1;
16933						}
16934						$dom[$key]['attribute']['colspan'] = $colspan;
16935						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
16936					}
16937					// text direction
16938					if (isset($dom[$key]['attribute']['dir'])) {
16939						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
16940					}
16941					// set foreground color attribute
16942					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
16943						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
16944					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
16945						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16946					}
16947					// set background color attribute
16948					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
16949						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
16950					}
16951					// set stroke color attribute
16952					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
16953						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
16954					}
16955					// check for width attribute
16956					if (isset($dom[$key]['attribute']['width'])) {
16957						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
16958					}
16959					// check for height attribute
16960					if (isset($dom[$key]['attribute']['height'])) {
16961						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
16962					}
16963					// check for text alignment
16964					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
16965						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
16966					}
16967					// check for text rendering mode (the following attributes do not exist in HTML)
16968					if (isset($dom[$key]['attribute']['stroke'])) {
16969						// font stroke width
16970						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
16971					}
16972					if (isset($dom[$key]['attribute']['fill'])) {
16973						// font fill
16974						if ($dom[$key]['attribute']['fill'] == 'true') {
16975							$dom[$key]['fill'] = true;
16976						} else {
16977							$dom[$key]['fill'] = false;
16978						}
16979					}
16980					if (isset($dom[$key]['attribute']['clip'])) {
16981						// clipping mode
16982						if ($dom[$key]['attribute']['clip'] == 'true') {
16983							$dom[$key]['clip'] = true;
16984						} else {
16985							$dom[$key]['clip'] = false;
16986						}
16987					}
16988				} // end opening tag
16989			} else {
16990				// text
16991				$dom[$key]['tag'] = false;
16992				$dom[$key]['block'] = false;
16993				$dom[$key]['parent'] = end($level);
16994				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
16995				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
16996					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
16997					if (function_exists('mb_convert_case')) {
16998						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
16999						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17000							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17001						}
17002					} elseif (!$this->isunicode) {
17003						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17004							case 'capitalize': {
17005								$element = ucwords(strtolower($element));
17006								break;
17007							}
17008							case 'uppercase': {
17009								$element = strtoupper($element);
17010								break;
17011							}
17012							case 'lowercase': {
17013								$element = strtolower($element);
17014								break;
17015							}
17016						}
17017					}
17018				}
17019				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17020			}
17021			++$elkey;
17022			++$key;
17023		}
17024		return $dom;
17025	}
17026
17027	/**
17028	 * Returns the string used to find spaces
17029	 * @return string
17030	 * @protected
17031	 * @author Nicola Asuni
17032	 * @since 4.8.024 (2010-01-15)
17033	 */
17034	protected function getSpaceString() {
17035		$spacestr = chr(32);
17036		if ($this->isUnicodeFont()) {
17037			$spacestr = chr(0).chr(32);
17038		}
17039		return $spacestr;
17040	}
17041
17042	/**
17043	 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17044	 * @param $data (string) serialized data
17045	 * @return string
17046	 * @public static
17047	 */
17048	protected function getHashForTCPDFtagParams($data) {
17049		return md5(strlen($data).$this->file_id.$data);
17050	}
17051
17052	/**
17053	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17054	 * @param $data (array) parameters array
17055	 * @return string containing serialized data
17056	 * @public static
17057	 */
17058	public function serializeTCPDFtagParameters($data) {
17059		$encoded = urlencode(json_encode($data));
17060		return $this->getHashForTCPDFtagParams($encoded).$encoded;
17061	}
17062
17063	/**
17064	 * Unserialize parameters to be used with TCPDF tag in HTML code.
17065	 * @param $data (string) serialized data
17066	 * @return array containing unserialized data
17067	 * @protected static
17068	 */
17069	protected function unserializeTCPDFtagParameters($data) {
17070		$hash = substr($data, 0, 32);
17071		$encoded = substr($data, 32);
17072		if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17073			$this->Error('Invalid parameters');
17074		}
17075		return json_decode(urldecode($encoded), true);
17076	}
17077
17078	/**
17079	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17080	 * 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 />
17081	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17082	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17083	 * 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
17084	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17085	 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17086	 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17087	 * @param $x (float) upper-left corner X coordinate
17088	 * @param $y (float) upper-left corner Y coordinate
17089	 * @param $html (string) html text to print. Default value: empty string.
17090	 * @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)))
17091	 * @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>
17092Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17093	 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17094	 * @param $reseth (boolean) if true reset the last cell height (default true).
17095	 * @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>
17096	 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17097	 * @see Multicell(), writeHTML()
17098	 * @public
17099	 */
17100	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17101		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17102	}
17103
17104	/**
17105	 * Allows to preserve some HTML formatting (limited support).<br />
17106	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17107	 * 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
17108	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17109	 * @param $html (string) text to display
17110	 * @param $ln (boolean) if true add a new line after text (default = true)
17111	 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17112	 * @param $reseth (boolean) if true reset the last cell height (default false).
17113	 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17114	 * @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>
17115	 * @public
17116	 */
17117	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17118		$gvars = $this->getGraphicVars();
17119		// store current values
17120		$prev_cell_margin = $this->cell_margin;
17121		$prev_cell_padding = $this->cell_padding;
17122		$prevPage = $this->page;
17123		$prevlMargin = $this->lMargin;
17124		$prevrMargin = $this->rMargin;
17125		$curfontname = $this->FontFamily;
17126		$curfontstyle = $this->FontStyle;
17127		$curfontsize = $this->FontSizePt;
17128		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17129		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17130		$curfontstretcing = $this->font_stretching;
17131		$curfonttracking = $this->font_spacing;
17132		$this->newline = true;
17133		$newline = true;
17134		$startlinepage = $this->page;
17135		$minstartliney = $this->y;
17136		$maxbottomliney = 0;
17137		$startlinex = $this->x;
17138		$startliney = $this->y;
17139		$yshift = 0;
17140		$loop = 0;
17141		$curpos = 0;
17142		$this_method_vars = array();
17143		$undo = false;
17144		$fontaligned = false;
17145		$reverse_dir = false; // true when the text direction is reversed
17146		$this->premode = false;
17147		if ($this->inxobj) {
17148			// we are inside an XObject template
17149			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17150		} elseif (isset($this->PageAnnots[$this->page])) {
17151			$pask = count($this->PageAnnots[$this->page]);
17152		} else {
17153			$pask = 0;
17154		}
17155		if ($this->inxobj) {
17156			// we are inside an XObject template
17157			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17158		} elseif (!$this->InFooter) {
17159			if (isset($this->footerlen[$this->page])) {
17160				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17161			} else {
17162				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17163			}
17164			$startlinepos = $this->footerpos[$this->page];
17165		} else {
17166			// we are inside the footer
17167			$startlinepos = $this->pagelen[$this->page];
17168		}
17169		$lalign = $align;
17170		$plalign = $align;
17171		if ($this->rtl) {
17172			$w = $this->x - $this->lMargin;
17173		} else {
17174			$w = $this->w - $this->rMargin - $this->x;
17175		}
17176		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17177		if ($cell) {
17178			if ($this->rtl) {
17179				$this->x -= $this->cell_padding['R'];
17180				$this->lMargin += $this->cell_padding['L'];
17181			} else {
17182				$this->x += $this->cell_padding['L'];
17183				$this->rMargin += $this->cell_padding['R'];
17184			}
17185		}
17186		if ($this->customlistindent >= 0) {
17187			$this->listindent = $this->customlistindent;
17188		} else {
17189			$this->listindent = $this->GetStringWidth('000000');
17190		}
17191		$this->listindentlevel = 0;
17192		// save previous states
17193		$prev_cell_height_ratio = $this->cell_height_ratio;
17194		$prev_listnum = $this->listnum;
17195		$prev_listordered = $this->listordered;
17196		$prev_listcount = $this->listcount;
17197		$prev_lispacer = $this->lispacer;
17198		$this->listnum = 0;
17199		$this->listordered = array();
17200		$this->listcount = array();
17201		$this->lispacer = '';
17202		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17203			// reset row height
17204			$this->resetLastH();
17205		}
17206		$dom = $this->getHtmlDomArray($html);
17207		$maxel = count($dom);
17208		$key = 0;
17209		while ($key < $maxel) {
17210			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17211				// store the node key
17212				$hidden_node_key = $key;
17213				if ($dom[$key]['self']) {
17214					// skip just this self-closing tag
17215					++$key;
17216				} else {
17217					// skip this and all children tags
17218					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17219						// skip hidden objects
17220						++$key;
17221					}
17222					++$key;
17223				}
17224			}
17225			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17226				// check for pagebreak
17227				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17228					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17229					$this->checkPageBreak($this->PageBreakTrigger + 1);
17230					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17231				}
17232				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17233					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17234					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17235					$this->checkPageBreak($this->PageBreakTrigger + 1);
17236					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17237				}
17238			}
17239			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17240				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17241					$dom[$key]['attribute']['nobr'] = false;
17242				} else {
17243					// store current object
17244					$this->startTransaction();
17245					// save this method vars
17246					$this_method_vars['html'] = $html;
17247					$this_method_vars['ln'] = $ln;
17248					$this_method_vars['fill'] = $fill;
17249					$this_method_vars['reseth'] = $reseth;
17250					$this_method_vars['cell'] = $cell;
17251					$this_method_vars['align'] = $align;
17252					$this_method_vars['gvars'] = $gvars;
17253					$this_method_vars['prevPage'] = $prevPage;
17254					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17255					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17256					$this_method_vars['prevlMargin'] = $prevlMargin;
17257					$this_method_vars['prevrMargin'] = $prevrMargin;
17258					$this_method_vars['curfontname'] = $curfontname;
17259					$this_method_vars['curfontstyle'] = $curfontstyle;
17260					$this_method_vars['curfontsize'] = $curfontsize;
17261					$this_method_vars['curfontascent'] = $curfontascent;
17262					$this_method_vars['curfontdescent'] = $curfontdescent;
17263					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17264					$this_method_vars['curfonttracking'] = $curfonttracking;
17265					$this_method_vars['minstartliney'] = $minstartliney;
17266					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17267					$this_method_vars['yshift'] = $yshift;
17268					$this_method_vars['startlinepage'] = $startlinepage;
17269					$this_method_vars['startlinepos'] = $startlinepos;
17270					$this_method_vars['startlinex'] = $startlinex;
17271					$this_method_vars['startliney'] = $startliney;
17272					$this_method_vars['newline'] = $newline;
17273					$this_method_vars['loop'] = $loop;
17274					$this_method_vars['curpos'] = $curpos;
17275					$this_method_vars['pask'] = $pask;
17276					$this_method_vars['lalign'] = $lalign;
17277					$this_method_vars['plalign'] = $plalign;
17278					$this_method_vars['w'] = $w;
17279					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17280					$this_method_vars['prev_listnum'] = $prev_listnum;
17281					$this_method_vars['prev_listordered'] = $prev_listordered;
17282					$this_method_vars['prev_listcount'] = $prev_listcount;
17283					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17284					$this_method_vars['fontaligned'] = $fontaligned;
17285					$this_method_vars['key'] = $key;
17286					$this_method_vars['dom'] = $dom;
17287				}
17288			}
17289			// print THEAD block
17290			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17291				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17292					$this->inthead = true;
17293					// print table header (thead)
17294					$this->writeHTML($this->thead, false, false, false, false, '');
17295					// check if we are on a new page or on a new column
17296					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17297						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17298						// restore previous object
17299						$this->rollbackTransaction(true);
17300						// restore previous values
17301						foreach ($this_method_vars as $vkey => $vval) {
17302							$$vkey = $vval;
17303						}
17304						// disable table header
17305						$tmp_thead = $this->thead;
17306						$this->thead = '';
17307						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17308						$pre_y = $this->y;
17309						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17310							// fix for multicolumn mode
17311							$startliney = $this->y;
17312						}
17313						$this->start_transaction_page = $this->page;
17314						$this->start_transaction_y = $this->y;
17315						// restore table header
17316						$this->thead = $tmp_thead;
17317						// fix table border properties
17318						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17319							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17320						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17321							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17322						} else {
17323							$tmp_cellspacing = 0;
17324						}
17325						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17326						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17327						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17328						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17329						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17330						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17331						// print table header (thead)
17332						$this->writeHTML($this->thead, false, false, false, false, '');
17333					}
17334				}
17335				// move $key index forward to skip THEAD block
17336				while ( ($key < $maxel) AND (!(
17337					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17338					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17339					++$key;
17340				}
17341			}
17342			if ($dom[$key]['tag'] OR ($key == 0)) {
17343				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17344					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17345				}
17346				// vertically align image in line
17347				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17348					// get image height
17349					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17350					$autolinebreak = false;
17351					if (!empty($dom[$key]['width'])) {
17352						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17353						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17354							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17355							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17356							// add automatic line break
17357							$autolinebreak = true;
17358							$this->Ln('', $cell);
17359							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17360								// go back to evaluate this line break
17361								--$key;
17362							}
17363						}
17364					}
17365					if (!$autolinebreak) {
17366						if ($this->inPageBody()) {
17367							$pre_y = $this->y;
17368							// check for page break
17369							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17370								// fix for multicolumn mode
17371								$startliney = $this->y;
17372							}
17373						}
17374						if ($this->page > $startlinepage) {
17375							// fix line splitted over two pages
17376							if (isset($this->footerlen[$startlinepage])) {
17377								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17378							}
17379							// line to be moved one page forward
17380							$pagebuff = $this->getPageBuffer($startlinepage);
17381							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17382							$tstart = substr($pagebuff, 0, $startlinepos);
17383							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17384							// remove line from previous page
17385							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17386							$pagebuff = $this->getPageBuffer($this->page);
17387							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17388							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17389							// add line start to current page
17390							$yshift = ($minstartliney - $this->y);
17391							if ($fontaligned) {
17392								$yshift += ($curfontsize / $this->k);
17393							}
17394							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17395							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17396							// shift the annotations and links
17397							if (isset($this->PageAnnots[$this->page])) {
17398								$next_pask = count($this->PageAnnots[$this->page]);
17399							} else {
17400								$next_pask = 0;
17401							}
17402							if (isset($this->PageAnnots[$startlinepage])) {
17403								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17404									if ($pak >= $pask) {
17405										$this->PageAnnots[$this->page][] = $pac;
17406										unset($this->PageAnnots[$startlinepage][$pak]);
17407										$npak = count($this->PageAnnots[$this->page]) - 1;
17408										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17409									}
17410								}
17411							}
17412							$pask = $next_pask;
17413							$startlinepos = $this->cntmrk[$this->page];
17414							$startlinepage = $this->page;
17415							$startliney = $this->y;
17416							$this->newline = false;
17417						}
17418						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17419						$minstartliney = min($this->y, $minstartliney);
17420						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17421					}
17422				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17423					// account for different font size
17424					$pfontname = $curfontname;
17425					$pfontstyle = $curfontstyle;
17426					$pfontsize = $curfontsize;
17427					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17428					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17429					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17430					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17431					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17432					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17433						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17434						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17435						if (($key < ($maxel - 1)) AND (
17436								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17437								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17438								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17439								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17440								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17441							)) {
17442							if ($this->page > $startlinepage) {
17443								// fix lines splitted over two pages
17444								if (isset($this->footerlen[$startlinepage])) {
17445									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17446								}
17447								// line to be moved one page forward
17448								$pagebuff = $this->getPageBuffer($startlinepage);
17449								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17450								$tstart = substr($pagebuff, 0, $startlinepos);
17451								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17452								// remove line start from previous page
17453								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17454								$pagebuff = $this->getPageBuffer($this->page);
17455								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17456								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17457								// add line start to current page
17458								$yshift = ($minstartliney - $this->y);
17459								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17460								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17461								// shift the annotations and links
17462								if (isset($this->PageAnnots[$this->page])) {
17463									$next_pask = count($this->PageAnnots[$this->page]);
17464								} else {
17465									$next_pask = 0;
17466								}
17467								if (isset($this->PageAnnots[$startlinepage])) {
17468									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17469										if ($pak >= $pask) {
17470											$this->PageAnnots[$this->page][] = $pac;
17471											unset($this->PageAnnots[$startlinepage][$pak]);
17472											$npak = count($this->PageAnnots[$this->page]) - 1;
17473											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17474										}
17475									}
17476								}
17477								$pask = $next_pask;
17478								$startlinepos = $this->cntmrk[$this->page];
17479								$startlinepage = $this->page;
17480								$startliney = $this->y;
17481							}
17482							if (!isset($dom[$key]['line-height'])) {
17483								$dom[$key]['line-height'] = $this->cell_height_ratio;
17484							}
17485							if (!$dom[$key]['block']) {
17486								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']))) {
17487									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17488								}
17489								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17490									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17491									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)))) {
17492										$minstartliney = min($this->y, $line_align_data[1]);
17493										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17494									} else {
17495										$minstartliney = min($this->y, $minstartliney);
17496										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17497									}
17498									$line_align_data = $current_line_align_data;
17499								}
17500							}
17501							$this->cell_height_ratio = $dom[$key]['line-height'];
17502							$fontaligned = true;
17503						}
17504						$this->SetFont($fontname, $fontstyle, $fontsize);
17505						// reset row height
17506						$this->resetLastH();
17507						$curfontname = $fontname;
17508						$curfontstyle = $fontstyle;
17509						$curfontsize = $fontsize;
17510						$curfontascent = $fontascent;
17511						$curfontdescent = $fontdescent;
17512					}
17513				}
17514				// set text rendering mode
17515				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17516				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17517				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17518				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17519				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17520					$this->setFontStretching($dom[$key]['font-stretch']);
17521				}
17522				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17523					$this->setFontSpacing($dom[$key]['letter-spacing']);
17524				}
17525				if (($plalign == 'J') AND $dom[$key]['block']) {
17526					$plalign = '';
17527				}
17528				// get current position on page buffer
17529				$curpos = $this->pagelen[$startlinepage];
17530				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17531					$this->SetFillColorArray($dom[$key]['bgcolor']);
17532					$wfill = true;
17533				} else {
17534					$wfill = $fill | false;
17535				}
17536				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17537					$this->SetTextColorArray($dom[$key]['fgcolor']);
17538				}
17539				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17540					$this->SetDrawColorArray($dom[$key]['strokecolor']);
17541				}
17542				if (isset($dom[$key]['align'])) {
17543					$lalign = $dom[$key]['align'];
17544				}
17545				if (TCPDF_STATIC::empty_string($lalign)) {
17546					$lalign = $align;
17547				}
17548			}
17549			// align lines
17550			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17551				$newline = true;
17552				$fontaligned = false;
17553				// we are at the beginning of a new line
17554				if (isset($startlinex)) {
17555					$yshift = ($minstartliney - $startliney);
17556					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17557						$yshift = 0;
17558					}
17559					$t_x = 0;
17560					// the last line must be shifted to be aligned as requested
17561					$linew = abs($this->endlinex - $startlinex);
17562					if ($this->inxobj) {
17563						// we are inside an XObject template
17564						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17565						if (isset($opentagpos)) {
17566							$midpos = $opentagpos;
17567						} else {
17568							$midpos = 0;
17569						}
17570						if ($midpos > 0) {
17571							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17572							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17573						} else {
17574							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17575							$pend = '';
17576						}
17577					} else {
17578						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17579						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17580							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17581							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17582						} elseif (isset($opentagpos)) {
17583							$midpos = $opentagpos;
17584						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17585							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17586							$midpos = $this->footerpos[$startlinepage];
17587						} else {
17588							$midpos = 0;
17589						}
17590						if ($midpos > 0) {
17591							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17592							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17593						} else {
17594							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17595							$pend = '';
17596						}
17597					}
17598					if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17599						// calculate shifting amount
17600						$tw = $w;
17601						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17602							$tw += $this->cell_padding['R'];
17603						}
17604						if ($this->lMargin != $prevlMargin) {
17605							$tw += ($prevlMargin - $this->lMargin);
17606						}
17607						if ($this->rMargin != $prevrMargin) {
17608							$tw += ($prevrMargin - $this->rMargin);
17609						}
17610						$one_space_width = $this->GetStringWidth(chr(32));
17611						$no = 0; // number of spaces on a line contained on a single block
17612						if ($this->isRTLTextDir()) { // RTL
17613							// remove left space if exist
17614							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17615							if ($pos1 > 0) {
17616								$pos1 = intval($pos1);
17617								if ($this->isUnicodeFont()) {
17618									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17619									$spacelen = 2;
17620								} else {
17621									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17622									$spacelen = 1;
17623								}
17624								if ($pos1 == $pos2) {
17625									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17626									if (substr($pmid, $pos1, 4) == '[()]') {
17627										$linew -= $one_space_width;
17628									} elseif ($pos1 == strpos($pmid, '[(')) {
17629										$no = 1;
17630									}
17631								}
17632							}
17633						} else { // LTR
17634							// remove right space if exist
17635							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17636							if ($pos1 > 0) {
17637								$pos1 = intval($pos1);
17638								if ($this->isUnicodeFont()) {
17639									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17640									$spacelen = 2;
17641								} else {
17642									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17643									$spacelen = 1;
17644								}
17645								if ($pos1 == $pos2) {
17646									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17647									$linew -= $one_space_width;
17648								}
17649							}
17650						}
17651						$mdiff = ($tw - $linew);
17652						if ($plalign == 'C') {
17653							if ($this->rtl) {
17654								$t_x = -($mdiff / 2);
17655							} else {
17656								$t_x = ($mdiff / 2);
17657							}
17658						} elseif ($plalign == 'R') {
17659							// right alignment on LTR document
17660							$t_x = $mdiff;
17661						} elseif ($plalign == 'L') {
17662							// left alignment on RTL document
17663							$t_x = -$mdiff;
17664						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17665							// Justification
17666							if ($this->isRTLTextDir()) {
17667								// align text on the left
17668								$t_x = -$mdiff;
17669							}
17670							$ns = 0; // number of spaces
17671							$pmidtemp = $pmid;
17672							// escape special characters
17673							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17674							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17675							// search spaces
17676							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17677								$spacestr = $this->getSpaceString();
17678								$maxkk = count($lnstring[1]) - 1;
17679								for ($kk=0; $kk <= $maxkk; ++$kk) {
17680									// restore special characters
17681									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17682									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17683									// store number of spaces on the strings
17684									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17685									// count total spaces on line
17686									$ns += $lnstring[2][$kk];
17687									$lnstring[3][$kk] = $ns;
17688								}
17689								if ($ns == 0) {
17690									$ns = 1;
17691								}
17692								// calculate additional space to add to each existing space
17693								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17694								if ($this->FontSize <= 0) {
17695									$this->FontSize = 1;
17696								}
17697								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17698								if ($this->font_spacing != 0) {
17699									// fixed spacing mode
17700									$osw = -1000 * $this->font_spacing / $this->FontSize;
17701									$spacewidthu += $osw;
17702								}
17703								$nsmax = $ns;
17704								$ns = 0;
17705								reset($lnstring);
17706								$offset = 0;
17707								$strcount = 0;
17708								$prev_epsposbeg = 0;
17709								$textpos = 0;
17710								if ($this->isRTLTextDir()) {
17711									$textpos = $this->wPt;
17712								}
17713								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17714									// check if we are inside a string section '[( ... )]'
17715									$stroffset = strpos($pmid, '[(', $offset);
17716									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17717										// set offset to the end of string section
17718										$offset = strpos($pmid, ')]', $stroffset);
17719										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17720											$offset = strpos($pmid, ')]', ($offset + 1));
17721										}
17722										if ($offset === false) {
17723											$this->Error('HTML Justification: malformed PDF code.');
17724										}
17725										continue;
17726									}
17727									if ($this->isRTLTextDir()) {
17728										$spacew = ($spacewidth * ($nsmax - $ns));
17729									} else {
17730										$spacew = ($spacewidth * $ns);
17731									}
17732									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17733									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17734									if ($epsposend !== null) {
17735										$epsposend += strlen($this->epsmarker.'Q');
17736										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17737										if ($epsposbeg === null) {
17738											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17739											$prev_epsposbeg = $epsposbeg;
17740										}
17741										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17742											// shift EPS images
17743											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17744											$pmid_b = substr($pmid, 0, $epsposbeg);
17745											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17746											$pmid_e = substr($pmid, $epsposend);
17747											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17748											$offset = $epsposend;
17749											continue;
17750										}
17751									}
17752									$currentxpos = 0;
17753									// shift blocks of code
17754									switch ($strpiece[2][0]) {
17755										case 'Td':
17756										case 'cm':
17757										case 'm':
17758										case 'l': {
17759											// get current X position
17760											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17761											if (!isset($xmatches[1])) {
17762												break;
17763											}
17764											$currentxpos = $xmatches[1];
17765											$textpos = $currentxpos;
17766											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17767												$ns = $lnstring[3][$strcount];
17768												if ($this->isRTLTextDir()) {
17769													$spacew = ($spacewidth * ($nsmax - $ns));
17770												}
17771												++$strcount;
17772											}
17773											// justify block
17774											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17775												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17776												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17777												unset($pmatch, $newpmid);
17778											}
17779											break;
17780										}
17781										case 're': {
17782											// justify block
17783											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17784												$this->lispacer = '';
17785												break;
17786											}
17787											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17788											if (!isset($xmatches[1])) {
17789												break;
17790											}
17791											$currentxpos = $xmatches[1];
17792											$x_diff = 0;
17793											$w_diff = 0;
17794											if ($this->isRTLTextDir()) { // RTL
17795												if ($currentxpos < $textpos) {
17796													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17797													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17798												} else {
17799													if ($strcount > 0) {
17800														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17801														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17802													}
17803												}
17804											} else { // LTR
17805												if ($currentxpos > $textpos) {
17806													if ($strcount > 0) {
17807														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17808													}
17809													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17810												} else {
17811													if ($strcount > 1) {
17812														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17813													}
17814													if ($strcount > 0) {
17815														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17816													}
17817												}
17818											}
17819											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17820												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17821												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17822												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17823												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17824												unset($pmatch, $newpmid, $newx, $neww);
17825											}
17826											break;
17827										}
17828										case 'c': {
17829											// get current X position
17830											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);
17831											if (!isset($xmatches[1])) {
17832												break;
17833											}
17834											$currentxpos = $xmatches[1];
17835											// justify block
17836											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) {
17837												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17838												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17839												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17840												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17841												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17842												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17843											}
17844											break;
17845										}
17846									}
17847									// shift the annotations and links
17848									$cxpos = ($currentxpos / $this->k);
17849									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17850									if ($this->inxobj) {
17851										// we are inside an XObject template
17852										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17853											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17854												if ($cxpos > $lmpos) {
17855													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17856													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17857												} else {
17858													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17859												}
17860												break;
17861											}
17862										}
17863									} elseif (isset($this->PageAnnots[$this->page])) {
17864										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17865											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17866												if ($cxpos > $lmpos) {
17867													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17868													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17869												} else {
17870													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17871												}
17872												break;
17873											}
17874										}
17875									}
17876								} // end of while
17877								// remove markers
17878								$pmid = str_replace('x*#!#*x', '', $pmid);
17879								if ($this->isUnicodeFont()) {
17880									// multibyte characters
17881									$spacew = $spacewidthu;
17882									if ($this->font_stretching != 100) {
17883										// word spacing is affected by stretching
17884										$spacew /= ($this->font_stretching / 100);
17885									}
17886									// escape special characters
17887									$pos = 0;
17888									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17889									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17890									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17891										foreach($pamatch[0] as $pk => $pmatch) {
17892											$replace = $pamatch[1][$pk];
17893											$replace = str_replace('#!#OP#!#', '(', $replace);
17894											$replace = str_replace('#!#CP#!#', ')', $replace);
17895											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17896											$pos = strpos($pmid, $pmatch, $pos);
17897											if ($pos !== FALSE) {
17898												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17899											}
17900											++$pos;
17901										}
17902										unset($pamatch);
17903									}
17904									if ($this->inxobj) {
17905										// we are inside an XObject template
17906										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17907									} else {
17908										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17909									}
17910									$endlinepos = strlen($pstart."\n".$pmid."\n");
17911								} else {
17912									// non-unicode (single-byte characters)
17913									if ($this->font_stretching != 100) {
17914										// word spacing (Tw) is affected by stretching
17915										$spacewidth /= ($this->font_stretching / 100);
17916									}
17917									$rs = sprintf('%F Tw', $spacewidth);
17918									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17919									if ($this->inxobj) {
17920										// we are inside an XObject template
17921										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17922									} else {
17923										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17924									}
17925									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17926								}
17927							}
17928						} // end of J
17929					} // end if $startlinex
17930					if (($t_x != 0) OR ($yshift < 0)) {
17931						// shift the line
17932						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
17933						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
17934						$endlinepos = strlen($pstart);
17935						if ($this->inxobj) {
17936							// we are inside an XObject template
17937							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
17938							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17939								if ($pak >= $pask) {
17940									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
17941									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
17942								}
17943							}
17944						} else {
17945							$this->setPageBuffer($startlinepage, $pstart.$pend);
17946							// shift the annotations and links
17947							if (isset($this->PageAnnots[$this->page])) {
17948								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17949									if ($pak >= $pask) {
17950										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
17951										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
17952									}
17953								}
17954							}
17955						}
17956						$this->y -= $yshift;
17957					}
17958				}
17959				$pbrk = $this->checkPageBreak($this->lasth);
17960				$this->newline = false;
17961				$startlinex = $this->x;
17962				$startliney = $this->y;
17963				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
17964					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
17965				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
17966					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
17967				} else {
17968					$minstartliney = $startliney;
17969					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
17970				}
17971				$startlinepage = $this->page;
17972				if (isset($endlinepos) AND (!$pbrk)) {
17973					$startlinepos = $endlinepos;
17974				} else {
17975					if ($this->inxobj) {
17976						// we are inside an XObject template
17977						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17978					} elseif (!$this->InFooter) {
17979						if (isset($this->footerlen[$this->page])) {
17980							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17981						} else {
17982							$this->footerpos[$this->page] = $this->pagelen[$this->page];
17983						}
17984						$startlinepos = $this->footerpos[$this->page];
17985					} else {
17986						$startlinepos = $this->pagelen[$this->page];
17987					}
17988				}
17989				unset($endlinepos);
17990				$plalign = $lalign;
17991				if (isset($this->PageAnnots[$this->page])) {
17992					$pask = count($this->PageAnnots[$this->page]);
17993				} else {
17994					$pask = 0;
17995				}
17996				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
17997					AND (isset($this->emptypagemrk[$this->page]))
17998					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
17999					$this->SetFont($fontname, $fontstyle, $fontsize);
18000					if ($wfill) {
18001						$this->SetFillColorArray($this->bgcolor);
18002					}
18003				}
18004			} // end newline
18005			if (isset($opentagpos)) {
18006				unset($opentagpos);
18007			}
18008			if ($dom[$key]['tag']) {
18009				if ($dom[$key]['opening']) {
18010					// get text indentation (if any)
18011					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18012						$this->textindent = $dom[$key]['text-indent'];
18013						$this->newline = true;
18014					}
18015					// table
18016					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18017						// available page width
18018						if ($this->rtl) {
18019							$wtmp = $this->x - $this->lMargin;
18020						} else {
18021							$wtmp = $this->w - $this->rMargin - $this->x;
18022						}
18023						// get cell spacing
18024						if (isset($dom[$key]['attribute']['cellspacing'])) {
18025							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18026							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18027						} elseif (isset($dom[$key]['border-spacing'])) {
18028							$cellspacing = $dom[$key]['border-spacing'];
18029						} else {
18030							$cellspacing = array('H' => 0, 'V' => 0);
18031						}
18032						// table width
18033						if (isset($dom[$key]['width'])) {
18034							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18035						} else {
18036							$table_width = $wtmp;
18037						}
18038						$table_width -= (2 * $cellspacing['H']);
18039						if (!$this->inthead) {
18040							$this->y += $cellspacing['V'];
18041						}
18042						if ($this->rtl) {
18043							$cellspacingx = -$cellspacing['H'];
18044						} else {
18045							$cellspacingx = $cellspacing['H'];
18046						}
18047						// total table width without cellspaces
18048						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18049						// minimum column width
18050						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18051						// array of custom column widths
18052						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18053					}
18054					// table row
18055					if ($dom[$key]['value'] == 'tr') {
18056						// reset column counter
18057						$colid = 0;
18058					}
18059					// table cell
18060					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18061						$trid = $dom[$key]['parent'];
18062						$table_el = $dom[$trid]['parent'];
18063						if (!isset($dom[$table_el]['cols'])) {
18064							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18065						}
18066						// store border info
18067						$tdborder = 0;
18068						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18069							$tdborder = $dom[$key]['border'];
18070						}
18071						$colspan = intval($dom[$key]['attribute']['colspan']);
18072						if ($colspan <= 0) {
18073							$colspan = 1;
18074						}
18075						$old_cell_padding = $this->cell_padding;
18076						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18077							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18078							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18079						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18080							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18081						} else {
18082							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18083						}
18084						$this->cell_padding = $current_cell_padding;
18085						if (isset($dom[$key]['height'])) {
18086							// minimum cell height
18087							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18088						} else {
18089							$cellh = 0;
18090						}
18091						if (isset($dom[$key]['content'])) {
18092							$cell_content = $dom[$key]['content'];
18093						} else {
18094							$cell_content = '&nbsp;';
18095						}
18096						$tagtype = $dom[$key]['value'];
18097						$parentid = $key;
18098						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18099							// move $key index forward
18100							++$key;
18101						}
18102						if (!isset($dom[$trid]['startpage'])) {
18103							$dom[$trid]['startpage'] = $this->page;
18104						} else {
18105							$this->setPage($dom[$trid]['startpage']);
18106						}
18107						if (!isset($dom[$trid]['startcolumn'])) {
18108							$dom[$trid]['startcolumn'] = $this->current_column;
18109						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18110							$tmpx = $this->x;
18111							$this->selectColumn($dom[$trid]['startcolumn']);
18112							$this->x = $tmpx;
18113						}
18114						if (!isset($dom[$trid]['starty'])) {
18115							$dom[$trid]['starty'] = $this->y;
18116						} else {
18117							$this->y = $dom[$trid]['starty'];
18118						}
18119						if (!isset($dom[$trid]['startx'])) {
18120							$dom[$trid]['startx'] = $this->x;
18121							$this->x += $cellspacingx;
18122						} else {
18123							$this->x += ($cellspacingx / 2);
18124						}
18125						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18126							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18127						} else {
18128							$rowspan = 1;
18129						}
18130						// skip row-spanned cells started on the previous rows
18131						if (isset($dom[$table_el]['rowspans'])) {
18132							$rsk = 0;
18133							$rskmax = count($dom[$table_el]['rowspans']);
18134							while ($rsk < $rskmax) {
18135								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18136								$rsstartx = $trwsp['startx'];
18137								$rsendx = $trwsp['endx'];
18138								// account for margin changes
18139								if ($trwsp['startpage'] < $this->page) {
18140									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18141										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18142										$rsstartx -= $dl;
18143										$rsendx -= $dl;
18144									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18145										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18146										$rsstartx += $dl;
18147										$rsendx += $dl;
18148									}
18149								}
18150								if (($trwsp['rowspan'] > 0)
18151									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18152									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18153									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18154									// set the starting X position of the current cell
18155									$this->x = $rsendx + $cellspacingx;
18156									// increment column indicator
18157									$colid += $trwsp['colspan'];
18158									if (($trwsp['rowspan'] == 1)
18159										AND (isset($dom[$trid]['endy']))
18160										AND (isset($dom[$trid]['endpage']))
18161										AND (isset($dom[$trid]['endcolumn']))
18162										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18163										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18164										// set ending Y position for row
18165										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18166										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18167									}
18168									$rsk = 0;
18169								} else {
18170									++$rsk;
18171								}
18172							}
18173						}
18174						if (isset($dom[$parentid]['width'])) {
18175							// user specified width
18176							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18177							$tmpcw = ($cellw / $colspan);
18178							for ($i = 0; $i < $colspan; ++$i) {
18179								$table_colwidths[($colid + $i)] = $tmpcw;
18180							}
18181						} else {
18182							// inherit column width
18183							$cellw = 0;
18184							for ($i = 0; $i < $colspan; ++$i) {
18185								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18186							}
18187						}
18188						$cellw += (($colspan - 1) * $cellspacing['H']);
18189						// increment column indicator
18190						$colid += $colspan;
18191						// add rowspan information to table element
18192						if ($rowspan > 1) {
18193							$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));
18194						}
18195						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18196						if ($rowspan > 1) {
18197							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18198						}
18199						// push background colors
18200						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18201							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18202						}
18203						// store border info
18204						if (isset($tdborder) AND !empty($tdborder)) {
18205							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18206						}
18207						$prevLastH = $this->lasth;
18208						// store some info for multicolumn mode
18209						if ($this->rtl) {
18210							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18211						} else {
18212							$this->colxshift['x'] = $this->x - $this->lMargin;
18213						}
18214						$this->colxshift['s'] = $cellspacing;
18215						$this->colxshift['p'] = $current_cell_padding;
18216						// ****** write the cell content ******
18217						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18218						// restore some values
18219						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18220						$this->lasth = $prevLastH;
18221						$this->cell_padding = $old_cell_padding;
18222						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18223						// update the end of row position
18224						if ($rowspan <= 1) {
18225							if (isset($dom[$trid]['endy'])) {
18226								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18227									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18228								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18229									$dom[$trid]['endy'] = $this->y;
18230								}
18231							} else {
18232								$dom[$trid]['endy'] = $this->y;
18233							}
18234							if (isset($dom[$trid]['endpage'])) {
18235								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18236							} else {
18237								$dom[$trid]['endpage'] = $this->page;
18238							}
18239							if (isset($dom[$trid]['endcolumn'])) {
18240								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18241							} else {
18242								$dom[$trid]['endcolumn'] = $this->current_column;
18243							}
18244						} else {
18245							// account for row-spanned cells
18246							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18247							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18248							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18249							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18250						}
18251						if (isset($dom[$table_el]['rowspans'])) {
18252							// update endy and endpage on rowspanned cells
18253							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18254								if ($trwsp['rowspan'] > 0) {
18255									if (isset($dom[$trid]['endpage'])) {
18256										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18257											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18258										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18259											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18260											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18261											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18262										} else {
18263											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18264										}
18265									}
18266								}
18267							}
18268						}
18269						$this->x += ($cellspacingx / 2);
18270					} else {
18271						// opening tag (or self-closing tag)
18272						if (!isset($opentagpos)) {
18273							if ($this->inxobj) {
18274								// we are inside an XObject template
18275								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18276							} elseif (!$this->InFooter) {
18277								if (isset($this->footerlen[$this->page])) {
18278									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18279								} else {
18280									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18281								}
18282								$opentagpos = $this->footerpos[$this->page];
18283							}
18284						}
18285						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18286					}
18287				} else { // closing tag
18288					$prev_numpages = $this->numpages;
18289					$old_bordermrk = $this->bordermrk[$this->page];
18290					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18291					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18292						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18293					}
18294					if ($prev_numpages > $this->numpages) {
18295						$startlinepage = $this->page;
18296					}
18297				}
18298			} elseif (strlen($dom[$key]['value']) > 0) {
18299				// print list-item
18300				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18301					$this->SetFont($pfontname, $pfontstyle, $pfontsize);
18302					$this->resetLastH();
18303					$minstartliney = $this->y;
18304					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18305					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18306						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18307					}
18308					$this->SetFont($curfontname, $curfontstyle, $curfontsize);
18309					$this->resetLastH();
18310					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18311						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18312						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18313						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18314						$minstartliney = min($this->y, $minstartliney);
18315						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18316					}
18317				}
18318				// text
18319				$this->htmlvspace = 0;
18320				$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']);
18321				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18322					// reverse spaces order
18323					$lsp = ''; // left spaces
18324					$rsp = ''; // right spaces
18325					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18326						$lsp = $matches[1];
18327					}
18328					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18329						$rsp = $matches[1];
18330					}
18331					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18332				}
18333				if ($newline) {
18334					if (!$this->premode) {
18335						$prelen = strlen($dom[$key]['value']);
18336						if ($this->isRTLTextDir() AND !$isRTLString) {
18337							// right trim except non-breaking space
18338							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18339						} else {
18340							// left trim except non-breaking space
18341							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18342						}
18343						$postlen = strlen($dom[$key]['value']);
18344						if (($postlen == 0) AND ($prelen > 0)) {
18345							$dom[$key]['trimmed_space'] = true;
18346						}
18347					}
18348					$newline = false;
18349					$firstblock = true;
18350				} else {
18351					$firstblock = false;
18352					// replace empty multiple spaces string with a single space
18353					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18354				}
18355				$strrest = '';
18356				if ($this->rtl) {
18357					$this->x -= $this->textindent;
18358				} else {
18359					$this->x += $this->textindent;
18360				}
18361				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18362					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18363					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18364						// HTML <a> Link
18365						$hrefcolor = '';
18366						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18367							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18368						}
18369						$hrefstyle = -1;
18370						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18371							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18372						}
18373						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18374					} else {
18375						$wadj = 0; // space to leave for block continuity
18376						if ($this->rtl) {
18377							$cwa = ($this->x - $this->lMargin);
18378						} else {
18379							$cwa = ($this->w - $this->rMargin - $this->x);
18380						}
18381						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18382							// check the next text blocks for continuity
18383							$nkey = ($key + 1);
18384							$write_block = true;
18385							$same_textdir = true;
18386							$tmp_fontname = $this->FontFamily;
18387							$tmp_fontstyle = $this->FontStyle;
18388							$tmp_fontsize = $this->FontSizePt;
18389							while ($write_block AND isset($dom[$nkey])) {
18390								if ($dom[$nkey]['tag']) {
18391									if ($dom[$nkey]['block']) {
18392										// end of block
18393										$write_block = false;
18394									}
18395									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18396									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18397									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18398									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18399								} else {
18400									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18401									if (isset($nextstr[0]) AND $same_textdir) {
18402										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18403										if (isset($nextstr[1])) {
18404											$write_block = false;
18405										}
18406									}
18407								}
18408								++$nkey;
18409							}
18410						}
18411						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18412							$wadj = 0;
18413							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18414							$numblks = count($nextstr);
18415							if ($numblks > 1) {
18416								// try to split on blank spaces
18417								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18418							} else {
18419								// set the entire block on new line
18420								$wadj = $this->GetStringWidth($nextstr[0]);
18421							}
18422						}
18423						// check for reversed text direction
18424						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18425							// LTR text on RTL direction or RTL text on LTR direction
18426							$reverse_dir = true;
18427							$this->rtl = !$this->rtl;
18428							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18429							if ($this->rtl) {
18430								$this->x += $revshift;
18431							} else {
18432								$this->x -= $revshift;
18433							}
18434							$xws = $this->x;
18435						}
18436						// ****** write only until the end of the line and get the rest ******
18437						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18438						// restore default direction
18439						if ($reverse_dir AND ($wadj == 0)) {
18440							$this->x = $xws;
18441							$this->rtl = !$this->rtl;
18442							$reverse_dir = false;
18443						}
18444					}
18445				}
18446				$this->textindent = 0;
18447				if (strlen($strrest) > 0) {
18448					// store the remaining string on the previous $key position
18449					$this->newline = true;
18450					if ($strrest == $dom[$key]['value']) {
18451						// used to avoid infinite loop
18452						++$loop;
18453					} else {
18454						$loop = 0;
18455					}
18456					$dom[$key]['value'] = $strrest;
18457					if ($cell) {
18458						if ($this->rtl) {
18459							$this->x -= $this->cell_padding['R'];
18460						} else {
18461							$this->x += $this->cell_padding['L'];
18462						}
18463					}
18464					if ($loop < 3) {
18465						--$key;
18466					}
18467				} else {
18468					$loop = 0;
18469					// add the positive font spacing of the last character (if any)
18470					 if ($this->font_spacing > 0) {
18471					 	if ($this->rtl) {
18472							$this->x -= $this->font_spacing;
18473						} else {
18474							$this->x += $this->font_spacing;
18475						}
18476					}
18477				}
18478			}
18479			++$key;
18480			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')) {
18481				// check if we are on a new page or on a new column
18482				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18483					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18484					// restore previous object
18485					$this->rollbackTransaction(true);
18486					// restore previous values
18487					foreach ($this_method_vars as $vkey => $vval) {
18488						$$vkey = $vval;
18489					}
18490					if (!empty($dom[$key]['thead'])) {
18491						$this->inthead = true;
18492					}
18493					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18494					$pre_y = $this->y;
18495					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18496						$startliney = $this->y;
18497					}
18498					$undo = true; // avoid infinite loop
18499				} else {
18500					$undo = false;
18501				}
18502			}
18503		} // end for each $key
18504		// align the last line
18505		if (isset($startlinex)) {
18506			$yshift = ($minstartliney - $startliney);
18507			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18508				$yshift = 0;
18509			}
18510			$t_x = 0;
18511			// the last line must be shifted to be aligned as requested
18512			$linew = abs($this->endlinex - $startlinex);
18513			if ($this->inxobj) {
18514				// we are inside an XObject template
18515				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18516				if (isset($opentagpos)) {
18517					$midpos = $opentagpos;
18518				} else {
18519					$midpos = 0;
18520				}
18521				if ($midpos > 0) {
18522					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18523					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18524				} else {
18525					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18526					$pend = '';
18527				}
18528			} else {
18529				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18530				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18531					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18532					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18533				} elseif (isset($opentagpos)) {
18534					$midpos = $opentagpos;
18535				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18536					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18537					$midpos = $this->footerpos[$startlinepage];
18538				} else {
18539					$midpos = 0;
18540				}
18541				if ($midpos > 0) {
18542					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18543					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18544				} else {
18545					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18546					$pend = '';
18547				}
18548			}
18549			if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18550				// calculate shifting amount
18551				$tw = $w;
18552				if ($this->lMargin != $prevlMargin) {
18553					$tw += ($prevlMargin - $this->lMargin);
18554				}
18555				if ($this->rMargin != $prevrMargin) {
18556					$tw += ($prevrMargin - $this->rMargin);
18557				}
18558				$one_space_width = $this->GetStringWidth(chr(32));
18559				$no = 0; // number of spaces on a line contained on a single block
18560				if ($this->isRTLTextDir()) { // RTL
18561					// remove left space if exist
18562					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18563					if ($pos1 > 0) {
18564						$pos1 = intval($pos1);
18565						if ($this->isUnicodeFont()) {
18566							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18567							$spacelen = 2;
18568						} else {
18569							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18570							$spacelen = 1;
18571						}
18572						if ($pos1 == $pos2) {
18573							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18574							if (substr($pmid, $pos1, 4) == '[()]') {
18575								$linew -= $one_space_width;
18576							} elseif ($pos1 == strpos($pmid, '[(')) {
18577								$no = 1;
18578							}
18579						}
18580					}
18581				} else { // LTR
18582					// remove right space if exist
18583					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18584					if ($pos1 > 0) {
18585						$pos1 = intval($pos1);
18586						if ($this->isUnicodeFont()) {
18587							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18588							$spacelen = 2;
18589						} else {
18590							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18591							$spacelen = 1;
18592						}
18593						if ($pos1 == $pos2) {
18594							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18595							$linew -= $one_space_width;
18596						}
18597					}
18598				}
18599				$mdiff = ($tw - $linew);
18600				if ($plalign == 'C') {
18601					if ($this->rtl) {
18602						$t_x = -($mdiff / 2);
18603					} else {
18604						$t_x = ($mdiff / 2);
18605					}
18606				} elseif ($plalign == 'R') {
18607					// right alignment on LTR document
18608					$t_x = $mdiff;
18609				} elseif ($plalign == 'L') {
18610					// left alignment on RTL document
18611					$t_x = -$mdiff;
18612				}
18613			} // end if startlinex
18614			if (($t_x != 0) OR ($yshift < 0)) {
18615				// shift the line
18616				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18617				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18618				$endlinepos = strlen($pstart);
18619				if ($this->inxobj) {
18620					// we are inside an XObject template
18621					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18622					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18623						if ($pak >= $pask) {
18624							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18625							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18626						}
18627					}
18628				} else {
18629					$this->setPageBuffer($startlinepage, $pstart.$pend);
18630					// shift the annotations and links
18631					if (isset($this->PageAnnots[$this->page])) {
18632						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18633							if ($pak >= $pask) {
18634								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18635								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18636							}
18637						}
18638					}
18639				}
18640				$this->y -= $yshift;
18641				$yshift = 0;
18642			}
18643		}
18644		// restore previous values
18645		$this->setGraphicVars($gvars);
18646		if ($this->num_columns > 1) {
18647			$this->selectColumn();
18648		} elseif ($this->page > $prevPage) {
18649			$this->lMargin = $this->pagedim[$this->page]['olm'];
18650			$this->rMargin = $this->pagedim[$this->page]['orm'];
18651		}
18652		// restore previous list state
18653		$this->cell_height_ratio = $prev_cell_height_ratio;
18654		$this->listnum = $prev_listnum;
18655		$this->listordered = $prev_listordered;
18656		$this->listcount = $prev_listcount;
18657		$this->lispacer = $prev_lispacer;
18658		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18659			$this->Ln($this->lasth);
18660			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18661				$this->y = $maxbottomliney;
18662			}
18663		}
18664		unset($dom);
18665	}
18666
18667	/**
18668	 * Process opening tags.
18669	 * @param $dom (array) html dom array
18670	 * @param $key (int) current element id
18671	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18672	 * @return $dom array
18673	 * @protected
18674	 */
18675	protected function openHTMLTagHandler($dom, $key, $cell) {
18676		$tag = $dom[$key];
18677		$parent = $dom[($dom[$key]['parent'])];
18678		$firsttag = ($key == 1);
18679		// check for text direction attribute
18680		if (isset($tag['dir'])) {
18681			$this->setTempRTL($tag['dir']);
18682		} else {
18683			$this->tmprtl = false;
18684		}
18685		if ($tag['block']) {
18686			$hbz = 0; // distance from y to line bottom
18687			$hb = 0; // vertical space between block tags
18688			// calculate vertical space for block tags
18689			if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18690				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18691			} elseif (isset($tag['fontsize'])) {
18692				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18693			} else {
18694				$cur_h = $this->getCellHeight($this->FontSize);
18695			}
18696			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18697				$on = $this->tagvspaces[$tag['value']][0]['n'];
18698			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18699				$on = 0.6;
18700			} else {
18701				$on = 1;
18702			}
18703			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18704				$hb = 0;
18705			} else {
18706				$hb = ($on * $cur_h);
18707			}
18708			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18709				if (isset($parent['fontsize'])) {
18710					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18711				} else {
18712					$hbz = $this->getCellHeight($this->FontSize);
18713				}
18714			}
18715			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18716				// fix vertical space after table
18717				$hbz = 0;
18718			}
18719			// closing vertical space
18720			$hbc = 0;
18721			if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18722				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18723			} elseif (isset($parent['fontsize'])) {
18724				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18725			} else {
18726				$pre_h = $this->getCellHeight($this->FontSize);
18727			}
18728			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18729				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18730			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18731				$cn = 0.6;
18732			} else {
18733				$cn = 1;
18734			}
18735			if (isset($this->tagvspaces[$tag['value']][1])) {
18736				$hbc = ($cn * $pre_h);
18737			}
18738		}
18739		// Opening tag
18740		switch($tag['value']) {
18741			case 'table': {
18742				$cp = 0;
18743				$cs = 0;
18744				$dom[$key]['rowspans'] = array();
18745				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18746					$this->htmlvspace = 0;
18747					// set table header
18748					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18749						// set table header
18750						$this->thead = $dom[$key]['thead'];
18751						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18752							$this->theadMargins = array();
18753							$this->theadMargins['cell_padding'] = $this->cell_padding;
18754							$this->theadMargins['lmargin'] = $this->lMargin;
18755							$this->theadMargins['rmargin'] = $this->rMargin;
18756							$this->theadMargins['page'] = $this->page;
18757							$this->theadMargins['cell'] = $cell;
18758							$this->theadMargins['gvars'] = $this->getGraphicVars();
18759						}
18760					}
18761				}
18762				// store current margins and page
18763				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18764				if (isset($tag['attribute']['cellpadding'])) {
18765					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18766					$this->SetCellPadding($pad);
18767				} elseif (isset($tag['padding'])) {
18768					$this->cell_padding = $tag['padding'];
18769				}
18770				if (isset($tag['attribute']['cellspacing'])) {
18771					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18772				} elseif (isset($tag['border-spacing'])) {
18773					$cs = $tag['border-spacing']['V'];
18774				}
18775				$prev_y = $this->y;
18776				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18777					$this->inthead = true;
18778					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18779					$this->checkPageBreak($this->PageBreakTrigger + 1);
18780				}
18781				break;
18782			}
18783			case 'tr': {
18784				// array of columns positions
18785				$dom[$key]['cellpos'] = array();
18786				break;
18787			}
18788			case 'hr': {
18789				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18790					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18791				} else {
18792					$hrHeight = $this->GetLineWidth();
18793				}
18794				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18795				$x = $this->GetX();
18796				$y = $this->GetY();
18797				$wtmp = $this->w - $this->lMargin - $this->rMargin;
18798				if ($cell) {
18799					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18800				}
18801				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18802					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18803				} else {
18804					$hrWidth = $wtmp;
18805				}
18806				$prevlinewidth = $this->GetLineWidth();
18807				$this->SetLineWidth($hrHeight);
18808				$this->Line($x, $y, $x + $hrWidth, $y);
18809				$this->SetLineWidth($prevlinewidth);
18810				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18811				break;
18812			}
18813			case 'a': {
18814				if (array_key_exists('href', $tag['attribute'])) {
18815					$this->HREF['url'] = $tag['attribute']['href'];
18816				}
18817				break;
18818			}
18819			case 'img': {
18820				if (empty($tag['attribute']['src'])) {
18821					break;
18822				}
18823				$imgsrc = $tag['attribute']['src'];
18824				if ($imgsrc[0] === '@') {
18825					// data stream
18826					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
18827					$type = '';
18828				} else {
18829					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
18830						// fix image path
18831						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
18832						if (($findroot === false) OR ($findroot > 1)) {
18833							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
18834								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
18835							} else {
18836								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
18837							}
18838						}
18839						$imgsrc = urldecode($imgsrc);
18840						$testscrtype = @parse_url($imgsrc);
18841						if (empty($testscrtype['query'])) {
18842							// convert URL to server path
18843							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
18844						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
18845							// convert URL to server path
18846							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
18847						}
18848					}
18849					// get image type
18850					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
18851				}
18852				if (!isset($tag['width'])) {
18853					$tag['width'] = 0;
18854				}
18855				if (!isset($tag['height'])) {
18856					$tag['height'] = 0;
18857				}
18858				//if (!isset($tag['attribute']['align'])) {
18859					// the only alignment supported is "bottom"
18860					// further development is required for other modes.
18861					$tag['attribute']['align'] = 'bottom';
18862				//}
18863				switch($tag['attribute']['align']) {
18864					case 'top': {
18865						$align = 'T';
18866						break;
18867					}
18868					case 'middle': {
18869						$align = 'M';
18870						break;
18871					}
18872					case 'bottom': {
18873						$align = 'B';
18874						break;
18875					}
18876					default: {
18877						$align = 'B';
18878						break;
18879					}
18880				}
18881				$prevy = $this->y;
18882				$xpos = $this->x;
18883				$imglink = '';
18884				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18885					$imglink = $this->HREF['url'];
18886					if ($imglink[0] == '#') {
18887						// convert url to internal link
18888						$lnkdata = explode(',', $imglink);
18889						if (isset($lnkdata[0])) {
18890							$page = intval(substr($lnkdata[0], 1));
18891							if (empty($page) OR ($page <= 0)) {
18892								$page = $this->page;
18893							}
18894							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18895								$lnky = floatval($lnkdata[1]);
18896							} else {
18897								$lnky = 0;
18898							}
18899							$imglink = $this->AddLink();
18900							$this->SetLink($imglink, $lnky, $page);
18901						}
18902					}
18903				}
18904				$border = 0;
18905				if (isset($tag['border']) AND !empty($tag['border'])) {
18906					// currently only support 1 (frame) or a combination of 'LTRB'
18907					$border = $tag['border'];
18908				}
18909				$iw = '';
18910				if (isset($tag['width'])) {
18911					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18912				}
18913				$ih = '';
18914				if (isset($tag['height'])) {
18915					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18916				}
18917				if (($type == 'eps') OR ($type == 'ai')) {
18918					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18919				} elseif ($type == 'svg') {
18920					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18921				} else {
18922					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18923				}
18924				switch($align) {
18925					case 'T': {
18926						$this->y = $prevy;
18927						break;
18928					}
18929					case 'M': {
18930						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18931						break;
18932					}
18933					case 'B': {
18934						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
18935						break;
18936					}
18937				}
18938				break;
18939			}
18940			case 'dl': {
18941				++$this->listnum;
18942				if ($this->listnum == 1) {
18943					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18944				} else {
18945					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18946				}
18947				break;
18948			}
18949			case 'dt': {
18950				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18951				break;
18952			}
18953			case 'dd': {
18954				if ($this->rtl) {
18955					$this->rMargin += $this->listindent;
18956				} else {
18957					$this->lMargin += $this->listindent;
18958				}
18959				++$this->listindentlevel;
18960				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18961				break;
18962			}
18963			case 'ul':
18964			case 'ol': {
18965				++$this->listnum;
18966				if ($tag['value'] == 'ol') {
18967					$this->listordered[$this->listnum] = true;
18968				} else {
18969					$this->listordered[$this->listnum] = false;
18970				}
18971				if (isset($tag['attribute']['start'])) {
18972					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
18973				} else {
18974					$this->listcount[$this->listnum] = 0;
18975				}
18976				if ($this->rtl) {
18977					$this->rMargin += $this->listindent;
18978					$this->x -= $this->listindent;
18979				} else {
18980					$this->lMargin += $this->listindent;
18981					$this->x += $this->listindent;
18982				}
18983				++$this->listindentlevel;
18984				if ($this->listnum == 1) {
18985					if ($key > 1) {
18986						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18987					}
18988				} else {
18989					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18990				}
18991				break;
18992			}
18993			case 'li': {
18994				if ($key > 2) {
18995					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18996				}
18997				if ($this->listordered[$this->listnum]) {
18998					// ordered item
18999					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19000						$this->lispacer = $parent['attribute']['type'];
19001					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19002						$this->lispacer = $parent['listtype'];
19003					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19004						$this->lispacer = $this->lisymbol;
19005					} else {
19006						$this->lispacer = '#';
19007					}
19008					++$this->listcount[$this->listnum];
19009					if (isset($tag['attribute']['value'])) {
19010						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19011					}
19012				} else {
19013					// unordered item
19014					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19015						$this->lispacer = $parent['attribute']['type'];
19016					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19017						$this->lispacer = $parent['listtype'];
19018					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19019						$this->lispacer = $this->lisymbol;
19020					} else {
19021						$this->lispacer = '!';
19022					}
19023				}
19024				break;
19025			}
19026			case 'blockquote': {
19027				if ($this->rtl) {
19028					$this->rMargin += $this->listindent;
19029				} else {
19030					$this->lMargin += $this->listindent;
19031				}
19032				++$this->listindentlevel;
19033				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19034				break;
19035			}
19036			case 'br': {
19037				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19038				break;
19039			}
19040			case 'div': {
19041				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19042				break;
19043			}
19044			case 'p': {
19045				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19046				break;
19047			}
19048			case 'pre': {
19049				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19050				$this->premode = true;
19051				break;
19052			}
19053			case 'sup': {
19054				$this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19055				break;
19056			}
19057			case 'sub': {
19058				$this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19059				break;
19060			}
19061			case 'h1':
19062			case 'h2':
19063			case 'h3':
19064			case 'h4':
19065			case 'h5':
19066			case 'h6': {
19067				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19068				break;
19069			}
19070			// Form fields (since 4.8.000 - 2009-09-07)
19071			case 'form': {
19072				if (isset($tag['attribute']['action'])) {
19073					$this->form_action = $tag['attribute']['action'];
19074				} else {
19075					$this->Error('Please explicitly set action attribute path!');
19076				}
19077				if (isset($tag['attribute']['enctype'])) {
19078					$this->form_enctype = $tag['attribute']['enctype'];
19079				} else {
19080					$this->form_enctype = 'application/x-www-form-urlencoded';
19081				}
19082				if (isset($tag['attribute']['method'])) {
19083					$this->form_mode = $tag['attribute']['method'];
19084				} else {
19085					$this->form_mode = 'post';
19086				}
19087				break;
19088			}
19089			case 'input': {
19090				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19091					$name = $tag['attribute']['name'];
19092				} else {
19093					break;
19094				}
19095				$prop = array();
19096				$opt = array();
19097				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19098					$prop['readonly'] = true;
19099				}
19100				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19101					$value = $tag['attribute']['value'];
19102				}
19103				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19104					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19105				}
19106				$h = $this->getCellHeight($this->FontSize);
19107				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19108					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19109				} else {
19110					$w = $h;
19111				}
19112				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19113					$checked = true;
19114				} else {
19115					$checked = false;
19116				}
19117				if (isset($tag['align'])) {
19118					switch ($tag['align']) {
19119						case 'C': {
19120							$opt['q'] = 1;
19121							break;
19122						}
19123						case 'R': {
19124							$opt['q'] = 2;
19125							break;
19126						}
19127						case 'L':
19128						default: {
19129							break;
19130						}
19131					}
19132				}
19133				switch ($tag['attribute']['type']) {
19134					case 'text': {
19135						if (isset($value)) {
19136							$opt['v'] = $value;
19137						}
19138						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19139						break;
19140					}
19141					case 'password': {
19142						if (isset($value)) {
19143							$opt['v'] = $value;
19144						}
19145						$prop['password'] = 'true';
19146						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19147						break;
19148					}
19149					case 'checkbox': {
19150						if (!isset($value)) {
19151							break;
19152						}
19153						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19154						break;
19155					}
19156					case 'radio': {
19157						if (!isset($value)) {
19158							break;
19159						}
19160						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19161						break;
19162					}
19163					case 'submit': {
19164						if (!isset($value)) {
19165							$value = 'submit';
19166						}
19167						$w = $this->GetStringWidth($value) * 1.5;
19168						$h *= 1.6;
19169						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19170						$action = array();
19171						$action['S'] = 'SubmitForm';
19172						$action['F'] = $this->form_action;
19173						if ($this->form_enctype != 'FDF') {
19174							$action['Flags'] = array('ExportFormat');
19175						}
19176						if ($this->form_mode == 'get') {
19177							$action['Flags'] = array('GetMethod');
19178						}
19179						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19180						break;
19181					}
19182					case 'reset': {
19183						if (!isset($value)) {
19184							$value = 'reset';
19185						}
19186						$w = $this->GetStringWidth($value) * 1.5;
19187						$h *= 1.6;
19188						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19189						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19190						break;
19191					}
19192					case 'file': {
19193						$prop['fileSelect'] = 'true';
19194						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19195						if (!isset($value)) {
19196							$value = '*';
19197						}
19198						$w = $this->GetStringWidth($value) * 2;
19199						$h *= 1.2;
19200						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19201						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19202						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19203						break;
19204					}
19205					case 'hidden': {
19206						if (isset($value)) {
19207							$opt['v'] = $value;
19208						}
19209						$opt['f'] = array('invisible', 'hidden');
19210						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19211						break;
19212					}
19213					case 'image': {
19214						// THIS TYPE MUST BE FIXED
19215						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19216							$img = $tag['attribute']['src'];
19217						} else {
19218							break;
19219						}
19220						$value = 'img';
19221						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19222						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19223							$jsaction = $tag['attribute']['onclick'];
19224						} else {
19225							$jsaction = '';
19226						}
19227						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19228						break;
19229					}
19230					case 'button': {
19231						if (!isset($value)) {
19232							$value = ' ';
19233						}
19234						$w = $this->GetStringWidth($value) * 1.5;
19235						$h *= 1.6;
19236						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19237						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19238							$jsaction = $tag['attribute']['onclick'];
19239						} else {
19240							$jsaction = '';
19241						}
19242						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19243						break;
19244					}
19245				}
19246				break;
19247			}
19248			case 'textarea': {
19249				$prop = array();
19250				$opt = array();
19251				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19252					$prop['readonly'] = true;
19253				}
19254				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19255					$name = $tag['attribute']['name'];
19256				} else {
19257					break;
19258				}
19259				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19260					$opt['v'] = $tag['attribute']['value'];
19261				}
19262				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19263					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19264				} else {
19265					$w = 40;
19266				}
19267				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19268					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19269				} else {
19270					$h = 10;
19271				}
19272				$prop['multiline'] = 'true';
19273				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19274				break;
19275			}
19276			case 'select': {
19277				$h = $this->getCellHeight($this->FontSize);
19278				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19279					$h *= ($tag['attribute']['size'] + 1);
19280				}
19281				$prop = array();
19282				$opt = array();
19283				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19284					$name = $tag['attribute']['name'];
19285				} else {
19286					break;
19287				}
19288				$w = 0;
19289				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19290					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19291					$values = array();
19292					foreach ($options as $val) {
19293						if (strpos($val, '#!TaB!#') !== false) {
19294							$opts = explode('#!TaB!#', $val);
19295							$values[] = $opts;
19296							$w = max($w, $this->GetStringWidth($opts[1]));
19297						} else {
19298							$values[] = $val;
19299							$w = max($w, $this->GetStringWidth($val));
19300						}
19301					}
19302				} else {
19303					break;
19304				}
19305				$w *= 2;
19306				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19307					$prop['multipleSelection'] = 'true';
19308					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19309				} else {
19310					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19311				}
19312				break;
19313			}
19314			case 'tcpdf': {
19315				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19316					// Special tag used to call TCPDF methods
19317					if (isset($tag['attribute']['method'])) {
19318						$tcpdf_method = $tag['attribute']['method'];
19319						if (method_exists($this, $tcpdf_method)) {
19320							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19321								$params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19322								call_user_func_array(array($this, $tcpdf_method), $params);
19323							} else {
19324								$this->$tcpdf_method();
19325							}
19326							$this->newline = true;
19327						}
19328					}
19329				}
19330				break;
19331			}
19332			default: {
19333				break;
19334			}
19335		}
19336		// define tags that support borders and background colors
19337		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19338		if (in_array($tag['value'], $bordertags)) {
19339			// set border
19340			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19341		}
19342		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19343			$pba = $dom[$key]['attribute']['pagebreakafter'];
19344			// check for pagebreak
19345			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19346				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19347				$this->checkPageBreak($this->PageBreakTrigger + 1);
19348			}
19349			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19350				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19351				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19352				$this->checkPageBreak($this->PageBreakTrigger + 1);
19353			}
19354		}
19355		return $dom;
19356	}
19357
19358	/**
19359	 * Process closing tags.
19360	 * @param $dom (array) html dom array
19361	 * @param $key (int) current element id
19362	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19363	 * @param $maxbottomliney (int) maximum y value of current line
19364	 * @return $dom array
19365	 * @protected
19366	 */
19367	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19368		$tag = $dom[$key];
19369		$parent = $dom[($dom[$key]['parent'])];
19370		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19371		$in_table_head = false;
19372		// maximum x position (used to draw borders)
19373		if ($this->rtl) {
19374			$xmax = $this->w;
19375		} else {
19376			$xmax = 0;
19377		}
19378		if ($tag['block']) {
19379			$hbz = 0; // distance from y to line bottom
19380			$hb = 0; // vertical space between block tags
19381			// calculate vertical space for block tags
19382			if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19383				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19384			} elseif (isset($parent['fontsize'])) {
19385				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19386			} else {
19387				$pre_h = $this->getCellHeight($this->FontSize);
19388			}
19389			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19390				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19391			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19392				$cn = 0.6;
19393			} else {
19394				$cn = 1;
19395			}
19396			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19397				$hb = 0;
19398			} else {
19399				$hb = ($cn * $pre_h);
19400			}
19401			if ($maxbottomliney > $this->PageBreakTrigger) {
19402				$hbz = $this->getCellHeight($this->FontSize);
19403			} elseif ($this->y < $maxbottomliney) {
19404				$hbz = ($maxbottomliney - $this->y);
19405			}
19406		}
19407		// Closing tag
19408		switch($tag['value']) {
19409			case 'tr': {
19410				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19411				if (!isset($parent['endy'])) {
19412					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19413					$parent['endy'] = $this->y;
19414				}
19415				if (!isset($parent['endpage'])) {
19416					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19417					$parent['endpage'] = $this->page;
19418				}
19419				if (!isset($parent['endcolumn'])) {
19420					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19421					$parent['endcolumn'] = $this->current_column;
19422				}
19423				// update row-spanned cells
19424				if (isset($dom[$table_el]['rowspans'])) {
19425					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19426						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19427						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19428							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19429								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19430							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19431								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19432								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19433								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19434							}
19435						}
19436					}
19437					// report new endy and endpage to the rowspanned cells
19438					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19439						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19440							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19441							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19442							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19443							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19444							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19445							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19446						}
19447					}
19448					// update remaining rowspanned cells
19449					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19450						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19451							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19452							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19453							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19454						}
19455					}
19456				}
19457				$prev_page = $this->page;
19458				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19459				if ($this->num_columns > 1) {
19460					if (($prev_page < $this->page)
19461						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19462							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19463						// page jump
19464						$this->selectColumn(0);
19465						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19466						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19467					} else {
19468						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19469						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19470					}
19471				} else {
19472					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19473				}
19474				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19475					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19476				} elseif (isset($dom[$table_el]['border-spacing'])) {
19477					$this->y += $dom[$table_el]['border-spacing']['V'];
19478				}
19479				$this->Ln(0, $cell);
19480				if ($this->current_column == $parent['startcolumn']) {
19481					$this->x = $parent['startx'];
19482				}
19483				// account for booklet mode
19484				if ($this->page > $parent['startpage']) {
19485					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19486						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19487					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19488						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19489					}
19490				}
19491				break;
19492			}
19493			case 'tablehead':
19494				// closing tag used for the thead part
19495				$in_table_head = true;
19496				$this->inthead = false;
19497			case 'table': {
19498				$table_el = $parent;
19499				// set default border
19500				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19501					// set default border
19502					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19503				} else {
19504					$border = 0;
19505				}
19506				$default_border = $border;
19507				// fix bottom line alignment of last line before page break
19508				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19509					// update row-spanned cells
19510					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19511						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19512							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19513								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19514							}
19515							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19516								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19517							}
19518						}
19519					}
19520					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19521						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19522						$dom[$prevtrkey]['endy'] = $pgendy;
19523						// update row-spanned cells
19524						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19525							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19526								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19527									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19528									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19529								}
19530							}
19531						}
19532					}
19533					$prevtrkey = $trkey;
19534					$table_el = $dom[($dom[$key]['parent'])];
19535				}
19536				// for each row
19537				if (count($table_el['trids']) > 0) {
19538					unset($xmax);
19539				}
19540				foreach ($table_el['trids'] as $j => $trkey) {
19541					$parent = $dom[$trkey];
19542					if (!isset($xmax)) {
19543						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19544					}
19545					// for each cell on the row
19546					foreach ($parent['cellpos'] as $k => $cellpos) {
19547						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19548							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19549							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19550							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19551							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19552							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19553							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19554							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19555						} else {
19556							$endy = $parent['endy'];
19557							$startpage = $parent['startpage'];
19558							$endpage = $parent['endpage'];
19559							$startcolumn = $parent['startcolumn'];
19560							$endcolumn = $parent['endcolumn'];
19561						}
19562						if ($this->num_columns == 0) {
19563							$this->num_columns = 1;
19564						}
19565						if (isset($cellpos['border'])) {
19566							$border = $cellpos['border'];
19567						}
19568						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19569							$this->SetFillColorArray($cellpos['bgcolor']);
19570							$fill = true;
19571						} else {
19572							$fill = false;
19573						}
19574						$x = $cellpos['startx'];
19575						$y = $parent['starty'];
19576						$starty = $y;
19577						$w = abs($cellpos['endx'] - $cellpos['startx']);
19578						// get border modes
19579						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19580						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19581						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19582						// design borders around HTML cells.
19583						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19584							$ccode = '';
19585							$this->setPage($page);
19586							if ($this->num_columns < 2) {
19587								// single-column mode
19588								$this->x = $x;
19589								$this->y = $this->tMargin;
19590							}
19591							// account for margin changes
19592							if ($page > $startpage) {
19593								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19594									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19595								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19596									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19597								}
19598							}
19599							if ($startpage == $endpage) { // single page
19600								$deltacol = 0;
19601								$deltath = 0;
19602								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19603									$this->selectColumn($column);
19604									if ($startcolumn == $endcolumn) { // single column
19605										$cborder = $border;
19606										$h = $endy - $parent['starty'];
19607										$this->y = $y;
19608										$this->x = $x;
19609									} elseif ($column == $startcolumn) { // first column
19610										$cborder = $border_start;
19611										$this->y = $starty;
19612										$this->x = $x;
19613										$h = $this->h - $this->y - $this->bMargin;
19614										if ($this->rtl) {
19615											$deltacol = $this->x + $this->rMargin - $this->w;
19616										} else {
19617											$deltacol = $this->x - $this->lMargin;
19618										}
19619									} elseif ($column == $endcolumn) { // end column
19620										$cborder = $border_end;
19621										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19622											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19623										}
19624										$this->x += $deltacol;
19625										$h = $endy - $this->y;
19626									} else { // middle column
19627										$cborder = $border_middle;
19628										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19629											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19630										}
19631										$this->x += $deltacol;
19632										$h = $this->h - $this->y - $this->bMargin;
19633									}
19634									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19635								} // end for each column
19636							} elseif ($page == $startpage) { // first page
19637								$deltacol = 0;
19638								$deltath = 0;
19639								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19640									$this->selectColumn($column);
19641									if ($column == $startcolumn) { // first column
19642										$cborder = $border_start;
19643										$this->y = $starty;
19644										$this->x = $x;
19645										$h = $this->h - $this->y - $this->bMargin;
19646										if ($this->rtl) {
19647											$deltacol = $this->x + $this->rMargin - $this->w;
19648										} else {
19649											$deltacol = $this->x - $this->lMargin;
19650										}
19651									} else { // middle column
19652										$cborder = $border_middle;
19653										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19654											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19655										}
19656										$this->x += $deltacol;
19657										$h = $this->h - $this->y - $this->bMargin;
19658									}
19659									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19660								} // end for each column
19661							} elseif ($page == $endpage) { // last page
19662								$deltacol = 0;
19663								$deltath = 0;
19664								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19665									$this->selectColumn($column);
19666									if ($column == $endcolumn) { // end column
19667										$cborder = $border_end;
19668										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19669											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19670										}
19671										$this->x += $deltacol;
19672										$h = $endy - $this->y;
19673									} else { // middle column
19674										$cborder = $border_middle;
19675										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19676											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19677										}
19678										$this->x += $deltacol;
19679										$h = $this->h - $this->y - $this->bMargin;
19680									}
19681									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19682								} // end for each column
19683							} else { // middle page
19684								$deltacol = 0;
19685								$deltath = 0;
19686								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19687									$this->selectColumn($column);
19688									$cborder = $border_middle;
19689									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19690										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19691									}
19692									$this->x += $deltacol;
19693									$h = $this->h - $this->y - $this->bMargin;
19694									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19695								} // end for each column
19696							}
19697							if (!empty($cborder) OR !empty($fill)) {
19698								$offsetlen = strlen($ccode);
19699								// draw border and fill
19700								if ($this->inxobj) {
19701									// we are inside an XObject template
19702									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19703										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19704										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19705										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19706									} else {
19707										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19708										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19709									}
19710									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19711									$pstart = substr($pagebuff, 0, $pagemark);
19712									$pend = substr($pagebuff, $pagemark);
19713									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19714								} else {
19715									// draw border and fill
19716									if (end($this->transfmrk[$this->page]) !== false) {
19717										$pagemarkkey = key($this->transfmrk[$this->page]);
19718										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19719									} elseif ($this->InFooter) {
19720										$pagemark = $this->footerpos[$this->page];
19721									} else {
19722										$pagemark = $this->intmrk[$this->page];
19723									}
19724									$pagebuff = $this->getPageBuffer($this->page);
19725									$pstart = substr($pagebuff, 0, $pagemark);
19726									$pend = substr($pagebuff, $pagemark);
19727									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19728								}
19729							}
19730						} // end for each page
19731						// restore default border
19732						$border = $default_border;
19733					} // end for each cell on the row
19734					if (isset($table_el['attribute']['cellspacing'])) {
19735						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19736					} elseif (isset($table_el['border-spacing'])) {
19737						$this->y += $table_el['border-spacing']['V'];
19738					}
19739					$this->Ln(0, $cell);
19740					$this->x = $parent['startx'];
19741					if ($endpage > $startpage) {
19742						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19743							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19744						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19745							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19746						}
19747					}
19748				}
19749				if (!$in_table_head) { // we are not inside a thead section
19750					$this->cell_padding = $table_el['old_cell_padding'];
19751					// reset row height
19752					$this->resetLastH();
19753					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19754						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19755						if (($plendiff > 0) AND ($plendiff < 60)) {
19756							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19757							if (substr($pagediff, 0, 5) == 'BT /F') {
19758								// the difference is only a font setting
19759								$plendiff = 0;
19760							}
19761						}
19762						if ($plendiff == 0) {
19763							// remove last blank page
19764							$this->deletePage($this->numpages);
19765						}
19766					}
19767					if (isset($this->theadMargins['top'])) {
19768						// restore top margin
19769						$this->tMargin = $this->theadMargins['top'];
19770					}
19771					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19772						// reset main table header
19773						$this->thead = '';
19774						$this->theadMargins = array();
19775						$this->pagedim[$this->page]['tm'] = $this->tMargin;
19776					}
19777				}
19778				$parent = $table_el;
19779				break;
19780			}
19781			case 'a': {
19782				$this->HREF = array();
19783				break;
19784			}
19785			case 'sup': {
19786				$this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19787				break;
19788			}
19789			case 'sub': {
19790				$this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19791				break;
19792			}
19793			case 'div': {
19794				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19795				break;
19796			}
19797			case 'blockquote': {
19798				if ($this->rtl) {
19799					$this->rMargin -= $this->listindent;
19800				} else {
19801					$this->lMargin -= $this->listindent;
19802				}
19803				--$this->listindentlevel;
19804				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19805				break;
19806			}
19807			case 'p': {
19808				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19809				break;
19810			}
19811			case 'pre': {
19812				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19813				$this->premode = false;
19814				break;
19815			}
19816			case 'dl': {
19817				--$this->listnum;
19818				if ($this->listnum <= 0) {
19819					$this->listnum = 0;
19820					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19821				} else {
19822					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19823				}
19824				$this->resetLastH();
19825				break;
19826			}
19827			case 'dt': {
19828				$this->lispacer = '';
19829				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19830				break;
19831			}
19832			case 'dd': {
19833				$this->lispacer = '';
19834				if ($this->rtl) {
19835					$this->rMargin -= $this->listindent;
19836				} else {
19837					$this->lMargin -= $this->listindent;
19838				}
19839				--$this->listindentlevel;
19840				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19841				break;
19842			}
19843			case 'ul':
19844			case 'ol': {
19845				--$this->listnum;
19846				$this->lispacer = '';
19847				if ($this->rtl) {
19848					$this->rMargin -= $this->listindent;
19849				} else {
19850					$this->lMargin -= $this->listindent;
19851				}
19852				--$this->listindentlevel;
19853				if ($this->listnum <= 0) {
19854					$this->listnum = 0;
19855					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19856				} else {
19857					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19858				}
19859				$this->resetLastH();
19860				break;
19861			}
19862			case 'li': {
19863				$this->lispacer = '';
19864				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19865				break;
19866			}
19867			case 'h1':
19868			case 'h2':
19869			case 'h3':
19870			case 'h4':
19871			case 'h5':
19872			case 'h6': {
19873				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19874				break;
19875			}
19876			// Form fields (since 4.8.000 - 2009-09-07)
19877			case 'form': {
19878				$this->form_action = '';
19879				$this->form_enctype = 'application/x-www-form-urlencoded';
19880				break;
19881			}
19882			default : {
19883				break;
19884			}
19885		}
19886		// draw border and background (if any)
19887		$this->drawHTMLTagBorder($parent, $xmax);
19888		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19889			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19890			// check for pagebreak
19891			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19892				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19893				$this->checkPageBreak($this->PageBreakTrigger + 1);
19894			}
19895			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19896				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19897				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19898				$this->checkPageBreak($this->PageBreakTrigger + 1);
19899			}
19900		}
19901		$this->tmprtl = false;
19902		return $dom;
19903	}
19904
19905	/**
19906	 * Add vertical spaces if needed.
19907	 * @param $hbz (string) Distance between current y and line bottom.
19908	 * @param $hb (string) The height of the break.
19909	 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19910	 * @param $firsttag (boolean) set to true when the tag is the first.
19911	 * @param $lasttag (boolean) set to true when the tag is the last.
19912	 * @protected
19913	 */
19914	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19915		if ($firsttag) {
19916			$this->Ln(0, $cell);
19917			$this->htmlvspace = 0;
19918			return;
19919		}
19920		if ($lasttag) {
19921			$this->Ln($hbz, $cell);
19922			$this->htmlvspace = 0;
19923			return;
19924		}
19925		if ($hb < $this->htmlvspace) {
19926			$hd = 0;
19927		} else {
19928			$hd = $hb - $this->htmlvspace;
19929			$this->htmlvspace = $hb;
19930		}
19931		$this->Ln(($hbz + $hd), $cell);
19932	}
19933
19934	/**
19935	 * Return the starting coordinates to draw an html border
19936	 * @return array containing top-left border coordinates
19937	 * @protected
19938	 * @since 5.7.000 (2010-08-03)
19939	 */
19940	protected function getBorderStartPosition() {
19941		if ($this->rtl) {
19942			$xmax = $this->lMargin;
19943		} else {
19944			$xmax = $this->w - $this->rMargin;
19945		}
19946		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
19947	}
19948
19949	/**
19950	 * Draw an HTML block border and fill
19951	 * @param $tag (array) array of tag properties.
19952	 * @param $xmax (int) end X coordinate for border.
19953	 * @protected
19954	 * @since 5.7.000 (2010-08-03)
19955	 */
19956	protected function drawHTMLTagBorder($tag, $xmax) {
19957		if (!isset($tag['borderposition'])) {
19958			// nothing to draw
19959			return;
19960		}
19961		$prev_x = $this->x;
19962		$prev_y = $this->y;
19963		$prev_lasth = $this->lasth;
19964		$border = 0;
19965		$fill = false;
19966		$this->lasth = 0;
19967		if (isset($tag['border']) AND !empty($tag['border'])) {
19968			// get border style
19969			$border = $tag['border'];
19970			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
19971				// border for table header
19972				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19973			}
19974		}
19975		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
19976			// get background color
19977			$old_bgcolor = $this->bgcolor;
19978			$this->SetFillColorArray($tag['bgcolor']);
19979			$fill = true;
19980		}
19981		if (!$border AND !$fill) {
19982			// nothing to draw
19983			return;
19984		}
19985		if (isset($tag['attribute']['cellspacing'])) {
19986			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19987			$cellspacing = array('H' => $clsp, 'V' => $clsp);
19988		} elseif (isset($tag['border-spacing'])) {
19989			$cellspacing = $tag['border-spacing'];
19990		} else {
19991			$cellspacing = array('H' => 0, 'V' => 0);
19992		}
19993		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
19994			// draw the border externally respect the sqare edge.
19995			$border['mode'] = 'ext';
19996		}
19997		if ($this->rtl) {
19998			if ($xmax >= $tag['borderposition']['x']) {
19999				$xmax = $tag['borderposition']['xmax'];
20000			}
20001			$w = ($tag['borderposition']['x'] - $xmax);
20002		} else {
20003			if ($xmax <= $tag['borderposition']['x']) {
20004				$xmax = $tag['borderposition']['xmax'];
20005			}
20006			$w = ($xmax - $tag['borderposition']['x']);
20007		}
20008		if ($w <= 0) {
20009			return;
20010		}
20011		$w += $cellspacing['H'];
20012		$startpage = $tag['borderposition']['page'];
20013		$startcolumn = $tag['borderposition']['column'];
20014		$x = $tag['borderposition']['x'];
20015		$y = $tag['borderposition']['y'];
20016		$endpage = $this->page;
20017		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20018		$currentY = $this->y;
20019		$this->x = $x;
20020		// get latest column
20021		$endcolumn = $this->current_column;
20022		if ($this->num_columns == 0) {
20023			$this->num_columns = 1;
20024		}
20025		// get border modes
20026		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20027		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20028		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20029		// temporary disable page regions
20030		$temp_page_regions = $this->page_regions;
20031		$this->page_regions = array();
20032		// design borders around HTML cells.
20033		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20034			$ccode = '';
20035			$this->setPage($page);
20036			if ($this->num_columns < 2) {
20037				// single-column mode
20038				$this->x = $x;
20039				$this->y = $this->tMargin;
20040			}
20041			// account for margin changes
20042			if ($page > $startpage) {
20043				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20044					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20045				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20046					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20047				}
20048			}
20049			if ($startpage == $endpage) {
20050				// single page
20051				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20052					$this->selectColumn($column);
20053					if ($startcolumn == $endcolumn) { // single column
20054						$cborder = $border;
20055						$h = ($currentY - $y) + $cellspacing['V'];
20056						$this->y = $starty;
20057					} elseif ($column == $startcolumn) { // first column
20058						$cborder = $border_start;
20059						$this->y = $starty;
20060						$h = $this->h - $this->y - $this->bMargin;
20061					} elseif ($column == $endcolumn) { // end column
20062						$cborder = $border_end;
20063						$h = $currentY - $this->y;
20064					} else { // middle column
20065						$cborder = $border_middle;
20066						$h = $this->h - $this->y - $this->bMargin;
20067					}
20068					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20069				} // end for each column
20070			} elseif ($page == $startpage) { // first page
20071				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20072					$this->selectColumn($column);
20073					if ($column == $startcolumn) { // first column
20074						$cborder = $border_start;
20075						$this->y = $starty;
20076						$h = $this->h - $this->y - $this->bMargin;
20077					} else { // middle column
20078						$cborder = $border_middle;
20079						$h = $this->h - $this->y - $this->bMargin;
20080					}
20081					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20082				} // end for each column
20083			} elseif ($page == $endpage) { // last page
20084				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20085					$this->selectColumn($column);
20086					if ($column == $endcolumn) {
20087						// end column
20088						$cborder = $border_end;
20089						$h = $currentY - $this->y;
20090					} else {
20091						// middle column
20092						$cborder = $border_middle;
20093						$h = $this->h - $this->y - $this->bMargin;
20094					}
20095					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20096				} // end for each column
20097			} else { // middle page
20098				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20099					$this->selectColumn($column);
20100					$cborder = $border_middle;
20101					$h = $this->h - $this->y - $this->bMargin;
20102					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20103				} // end for each column
20104			}
20105			if ($cborder OR $fill) {
20106				$offsetlen = strlen($ccode);
20107				// draw border and fill
20108				if ($this->inxobj) {
20109					// we are inside an XObject template
20110					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20111						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20112						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20113						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20114					} else {
20115						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20116						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20117					}
20118					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20119					$pstart = substr($pagebuff, 0, $pagemark);
20120					$pend = substr($pagebuff, $pagemark);
20121					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20122				} else {
20123					if (end($this->transfmrk[$this->page]) !== false) {
20124						$pagemarkkey = key($this->transfmrk[$this->page]);
20125						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20126					} elseif ($this->InFooter) {
20127						$pagemark = $this->footerpos[$this->page];
20128					} else {
20129						$pagemark = $this->intmrk[$this->page];
20130					}
20131					$pagebuff = $this->getPageBuffer($this->page);
20132					$pstart = substr($pagebuff, 0, $pagemark);
20133					$pend = substr($pagebuff, $pagemark);
20134					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20135					$this->bordermrk[$this->page] += $offsetlen;
20136					$this->cntmrk[$this->page] += $offsetlen;
20137				}
20138			}
20139		} // end for each page
20140		// restore page regions
20141		$this->page_regions = $temp_page_regions;
20142		if (isset($old_bgcolor)) {
20143			// restore background color
20144			$this->SetFillColorArray($old_bgcolor);
20145		}
20146		// restore pointer position
20147		$this->x = $prev_x;
20148		$this->y = $prev_y;
20149		$this->lasth = $prev_lasth;
20150	}
20151
20152	/**
20153	 * Set the default bullet to be used as LI bullet symbol
20154	 * @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')
20155	 * @public
20156	 * @since 4.0.028 (2008-09-26)
20157	 */
20158	public function setLIsymbol($symbol='!') {
20159		// check for custom image symbol
20160		if (substr($symbol, 0, 4) == 'img|') {
20161			$this->lisymbol = $symbol;
20162			return;
20163		}
20164		$symbol = strtolower($symbol);
20165		$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');
20166		if (in_array($symbol, $valid_symbols)) {
20167			$this->lisymbol = $symbol;
20168		} else {
20169			$this->lisymbol = '';
20170		}
20171	}
20172
20173	/**
20174	 * Set the booklet mode for double-sided pages.
20175	 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20176	 * @param $inner (float) Inner page margin.
20177	 * @param $outer (float) Outer page margin.
20178	 * @public
20179	 * @since 4.2.000 (2008-10-29)
20180	 */
20181	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20182		$this->booklet = $booklet;
20183		if ($inner >= 0) {
20184			$this->lMargin = $inner;
20185		}
20186		if ($outer >= 0) {
20187			$this->rMargin = $outer;
20188		}
20189	}
20190
20191	/**
20192	 * Swap the left and right margins.
20193	 * @param $reverse (boolean) if true swap left and right margins.
20194	 * @protected
20195	 * @since 4.2.000 (2008-10-29)
20196	 */
20197	protected function swapMargins($reverse=true) {
20198		if ($reverse) {
20199			// swap left and right margins
20200			$mtemp = $this->original_lMargin;
20201			$this->original_lMargin = $this->original_rMargin;
20202			$this->original_rMargin = $mtemp;
20203			$deltam = $this->original_lMargin - $this->original_rMargin;
20204			$this->lMargin += $deltam;
20205			$this->rMargin -= $deltam;
20206		}
20207	}
20208
20209	/**
20210	 * Set the vertical spaces for HTML tags.
20211	 * The array must have the following structure (example):
20212	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20213	 * The first array level contains the tag names,
20214	 * the second level contains 0 for opening tags or 1 for closing tags,
20215	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20216	 * If the h parameter is not specified, default values are used.
20217	 * @param $tagvs (array) array of tags and relative vertical spaces.
20218	 * @public
20219	 * @since 4.2.001 (2008-10-30)
20220	 */
20221	public function setHtmlVSpace($tagvs) {
20222		$this->tagvspaces = $tagvs;
20223	}
20224
20225	/**
20226	 * Set custom width for list indentation.
20227	 * @param $width (float) width of the indentation. Use negative value to disable it.
20228	 * @public
20229	 * @since 4.2.007 (2008-11-12)
20230	 */
20231	public function setListIndentWidth($width) {
20232		return $this->customlistindent = floatval($width);
20233	}
20234
20235	/**
20236	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20237	 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20238	 * @public
20239	 * @since 4.2.010 (2008-11-14)
20240	 */
20241	public function setOpenCell($isopen) {
20242		$this->opencell = $isopen;
20243	}
20244
20245	/**
20246	 * Set the color and font style for HTML links.
20247	 * @param $color (array) RGB array of colors
20248	 * @param $fontstyle (string) additional font styles to add
20249	 * @public
20250	 * @since 4.4.003 (2008-12-09)
20251	 */
20252	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20253		$this->htmlLinkColorArray = $color;
20254		$this->htmlLinkFontStyle = $fontstyle;
20255	}
20256
20257	/**
20258	 * Convert HTML string containing value and unit of measure to user's units or points.
20259	 * @param $htmlval (string) String containing values and unit.
20260	 * @param $refsize (string) Reference value in points.
20261	 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20262	 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20263	 * @return float value in user's unit or point if $points=true
20264	 * @public
20265	 * @since 4.4.004 (2008-12-10)
20266	 */
20267	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20268		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20269		$retval = 0;
20270		$value = 0;
20271		$unit = 'px';
20272		if ($points) {
20273			$k = 1;
20274		} else {
20275			$k = $this->k;
20276		}
20277		if (in_array($defaultunit, $supportedunits)) {
20278			$unit = $defaultunit;
20279		}
20280		if (is_numeric($htmlval)) {
20281			$value = floatval($htmlval);
20282		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20283			$value = floatval($mnum[1]);
20284			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20285				if (in_array($munit[1], $supportedunits)) {
20286					$unit = $munit[1];
20287				}
20288			}
20289		}
20290		switch ($unit) {
20291			// percentage
20292			case '%': {
20293				$retval = (($value * $refsize) / 100);
20294				break;
20295			}
20296			// relative-size
20297			case 'em': {
20298				$retval = ($value * $refsize);
20299				break;
20300			}
20301			// height of lower case 'x' (about half the font-size)
20302			case 'ex': {
20303				$retval = ($value * ($refsize / 2));
20304				break;
20305			}
20306			// absolute-size
20307			case 'in': {
20308				$retval = (($value * $this->dpi) / $k);
20309				break;
20310			}
20311			// centimeters
20312			case 'cm': {
20313				$retval = (($value / 2.54 * $this->dpi) / $k);
20314				break;
20315			}
20316			// millimeters
20317			case 'mm': {
20318				$retval = (($value / 25.4 * $this->dpi) / $k);
20319				break;
20320			}
20321			// one pica is 12 points
20322			case 'pc': {
20323				$retval = (($value * 12) / $k);
20324				break;
20325			}
20326			// points
20327			case 'pt': {
20328				$retval = ($value / $k);
20329				break;
20330			}
20331			// pixels
20332			case 'px': {
20333				$retval = $this->pixelsToUnits($value);
20334				if ($points) {
20335					$retval *= $this->k;
20336				}
20337				break;
20338			}
20339		}
20340		return $retval;
20341	}
20342
20343	/**
20344	 * Output an HTML list bullet or ordered item symbol
20345	 * @param $listdepth (int) list nesting level
20346	 * @param $listtype (string) type of list
20347	 * @param $size (float) current font size
20348	 * @protected
20349	 * @since 4.4.004 (2008-12-10)
20350	 */
20351	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20352		if ($this->state != 2) {
20353			return;
20354		}
20355		$size /= $this->k;
20356		$fill = '';
20357		$bgcolor = $this->bgcolor;
20358		$color = $this->fgcolor;
20359		$strokecolor = $this->strokecolor;
20360		$width = 0;
20361		$textitem = '';
20362		$tmpx = $this->x;
20363		$lspace = $this->GetStringWidth('  ');
20364		if ($listtype == '^') {
20365			// special symbol used for avoid justification of rect bullet
20366			$this->lispacer = '';
20367			return;
20368		} elseif ($listtype == '!') {
20369			// set default list type for unordered list
20370			$deftypes = array('disc', 'circle', 'square');
20371			$listtype = $deftypes[($listdepth - 1) % 3];
20372		} elseif ($listtype == '#') {
20373			// set default list type for ordered list
20374			$listtype = 'decimal';
20375		} elseif (substr($listtype, 0, 4) == 'img|') {
20376			// custom image type ('img|type|width|height|image.ext')
20377			$img = explode('|', $listtype);
20378			$listtype = 'img';
20379		}
20380		switch ($listtype) {
20381			// unordered types
20382			case 'none': {
20383				break;
20384			}
20385			case 'disc': {
20386				$r = $size / 6;
20387				$lspace += (2 * $r);
20388				if ($this->rtl) {
20389					$this->x += $lspace;
20390				} else {
20391					$this->x -= $lspace;
20392				}
20393				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20394				break;
20395			}
20396			case 'circle': {
20397				$r = $size / 6;
20398				$lspace += (2 * $r);
20399				if ($this->rtl) {
20400					$this->x += $lspace;
20401				} else {
20402					$this->x -= $lspace;
20403				}
20404				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20405				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20406				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20407				$this->_out($prev_line_style); // restore line settings
20408				break;
20409			}
20410			case 'square': {
20411				$l = $size / 3;
20412				$lspace += $l;
20413				if ($this->rtl) {;
20414					$this->x += $lspace;
20415				} else {
20416					$this->x -= $lspace;
20417				}
20418				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20419				break;
20420			}
20421			case 'img': {
20422				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20423				$lspace += $img[2];
20424				if ($this->rtl) {;
20425					$this->x += $lspace;
20426				} else {
20427					$this->x -= $lspace;
20428				}
20429				$imgtype = strtolower($img[1]);
20430				$prev_y = $this->y;
20431				switch ($imgtype) {
20432					case 'svg': {
20433						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20434						break;
20435					}
20436					case 'ai':
20437					case 'eps': {
20438						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20439						break;
20440					}
20441					default: {
20442						$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);
20443						break;
20444					}
20445				}
20446				$this->y = $prev_y;
20447				break;
20448			}
20449			// ordered types
20450			// $this->listcount[$this->listnum];
20451			// $textitem
20452			case '1':
20453			case 'decimal': {
20454				$textitem = $this->listcount[$this->listnum];
20455				break;
20456			}
20457			case 'decimal-leading-zero': {
20458				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20459				break;
20460			}
20461			case 'i':
20462			case 'lower-roman': {
20463				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20464				break;
20465			}
20466			case 'I':
20467			case 'upper-roman': {
20468				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20469				break;
20470			}
20471			case 'a':
20472			case 'lower-alpha':
20473			case 'lower-latin': {
20474				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20475				break;
20476			}
20477			case 'A':
20478			case 'upper-alpha':
20479			case 'upper-latin': {
20480				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20481				break;
20482			}
20483			case 'lower-greek': {
20484				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20485				break;
20486			}
20487			/*
20488			// Types to be implemented (special handling)
20489			case 'hebrew': {
20490				break;
20491			}
20492			case 'armenian': {
20493				break;
20494			}
20495			case 'georgian': {
20496				break;
20497			}
20498			case 'cjk-ideographic': {
20499				break;
20500			}
20501			case 'hiragana': {
20502				break;
20503			}
20504			case 'katakana': {
20505				break;
20506			}
20507			case 'hiragana-iroha': {
20508				break;
20509			}
20510			case 'katakana-iroha': {
20511				break;
20512			}
20513			*/
20514			default: {
20515				$textitem = $this->listcount[$this->listnum];
20516			}
20517		}
20518		if (!TCPDF_STATIC::empty_string($textitem)) {
20519			// Check whether we need a new page or new column
20520			$prev_y = $this->y;
20521			$h = $this->getCellHeight($this->FontSize);
20522			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20523				$tmpx = $this->x;
20524			}
20525			// print ordered item
20526			if ($this->rtl) {
20527				$textitem = '.'.$textitem;
20528			} else {
20529				$textitem = $textitem.'.';
20530			}
20531			$lspace += $this->GetStringWidth($textitem);
20532			if ($this->rtl) {
20533				$this->x += $lspace;
20534			} else {
20535				$this->x -= $lspace;
20536			}
20537			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20538		}
20539		$this->x = $tmpx;
20540		$this->lispacer = '^';
20541		// restore colors
20542		$this->SetFillColorArray($bgcolor);
20543		$this->SetDrawColorArray($strokecolor);
20544		$this->SettextColorArray($color);
20545	}
20546
20547	/**
20548	 * Returns current graphic variables as array.
20549	 * @return array of graphic variables
20550	 * @protected
20551	 * @since 4.2.010 (2008-11-14)
20552	 */
20553	protected function getGraphicVars() {
20554		$grapvars = array(
20555			'FontFamily' => $this->FontFamily,
20556			'FontStyle' => $this->FontStyle,
20557			'FontSizePt' => $this->FontSizePt,
20558			'rMargin' => $this->rMargin,
20559			'lMargin' => $this->lMargin,
20560			'cell_padding' => $this->cell_padding,
20561			'cell_margin' => $this->cell_margin,
20562			'LineWidth' => $this->LineWidth,
20563			'linestyleWidth' => $this->linestyleWidth,
20564			'linestyleCap' => $this->linestyleCap,
20565			'linestyleJoin' => $this->linestyleJoin,
20566			'linestyleDash' => $this->linestyleDash,
20567			'textrendermode' => $this->textrendermode,
20568			'textstrokewidth' => $this->textstrokewidth,
20569			'DrawColor' => $this->DrawColor,
20570			'FillColor' => $this->FillColor,
20571			'TextColor' => $this->TextColor,
20572			'ColorFlag' => $this->ColorFlag,
20573			'bgcolor' => $this->bgcolor,
20574			'fgcolor' => $this->fgcolor,
20575			'htmlvspace' => $this->htmlvspace,
20576			'listindent' => $this->listindent,
20577			'listindentlevel' => $this->listindentlevel,
20578			'listnum' => $this->listnum,
20579			'listordered' => $this->listordered,
20580			'listcount' => $this->listcount,
20581			'lispacer' => $this->lispacer,
20582			'cell_height_ratio' => $this->cell_height_ratio,
20583			'font_stretching' => $this->font_stretching,
20584			'font_spacing' => $this->font_spacing,
20585			'alpha' => $this->alpha,
20586			// extended
20587			'lasth' => $this->lasth,
20588			'tMargin' => $this->tMargin,
20589			'bMargin' => $this->bMargin,
20590			'AutoPageBreak' => $this->AutoPageBreak,
20591			'PageBreakTrigger' => $this->PageBreakTrigger,
20592			'x' => $this->x,
20593			'y' => $this->y,
20594			'w' => $this->w,
20595			'h' => $this->h,
20596			'wPt' => $this->wPt,
20597			'hPt' => $this->hPt,
20598			'fwPt' => $this->fwPt,
20599			'fhPt' => $this->fhPt,
20600			'page' => $this->page,
20601			'current_column' => $this->current_column,
20602			'num_columns' => $this->num_columns
20603			);
20604		return $grapvars;
20605	}
20606
20607	/**
20608	 * Set graphic variables.
20609	 * @param $gvars (array) array of graphic variablesto restore
20610	 * @param $extended (boolean) if true restore extended graphic variables
20611	 * @protected
20612	 * @since 4.2.010 (2008-11-14)
20613	 */
20614	protected function setGraphicVars($gvars, $extended=false) {
20615		if ($this->state != 2) {
20616			 return;
20617		}
20618		$this->FontFamily = $gvars['FontFamily'];
20619		$this->FontStyle = $gvars['FontStyle'];
20620		$this->FontSizePt = $gvars['FontSizePt'];
20621		$this->rMargin = $gvars['rMargin'];
20622		$this->lMargin = $gvars['lMargin'];
20623		$this->cell_padding = $gvars['cell_padding'];
20624		$this->cell_margin = $gvars['cell_margin'];
20625		$this->LineWidth = $gvars['LineWidth'];
20626		$this->linestyleWidth = $gvars['linestyleWidth'];
20627		$this->linestyleCap = $gvars['linestyleCap'];
20628		$this->linestyleJoin = $gvars['linestyleJoin'];
20629		$this->linestyleDash = $gvars['linestyleDash'];
20630		$this->textrendermode = $gvars['textrendermode'];
20631		$this->textstrokewidth = $gvars['textstrokewidth'];
20632		$this->DrawColor = $gvars['DrawColor'];
20633		$this->FillColor = $gvars['FillColor'];
20634		$this->TextColor = $gvars['TextColor'];
20635		$this->ColorFlag = $gvars['ColorFlag'];
20636		$this->bgcolor = $gvars['bgcolor'];
20637		$this->fgcolor = $gvars['fgcolor'];
20638		$this->htmlvspace = $gvars['htmlvspace'];
20639		$this->listindent = $gvars['listindent'];
20640		$this->listindentlevel = $gvars['listindentlevel'];
20641		$this->listnum = $gvars['listnum'];
20642		$this->listordered = $gvars['listordered'];
20643		$this->listcount = $gvars['listcount'];
20644		$this->lispacer = $gvars['lispacer'];
20645		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20646		$this->font_stretching = $gvars['font_stretching'];
20647		$this->font_spacing = $gvars['font_spacing'];
20648		$this->alpha = $gvars['alpha'];
20649		if ($extended) {
20650			// restore extended values
20651			$this->lasth = $gvars['lasth'];
20652			$this->tMargin = $gvars['tMargin'];
20653			$this->bMargin = $gvars['bMargin'];
20654			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20655			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20656			$this->x = $gvars['x'];
20657			$this->y = $gvars['y'];
20658			$this->w = $gvars['w'];
20659			$this->h = $gvars['h'];
20660			$this->wPt = $gvars['wPt'];
20661			$this->hPt = $gvars['hPt'];
20662			$this->fwPt = $gvars['fwPt'];
20663			$this->fhPt = $gvars['fhPt'];
20664			$this->page = $gvars['page'];
20665			$this->current_column = $gvars['current_column'];
20666			$this->num_columns = $gvars['num_columns'];
20667		}
20668		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20669		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20670			$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20671		}
20672	}
20673
20674	/**
20675	 * Outputs the "save graphics state" operator 'q'
20676	 * @protected
20677	 */
20678	protected function _outSaveGraphicsState() {
20679		$this->_out('q');
20680	}
20681
20682	/**
20683	 * Outputs the "restore graphics state" operator 'Q'
20684	 * @protected
20685	 */
20686	protected function _outRestoreGraphicsState() {
20687		$this->_out('Q');
20688	}
20689
20690	/**
20691	 * Set buffer content (always append data).
20692	 * @param $data (string) data
20693	 * @protected
20694	 * @since 4.5.000 (2009-01-02)
20695	 */
20696	protected function setBuffer($data) {
20697		$this->bufferlen += strlen($data);
20698		$this->buffer .= $data;
20699	}
20700
20701	/**
20702	 * Replace the buffer content
20703	 * @param $data (string) data
20704	 * @protected
20705	 * @since 5.5.000 (2010-06-22)
20706	 */
20707	protected function replaceBuffer($data) {
20708		$this->bufferlen = strlen($data);
20709		$this->buffer = $data;
20710	}
20711
20712	/**
20713	 * Get buffer content.
20714	 * @return string buffer content
20715	 * @protected
20716	 * @since 4.5.000 (2009-01-02)
20717	 */
20718	protected function getBuffer() {
20719		return $this->buffer;
20720	}
20721
20722	/**
20723	 * Set page buffer content.
20724	 * @param $page (int) page number
20725	 * @param $data (string) page data
20726	 * @param $append (boolean) if true append data, false replace.
20727	 * @protected
20728	 * @since 4.5.000 (2008-12-31)
20729	 */
20730	protected function setPageBuffer($page, $data, $append=false) {
20731		if ($append) {
20732			$this->pages[$page] .= $data;
20733		} else {
20734			$this->pages[$page] = $data;
20735		}
20736		if ($append AND isset($this->pagelen[$page])) {
20737			$this->pagelen[$page] += strlen($data);
20738		} else {
20739			$this->pagelen[$page] = strlen($data);
20740		}
20741	}
20742
20743	/**
20744	 * Get page buffer content.
20745	 * @param $page (int) page number
20746	 * @return string page buffer content or false in case of error
20747	 * @protected
20748	 * @since 4.5.000 (2008-12-31)
20749	 */
20750	protected function getPageBuffer($page) {
20751		if (isset($this->pages[$page])) {
20752			return $this->pages[$page];
20753		}
20754		return false;
20755	}
20756
20757	/**
20758	 * Set image buffer content.
20759	 * @param $image (string) image key
20760	 * @param $data (array) image data
20761	 * @return int image index number
20762	 * @protected
20763	 * @since 4.5.000 (2008-12-31)
20764	 */
20765	protected function setImageBuffer($image, $data) {
20766		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20767			$this->imagekeys[$this->numimages] = $image;
20768			$data['i'] = $this->numimages;
20769			++$this->numimages;
20770		}
20771		$this->images[$image] = $data;
20772		return $data['i'];
20773	}
20774
20775	/**
20776	 * Set image buffer content for a specified sub-key.
20777	 * @param $image (string) image key
20778	 * @param $key (string) image sub-key
20779	 * @param $data (array) image data
20780	 * @protected
20781	 * @since 4.5.000 (2008-12-31)
20782	 */
20783	protected function setImageSubBuffer($image, $key, $data) {
20784		if (!isset($this->images[$image])) {
20785			$this->setImageBuffer($image, array());
20786		}
20787		$this->images[$image][$key] = $data;
20788	}
20789
20790	/**
20791	 * Get image buffer content.
20792	 * @param $image (string) image key
20793	 * @return string image buffer content or false in case of error
20794	 * @protected
20795	 * @since 4.5.000 (2008-12-31)
20796	 */
20797	protected function getImageBuffer($image) {
20798		if (isset($this->images[$image])) {
20799			return $this->images[$image];
20800		}
20801		return false;
20802	}
20803
20804	/**
20805	 * Set font buffer content.
20806	 * @param $font (string) font key
20807	 * @param $data (array) font data
20808	 * @protected
20809	 * @since 4.5.000 (2009-01-02)
20810	 */
20811	protected function setFontBuffer($font, $data) {
20812		$this->fonts[$font] = $data;
20813		if (!in_array($font, $this->fontkeys)) {
20814			$this->fontkeys[] = $font;
20815			// store object ID for current font
20816			++$this->n;
20817			$this->font_obj_ids[$font] = $this->n;
20818			$this->setFontSubBuffer($font, 'n', $this->n);
20819		}
20820	}
20821
20822	/**
20823	 * Set font buffer content.
20824	 * @param $font (string) font key
20825	 * @param $key (string) font sub-key
20826	 * @param $data (array) font data
20827	 * @protected
20828	 * @since 4.5.000 (2009-01-02)
20829	 */
20830	protected function setFontSubBuffer($font, $key, $data) {
20831		if (!isset($this->fonts[$font])) {
20832			$this->setFontBuffer($font, array());
20833		}
20834		$this->fonts[$font][$key] = $data;
20835	}
20836
20837	/**
20838	 * Get font buffer content.
20839	 * @param $font (string) font key
20840	 * @return string font buffer content or false in case of error
20841	 * @protected
20842	 * @since 4.5.000 (2009-01-02)
20843	 */
20844	protected function getFontBuffer($font) {
20845		if (isset($this->fonts[$font])) {
20846			return $this->fonts[$font];
20847		}
20848		return false;
20849	}
20850
20851	/**
20852	 * Move a page to a previous position.
20853	 * @param $frompage (int) number of the source page
20854	 * @param $topage (int) number of the destination page (must be less than $frompage)
20855	 * @return true in case of success, false in case of error.
20856	 * @public
20857	 * @since 4.5.000 (2009-01-02)
20858	 */
20859	public function movePage($frompage, $topage) {
20860		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20861			return false;
20862		}
20863		if ($frompage == $this->page) {
20864			// close the page before moving it
20865			$this->endPage();
20866		}
20867		// move all page-related states
20868		$tmppage = $this->getPageBuffer($frompage);
20869		$tmppagedim = $this->pagedim[$frompage];
20870		$tmppagelen = $this->pagelen[$frompage];
20871		$tmpintmrk = $this->intmrk[$frompage];
20872		$tmpbordermrk = $this->bordermrk[$frompage];
20873		$tmpcntmrk = $this->cntmrk[$frompage];
20874		$tmppageobjects = $this->pageobjects[$frompage];
20875		if (isset($this->footerpos[$frompage])) {
20876			$tmpfooterpos = $this->footerpos[$frompage];
20877		}
20878		if (isset($this->footerlen[$frompage])) {
20879			$tmpfooterlen = $this->footerlen[$frompage];
20880		}
20881		if (isset($this->transfmrk[$frompage])) {
20882			$tmptransfmrk = $this->transfmrk[$frompage];
20883		}
20884		if (isset($this->PageAnnots[$frompage])) {
20885			$tmpannots = $this->PageAnnots[$frompage];
20886		}
20887		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20888			for ($i = $frompage; $i > $topage; --$i) {
20889				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20890					--$this->pagegroups[$this->newpagegroup[$i]];
20891					break;
20892				}
20893			}
20894			for ($i = $topage; $i > 0; --$i) {
20895				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20896					++$this->pagegroups[$this->newpagegroup[$i]];
20897					break;
20898				}
20899			}
20900		}
20901		for ($i = $frompage; $i > $topage; --$i) {
20902			$j = $i - 1;
20903			// shift pages down
20904			$this->setPageBuffer($i, $this->getPageBuffer($j));
20905			$this->pagedim[$i] = $this->pagedim[$j];
20906			$this->pagelen[$i] = $this->pagelen[$j];
20907			$this->intmrk[$i] = $this->intmrk[$j];
20908			$this->bordermrk[$i] = $this->bordermrk[$j];
20909			$this->cntmrk[$i] = $this->cntmrk[$j];
20910			$this->pageobjects[$i] = $this->pageobjects[$j];
20911			if (isset($this->footerpos[$j])) {
20912				$this->footerpos[$i] = $this->footerpos[$j];
20913			} elseif (isset($this->footerpos[$i])) {
20914				unset($this->footerpos[$i]);
20915			}
20916			if (isset($this->footerlen[$j])) {
20917				$this->footerlen[$i] = $this->footerlen[$j];
20918			} elseif (isset($this->footerlen[$i])) {
20919				unset($this->footerlen[$i]);
20920			}
20921			if (isset($this->transfmrk[$j])) {
20922				$this->transfmrk[$i] = $this->transfmrk[$j];
20923			} elseif (isset($this->transfmrk[$i])) {
20924				unset($this->transfmrk[$i]);
20925			}
20926			if (isset($this->PageAnnots[$j])) {
20927				$this->PageAnnots[$i] = $this->PageAnnots[$j];
20928			} elseif (isset($this->PageAnnots[$i])) {
20929				unset($this->PageAnnots[$i]);
20930			}
20931			if (isset($this->newpagegroup[$j])) {
20932				$this->newpagegroup[$i] = $this->newpagegroup[$j];
20933				unset($this->newpagegroup[$j]);
20934			}
20935			if ($this->currpagegroup == $j) {
20936				$this->currpagegroup = $i;
20937			}
20938		}
20939		$this->setPageBuffer($topage, $tmppage);
20940		$this->pagedim[$topage] = $tmppagedim;
20941		$this->pagelen[$topage] = $tmppagelen;
20942		$this->intmrk[$topage] = $tmpintmrk;
20943		$this->bordermrk[$topage] = $tmpbordermrk;
20944		$this->cntmrk[$topage] = $tmpcntmrk;
20945		$this->pageobjects[$topage] = $tmppageobjects;
20946		if (isset($tmpfooterpos)) {
20947			$this->footerpos[$topage] = $tmpfooterpos;
20948		} elseif (isset($this->footerpos[$topage])) {
20949			unset($this->footerpos[$topage]);
20950		}
20951		if (isset($tmpfooterlen)) {
20952			$this->footerlen[$topage] = $tmpfooterlen;
20953		} elseif (isset($this->footerlen[$topage])) {
20954			unset($this->footerlen[$topage]);
20955		}
20956		if (isset($tmptransfmrk)) {
20957			$this->transfmrk[$topage] = $tmptransfmrk;
20958		} elseif (isset($this->transfmrk[$topage])) {
20959			unset($this->transfmrk[$topage]);
20960		}
20961		if (isset($tmpannots)) {
20962			$this->PageAnnots[$topage] = $tmpannots;
20963		} elseif (isset($this->PageAnnots[$topage])) {
20964			unset($this->PageAnnots[$topage]);
20965		}
20966		// adjust outlines
20967		$tmpoutlines = $this->outlines;
20968		foreach ($tmpoutlines as $key => $outline) {
20969			if (!$outline['f']) {
20970				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
20971					$this->outlines[$key]['p'] = ($outline['p'] + 1);
20972				} elseif ($outline['p'] == $frompage) {
20973					$this->outlines[$key]['p'] = $topage;
20974				}
20975			}
20976		}
20977		// adjust dests
20978		$tmpdests = $this->dests;
20979		foreach ($tmpdests as $key => $dest) {
20980			if (!$dest['f']) {
20981				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
20982					$this->dests[$key]['p'] = ($dest['p'] + 1);
20983				} elseif ($dest['p'] == $frompage) {
20984					$this->dests[$key]['p'] = $topage;
20985				}
20986			}
20987		}
20988		// adjust links
20989		$tmplinks = $this->links;
20990		foreach ($tmplinks as $key => $link) {
20991			if (!$link['f']) {
20992				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
20993					$this->links[$key]['p'] = ($link['p'] + 1);
20994				} elseif ($link['p'] == $frompage) {
20995					$this->links[$key]['p'] = $topage;
20996				}
20997			}
20998		}
20999		// adjust javascript
21000		$jfrompage = $frompage;
21001		$jtopage = $topage;
21002		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21003			foreach($pamatch[0] as $pk => $pmatch) {
21004				$pagenum = intval($pamatch[3][$pk]) + 1;
21005				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21006					$newpage = ($pagenum + 1);
21007				} elseif ($pagenum == $jfrompage) {
21008					$newpage = $jtopage;
21009				} else {
21010					$newpage = $pagenum;
21011				}
21012				--$newpage;
21013				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21014				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21015			}
21016			unset($pamatch);
21017		}
21018		// return to last page
21019		$this->lastPage(true);
21020		return true;
21021	}
21022
21023	/**
21024	 * Remove the specified page.
21025	 * @param $page (int) page to remove
21026	 * @return true in case of success, false in case of error.
21027	 * @public
21028	 * @since 4.6.004 (2009-04-23)
21029	 */
21030	public function deletePage($page) {
21031		if (($page < 1) OR ($page > $this->numpages)) {
21032			return false;
21033		}
21034		// delete current page
21035		unset($this->pages[$page]);
21036		unset($this->pagedim[$page]);
21037		unset($this->pagelen[$page]);
21038		unset($this->intmrk[$page]);
21039		unset($this->bordermrk[$page]);
21040		unset($this->cntmrk[$page]);
21041		foreach ($this->pageobjects[$page] as $oid) {
21042			if (isset($this->offsets[$oid])){
21043				unset($this->offsets[$oid]);
21044			}
21045		}
21046		unset($this->pageobjects[$page]);
21047		if (isset($this->footerpos[$page])) {
21048			unset($this->footerpos[$page]);
21049		}
21050		if (isset($this->footerlen[$page])) {
21051			unset($this->footerlen[$page]);
21052		}
21053		if (isset($this->transfmrk[$page])) {
21054			unset($this->transfmrk[$page]);
21055		}
21056		if (isset($this->PageAnnots[$page])) {
21057			unset($this->PageAnnots[$page]);
21058		}
21059		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21060			for ($i = $page; $i > 0; --$i) {
21061				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21062					--$this->pagegroups[$this->newpagegroup[$i]];
21063					break;
21064				}
21065			}
21066		}
21067		if (isset($this->pageopen[$page])) {
21068			unset($this->pageopen[$page]);
21069		}
21070		if ($page < $this->numpages) {
21071			// update remaining pages
21072			for ($i = $page; $i < $this->numpages; ++$i) {
21073				$j = $i + 1;
21074				// shift pages
21075				$this->setPageBuffer($i, $this->getPageBuffer($j));
21076				$this->pagedim[$i] = $this->pagedim[$j];
21077				$this->pagelen[$i] = $this->pagelen[$j];
21078				$this->intmrk[$i] = $this->intmrk[$j];
21079				$this->bordermrk[$i] = $this->bordermrk[$j];
21080				$this->cntmrk[$i] = $this->cntmrk[$j];
21081				$this->pageobjects[$i] = $this->pageobjects[$j];
21082				if (isset($this->footerpos[$j])) {
21083					$this->footerpos[$i] = $this->footerpos[$j];
21084				} elseif (isset($this->footerpos[$i])) {
21085					unset($this->footerpos[$i]);
21086				}
21087				if (isset($this->footerlen[$j])) {
21088					$this->footerlen[$i] = $this->footerlen[$j];
21089				} elseif (isset($this->footerlen[$i])) {
21090					unset($this->footerlen[$i]);
21091				}
21092				if (isset($this->transfmrk[$j])) {
21093					$this->transfmrk[$i] = $this->transfmrk[$j];
21094				} elseif (isset($this->transfmrk[$i])) {
21095					unset($this->transfmrk[$i]);
21096				}
21097				if (isset($this->PageAnnots[$j])) {
21098					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21099				} elseif (isset($this->PageAnnots[$i])) {
21100					unset($this->PageAnnots[$i]);
21101				}
21102				if (isset($this->newpagegroup[$j])) {
21103					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21104					unset($this->newpagegroup[$j]);
21105				}
21106				if ($this->currpagegroup == $j) {
21107					$this->currpagegroup = $i;
21108				}
21109				if (isset($this->pageopen[$j])) {
21110					$this->pageopen[$i] = $this->pageopen[$j];
21111				} elseif (isset($this->pageopen[$i])) {
21112					unset($this->pageopen[$i]);
21113				}
21114			}
21115			// remove last page
21116			unset($this->pages[$this->numpages]);
21117			unset($this->pagedim[$this->numpages]);
21118			unset($this->pagelen[$this->numpages]);
21119			unset($this->intmrk[$this->numpages]);
21120			unset($this->bordermrk[$this->numpages]);
21121			unset($this->cntmrk[$this->numpages]);
21122			foreach ($this->pageobjects[$this->numpages] as $oid) {
21123				if (isset($this->offsets[$oid])){
21124					unset($this->offsets[$oid]);
21125				}
21126			}
21127			unset($this->pageobjects[$this->numpages]);
21128			if (isset($this->footerpos[$this->numpages])) {
21129				unset($this->footerpos[$this->numpages]);
21130			}
21131			if (isset($this->footerlen[$this->numpages])) {
21132				unset($this->footerlen[$this->numpages]);
21133			}
21134			if (isset($this->transfmrk[$this->numpages])) {
21135				unset($this->transfmrk[$this->numpages]);
21136			}
21137			if (isset($this->PageAnnots[$this->numpages])) {
21138				unset($this->PageAnnots[$this->numpages]);
21139			}
21140			if (isset($this->newpagegroup[$this->numpages])) {
21141				unset($this->newpagegroup[$this->numpages]);
21142			}
21143			if ($this->currpagegroup == $this->numpages) {
21144				$this->currpagegroup = ($this->numpages - 1);
21145			}
21146			if (isset($this->pagegroups[$this->numpages])) {
21147				unset($this->pagegroups[$this->numpages]);
21148			}
21149			if (isset($this->pageopen[$this->numpages])) {
21150				unset($this->pageopen[$this->numpages]);
21151			}
21152		}
21153		--$this->numpages;
21154		$this->page = $this->numpages;
21155		// adjust outlines
21156		$tmpoutlines = $this->outlines;
21157		foreach ($tmpoutlines as $key => $outline) {
21158			if (!$outline['f']) {
21159				if ($outline['p'] > $page) {
21160					$this->outlines[$key]['p'] = $outline['p'] - 1;
21161				} elseif ($outline['p'] == $page) {
21162					unset($this->outlines[$key]);
21163				}
21164			}
21165		}
21166		// adjust dests
21167		$tmpdests = $this->dests;
21168		foreach ($tmpdests as $key => $dest) {
21169			if (!$dest['f']) {
21170				if ($dest['p'] > $page) {
21171					$this->dests[$key]['p'] = $dest['p'] - 1;
21172				} elseif ($dest['p'] == $page) {
21173					unset($this->dests[$key]);
21174				}
21175			}
21176		}
21177		// adjust links
21178		$tmplinks = $this->links;
21179		foreach ($tmplinks as $key => $link) {
21180			if (!$link['f']) {
21181				if ($link['p'] > $page) {
21182					$this->links[$key]['p'] = $link['p'] - 1;
21183				} elseif ($link['p'] == $page) {
21184					unset($this->links[$key]);
21185				}
21186			}
21187		}
21188		// adjust javascript
21189		$jpage = $page;
21190		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21191			foreach($pamatch[0] as $pk => $pmatch) {
21192				$pagenum = intval($pamatch[3][$pk]) + 1;
21193				if ($pagenum >= $jpage) {
21194					$newpage = ($pagenum - 1);
21195				} elseif ($pagenum == $jpage) {
21196					$newpage = 1;
21197				} else {
21198					$newpage = $pagenum;
21199				}
21200				--$newpage;
21201				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21202				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21203			}
21204			unset($pamatch);
21205		}
21206		// return to last page
21207		if ($this->numpages > 0) {
21208			$this->lastPage(true);
21209		}
21210		return true;
21211	}
21212
21213	/**
21214	 * Clone the specified page to a new page.
21215	 * @param $page (int) number of page to copy (0 = current page)
21216	 * @return true in case of success, false in case of error.
21217	 * @public
21218	 * @since 4.9.015 (2010-04-20)
21219	 */
21220	public function copyPage($page=0) {
21221		if ($page == 0) {
21222			// default value
21223			$page = $this->page;
21224		}
21225		if (($page < 1) OR ($page > $this->numpages)) {
21226			return false;
21227		}
21228		// close the last page
21229		$this->endPage();
21230		// copy all page-related states
21231		++$this->numpages;
21232		$this->page = $this->numpages;
21233		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21234		$this->pagedim[$this->page] = $this->pagedim[$page];
21235		$this->pagelen[$this->page] = $this->pagelen[$page];
21236		$this->intmrk[$this->page] = $this->intmrk[$page];
21237		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21238		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21239		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21240		$this->pageopen[$this->page] = false;
21241		if (isset($this->footerpos[$page])) {
21242			$this->footerpos[$this->page] = $this->footerpos[$page];
21243		}
21244		if (isset($this->footerlen[$page])) {
21245			$this->footerlen[$this->page] = $this->footerlen[$page];
21246		}
21247		if (isset($this->transfmrk[$page])) {
21248			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21249		}
21250		if (isset($this->PageAnnots[$page])) {
21251			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21252		}
21253		if (isset($this->newpagegroup[$page])) {
21254			// start a new group
21255			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21256			$this->currpagegroup = $this->newpagegroup[$this->page];
21257			$this->pagegroups[$this->currpagegroup] = 1;
21258		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21259			++$this->pagegroups[$this->currpagegroup];
21260		}
21261		// copy outlines
21262		$tmpoutlines = $this->outlines;
21263		foreach ($tmpoutlines as $key => $outline) {
21264			if ($outline['p'] == $page) {
21265				$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']);
21266			}
21267		}
21268		// copy links
21269		$tmplinks = $this->links;
21270		foreach ($tmplinks as $key => $link) {
21271			if ($link['p'] == $page) {
21272				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21273			}
21274		}
21275		// return to last page
21276		$this->lastPage(true);
21277		return true;
21278	}
21279
21280	/**
21281	 * Output a Table of Content Index (TOC).
21282	 * This method must be called after all Bookmarks were set.
21283	 * Before calling this method you have to open the page using the addTOCPage() method.
21284	 * After calling this method you have to call endTOCPage() to close the TOC page.
21285	 * You can override this method to achieve different styles.
21286	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21287	 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21288	 * @param $filler (string) string used to fill the space between text and page number.
21289	 * @param $toc_name (string) name to use for TOC bookmark.
21290	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21291	 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21292	 * @public
21293	 * @author Nicola Asuni
21294	 * @since 4.5.000 (2009-01-02)
21295	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21296	 */
21297	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21298		$fontsize = $this->FontSizePt;
21299		$fontfamily = $this->FontFamily;
21300		$fontstyle = $this->FontStyle;
21301		$w = $this->w - $this->lMargin - $this->rMargin;
21302		$spacer = $this->GetStringWidth(chr(32)) * 4;
21303		$lmargin = $this->lMargin;
21304		$rmargin = $this->rMargin;
21305		$x_start = $this->GetX();
21306		$page_first = $this->page;
21307		$current_page = $this->page;
21308		$page_fill_start = false;
21309		$page_fill_end = false;
21310		$current_column = $this->current_column;
21311		if (TCPDF_STATIC::empty_string($numbersfont)) {
21312			$numbersfont = $this->default_monospaced_font;
21313		}
21314		if (TCPDF_STATIC::empty_string($filler)) {
21315			$filler = ' ';
21316		}
21317		if (TCPDF_STATIC::empty_string($page)) {
21318			$gap = ' ';
21319		} else {
21320			$gap = '';
21321			if ($page < 1) {
21322				$page = 1;
21323			}
21324		}
21325		$this->SetFont($numbersfont, $fontstyle, $fontsize);
21326		$numwidth = $this->GetStringWidth('00000');
21327		$maxpage = 0; //used for pages on attached documents
21328		foreach ($this->outlines as $key => $outline) {
21329			// check for extra pages (used for attachments)
21330			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21331				$outline['p'] += ($this->page - $page_first);
21332			}
21333			if ($this->rtl) {
21334				$aligntext = 'R';
21335				$alignnum = 'L';
21336			} else {
21337				$aligntext = 'L';
21338				$alignnum = 'R';
21339			}
21340			if ($outline['l'] == 0) {
21341				$this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21342			} else {
21343				$this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21344			}
21345			$this->SetTextColorArray($outline['c']);
21346			// check for page break
21347			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21348			// set margins and X position
21349			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21350				$this->lMargin = $lmargin;
21351				$this->rMargin = $rmargin;
21352			} else {
21353				if ($this->current_column != $current_column) {
21354					if ($this->rtl) {
21355						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21356					} else {
21357						$x_start = $this->columns[$this->current_column]['x'];
21358					}
21359				}
21360				$lmargin = $this->lMargin;
21361				$rmargin = $this->rMargin;
21362				$current_page = $this->page;
21363				$current_column = $this->current_column;
21364			}
21365			$this->SetX($x_start);
21366			$indent = ($spacer * $outline['l']);
21367			if ($this->rtl) {
21368				$this->x -= $indent;
21369				$this->rMargin = $this->w - $this->x;
21370			} else {
21371				$this->x += $indent;
21372				$this->lMargin = $this->x;
21373			}
21374			$link = $this->AddLink();
21375			$this->SetLink($link, $outline['y'], $outline['p']);
21376			// write the text
21377			if ($this->rtl) {
21378				$txt = ' '.$outline['t'];
21379			} else {
21380				$txt = $outline['t'].' ';
21381			}
21382			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21383			if ($this->rtl) {
21384				$tw = $this->x - $this->lMargin;
21385			} else {
21386				$tw = $this->w - $this->rMargin - $this->x;
21387			}
21388			$this->SetFont($numbersfont, $fontstyle, $fontsize);
21389			if (TCPDF_STATIC::empty_string($page)) {
21390				$pagenum = $outline['p'];
21391			} else {
21392				// placemark to be replaced with the correct number
21393				$pagenum = '{#'.($outline['p']).'}';
21394				if ($this->isUnicodeFont()) {
21395					$pagenum = '{'.$pagenum.'}';
21396				}
21397				$maxpage = max($maxpage, $outline['p']);
21398			}
21399			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21400			$wfiller = $this->GetStringWidth($filler);
21401			if ($wfiller > 0) {
21402				$numfills = floor($fw / $wfiller);
21403			} else {
21404				$numfills = 0;
21405			}
21406			if ($numfills > 0) {
21407				$rowfill = str_repeat($filler, $numfills);
21408			} else {
21409				$rowfill = '';
21410			}
21411			if ($this->rtl) {
21412				$pagenum = $pagenum.$gap.$rowfill;
21413			} else {
21414				$pagenum = $rowfill.$gap.$pagenum;
21415			}
21416			// write the number
21417			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21418		}
21419		$page_last = $this->getPage();
21420		$numpages = ($page_last - $page_first + 1);
21421		// account for booklet mode
21422		if ($this->booklet) {
21423			// check if a blank page is required before TOC
21424			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21425			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21426			if ($page_fill_start) {
21427				// add a page at the end (to be moved before TOC)
21428				$this->addPage();
21429				++$page_last;
21430				++$numpages;
21431			}
21432			if ($page_fill_end) {
21433				// add a page at the end
21434				$this->addPage();
21435				++$page_last;
21436				++$numpages;
21437			}
21438		}
21439		$maxpage = max($maxpage, $page_last);
21440		if (!TCPDF_STATIC::empty_string($page)) {
21441			for ($p = $page_first; $p <= $page_last; ++$p) {
21442				// get page data
21443				$temppage = $this->getPageBuffer($p);
21444				for ($n = 1; $n <= $maxpage; ++$n) {
21445					// update page numbers
21446					$a = '{#'.$n.'}';
21447					// get page number aliases
21448					$pnalias = $this->getInternalPageNumberAliases($a);
21449					// calculate replacement number
21450					if (($n >= $page) AND ($n <= $this->numpages)) {
21451						$np = $n + $numpages;
21452					} else {
21453						$np = $n;
21454					}
21455					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21456					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21457					// replace aliases with numbers
21458					foreach ($pnalias['u'] as $u) {
21459						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21460						if ($this->rtl) {
21461							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21462						} else {
21463							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21464						}
21465						$temppage = str_replace($u, $nr, $temppage);
21466					}
21467					foreach ($pnalias['a'] as $a) {
21468						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21469						if ($this->rtl) {
21470							$nr = $na.' '.$sfill;
21471						} else {
21472							$nr = $sfill.' '.$na;
21473						}
21474						$temppage = str_replace($a, $nr, $temppage);
21475					}
21476				}
21477				// save changes
21478				$this->setPageBuffer($p, $temppage);
21479			}
21480			// move pages
21481			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21482			if ($page_fill_start) {
21483				$this->movePage($page_last, $page_first);
21484			}
21485			for ($i = 0; $i < $numpages; ++$i) {
21486				$this->movePage($page_last, $page);
21487			}
21488		}
21489	}
21490
21491	/**
21492	 * Output a Table Of Content Index (TOC) using HTML templates.
21493	 * This method must be called after all Bookmarks were set.
21494	 * Before calling this method you have to open the page using the addTOCPage() method.
21495	 * After calling this method you have to call endTOCPage() to close the TOC page.
21496	 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21497	 * @param $toc_name (string) name to use for TOC bookmark.
21498	 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21499	 * @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)
21500	 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21501	 * @param $color (array) RGB color array for title (values from 0 to 255).
21502	 * @public
21503	 * @author Nicola Asuni
21504	 * @since 5.0.001 (2010-05-06)
21505	 * @see addTOCPage(), endTOCPage(), addTOC()
21506	 */
21507	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21508		$filler = ' ';
21509		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21510		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21511		// set new style for link
21512		$this->htmlLinkColorArray = array();
21513		$this->htmlLinkFontStyle = '';
21514		$page_first = $this->getPage();
21515		$page_fill_start = false;
21516		$page_fill_end = false;
21517		// get the font type used for numbers in each template
21518		$current_font = $this->FontFamily;
21519		foreach ($templates as $level => $html) {
21520			$dom = $this->getHtmlDomArray($html);
21521			foreach ($dom as $key => $value) {
21522				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21523					$this->SetFont($dom[($key - 1)]['fontname']);
21524					$templates['F'.$level] = $this->isUnicodeFont();
21525				}
21526			}
21527		}
21528		$this->SetFont($current_font);
21529		$maxpage = 0; //used for pages on attached documents
21530		foreach ($this->outlines as $key => $outline) {
21531			// get HTML template
21532			$row = $templates[$outline['l']];
21533			if (TCPDF_STATIC::empty_string($page)) {
21534				$pagenum = $outline['p'];
21535			} else {
21536				// placemark to be replaced with the correct number
21537				$pagenum = '{#'.($outline['p']).'}';
21538				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21539					$pagenum = '{'.$pagenum.'}';
21540				}
21541				$maxpage = max($maxpage, $outline['p']);
21542			}
21543			// replace templates with current values
21544			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21545			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21546			// add link to page
21547			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21548			// write bookmark entry
21549			$this->writeHTML($row, false, false, true, false, '');
21550		}
21551		// restore link styles
21552		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21553		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21554		// move TOC page and replace numbers
21555		$page_last = $this->getPage();
21556		$numpages = ($page_last - $page_first + 1);
21557		// account for booklet mode
21558		if ($this->booklet) {
21559			// check if a blank page is required before TOC
21560			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21561			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21562			if ($page_fill_start) {
21563				// add a page at the end (to be moved before TOC)
21564				$this->addPage();
21565				++$page_last;
21566				++$numpages;
21567			}
21568			if ($page_fill_end) {
21569				// add a page at the end
21570				$this->addPage();
21571				++$page_last;
21572				++$numpages;
21573			}
21574		}
21575		$maxpage = max($maxpage, $page_last);
21576		if (!TCPDF_STATIC::empty_string($page)) {
21577			for ($p = $page_first; $p <= $page_last; ++$p) {
21578				// get page data
21579				$temppage = $this->getPageBuffer($p);
21580				for ($n = 1; $n <= $maxpage; ++$n) {
21581					// update page numbers
21582					$a = '{#'.$n.'}';
21583					// get page number aliases
21584					$pnalias = $this->getInternalPageNumberAliases($a);
21585					// calculate replacement number
21586					if ($n >= $page) {
21587						$np = $n + $numpages;
21588					} else {
21589						$np = $n;
21590					}
21591					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21592					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21593					// replace aliases with numbers
21594					foreach ($pnalias['u'] as $u) {
21595						if ($correct_align) {
21596							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21597							if ($this->rtl) {
21598								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21599							} else {
21600								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21601							}
21602						} else {
21603							$nr = $nu;
21604						}
21605						$temppage = str_replace($u, $nr, $temppage);
21606					}
21607					foreach ($pnalias['a'] as $a) {
21608						if ($correct_align) {
21609							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21610							if ($this->rtl) {
21611								$nr = $na.' '.$sfill;
21612							} else {
21613								$nr = $sfill.' '.$na;
21614							}
21615						} else {
21616							$nr = $na;
21617						}
21618						$temppage = str_replace($a, $nr, $temppage);
21619					}
21620				}
21621				// save changes
21622				$this->setPageBuffer($p, $temppage);
21623			}
21624			// move pages
21625			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21626			if ($page_fill_start) {
21627				$this->movePage($page_last, $page_first);
21628			}
21629			for ($i = 0; $i < $numpages; ++$i) {
21630				$this->movePage($page_last, $page);
21631			}
21632		}
21633	}
21634
21635	/**
21636	 * Stores a copy of the current TCPDF object used for undo operation.
21637	 * @public
21638	 * @since 4.5.029 (2009-03-19)
21639	 */
21640	public function startTransaction() {
21641		if (isset($this->objcopy)) {
21642			// remove previous copy
21643			$this->commitTransaction();
21644		}
21645		// record current page number and Y position
21646		$this->start_transaction_page = $this->page;
21647		$this->start_transaction_y = $this->y;
21648		// clone current object
21649		$this->objcopy = TCPDF_STATIC::objclone($this);
21650	}
21651
21652	/**
21653	 * Delete the copy of the current TCPDF object used for undo operation.
21654	 * @public
21655	 * @since 4.5.029 (2009-03-19)
21656	 */
21657	public function commitTransaction() {
21658		if (isset($this->objcopy)) {
21659			$this->objcopy->_destroy(true, true);
21660			unset($this->objcopy);
21661		}
21662	}
21663
21664	/**
21665	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21666	 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21667	 * @return TCPDF object.
21668	 * @public
21669	 * @since 4.5.029 (2009-03-19)
21670	 */
21671	public function rollbackTransaction($self=false) {
21672		if (isset($this->objcopy)) {
21673			$this->_destroy(true, true);
21674			if ($self) {
21675				$objvars = get_object_vars($this->objcopy);
21676				foreach ($objvars as $key => $value) {
21677					$this->$key = $value;
21678				}
21679			}
21680			return $this->objcopy;
21681		}
21682		return $this;
21683	}
21684
21685	// --- MULTI COLUMNS METHODS -----------------------
21686
21687	/**
21688	 * Set multiple columns of the same size
21689	 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21690	 * @param $width (int) column width
21691	 * @param $y (int) column starting Y position (leave empty for current Y position)
21692	 * @public
21693	 * @since 4.9.001 (2010-03-28)
21694	 */
21695	public function setEqualColumns($numcols=0, $width=0, $y='') {
21696		$this->columns = array();
21697		if ($numcols < 2) {
21698			$numcols = 0;
21699			$this->columns = array();
21700		} else {
21701			// maximum column width
21702			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21703			if (($width == 0) OR ($width > $maxwidth)) {
21704				$width = $maxwidth;
21705			}
21706			if (TCPDF_STATIC::empty_string($y)) {
21707				$y = $this->y;
21708			}
21709			// space between columns
21710			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21711			// fill the columns array (with, space, starting Y position)
21712			for ($i = 0; $i < $numcols; ++$i) {
21713				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21714			}
21715		}
21716		$this->num_columns = $numcols;
21717		$this->current_column = 0;
21718		$this->column_start_page = $this->page;
21719		$this->selectColumn(0);
21720	}
21721
21722	/**
21723	 * Remove columns and reset page margins.
21724	 * @public
21725	 * @since 5.9.072 (2011-04-26)
21726	 */
21727	public function resetColumns() {
21728		$this->lMargin = $this->original_lMargin;
21729		$this->rMargin = $this->original_rMargin;
21730		$this->setEqualColumns();
21731	}
21732
21733	/**
21734	 * Set columns array.
21735	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21736	 * @param $columns (array)
21737	 * @public
21738	 * @since 4.9.001 (2010-03-28)
21739	 */
21740	public function setColumnsArray($columns) {
21741		$this->columns = $columns;
21742		$this->num_columns = count($columns);
21743		$this->current_column = 0;
21744		$this->column_start_page = $this->page;
21745		$this->selectColumn(0);
21746	}
21747
21748	/**
21749	 * Set position at a given column
21750	 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21751	 * @public
21752	 * @since 4.9.001 (2010-03-28)
21753	 */
21754	public function selectColumn($col='') {
21755		if (is_string($col)) {
21756			$col = $this->current_column;
21757		} elseif ($col >= $this->num_columns) {
21758			$col = 0;
21759		}
21760		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21761		$enable_thead = false;
21762		if ($this->num_columns > 1) {
21763			if ($col != $this->current_column) {
21764				// move Y pointer at the top of the column
21765				if ($this->column_start_page == $this->page) {
21766					$this->y = $this->columns[$col]['y'];
21767				} else {
21768					$this->y = $this->tMargin;
21769				}
21770				// Avoid to write table headers more than once
21771				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21772					$enable_thead = true;
21773					$this->maxselcol['page'] = $this->page;
21774					$this->maxselcol['column'] = $col;
21775				}
21776			}
21777			$xshift = $this->colxshift;
21778			// set X position of the current column by case
21779			$listindent = ($this->listindentlevel * $this->listindent);
21780			// calculate column X position
21781			$colpos = 0;
21782			for ($i = 0; $i < $col; ++$i) {
21783				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21784			}
21785			if ($this->rtl) {
21786				$x = $this->w - $this->original_rMargin - $colpos;
21787				$this->rMargin = ($this->w - $x + $listindent);
21788				$this->lMargin = ($x - $this->columns[$col]['w']);
21789				$this->x = $x - $listindent;
21790			} else {
21791				$x = $this->original_lMargin + $colpos;
21792				$this->lMargin = ($x + $listindent);
21793				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21794				$this->x = $x + $listindent;
21795			}
21796			$this->columns[$col]['x'] = $x;
21797		}
21798		$this->current_column = $col;
21799		// fix for HTML mode
21800		$this->newline = true;
21801		// print HTML table header (if any)
21802		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21803			if ($enable_thead) {
21804				// print table header
21805				$this->writeHTML($this->thead, false, false, false, false, '');
21806				$this->y += $xshift['s']['V'];
21807				// store end of header position
21808				if (!isset($this->columns[$col]['th'])) {
21809					$this->columns[$col]['th'] = array();
21810				}
21811				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21812				$this->lasth = 0;
21813			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21814				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21815			}
21816		}
21817		// account for an html table cell over multiple columns
21818		if ($this->rtl) {
21819			$this->rMargin += $xshift['x'];
21820			$this->x -= ($xshift['x'] + $xshift['p']['R']);
21821		} else {
21822			$this->lMargin += $xshift['x'];
21823			$this->x += $xshift['x'] + $xshift['p']['L'];
21824		}
21825	}
21826
21827	/**
21828	 * Return the current column number
21829	 * @return int current column number
21830	 * @public
21831	 * @since 5.5.011 (2010-07-08)
21832	 */
21833	public function getColumn() {
21834		return $this->current_column;
21835	}
21836
21837	/**
21838	 * Return the current number of columns.
21839	 * @return int number of columns
21840	 * @public
21841	 * @since 5.8.018 (2010-08-25)
21842	 */
21843	public function getNumberOfColumns() {
21844		return $this->num_columns;
21845	}
21846
21847	/**
21848	 * Set Text rendering mode.
21849	 * @param $stroke (int) outline size in user units (0 = disable).
21850	 * @param $fill (boolean) if true fills the text (default).
21851	 * @param $clip (boolean) if true activate clipping mode
21852	 * @public
21853	 * @since 4.9.008 (2009-04-02)
21854	 */
21855	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21856		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21857		// convert text rendering parameters
21858		if ($stroke < 0) {
21859			$stroke = 0;
21860		}
21861		if ($fill === true) {
21862			if ($stroke > 0) {
21863				if ($clip === true) {
21864					// Fill, then stroke text and add to path for clipping
21865					$textrendermode = 6;
21866				} else {
21867					// Fill, then stroke text
21868					$textrendermode = 2;
21869				}
21870				$textstrokewidth = $stroke;
21871			} else {
21872				if ($clip === true) {
21873					// Fill text and add to path for clipping
21874					$textrendermode = 4;
21875				} else {
21876					// Fill text
21877					$textrendermode = 0;
21878				}
21879			}
21880		} else {
21881			if ($stroke > 0) {
21882				if ($clip === true) {
21883					// Stroke text and add to path for clipping
21884					$textrendermode = 5;
21885				} else {
21886					// Stroke text
21887					$textrendermode = 1;
21888				}
21889				$textstrokewidth = $stroke;
21890			} else {
21891				if ($clip === true) {
21892					// Add text to path for clipping
21893					$textrendermode = 7;
21894				} else {
21895					// Neither fill nor stroke text (invisible)
21896					$textrendermode = 3;
21897				}
21898			}
21899		}
21900		$this->textrendermode = $textrendermode;
21901		$this->textstrokewidth = $stroke;
21902	}
21903
21904	/**
21905	 * Set parameters for drop shadow effect for text.
21906	 * @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.
21907	 * @since 5.9.174 (2012-07-25)
21908	 * @public
21909	*/
21910	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21911		if (isset($params['enabled'])) {
21912			$this->txtshadow['enabled'] = $params['enabled']?true:false;
21913		} else {
21914			$this->txtshadow['enabled'] = false;
21915		}
21916		if (isset($params['depth_w'])) {
21917			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
21918		} else {
21919			$this->txtshadow['depth_w'] = 0;
21920		}
21921		if (isset($params['depth_h'])) {
21922			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
21923		} else {
21924			$this->txtshadow['depth_h'] = 0;
21925		}
21926		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21927			$this->txtshadow['color'] = $params['color'];
21928		} else {
21929			$this->txtshadow['color'] = $this->strokecolor;
21930		}
21931		if (isset($params['opacity'])) {
21932			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
21933		} else {
21934			$this->txtshadow['opacity'] = 1;
21935		}
21936		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'))) {
21937			$this->txtshadow['blend_mode'] = $params['blend_mode'];
21938		} else {
21939			$this->txtshadow['blend_mode'] = 'Normal';
21940		}
21941		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
21942			$this->txtshadow['enabled'] = false;
21943		}
21944	}
21945
21946	/**
21947	 * Return the text shadow parameters array.
21948	 * @return Array of parameters.
21949	 * @since 5.9.174 (2012-07-25)
21950	 * @public
21951	 */
21952	public function getTextShadow() {
21953		return $this->txtshadow;
21954	}
21955
21956	/**
21957	 * Returns an array of chars containing soft hyphens.
21958	 * @param $word (array) array of chars
21959	 * @param $patterns (array) Array of hypenation patterns.
21960	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
21961	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
21962	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
21963	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
21964	 * @param $charmax (int) Maximum length of broken piece of word.
21965	 * @return array text with soft hyphens
21966	 * @author Nicola Asuni
21967	 * @since 4.9.012 (2010-04-12)
21968	 * @protected
21969	 */
21970	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
21971		$hyphenword = array(); // hyphens positions
21972		$numchars = count($word);
21973		if ($numchars <= $charmin) {
21974			return $word;
21975		}
21976		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
21977		// some words will be returned as-is
21978		$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})(\]?)$/';
21979		if (preg_match($pattern, $word_string) > 0) {
21980			// email
21981			return $word;
21982		}
21983		$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})(\]?)$/';
21984		if (preg_match($pattern, $word_string) > 0) {
21985			// URL
21986			return $word;
21987		}
21988		if (isset($dictionary[$word_string])) {
21989			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
21990		}
21991		// surround word with '_' characters
21992		$tmpword = array_merge(array(46), $word, array(46));
21993		$tmpnumchars = $numchars + 2;
21994		$maxpos = $tmpnumchars - 1;
21995		for ($pos = 0; $pos < $maxpos; ++$pos) {
21996			$imax = min(($tmpnumchars - $pos), $charmax);
21997			for ($i = 1; $i <= $imax; ++$i) {
21998				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
21999				if (isset($patterns[$subword])) {
22000					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22001					$pattern_length = count($pattern);
22002					$digits = 1;
22003					for ($j = 0; $j < $pattern_length; ++$j) {
22004						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22005						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22006							if ($j == 0) {
22007								$zero = $pos - 1;
22008							} else {
22009								$zero = $pos + $j - $digits;
22010							}
22011							// get hyphenation level
22012							$level = ($pattern[$j] - 48);
22013							// if two levels from two different patterns match at the same point, the higher one is selected.
22014							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22015								$hyphenword[$zero] = $level;
22016							}
22017							++$digits;
22018						}
22019					}
22020				}
22021			}
22022		}
22023		$inserted = 0;
22024		$maxpos = $numchars - $rightmin;
22025		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22026			// only odd levels indicate allowed hyphenation points
22027			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22028				// 173 = soft hyphen character
22029				array_splice($word, $i + $inserted, 0, 173);
22030				++$inserted;
22031			}
22032		}
22033		return $word;
22034	}
22035
22036	/**
22037	 * Returns text with soft hyphens.
22038	 * @param $text (string) text to process
22039	 * @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/
22040	 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22041	 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22042	 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22043	 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22044	 * @param $charmax (int) Maximum length of broken piece of word.
22045	 * @return array text with soft hyphens
22046	 * @author Nicola Asuni
22047	 * @since 4.9.012 (2010-04-12)
22048	 * @public
22049	 */
22050	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22051		$text = $this->unhtmlentities($text);
22052		$word = array(); // last word
22053		$txtarr = array(); // text to be returned
22054		$intag = false; // true if we are inside an HTML tag
22055		$skip = false; // true to skip hyphenation
22056		if (!is_array($patterns)) {
22057			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22058		}
22059		// get array of characters
22060		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22061		// for each char
22062		foreach ($unichars as $char) {
22063			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22064				// letter character
22065				$word[] = $char;
22066			} else {
22067				// other type of character
22068				if (!TCPDF_STATIC::empty_string($word)) {
22069					// hypenate the word
22070					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22071					$word = array();
22072				}
22073				$txtarr[] = $char;
22074				if (chr($char) == '<') {
22075					// we are inside an HTML tag
22076					$intag = true;
22077				} elseif ($intag AND (chr($char) == '>')) {
22078					// end of HTML tag
22079					$intag = false;
22080					// check for style tag
22081					$expected = array(115, 116, 121, 108, 101); // = 'style'
22082					$current = array_slice($txtarr, -6, 5); // last 5 chars
22083					$compare = array_diff($expected, $current);
22084					if (empty($compare)) {
22085						// check if it is a closing tag
22086						$expected = array(47); // = '/'
22087						$current = array_slice($txtarr, -7, 1);
22088						$compare = array_diff($expected, $current);
22089						if (empty($compare)) {
22090							// closing style tag
22091							$skip = false;
22092						} else {
22093							// opening style tag
22094							$skip = true;
22095						}
22096					}
22097				}
22098			}
22099		}
22100		if (!TCPDF_STATIC::empty_string($word)) {
22101			// hypenate the word
22102			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22103		}
22104		// convert char array to string and return
22105		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22106	}
22107
22108	/**
22109	 * Enable/disable rasterization of vector images using ImageMagick library.
22110	 * @param $mode (boolean) if true enable rasterization, false otherwise.
22111	 * @public
22112	 * @since 5.0.000 (2010-04-27)
22113	 */
22114	public function setRasterizeVectorImages($mode) {
22115		$this->rasterize_vector_images = $mode;
22116	}
22117
22118	/**
22119	 * Enable or disable default option for font subsetting.
22120	 * @param $enable (boolean) if true enable font subsetting by default.
22121	 * @author Nicola Asuni
22122	 * @public
22123	 * @since 5.3.002 (2010-06-07)
22124	 */
22125	public function setFontSubsetting($enable=true) {
22126		if ($this->pdfa_mode) {
22127			$this->font_subsetting = false;
22128		} else {
22129			$this->font_subsetting = $enable ? true : false;
22130		}
22131	}
22132
22133	/**
22134	 * Return the default option for font subsetting.
22135	 * @return boolean default font subsetting state.
22136	 * @author Nicola Asuni
22137	 * @public
22138	 * @since 5.3.002 (2010-06-07)
22139	 */
22140	public function getFontSubsetting() {
22141		return $this->font_subsetting;
22142	}
22143
22144	/**
22145	 * Left trim the input string
22146	 * @param $str (string) string to trim
22147	 * @param $replace (string) string that replace spaces.
22148	 * @return left trimmed string
22149	 * @author Nicola Asuni
22150	 * @public
22151	 * @since 5.8.000 (2010-08-11)
22152	 */
22153	public function stringLeftTrim($str, $replace='') {
22154		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22155	}
22156
22157	/**
22158	 * Right trim the input string
22159	 * @param $str (string) string to trim
22160	 * @param $replace (string) string that replace spaces.
22161	 * @return right trimmed string
22162	 * @author Nicola Asuni
22163	 * @public
22164	 * @since 5.8.000 (2010-08-11)
22165	 */
22166	public function stringRightTrim($str, $replace='') {
22167		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22168	}
22169
22170	/**
22171	 * Trim the input string
22172	 * @param $str (string) string to trim
22173	 * @param $replace (string) string that replace spaces.
22174	 * @return trimmed string
22175	 * @author Nicola Asuni
22176	 * @public
22177	 * @since 5.8.000 (2010-08-11)
22178	 */
22179	public function stringTrim($str, $replace='') {
22180		$str = $this->stringLeftTrim($str, $replace);
22181		$str = $this->stringRightTrim($str, $replace);
22182		return $str;
22183	}
22184
22185	/**
22186	 * Return true if the current font is unicode type.
22187	 * @return true for unicode font, false otherwise.
22188	 * @author Nicola Asuni
22189	 * @public
22190	 * @since 5.8.002 (2010-08-14)
22191	 */
22192	public function isUnicodeFont() {
22193		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22194	}
22195
22196	/**
22197	 * Return normalized font name
22198	 * @param $fontfamily (string) property string containing font family names
22199	 * @return string normalized font name
22200	 * @author Nicola Asuni
22201	 * @public
22202	 * @since 5.8.004 (2010-08-17)
22203	 */
22204	public function getFontFamilyName($fontfamily) {
22205		// remove spaces and symbols
22206		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22207		// extract all font names
22208		$fontslist = preg_split('/[,]/', $fontfamily);
22209		// find first valid font name
22210		foreach ($fontslist as $font) {
22211			// replace font variations
22212			$font = preg_replace('/regular$/', '', $font);
22213			$font = preg_replace('/italic$/', 'I', $font);
22214			$font = preg_replace('/oblique$/', 'I', $font);
22215			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22216			// replace common family names and core fonts
22217			$pattern = array();
22218			$replacement = array();
22219			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22220			$replacement[] = 'times';
22221			$pattern[] = '/^sansserif/';
22222			$replacement[] = 'helvetica';
22223			$pattern[] = '/^monospace/';
22224			$replacement[] = 'courier';
22225			$font = preg_replace($pattern, $replacement, $font);
22226			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22227				return $font;
22228			}
22229		}
22230		// return current font as default
22231		return $this->CurrentFont['fontkey'];
22232	}
22233
22234	/**
22235	 * Start a new XObject Template.
22236	 * 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).
22237	 * 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.
22238	 * Note: X,Y coordinates will be reset to 0,0.
22239	 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22240	 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22241	 * @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).
22242	 * @return int the XObject Template ID in case of success or false in case of error.
22243	 * @author Nicola Asuni
22244	 * @public
22245	 * @since 5.8.017 (2010-08-24)
22246	 * @see endTemplate(), printTemplate()
22247	 */
22248	public function startTemplate($w=0, $h=0, $group=false) {
22249		if ($this->inxobj) {
22250			// we are already inside an XObject template
22251			return false;
22252		}
22253		$this->inxobj = true;
22254		++$this->n;
22255		// XObject ID
22256		$this->xobjid = 'XT'.$this->n;
22257		// object ID
22258		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22259		// store current graphic state
22260		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22261		// initialize data
22262		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22263		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22264		$this->xobjects[$this->xobjid]['outdata'] = '';
22265		$this->xobjects[$this->xobjid]['xobjects'] = array();
22266		$this->xobjects[$this->xobjid]['images'] = array();
22267		$this->xobjects[$this->xobjid]['fonts'] = array();
22268		$this->xobjects[$this->xobjid]['annotations'] = array();
22269		$this->xobjects[$this->xobjid]['extgstates'] = array();
22270		$this->xobjects[$this->xobjid]['gradients'] = array();
22271		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22272		// set new environment
22273		$this->num_columns = 1;
22274		$this->current_column = 0;
22275		$this->SetAutoPageBreak(false);
22276		if (($w === '') OR ($w <= 0)) {
22277			$w = $this->w - $this->lMargin - $this->rMargin;
22278		}
22279		if (($h === '') OR ($h <= 0)) {
22280			$h = $this->h - $this->tMargin - $this->bMargin;
22281		}
22282		$this->xobjects[$this->xobjid]['x'] = 0;
22283		$this->xobjects[$this->xobjid]['y'] = 0;
22284		$this->xobjects[$this->xobjid]['w'] = $w;
22285		$this->xobjects[$this->xobjid]['h'] = $h;
22286		$this->w = $w;
22287		$this->h = $h;
22288		$this->wPt = $this->w * $this->k;
22289		$this->hPt = $this->h * $this->k;
22290		$this->fwPt = $this->wPt;
22291		$this->fhPt = $this->hPt;
22292		$this->x = 0;
22293		$this->y = 0;
22294		$this->lMargin = 0;
22295		$this->rMargin = 0;
22296		$this->tMargin = 0;
22297		$this->bMargin = 0;
22298		// set group mode
22299		$this->xobjects[$this->xobjid]['group'] = $group;
22300		return $this->xobjid;
22301	}
22302
22303	/**
22304	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22305	 * 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).
22306	 * 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.
22307	 * @return int the XObject Template ID in case of success or false in case of error.
22308	 * @author Nicola Asuni
22309	 * @public
22310	 * @since 5.8.017 (2010-08-24)
22311	 * @see startTemplate(), printTemplate()
22312	 */
22313	public function endTemplate() {
22314		if (!$this->inxobj) {
22315			// we are not inside a template
22316			return false;
22317		}
22318		$this->inxobj = false;
22319		// restore previous graphic state
22320		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22321		return $this->xobjid;
22322	}
22323
22324	/**
22325	 * Print an XObject Template.
22326	 * You can print an XObject Template inside the currently opened Template.
22327	 * 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).
22328	 * 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.
22329	 * @param $id (string) The ID of XObject Template to print.
22330	 * @param $x (int) X position in user units (empty string = current x position)
22331	 * @param $y (int) Y position in user units (empty string = current y position)
22332	 * @param $w (int) Width in user units (zero = remaining page width)
22333	 * @param $h (int) Height in user units (zero = remaining page height)
22334	 * @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>
22335	 * @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>
22336	 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22337	 * @author Nicola Asuni
22338	 * @public
22339	 * @since 5.8.017 (2010-08-24)
22340	 * @see startTemplate(), endTemplate()
22341	 */
22342	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22343		if ($this->state != 2) {
22344			 return;
22345		}
22346		if (!isset($this->xobjects[$id])) {
22347			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22348		}
22349		if ($this->inxobj) {
22350			if ($id == $this->xobjid) {
22351				// close current template
22352				$this->endTemplate();
22353			} else {
22354				// use the template as resource for the template currently opened
22355				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22356			}
22357		}
22358		// set default values
22359		if ($x === '') {
22360			$x = $this->x;
22361		}
22362		if ($y === '') {
22363			$y = $this->y;
22364		}
22365		// check page for no-write regions and adapt page margins if necessary
22366		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22367		$ow = $this->xobjects[$id]['w'];
22368		if ($ow <= 0) {
22369			$ow = 1;
22370		}
22371		$oh = $this->xobjects[$id]['h'];
22372		if ($oh <= 0) {
22373			$oh = 1;
22374		}
22375		// calculate template width and height on document
22376		if (($w <= 0) AND ($h <= 0)) {
22377			$w = $ow;
22378			$h = $oh;
22379		} elseif ($w <= 0) {
22380			$w = $h * $ow / $oh;
22381		} elseif ($h <= 0) {
22382			$h = $w * $oh / $ow;
22383		}
22384		// fit the template on available space
22385		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22386		// set page alignment
22387		$rb_y = $y + $h;
22388		// set alignment
22389		if ($this->rtl) {
22390			if ($palign == 'L') {
22391				$xt = $this->lMargin;
22392			} elseif ($palign == 'C') {
22393				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22394			} elseif ($palign == 'R') {
22395				$xt = $this->w - $this->rMargin - $w;
22396			} else {
22397				$xt = $x - $w;
22398			}
22399			$rb_x = $xt;
22400		} else {
22401			if ($palign == 'L') {
22402				$xt = $this->lMargin;
22403			} elseif ($palign == 'C') {
22404				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22405			} elseif ($palign == 'R') {
22406				$xt = $this->w - $this->rMargin - $w;
22407			} else {
22408				$xt = $x;
22409			}
22410			$rb_x = $xt + $w;
22411		}
22412		// print XObject Template + Transformation matrix
22413		$this->StartTransform();
22414		// translate and scale
22415		$sx = ($w / $ow);
22416		$sy = ($h / $oh);
22417		$tm = array();
22418		$tm[0] = $sx;
22419		$tm[1] = 0;
22420		$tm[2] = 0;
22421		$tm[3] = $sy;
22422		$tm[4] = $xt * $this->k;
22423		$tm[5] = ($this->h - $h - $y) * $this->k;
22424		$this->Transform($tm);
22425		// set object
22426		$this->_out('/'.$id.' Do');
22427		$this->StopTransform();
22428		// add annotations
22429		if (!empty($this->xobjects[$id]['annotations'])) {
22430			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22431				// transform original coordinates
22432				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22433				$ax = ($coordlt[4] / $this->k);
22434				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22435				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22436				$aw = ($coordrb[4] / $this->k) - $ax;
22437				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22438				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22439			}
22440		}
22441		// set pointer to align the next text/objects
22442		switch($align) {
22443			case 'T': {
22444				$this->y = $y;
22445				$this->x = $rb_x;
22446				break;
22447			}
22448			case 'M': {
22449				$this->y = $y + round($h/2);
22450				$this->x = $rb_x;
22451				break;
22452			}
22453			case 'B': {
22454				$this->y = $rb_y;
22455				$this->x = $rb_x;
22456				break;
22457			}
22458			case 'N': {
22459				$this->SetY($rb_y);
22460				break;
22461			}
22462			default:{
22463				break;
22464			}
22465		}
22466	}
22467
22468	/**
22469	 * Set the percentage of character stretching.
22470	 * @param $perc (int) percentage of stretching (100 = no stretching)
22471	 * @author Nicola Asuni
22472	 * @public
22473	 * @since 5.9.000 (2010-09-29)
22474	 */
22475	public function setFontStretching($perc=100) {
22476		$this->font_stretching = $perc;
22477	}
22478
22479	/**
22480	 * Get the percentage of character stretching.
22481	 * @return float stretching value
22482	 * @author Nicola Asuni
22483	 * @public
22484	 * @since 5.9.000 (2010-09-29)
22485	 */
22486	public function getFontStretching() {
22487		return $this->font_stretching;
22488	}
22489
22490	/**
22491	 * Set the amount to increase or decrease the space between characters in a text.
22492	 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22493	 * @author Nicola Asuni
22494	 * @public
22495	 * @since 5.9.000 (2010-09-29)
22496	 */
22497	public function setFontSpacing($spacing=0) {
22498		$this->font_spacing = $spacing;
22499	}
22500
22501	/**
22502	 * Get the amount to increase or decrease the space between characters in a text.
22503	 * @return int font spacing (tracking) value
22504	 * @author Nicola Asuni
22505	 * @public
22506	 * @since 5.9.000 (2010-09-29)
22507	 */
22508	public function getFontSpacing() {
22509		return $this->font_spacing;
22510	}
22511
22512	/**
22513	 * Return an array of no-write page regions
22514	 * @return array of no-write page regions
22515	 * @author Nicola Asuni
22516	 * @public
22517	 * @since 5.9.003 (2010-10-13)
22518	 * @see setPageRegions(), addPageRegion()
22519	 */
22520	public function getPageRegions() {
22521		return $this->page_regions;
22522	}
22523
22524	/**
22525	 * Set no-write regions on page.
22526	 * 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.
22527	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22528	 * You can set multiple regions for the same page.
22529	 * @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.
22530	 * @author Nicola Asuni
22531	 * @public
22532	 * @since 5.9.003 (2010-10-13)
22533	 * @see addPageRegion(), getPageRegions()
22534	 */
22535	public function setPageRegions($regions=array()) {
22536		// empty current regions array
22537		$this->page_regions = array();
22538		// add regions
22539		foreach ($regions as $data) {
22540			$this->addPageRegion($data);
22541		}
22542	}
22543
22544	/**
22545	 * Add a single no-write region on selected page.
22546	 * 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.
22547	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22548	 * You can set multiple regions for the same page.
22549	 * @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).
22550	 * @author Nicola Asuni
22551	 * @public
22552	 * @since 5.9.003 (2010-10-13)
22553	 * @see setPageRegions(), getPageRegions()
22554	 */
22555	public function addPageRegion($region) {
22556		if (!isset($region['page']) OR empty($region['page'])) {
22557			$region['page'] = $this->page;
22558		}
22559		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22560			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22561			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22562			$this->page_regions[] = $region;
22563		}
22564	}
22565
22566	/**
22567	 * Remove a single no-write region.
22568	 * @param $key (int) region key
22569	 * @author Nicola Asuni
22570	 * @public
22571	 * @since 5.9.003 (2010-10-13)
22572	 * @see setPageRegions(), getPageRegions()
22573	 */
22574	public function removePageRegion($key) {
22575		if (isset($this->page_regions[$key])) {
22576			unset($this->page_regions[$key]);
22577		}
22578	}
22579
22580	/**
22581	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22582	 * 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.
22583	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22584	 * @param $h (float) height of the text/image/object to print in user units
22585	 * @param $x (float) current X coordinate in user units
22586	 * @param $y (float) current Y coordinate in user units
22587	 * @return array($x, $y)
22588	 * @author Nicola Asuni
22589	 * @protected
22590	 * @since 5.9.003 (2010-10-13)
22591	 */
22592	protected function checkPageRegions($h, $x, $y) {
22593		// set default values
22594		if ($x === '') {
22595			$x = $this->x;
22596		}
22597		if ($y === '') {
22598			$y = $this->y;
22599		}
22600		if (!$this->check_page_regions OR empty($this->page_regions)) {
22601			// no page regions defined
22602			return array($x, $y);
22603		}
22604		if (empty($h)) {
22605			$h = $this->getCellHeight($this->FontSize);
22606		}
22607		// check for page break
22608		if ($this->checkPageBreak($h, $y)) {
22609			// the content will be printed on a new page
22610			$x = $this->x;
22611			$y = $this->y;
22612		}
22613		if ($this->num_columns > 1) {
22614			if ($this->rtl) {
22615				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22616			} else {
22617				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22618			}
22619		} else {
22620			if ($this->rtl) {
22621				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22622			} else {
22623				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22624			}
22625		}
22626		// adjust coordinates and page margins
22627		foreach ($this->page_regions as $regid => $regdata) {
22628			if ($regdata['page'] == $this->page) {
22629				// check region boundaries
22630				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22631					// Y is inside the region
22632					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22633					$yt = max($y, $regdata['yt']);
22634					$yb = min(($yt + $h), $regdata['yb']);
22635					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22636					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22637					if ($regdata['side'] == 'L') { // left side
22638						$new_margin = max($xt, $xb);
22639						if ($this->lMargin < $new_margin) {
22640							if ($this->rtl) {
22641								// adjust left page margin
22642								$this->lMargin = max(0, $new_margin);
22643							}
22644							if ($x < $new_margin) {
22645								// adjust x position
22646								$x = $new_margin;
22647								if ($new_margin > ($this->w - $this->rMargin)) {
22648									// adjust y position
22649									$y = $regdata['yb'] - $h;
22650								}
22651							}
22652						}
22653					} elseif ($regdata['side'] == 'R') { // right side
22654						$new_margin = min($xt, $xb);
22655						if (($this->w - $this->rMargin) > $new_margin) {
22656							if (!$this->rtl) {
22657								// adjust right page margin
22658								$this->rMargin = max(0, ($this->w - $new_margin));
22659							}
22660							if ($x > $new_margin) {
22661								// adjust x position
22662								$x = $new_margin;
22663								if ($new_margin > $this->lMargin) {
22664									// adjust y position
22665									$y = $regdata['yb'] - $h;
22666								}
22667							}
22668						}
22669					}
22670				}
22671			}
22672		}
22673		return array($x, $y);
22674	}
22675
22676	// --- SVG METHODS ---------------------------------------------------------
22677
22678	/**
22679	 * Embedd a Scalable Vector Graphics (SVG) image.
22680	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22681	 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22682	 * @param $x (float) Abscissa of the upper-left corner.
22683	 * @param $y (float) Ordinate of the upper-left corner.
22684	 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22685	 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22686	 * @param $link (mixed) URL or identifier returned by AddLink().
22687	 * @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.
22688	 * @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>
22689	 * @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)))
22690	 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22691	 * @author Nicola Asuni
22692	 * @since 5.0.000 (2010-05-02)
22693	 * @public
22694	 */
22695	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22696		if ($this->state != 2) {
22697			 return;
22698		}
22699		// reset SVG vars
22700		$this->svggradients = array();
22701		$this->svggradientid = 0;
22702		$this->svgdefsmode = false;
22703		$this->svgdefs = array();
22704		$this->svgclipmode = false;
22705		$this->svgclippaths = array();
22706		$this->svgcliptm = array();
22707		$this->svgclipid = 0;
22708		$this->svgtext = '';
22709		$this->svgtextmode = array();
22710		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22711			// convert SVG to raster image using GD or ImageMagick libraries
22712			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22713		}
22714		if ($file[0] === '@') { // image from string
22715			$this->svgdir = '';
22716			$svgdata = substr($file, 1);
22717		} else { // SVG file
22718			$this->svgdir = dirname($file);
22719			$svgdata = TCPDF_STATIC::fileGetContents($file);
22720		}
22721		if ($svgdata === FALSE) {
22722			$this->Error('SVG file not found: '.$file);
22723		}
22724		if ($x === '') {
22725			$x = $this->x;
22726		}
22727		if ($y === '') {
22728			$y = $this->y;
22729		}
22730		// check page for no-write regions and adapt page margins if necessary
22731		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22732		$k = $this->k;
22733		$ox = 0;
22734		$oy = 0;
22735		$ow = $w;
22736		$oh = $h;
22737		$aspect_ratio_align = 'xMidYMid';
22738		$aspect_ratio_ms = 'meet';
22739		$regs = array();
22740		// get original image width and height
22741		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22742		if (isset($regs[1]) AND !empty($regs[1])) {
22743			$tmp = array();
22744			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22745				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22746			}
22747			$tmp = array();
22748			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22749				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22750			}
22751			$tmp = array();
22752			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22753				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22754			}
22755			$tmp = array();
22756			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22757				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22758			}
22759			$tmp = array();
22760			$view_box = array();
22761			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22762				if (count($tmp) == 5) {
22763					array_shift($tmp);
22764					foreach ($tmp as $key => $val) {
22765						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22766					}
22767					$ox = $view_box[0];
22768					$oy = $view_box[1];
22769				}
22770				// get aspect ratio
22771				$tmp = array();
22772				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22773					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22774					switch (count($aspect_ratio)) {
22775						case 3: {
22776							$aspect_ratio_align = $aspect_ratio[1];
22777							$aspect_ratio_ms = $aspect_ratio[2];
22778							break;
22779						}
22780						case 2: {
22781							$aspect_ratio_align = $aspect_ratio[0];
22782							$aspect_ratio_ms = $aspect_ratio[1];
22783							break;
22784						}
22785						case 1: {
22786							$aspect_ratio_align = $aspect_ratio[0];
22787							$aspect_ratio_ms = 'meet';
22788							break;
22789						}
22790					}
22791				}
22792			}
22793		}
22794		if ($ow <= 0) {
22795			$ow = 1;
22796		}
22797		if ($oh <= 0) {
22798			$oh = 1;
22799		}
22800		// calculate image width and height on document
22801		if (($w <= 0) AND ($h <= 0)) {
22802			// convert image size to document unit
22803			$w = $ow;
22804			$h = $oh;
22805		} elseif ($w <= 0) {
22806			$w = $h * $ow / $oh;
22807		} elseif ($h <= 0) {
22808			$h = $w * $oh / $ow;
22809		}
22810		// fit the image on available space
22811		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22812		if ($this->rasterize_vector_images) {
22813			// convert SVG to raster image using GD or ImageMagick libraries
22814			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22815		}
22816		// set alignment
22817		$this->img_rb_y = $y + $h;
22818		// set alignment
22819		if ($this->rtl) {
22820			if ($palign == 'L') {
22821				$ximg = $this->lMargin;
22822			} elseif ($palign == 'C') {
22823				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22824			} elseif ($palign == 'R') {
22825				$ximg = $this->w - $this->rMargin - $w;
22826			} else {
22827				$ximg = $x - $w;
22828			}
22829			$this->img_rb_x = $ximg;
22830		} else {
22831			if ($palign == 'L') {
22832				$ximg = $this->lMargin;
22833			} elseif ($palign == 'C') {
22834				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22835			} elseif ($palign == 'R') {
22836				$ximg = $this->w - $this->rMargin - $w;
22837			} else {
22838				$ximg = $x;
22839			}
22840			$this->img_rb_x = $ximg + $w;
22841		}
22842		// store current graphic vars
22843		$gvars = $this->getGraphicVars();
22844		// store SVG position and scale factors
22845		$svgoffset_x = ($ximg - $ox) * $this->k;
22846		$svgoffset_y = -($y - $oy) * $this->k;
22847		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22848			$ow = $view_box[2];
22849			$oh = $view_box[3];
22850		} else {
22851			if ($ow <= 0) {
22852				$ow = $w;
22853			}
22854			if ($oh <= 0) {
22855				$oh = $h;
22856			}
22857		}
22858		$svgscale_x = $w / $ow;
22859		$svgscale_y = $h / $oh;
22860		// scaling and alignment
22861		if ($aspect_ratio_align != 'none') {
22862			// store current scaling values
22863			$svgscale_old_x = $svgscale_x;
22864			$svgscale_old_y = $svgscale_y;
22865			// force uniform scaling
22866			if ($aspect_ratio_ms == 'slice') {
22867				// the entire viewport is covered by the viewBox
22868				if ($svgscale_x > $svgscale_y) {
22869					$svgscale_y = $svgscale_x;
22870				} elseif ($svgscale_x < $svgscale_y) {
22871					$svgscale_x = $svgscale_y;
22872				}
22873			} else { // meet
22874				// the entire viewBox is visible within the viewport
22875				if ($svgscale_x < $svgscale_y) {
22876					$svgscale_y = $svgscale_x;
22877				} elseif ($svgscale_x > $svgscale_y) {
22878					$svgscale_x = $svgscale_y;
22879				}
22880			}
22881			// correct X alignment
22882			switch (substr($aspect_ratio_align, 1, 3)) {
22883				case 'Min': {
22884					// do nothing
22885					break;
22886				}
22887				case 'Max': {
22888					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22889					break;
22890				}
22891				default:
22892				case 'Mid': {
22893					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22894					break;
22895				}
22896			}
22897			// correct Y alignment
22898			switch (substr($aspect_ratio_align, 5)) {
22899				case 'Min': {
22900					// do nothing
22901					break;
22902				}
22903				case 'Max': {
22904					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22905					break;
22906				}
22907				default:
22908				case 'Mid': {
22909					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22910					break;
22911				}
22912			}
22913		}
22914		// store current page break mode
22915		$page_break_mode = $this->AutoPageBreak;
22916		$page_break_margin = $this->getBreakMargin();
22917		$cell_padding = $this->cell_padding;
22918		$this->SetCellPadding(0);
22919		$this->SetAutoPageBreak(false);
22920		// save the current graphic state
22921		$this->_out('q'.$this->epsmarker);
22922		// set initial clipping mask
22923		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22924		// scale and translate
22925		$e = $ox * $this->k * (1 - $svgscale_x);
22926		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22927		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22928		// creates a new XML parser to be used by the other XML functions
22929		$this->parser = xml_parser_create('UTF-8');
22930		// the following function allows to use parser inside object
22931		xml_set_object($this->parser, $this);
22932		// disable case-folding for this XML parser
22933		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
22934		// sets the element handler functions for the XML parser
22935		xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
22936		// sets the character data handler function for the XML parser
22937		xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
22938		// start parsing an XML document
22939		if (!xml_parse($this->parser, $svgdata)) {
22940			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
22941			$this->Error($error_message);
22942		}
22943		// free this XML parser
22944		xml_parser_free($this->parser);
22945		// restore previous graphic state
22946		$this->_out($this->epsmarker.'Q');
22947		// restore graphic vars
22948		$this->setGraphicVars($gvars);
22949		$this->lasth = $gvars['lasth'];
22950		if (!empty($border)) {
22951			$bx = $this->x;
22952			$by = $this->y;
22953			$this->x = $ximg;
22954			if ($this->rtl) {
22955				$this->x += $w;
22956			}
22957			$this->y = $y;
22958			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
22959			$this->x = $bx;
22960			$this->y = $by;
22961		}
22962		if ($link) {
22963			$this->Link($ximg, $y, $w, $h, $link, 0);
22964		}
22965		// set pointer to align the next text/objects
22966		switch($align) {
22967			case 'T':{
22968				$this->y = $y;
22969				$this->x = $this->img_rb_x;
22970				break;
22971			}
22972			case 'M':{
22973				$this->y = $y + round($h/2);
22974				$this->x = $this->img_rb_x;
22975				break;
22976			}
22977			case 'B':{
22978				$this->y = $this->img_rb_y;
22979				$this->x = $this->img_rb_x;
22980				break;
22981			}
22982			case 'N':{
22983				$this->SetY($this->img_rb_y);
22984				break;
22985			}
22986			default:{
22987				// restore pointer to starting position
22988				$this->x = $gvars['x'];
22989				$this->y = $gvars['y'];
22990				$this->page = $gvars['page'];
22991				$this->current_column = $gvars['current_column'];
22992				$this->tMargin = $gvars['tMargin'];
22993				$this->bMargin = $gvars['bMargin'];
22994				$this->w = $gvars['w'];
22995				$this->h = $gvars['h'];
22996				$this->wPt = $gvars['wPt'];
22997				$this->hPt = $gvars['hPt'];
22998				$this->fwPt = $gvars['fwPt'];
22999				$this->fhPt = $gvars['fhPt'];
23000				break;
23001			}
23002		}
23003		$this->endlinex = $this->img_rb_x;
23004		// restore page break
23005		$this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23006		$this->cell_padding = $cell_padding;
23007	}
23008
23009	/**
23010	 * Convert SVG transformation matrix to PDF.
23011	 * @param $tm (array) original SVG transformation matrix
23012	 * @return array transformation matrix
23013	 * @protected
23014	 * @since 5.0.000 (2010-05-02)
23015	 */
23016	protected function convertSVGtMatrix($tm) {
23017		$a = $tm[0];
23018		$b = -$tm[1];
23019		$c = -$tm[2];
23020		$d = $tm[3];
23021		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23022		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23023		$x = 0;
23024		$y = $this->h * $this->k;
23025		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23026		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23027		return array($a, $b, $c, $d, $e, $f);
23028	}
23029
23030	/**
23031	 * Apply SVG graphic transformation matrix.
23032	 * @param $tm (array) original SVG transformation matrix
23033	 * @protected
23034	 * @since 5.0.000 (2010-05-02)
23035	 */
23036	protected function SVGTransform($tm) {
23037		$this->Transform($this->convertSVGtMatrix($tm));
23038	}
23039
23040	/**
23041	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23042	 * @param $svgstyle (array) array of SVG styles to apply
23043	 * @param $prevsvgstyle (array) array of previous SVG style
23044	 * @param $x (int) X origin of the bounding box
23045	 * @param $y (int) Y origin of the bounding box
23046	 * @param $w (int) width of the bounding box
23047	 * @param $h (int) height of the bounding box
23048	 * @param $clip_function (string) clip function
23049	 * @param $clip_params (array) array of parameters for clipping function
23050	 * @return object style
23051	 * @author Nicola Asuni
23052	 * @since 5.0.000 (2010-05-02)
23053	 * @protected
23054	 */
23055	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23056		if ($this->state != 2) {
23057			 return;
23058		}
23059		$objstyle = '';
23060		$minlen = (0.01 / $this->k); // minimum acceptable length
23061		if (!isset($svgstyle['opacity'])) {
23062			return $objstyle;
23063		}
23064		// clip-path
23065		$regs = array();
23066		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23067			$clip_path = $this->svgclippaths[$regs[1]];
23068			foreach ($clip_path as $cp) {
23069				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23070			}
23071		}
23072		// opacity
23073		if ($svgstyle['opacity'] != 1) {
23074			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23075		}
23076		// color
23077		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23078		$this->SetFillColorArray($fill_color);
23079		// text color
23080		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23081		$this->SetTextColorArray($text_color);
23082		// clip
23083		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23084			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23085			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23086			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23087			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23088			$cx = $x + $left;
23089			$cy = $y + $top;
23090			$cw = $w - $left - $right;
23091			$ch = $h - $top - $bottom;
23092			if ($svgstyle['clip-rule'] == 'evenodd') {
23093				$clip_rule = 'CNZ';
23094			} else {
23095				$clip_rule = 'CEO';
23096			}
23097			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23098		}
23099		// fill
23100		$regs = array();
23101		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23102			// gradient
23103			$gradient = $this->svggradients[$regs[1]];
23104			if (isset($gradient['xref'])) {
23105				// reference to another gradient definition
23106				$newgradient = $this->svggradients[$gradient['xref']];
23107				$newgradient['coords'] = $gradient['coords'];
23108				$newgradient['mode'] = $gradient['mode'];
23109				$newgradient['type'] = $gradient['type'];
23110				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23111				if (isset($gradient['gradientTransform'])) {
23112					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23113				}
23114				$gradient = $newgradient;
23115			}
23116			//save current Graphic State
23117			$this->_outSaveGraphicsState();
23118			//set clipping area
23119			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23120				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23121				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23122					list($x, $y, $w, $h) = $bbox;
23123				}
23124			}
23125			if ($gradient['mode'] == 'measure') {
23126				if (!isset($gradient['coords'][4])) {
23127					$gradient['coords'][4] = 0.5;
23128				}
23129				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23130					$gtm = $gradient['gradientTransform'];
23131					// apply transformation matrix
23132					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23133					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23134					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23135					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23136					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23137					$gradient['coords'][0] = $xa;
23138					$gradient['coords'][1] = $ya;
23139					$gradient['coords'][2] = $xb;
23140					$gradient['coords'][3] = $yb;
23141					$gradient['coords'][4] = $r;
23142				}
23143				// convert SVG coordinates to user units
23144				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23145				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23146				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23147				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23148				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23149				if ($w <= $minlen) {
23150					$w = $minlen;
23151				}
23152				if ($h <= $minlen) {
23153					$h = $minlen;
23154				}
23155				// shift units
23156				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23157					// convert to SVG coordinate system
23158					$gradient['coords'][0] += $x;
23159					$gradient['coords'][1] += $y;
23160					$gradient['coords'][2] += $x;
23161					$gradient['coords'][3] += $y;
23162				}
23163				// calculate percentages
23164				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23165				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23166				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23167				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23168				$gradient['coords'][4] /= $w;
23169			} elseif ($gradient['mode'] == 'percentage') {
23170				foreach($gradient['coords'] as $key => $val) {
23171					$gradient['coords'][$key] = (intval($val) / 100);
23172					if ($val < 0) {
23173						$gradient['coords'][$key] = 0;
23174					} elseif ($val > 1) {
23175						$gradient['coords'][$key] = 1;
23176					}
23177				}
23178			}
23179			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23180				// single color (no shading)
23181				$gradient['coords'][0] = 1;
23182				$gradient['coords'][1] = 0;
23183				$gradient['coords'][2] = 0.999;
23184				$gradient['coords'][3] = 0;
23185			}
23186			// swap Y coordinates
23187			$tmp = $gradient['coords'][1];
23188			$gradient['coords'][1] = $gradient['coords'][3];
23189			$gradient['coords'][3] = $tmp;
23190			// set transformation map for gradient
23191			$cy = ($this->h - $y);
23192			if ($gradient['type'] == 3) {
23193				// circular gradient
23194				$cy -= ($gradient['coords'][1] * ($w + $h));
23195				$h = $w = max($w, $h);
23196			} else {
23197				$cy -= $h;
23198			}
23199			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23200			if (count($gradient['stops']) > 1) {
23201				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23202			}
23203		} elseif ($svgstyle['fill'] != 'none') {
23204			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23205			if ($svgstyle['fill-opacity'] != 1) {
23206				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23207			}
23208			$this->SetFillColorArray($fill_color);
23209			if ($svgstyle['fill-rule'] == 'evenodd') {
23210				$objstyle .= 'F*';
23211			} else {
23212				$objstyle .= 'F';
23213			}
23214		}
23215		// stroke
23216		if ($svgstyle['stroke'] != 'none') {
23217			if ($svgstyle['stroke-opacity'] != 1) {
23218				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23219			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23220				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23221			}
23222			$stroke_style = array(
23223				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23224				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23225				'cap' => $svgstyle['stroke-linecap'],
23226				'join' => $svgstyle['stroke-linejoin']
23227				);
23228			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23229				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23230			}
23231			$this->SetLineStyle($stroke_style);
23232			$objstyle .= 'D';
23233		}
23234		// font
23235		$regs = array();
23236		if (!empty($svgstyle['font'])) {
23237			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23238				$font_family = $this->getFontFamilyName($regs[1]);
23239			} else {
23240				$font_family = $svgstyle['font-family'];
23241			}
23242			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23243				$font_size = trim($regs[1]);
23244			} else {
23245				$font_size = $svgstyle['font-size'];
23246			}
23247			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23248				$font_style = trim($regs[1]);
23249			} else {
23250				$font_style = $svgstyle['font-style'];
23251			}
23252			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23253				$font_weight = trim($regs[1]);
23254			} else {
23255				$font_weight = $svgstyle['font-weight'];
23256			}
23257			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23258				$font_stretch = trim($regs[1]);
23259			} else {
23260				$font_stretch = $svgstyle['font-stretch'];
23261			}
23262			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23263				$font_spacing = trim($regs[1]);
23264			} else {
23265				$font_spacing = $svgstyle['letter-spacing'];
23266			}
23267		} else {
23268			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23269			$font_size = $svgstyle['font-size'];
23270			$font_style = $svgstyle['font-style'];
23271			$font_weight = $svgstyle['font-weight'];
23272			$font_stretch = $svgstyle['font-stretch'];
23273			$font_spacing = $svgstyle['letter-spacing'];
23274		}
23275		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23276		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23277		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23278		switch ($font_style) {
23279			case 'italic': {
23280				$font_style = 'I';
23281				break;
23282			}
23283			case 'oblique': {
23284				$font_style = 'I';
23285				break;
23286			}
23287			default:
23288			case 'normal': {
23289				$font_style = '';
23290				break;
23291			}
23292		}
23293		switch ($font_weight) {
23294			case 'bold':
23295			case 'bolder': {
23296				$font_style .= 'B';
23297				break;
23298			}
23299			case 'normal': {
23300				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23301					$font_family = substr($font_family, 0, -2).'I';
23302				} elseif (substr($font_family, -1) == 'B') {
23303					$font_family = substr($font_family, 0, -1);
23304				}
23305				break;
23306			}
23307		}
23308		switch ($svgstyle['text-decoration']) {
23309			case 'underline': {
23310				$font_style .= 'U';
23311				break;
23312			}
23313			case 'overline': {
23314				$font_style .= 'O';
23315				break;
23316			}
23317			case 'line-through': {
23318				$font_style .= 'D';
23319				break;
23320			}
23321			default:
23322			case 'none': {
23323				break;
23324			}
23325		}
23326		$this->SetFont($font_family, $font_style, $font_size);
23327		$this->setFontStretching($font_stretch);
23328		$this->setFontSpacing($font_spacing);
23329		return $objstyle;
23330	}
23331
23332	/**
23333	 * Draws an SVG path
23334	 * @param $d (string) attribute d of the path SVG element
23335	 * @param $style (string) Style of rendering. Possible values are:
23336	 * <ul>
23337	 *	 <li>D or empty string: Draw (default).</li>
23338	 *	 <li>F: Fill.</li>
23339	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23340	 *	 <li>DF or FD: Draw and fill.</li>
23341	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23342	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23343	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23344	 * </ul>
23345	 * @return array of container box measures (x, y, w, h)
23346	 * @author Nicola Asuni
23347	 * @since 5.0.000 (2010-05-02)
23348	 * @protected
23349	 */
23350	protected function SVGPath($d, $style='') {
23351		if ($this->state != 2) {
23352			 return;
23353		}
23354		// set fill/stroke style
23355		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23356		if (empty($op)) {
23357			return;
23358		}
23359		$paths = array();
23360		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23361		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23362		$x = 0;
23363		$y = 0;
23364		$x1 = 0;
23365		$y1 = 0;
23366		$x2 = 0;
23367		$y2 = 0;
23368		$xmin = 2147483647;
23369		$xmax = 0;
23370		$ymin = 2147483647;
23371		$ymax = 0;
23372		$relcoord = false;
23373		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23374		$firstcmd = true; // used to print first point
23375		// draw curve pieces
23376		foreach ($paths as $key => $val) {
23377			// get curve type
23378			$cmd = trim($val[1]);
23379			if (strtolower($cmd) == $cmd) {
23380				// use relative coordinated instead of absolute
23381				$relcoord = true;
23382				$xoffset = $x;
23383				$yoffset = $y;
23384			} else {
23385				$relcoord = false;
23386				$xoffset = 0;
23387				$yoffset = 0;
23388			}
23389			$params = array();
23390			if (isset($val[2])) {
23391				// get curve parameters
23392				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23393				$params = array();
23394				foreach ($rawparams as $ck => $cp) {
23395					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23396					if (abs($params[$ck]) < $minlen) {
23397						// approximate little values to zero
23398						$params[$ck] = 0;
23399					}
23400				}
23401			}
23402			// store current origin point
23403			$x0 = $x;
23404			$y0 = $y;
23405			switch (strtoupper($cmd)) {
23406				case 'M': { // moveto
23407					foreach ($params as $ck => $cp) {
23408						if (($ck % 2) == 0) {
23409							$x = $cp + $xoffset;
23410						} else {
23411							$y = $cp + $yoffset;
23412							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23413								if ($ck == 1) {
23414									$this->_outPoint($x, $y);
23415									$firstcmd = false;
23416								} else {
23417									$this->_outLine($x, $y);
23418								}
23419								$x0 = $x;
23420								$y0 = $y;
23421							}
23422							$xmin = min($xmin, $x);
23423							$ymin = min($ymin, $y);
23424							$xmax = max($xmax, $x);
23425							$ymax = max($ymax, $y);
23426							if ($relcoord) {
23427								$xoffset = $x;
23428								$yoffset = $y;
23429							}
23430						}
23431					}
23432					break;
23433				}
23434				case 'L': { // lineto
23435					foreach ($params as $ck => $cp) {
23436						if (($ck % 2) == 0) {
23437							$x = $cp + $xoffset;
23438						} else {
23439							$y = $cp + $yoffset;
23440							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23441								$this->_outLine($x, $y);
23442								$x0 = $x;
23443								$y0 = $y;
23444							}
23445							$xmin = min($xmin, $x);
23446							$ymin = min($ymin, $y);
23447							$xmax = max($xmax, $x);
23448							$ymax = max($ymax, $y);
23449							if ($relcoord) {
23450								$xoffset = $x;
23451								$yoffset = $y;
23452							}
23453						}
23454					}
23455					break;
23456				}
23457				case 'H': { // horizontal lineto
23458					foreach ($params as $ck => $cp) {
23459						$x = $cp + $xoffset;
23460						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23461							$this->_outLine($x, $y);
23462							$x0 = $x;
23463							$y0 = $y;
23464						}
23465						$xmin = min($xmin, $x);
23466						$xmax = max($xmax, $x);
23467						if ($relcoord) {
23468							$xoffset = $x;
23469						}
23470					}
23471					break;
23472				}
23473				case 'V': { // vertical lineto
23474					foreach ($params as $ck => $cp) {
23475						$y = $cp + $yoffset;
23476						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23477							$this->_outLine($x, $y);
23478							$x0 = $x;
23479							$y0 = $y;
23480						}
23481						$ymin = min($ymin, $y);
23482						$ymax = max($ymax, $y);
23483						if ($relcoord) {
23484							$yoffset = $y;
23485						}
23486					}
23487					break;
23488				}
23489				case 'C': { // curveto
23490					foreach ($params as $ck => $cp) {
23491						$params[$ck] = $cp;
23492						if ((($ck + 1) % 6) == 0) {
23493							$x1 = $params[($ck - 5)] + $xoffset;
23494							$y1 = $params[($ck - 4)] + $yoffset;
23495							$x2 = $params[($ck - 3)] + $xoffset;
23496							$y2 = $params[($ck - 2)] + $yoffset;
23497							$x = $params[($ck - 1)] + $xoffset;
23498							$y = $params[($ck)] + $yoffset;
23499							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23500							$xmin = min($xmin, $x, $x1, $x2);
23501							$ymin = min($ymin, $y, $y1, $y2);
23502							$xmax = max($xmax, $x, $x1, $x2);
23503							$ymax = max($ymax, $y, $y1, $y2);
23504							if ($relcoord) {
23505								$xoffset = $x;
23506								$yoffset = $y;
23507							}
23508						}
23509					}
23510					break;
23511				}
23512				case 'S': { // shorthand/smooth curveto
23513					foreach ($params as $ck => $cp) {
23514						$params[$ck] = $cp;
23515						if ((($ck + 1) % 4) == 0) {
23516							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23517								$x1 = (2 * $x) - $x2;
23518								$y1 = (2 * $y) - $y2;
23519							} else {
23520								$x1 = $x;
23521								$y1 = $y;
23522							}
23523							$x2 = $params[($ck - 3)] + $xoffset;
23524							$y2 = $params[($ck - 2)] + $yoffset;
23525							$x = $params[($ck - 1)] + $xoffset;
23526							$y = $params[($ck)] + $yoffset;
23527							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23528							$xmin = min($xmin, $x, $x1, $x2);
23529							$ymin = min($ymin, $y, $y1, $y2);
23530							$xmax = max($xmax, $x, $x1, $x2);
23531							$ymax = max($ymax, $y, $y1, $y2);
23532							if ($relcoord) {
23533								$xoffset = $x;
23534								$yoffset = $y;
23535							}
23536						}
23537					}
23538					break;
23539				}
23540				case 'Q': { // quadratic Bezier curveto
23541					foreach ($params as $ck => $cp) {
23542						$params[$ck] = $cp;
23543						if ((($ck + 1) % 4) == 0) {
23544							// convert quadratic points to cubic points
23545							$x1 = $params[($ck - 3)] + $xoffset;
23546							$y1 = $params[($ck - 2)] + $yoffset;
23547							$xa = ($x + (2 * $x1)) / 3;
23548							$ya = ($y + (2 * $y1)) / 3;
23549							$x = $params[($ck - 1)] + $xoffset;
23550							$y = $params[($ck)] + $yoffset;
23551							$xb = ($x + (2 * $x1)) / 3;
23552							$yb = ($y + (2 * $y1)) / 3;
23553							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23554							$xmin = min($xmin, $x, $xa, $xb);
23555							$ymin = min($ymin, $y, $ya, $yb);
23556							$xmax = max($xmax, $x, $xa, $xb);
23557							$ymax = max($ymax, $y, $ya, $yb);
23558							if ($relcoord) {
23559								$xoffset = $x;
23560								$yoffset = $y;
23561							}
23562						}
23563					}
23564					break;
23565				}
23566				case 'T': { // shorthand/smooth quadratic Bezier curveto
23567					foreach ($params as $ck => $cp) {
23568						$params[$ck] = $cp;
23569						if (($ck % 2) != 0) {
23570							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23571								$x1 = (2 * $x) - $x1;
23572								$y1 = (2 * $y) - $y1;
23573							} else {
23574								$x1 = $x;
23575								$y1 = $y;
23576							}
23577							// convert quadratic points to cubic points
23578							$xa = ($x + (2 * $x1)) / 3;
23579							$ya = ($y + (2 * $y1)) / 3;
23580							$x = $params[($ck - 1)] + $xoffset;
23581							$y = $params[($ck)] + $yoffset;
23582							$xb = ($x + (2 * $x1)) / 3;
23583							$yb = ($y + (2 * $y1)) / 3;
23584							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23585							$xmin = min($xmin, $x, $xa, $xb);
23586							$ymin = min($ymin, $y, $ya, $yb);
23587							$xmax = max($xmax, $x, $xa, $xb);
23588							$ymax = max($ymax, $y, $ya, $yb);
23589							if ($relcoord) {
23590								$xoffset = $x;
23591								$yoffset = $y;
23592							}
23593						}
23594					}
23595					break;
23596				}
23597				case 'A': { // elliptical arc
23598					foreach ($params as $ck => $cp) {
23599						$params[$ck] = $cp;
23600						if ((($ck + 1) % 7) == 0) {
23601							$x0 = $x;
23602							$y0 = $y;
23603							$rx = abs($params[($ck - 6)]);
23604							$ry = abs($params[($ck - 5)]);
23605							$ang = -$rawparams[($ck - 4)];
23606							$angle = deg2rad($ang);
23607							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23608							$fs = $rawparams[($ck - 2)]; // sweep-flag
23609							$x = $params[($ck - 1)] + $xoffset;
23610							$y = $params[$ck] + $yoffset;
23611							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23612								// endpoints are almost identical
23613								$xmin = min($xmin, $x);
23614								$ymin = min($ymin, $y);
23615								$xmax = max($xmax, $x);
23616								$ymax = max($ymax, $y);
23617							} else {
23618								$cos_ang = cos($angle);
23619								$sin_ang = sin($angle);
23620								$a = (($x0 - $x) / 2);
23621								$b = (($y0 - $y) / 2);
23622								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23623								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23624								$rx2 = $rx * $rx;
23625								$ry2 = $ry * $ry;
23626								$xa2 = $xa * $xa;
23627								$ya2 = $ya * $ya;
23628								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23629								if ($delta > 1) {
23630									$rx *= sqrt($delta);
23631									$ry *= sqrt($delta);
23632									$rx2 = $rx * $rx;
23633									$ry2 = $ry * $ry;
23634								}
23635								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23636								if ($numerator < 0) {
23637									$root = 0;
23638								} else {
23639									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23640								}
23641								if ($fa == $fs){
23642									$root *= -1;
23643								}
23644								$cax = $root * (($rx * $ya) / $ry);
23645								$cay = -$root * (($ry * $xa) / $rx);
23646								// coordinates of ellipse center
23647								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23648								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23649								// get angles
23650								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23651								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23652								if (($fs == 0) AND ($dang > 0)) {
23653									$dang -= (2 * M_PI);
23654								} elseif (($fs == 1) AND ($dang < 0)) {
23655									$dang += (2 * M_PI);
23656								}
23657								$angf = $angs - $dang;
23658								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23659									// reverse angles
23660									$tmp = $angs;
23661									$angs = $angf;
23662									$angf = $tmp;
23663								}
23664								$angs = round(rad2deg($angs), 6);
23665								$angf = round(rad2deg($angf), 6);
23666								// covent angles to positive values
23667								if (($angs < 0) AND ($angf < 0)) {
23668									$angs += 360;
23669									$angf += 360;
23670								}
23671								$pie = false;
23672								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23673									$pie = true;
23674								}
23675								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23676								$xmin = min($xmin, $x, $axmin);
23677								$ymin = min($ymin, $y, $aymin);
23678								$xmax = max($xmax, $x, $axmax);
23679								$ymax = max($ymax, $y, $aymax);
23680							}
23681							if ($relcoord) {
23682								$xoffset = $x;
23683								$yoffset = $y;
23684							}
23685						}
23686					}
23687					break;
23688				}
23689				case 'Z': {
23690					$this->_out('h');
23691					break;
23692				}
23693			}
23694			$firstcmd = false;
23695		} // end foreach
23696		if (!empty($op)) {
23697			$this->_out($op);
23698		}
23699		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23700	}
23701
23702	/**
23703	 * Return the tag name without the namespace
23704	 * @param $name (string) Tag name
23705	 * @protected
23706	 */
23707	protected function removeTagNamespace($name) {
23708		if(strpos($name, ':') !== false) {
23709			$parts = explode(':', $name);
23710			return $parts[(sizeof($parts) - 1)];
23711		}
23712		return $name;
23713	}
23714
23715	/**
23716	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23717	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23718	 * @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.
23719	 * @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.
23720	 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23721	 * @author Nicola Asuni
23722	 * @since 5.0.000 (2010-05-02)
23723	 * @protected
23724	 */
23725	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23726		$name = $this->removeTagNamespace($name);
23727		// check if we are in clip mode
23728		if ($this->svgclipmode) {
23729			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23730			return;
23731		}
23732		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23733			if (isset($attribs['id'])) {
23734				$attribs['child_elements'] = array();
23735				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23736				return;
23737			}
23738			if (end($this->svgdefs) !== FALSE) {
23739				$last_svgdefs_id = key($this->svgdefs);
23740				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23741					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23742					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23743					return;
23744				}
23745			}
23746			return;
23747		}
23748		$clipping = false;
23749		if ($parser == 'clip-path') {
23750			// set clipping mode
23751			$clipping = true;
23752		}
23753		// get styling properties
23754		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23755		$svgstyle = $this->svgstyles[0]; // set default style
23756		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23757			// default fill attribute for clipping
23758			$attribs['fill'] = 'none';
23759		}
23760		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23761			// fix style for regular expression
23762			$attribs['style'] = ';'.$attribs['style'];
23763		}
23764		foreach ($prev_svgstyle as $key => $val) {
23765			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23766				// inherit previous value
23767				$svgstyle[$key] = $val;
23768			}
23769			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23770				// specific attribute settings
23771				if ($attribs[$key] == 'inherit') {
23772					$svgstyle[$key] = $val;
23773				} else {
23774					$svgstyle[$key] = $attribs[$key];
23775				}
23776			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23777				// CSS style syntax
23778				$attrval = array();
23779				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23780					if ($attrval[1] == 'inherit') {
23781						$svgstyle[$key] = $val;
23782					} else {
23783						$svgstyle[$key] = $attrval[1];
23784					}
23785				}
23786			}
23787		}
23788		// transformation matrix
23789		if (!empty($ctm)) {
23790			$tm = $ctm;
23791		} else {
23792			$tm = array(1,0,0,1,0,0);
23793		}
23794		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23795			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23796		}
23797		$svgstyle['transfmatrix'] = $tm;
23798		$invisible = false;
23799		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23800			// the current graphics element is invisible (nothing is painted)
23801			$invisible = true;
23802		}
23803		// process tag
23804		switch($name) {
23805			case 'defs': {
23806				$this->svgdefsmode = true;
23807				break;
23808			}
23809			// clipPath
23810			case 'clipPath': {
23811				if ($invisible) {
23812					break;
23813				}
23814				$this->svgclipmode = true;
23815				if (!isset($attribs['id'])) {
23816					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23817				}
23818				$this->svgclipid = $attribs['id'];
23819				$this->svgclippaths[$this->svgclipid] = array();
23820				$this->svgcliptm[$this->svgclipid] = $tm;
23821				break;
23822			}
23823			case 'svg': {
23824				// start of SVG object
23825				if(++$this->svg_tag_depth <= 1) {
23826					break;
23827				}
23828				// inner SVG
23829				array_push($this->svgstyles, $svgstyle);
23830				$this->StartTransform();
23831				$svgX = (isset($attribs['x'])?$attribs['x']:0);
23832				$svgY = (isset($attribs['y'])?$attribs['y']:0);
23833				$svgW = (isset($attribs['width'])?$attribs['width']:0);
23834				$svgH = (isset($attribs['height'])?$attribs['height']:0);
23835				// set x, y position using transform matrix
23836				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23837				$this->SVGTransform($tm);
23838				// set clipping for width and height
23839				$x = 0;
23840				$y = 0;
23841				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23842				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23843				// draw clipping rect
23844				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23845				// parse viewbox, calculate extra transformation matrix
23846				if (isset($attribs['viewBox'])) {
23847					$tmp = array();
23848					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23849					$tmp = $tmp[0];
23850					if (sizeof($tmp) == 4) {
23851						$vx = $tmp[0];
23852						$vy = $tmp[1];
23853						$vw = $tmp[2];
23854						$vh = $tmp[3];
23855						// get aspect ratio
23856						$tmp = array();
23857						$aspectX = 'xMid';
23858						$aspectY = 'YMid';
23859						$fit = 'meet';
23860						if (isset($attribs['preserveAspectRatio'])) {
23861							if($attribs['preserveAspectRatio'] == 'none') {
23862								$fit = 'none';
23863							} else {
23864								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
23865								$tmp = $tmp[0];
23866								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
23867									$aspectX = substr($tmp[0], 0, 4);
23868									$aspectY = substr($tmp[0], 4, 4);
23869									$fit = $tmp[1];
23870								}
23871							}
23872						}
23873						$wr = ($svgW / $vw);
23874						$hr = ($svgH / $vh);
23875						$ax = $ay = 0;
23876						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
23877							if ($aspectX == 'xMax') {
23878								$ax = (($vw * ($wr / $hr)) - $vw);
23879							}
23880							if ($aspectX == 'xMid') {
23881								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
23882							}
23883							$wr = $hr;
23884						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
23885							if ($aspectY == 'YMax') {
23886								$ay = (($vh * ($hr / $wr)) - $vh);
23887							}
23888							if ($aspectY == 'YMid') {
23889								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
23890							}
23891							$hr = $wr;
23892						}
23893						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
23894						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
23895						$this->SVGTransform($tm);
23896					}
23897				}
23898				$this->setSVGStyles($svgstyle, $prev_svgstyle);
23899				break;
23900			}
23901			case 'g': {
23902				// group together related graphics elements
23903				array_push($this->svgstyles, $svgstyle);
23904				$this->StartTransform();
23905				$x = (isset($attribs['x'])?$attribs['x']:0);
23906				$y = (isset($attribs['y'])?$attribs['y']:0);
23907				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23908				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23909				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23910				$this->SVGTransform($tm);
23911				$this->setSVGStyles($svgstyle, $prev_svgstyle);
23912				break;
23913			}
23914			case 'linearGradient': {
23915				if ($this->pdfa_mode) {
23916					break;
23917				}
23918				if (!isset($attribs['id'])) {
23919					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23920				}
23921				$this->svggradientid = $attribs['id'];
23922				$this->svggradients[$this->svggradientid] = array();
23923				$this->svggradients[$this->svggradientid]['type'] = 2;
23924				$this->svggradients[$this->svggradientid]['stops'] = array();
23925				if (isset($attribs['gradientUnits'])) {
23926					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23927				} else {
23928					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23929				}
23930				//$attribs['spreadMethod']
23931				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
23932					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
23933						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
23934						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
23935						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
23936					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23937				} else {
23938					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
23939				}
23940				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
23941				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
23942				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
23943				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
23944				if (isset($attribs['gradientTransform'])) {
23945					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23946				}
23947				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
23948				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23949					// gradient is defined on another place
23950					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23951				}
23952				break;
23953			}
23954			case 'radialGradient': {
23955				if ($this->pdfa_mode) {
23956					break;
23957				}
23958				if (!isset($attribs['id'])) {
23959					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23960				}
23961				$this->svggradientid = $attribs['id'];
23962				$this->svggradients[$this->svggradientid] = array();
23963				$this->svggradients[$this->svggradientid]['type'] = 3;
23964				$this->svggradients[$this->svggradientid]['stops'] = array();
23965				if (isset($attribs['gradientUnits'])) {
23966					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23967				} else {
23968					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23969				}
23970				//$attribs['spreadMethod']
23971				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
23972					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
23973					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
23974					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23975				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
23976					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
23977				} else {
23978					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
23979				}
23980				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
23981				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
23982				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
23983				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
23984				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
23985				if (isset($attribs['gradientTransform'])) {
23986					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23987				}
23988				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
23989				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23990					// gradient is defined on another place
23991					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23992				}
23993				break;
23994			}
23995			case 'stop': {
23996				// gradient stops
23997				if (substr($attribs['offset'], -1) == '%') {
23998					$offset = floatval(substr($attribs['offset'], -1)) / 100;
23999				} else {
24000					$offset = floatval($attribs['offset']);
24001					if ($offset > 1) {
24002						$offset /= 100;
24003					}
24004				}
24005				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24006				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24007				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24008				break;
24009			}
24010			// paths
24011			case 'path': {
24012				if ($invisible) {
24013					break;
24014				}
24015				if (isset($attribs['d'])) {
24016					$d = trim($attribs['d']);
24017					if (!empty($d)) {
24018						$x = (isset($attribs['x'])?$attribs['x']:0);
24019						$y = (isset($attribs['y'])?$attribs['y']:0);
24020						$w = (isset($attribs['width'])?$attribs['width']:1);
24021						$h = (isset($attribs['height'])?$attribs['height']:1);
24022						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24023						if ($clipping) {
24024							$this->SVGTransform($tm);
24025							$this->SVGPath($d, 'CNZ');
24026						} else {
24027							$this->StartTransform();
24028							$this->SVGTransform($tm);
24029							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24030							if (!empty($obstyle)) {
24031								$this->SVGPath($d, $obstyle);
24032							}
24033							$this->StopTransform();
24034						}
24035					}
24036				}
24037				break;
24038			}
24039			// shapes
24040			case 'rect': {
24041				if ($invisible) {
24042					break;
24043				}
24044				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24045				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24046				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24047				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24048				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24049				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24050				if ($clipping) {
24051					$this->SVGTransform($tm);
24052					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24053				} else {
24054					$this->StartTransform();
24055					$this->SVGTransform($tm);
24056					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24057					if (!empty($obstyle)) {
24058						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24059					}
24060					$this->StopTransform();
24061				}
24062				break;
24063			}
24064			case 'circle': {
24065				if ($invisible) {
24066					break;
24067				}
24068				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24069				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24070				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24071				$x = ($cx - $r);
24072				$y = ($cy - $r);
24073				$w = (2 * $r);
24074				$h = $w;
24075				if ($clipping) {
24076					$this->SVGTransform($tm);
24077					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24078				} else {
24079					$this->StartTransform();
24080					$this->SVGTransform($tm);
24081					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24082					if (!empty($obstyle)) {
24083						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24084					}
24085					$this->StopTransform();
24086				}
24087				break;
24088			}
24089			case 'ellipse': {
24090				if ($invisible) {
24091					break;
24092				}
24093				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24094				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24095				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24096				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24097				$x = ($cx - $rx);
24098				$y = ($cy - $ry);
24099				$w = (2 * $rx);
24100				$h = (2 * $ry);
24101				if ($clipping) {
24102					$this->SVGTransform($tm);
24103					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24104				} else {
24105					$this->StartTransform();
24106					$this->SVGTransform($tm);
24107					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24108					if (!empty($obstyle)) {
24109						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24110					}
24111					$this->StopTransform();
24112				}
24113				break;
24114			}
24115			case 'line': {
24116				if ($invisible) {
24117					break;
24118				}
24119				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24120				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24121				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24122				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24123				$x = $x1;
24124				$y = $y1;
24125				$w = abs($x2 - $x1);
24126				$h = abs($y2 - $y1);
24127				if (!$clipping) {
24128					$this->StartTransform();
24129					$this->SVGTransform($tm);
24130					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24131					$this->Line($x1, $y1, $x2, $y2);
24132					$this->StopTransform();
24133				}
24134				break;
24135			}
24136			case 'polyline':
24137			case 'polygon': {
24138				if ($invisible) {
24139					break;
24140				}
24141				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24142				$points = trim($points);
24143				// note that point may use a complex syntax not covered here
24144				$points = preg_split('/[\,\s]+/si', $points);
24145				if (count($points) < 4) {
24146					break;
24147				}
24148				$p = array();
24149				$xmin = 2147483647;
24150				$xmax = 0;
24151				$ymin = 2147483647;
24152				$ymax = 0;
24153				foreach ($points as $key => $val) {
24154					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24155					if (($key % 2) == 0) {
24156						// X coordinate
24157						$xmin = min($xmin, $p[$key]);
24158						$xmax = max($xmax, $p[$key]);
24159					} else {
24160						// Y coordinate
24161						$ymin = min($ymin, $p[$key]);
24162						$ymax = max($ymax, $p[$key]);
24163					}
24164				}
24165				$x = $xmin;
24166				$y = $ymin;
24167				$w = ($xmax - $xmin);
24168				$h = ($ymax - $ymin);
24169				if ($name == 'polyline') {
24170					$this->StartTransform();
24171					$this->SVGTransform($tm);
24172					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24173					if (!empty($obstyle)) {
24174						$this->PolyLine($p, $obstyle, array(), array());
24175					}
24176					$this->StopTransform();
24177				} else { // polygon
24178					if ($clipping) {
24179						$this->SVGTransform($tm);
24180						$this->Polygon($p, 'CNZ', array(), array(), true);
24181					} else {
24182						$this->StartTransform();
24183						$this->SVGTransform($tm);
24184						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24185						if (!empty($obstyle)) {
24186							$this->Polygon($p, $obstyle, array(), array(), true);
24187						}
24188						$this->StopTransform();
24189					}
24190				}
24191				break;
24192			}
24193			// image
24194			case 'image': {
24195				if ($invisible) {
24196					break;
24197				}
24198				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24199					break;
24200				}
24201				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24202				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24203				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24204				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24205				$img = $attribs['xlink:href'];
24206				if (!$clipping) {
24207					$this->StartTransform();
24208					$this->SVGTransform($tm);
24209					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24210					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24211						// embedded image encoded as base64
24212						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24213					} else {
24214						// fix image path
24215						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24216							// replace relative path with full server path
24217							$img = $this->svgdir.'/'.$img;
24218						}
24219						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24220							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24221							if (($findroot === false) OR ($findroot > 1)) {
24222								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24223									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24224								} else {
24225									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24226								}
24227							}
24228						}
24229						$img = urldecode($img);
24230						$testscrtype = @parse_url($img);
24231						if (empty($testscrtype['query'])) {
24232							// convert URL to server path
24233							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24234						} elseif (preg_match('|^https?://|', $img) !== 1) {
24235							// convert server path to URL
24236							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24237						}
24238					}
24239					// get image type
24240					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24241					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24242						$this->ImageEps($img, $x, $y, $w, $h);
24243					} elseif ($imgtype == 'svg') {
24244						// store SVG vars
24245						$svggradients = $this->svggradients;
24246						$svggradientid = $this->svggradientid;
24247						$svgdefsmode = $this->svgdefsmode;
24248						$svgdefs = $this->svgdefs;
24249						$svgclipmode = $this->svgclipmode;
24250						$svgclippaths = $this->svgclippaths;
24251						$svgcliptm = $this->svgcliptm;
24252						$svgclipid = $this->svgclipid;
24253						$svgtext = $this->svgtext;
24254						$svgtextmode = $this->svgtextmode;
24255						$this->ImageSVG($img, $x, $y, $w, $h);
24256						// restore SVG vars
24257						$this->svggradients = $svggradients;
24258						$this->svggradientid = $svggradientid;
24259						$this->svgdefsmode = $svgdefsmode;
24260						$this->svgdefs = $svgdefs;
24261						$this->svgclipmode = $svgclipmode;
24262						$this->svgclippaths = $svgclippaths;
24263						$this->svgcliptm = $svgcliptm;
24264						$this->svgclipid = $svgclipid;
24265						$this->svgtext = $svgtext;
24266						$this->svgtextmode = $svgtextmode;
24267					} else {
24268						$this->Image($img, $x, $y, $w, $h);
24269					}
24270					$this->StopTransform();
24271				}
24272				break;
24273			}
24274			// text
24275			case 'text':
24276			case 'tspan': {
24277				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24278					// @TODO: unsupported feature
24279				}
24280				// only basic support - advanced features must be implemented
24281				$this->svgtextmode['invisible'] = $invisible;
24282				if ($invisible) {
24283					break;
24284				}
24285				array_push($this->svgstyles, $svgstyle);
24286				if (isset($attribs['x'])) {
24287					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24288				} elseif ($name == 'tspan') {
24289					$x = $this->x;
24290				} else {
24291					$x = 0;
24292				}
24293				if (isset($attribs['dx'])) {
24294					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24295				}
24296				if (isset($attribs['y'])) {
24297					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24298				} elseif ($name == 'tspan') {
24299					$y = $this->y;
24300				} else {
24301					$y = 0;
24302				}
24303				if (isset($attribs['dy'])) {
24304					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24305				}
24306				$svgstyle['text-color'] = $svgstyle['fill'];
24307				$this->svgtext = '';
24308				if (isset($svgstyle['text-anchor'])) {
24309					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24310				} else {
24311					$this->svgtextmode['text-anchor'] = 'start';
24312				}
24313				if (isset($svgstyle['direction'])) {
24314					if ($svgstyle['direction'] == 'rtl') {
24315						$this->svgtextmode['rtl'] = true;
24316					} else {
24317						$this->svgtextmode['rtl'] = false;
24318					}
24319				} else {
24320					$this->svgtextmode['rtl'] = false;
24321				}
24322				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24323					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24324				} else {
24325					$this->svgtextmode['stroke'] = false;
24326				}
24327				$this->StartTransform();
24328				$this->SVGTransform($tm);
24329				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24330				$this->x = $x;
24331				$this->y = $y;
24332				break;
24333			}
24334			// use
24335			case 'use': {
24336				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24337					$svgdefid = substr($attribs['xlink:href'], 1);
24338					if (isset($this->svgdefs[$svgdefid])) {
24339						$use = $this->svgdefs[$svgdefid];
24340						if (isset($attribs['xlink:href'])) {
24341							unset($attribs['xlink:href']);
24342						}
24343						if (isset($attribs['id'])) {
24344							unset($attribs['id']);
24345						}
24346						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24347							$attribs['x'] += $use['attribs']['x'];
24348						}
24349						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24350							$attribs['y'] += $use['attribs']['y'];
24351						}
24352						if (empty($attribs['style'])) {
24353							$attribs['style'] = '';
24354						}
24355						if (!empty($use['attribs']['style'])) {
24356							// merge styles
24357							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24358						}
24359						$attribs = array_merge($use['attribs'], $attribs);
24360						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24361						return;
24362					}
24363				}
24364				break;
24365			}
24366			default: {
24367				break;
24368			}
24369		} // end of switch
24370		// process child elements
24371		if (!empty($attribs['child_elements'])) {
24372			$child_elements = $attribs['child_elements'];
24373			unset($attribs['child_elements']);
24374			foreach($child_elements as $child_element) {
24375				if (empty($child_element['attribs']['closing_tag'])) {
24376					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24377				} else {
24378					if (isset($child_element['attribs']['content'])) {
24379						$this->svgtext = $child_element['attribs']['content'];
24380					}
24381					$this->endSVGElementHandler('child-tag', $child_element['name']);
24382				}
24383			}
24384		}
24385	}
24386
24387	/**
24388	 * Sets the closing SVG element handler function for the XML parser.
24389	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24390	 * @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.
24391	 * @author Nicola Asuni
24392	 * @since 5.0.000 (2010-05-02)
24393	 * @protected
24394	 */
24395	protected function endSVGElementHandler($parser, $name) {
24396		$name = $this->removeTagNamespace($name);
24397		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24398			if (end($this->svgdefs) !== FALSE) {
24399				$last_svgdefs_id = key($this->svgdefs);
24400				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24401					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24402						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24403							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24404							return;
24405						}
24406					}
24407					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24408						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24409						return;
24410					}
24411				}
24412			}
24413			return;
24414		}
24415		switch($name) {
24416			case 'defs': {
24417				$this->svgdefsmode = false;
24418				break;
24419			}
24420			// clipPath
24421			case 'clipPath': {
24422				$this->svgclipmode = false;
24423				break;
24424			}
24425			case 'svg': {
24426				if (--$this->svg_tag_depth <= 0) {
24427					break;
24428				}
24429			}
24430			case 'g': {
24431				// ungroup: remove last style from array
24432				array_pop($this->svgstyles);
24433				$this->StopTransform();
24434				break;
24435			}
24436			case 'text':
24437			case 'tspan': {
24438				if ($this->svgtextmode['invisible']) {
24439					// This implementation must be fixed to following the rule:
24440					// 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.
24441					break;
24442				}
24443				// print text
24444				$text = $this->svgtext;
24445				//$text = $this->stringTrim($text);
24446				$textlen = $this->GetStringWidth($text);
24447				if ($this->svgtextmode['text-anchor'] != 'start') {
24448					// check if string is RTL text
24449					if ($this->svgtextmode['text-anchor'] == 'end') {
24450						if ($this->svgtextmode['rtl']) {
24451							$this->x += $textlen;
24452						} else {
24453							$this->x -= $textlen;
24454						}
24455					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24456						if ($this->svgtextmode['rtl']) {
24457							$this->x += ($textlen / 2);
24458						} else {
24459							$this->x -= ($textlen / 2);
24460						}
24461					}
24462				}
24463				$textrendermode = $this->textrendermode;
24464				$textstrokewidth = $this->textstrokewidth;
24465				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24466				if ($name == 'text') {
24467					// store current coordinates
24468					$tmpx = $this->x;
24469					$tmpy = $this->y;
24470				}
24471				// print the text
24472				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24473				if ($name == 'text') {
24474					// restore coordinates
24475					$this->x = $tmpx;
24476					$this->y = $tmpy;
24477				}
24478				// restore previous rendering mode
24479				$this->textrendermode = $textrendermode;
24480				$this->textstrokewidth = $textstrokewidth;
24481				$this->svgtext = '';
24482				$this->StopTransform();
24483				if (!$this->svgdefsmode) {
24484					array_pop($this->svgstyles);
24485				}
24486				break;
24487			}
24488			default: {
24489				break;
24490			}
24491		}
24492	}
24493
24494	/**
24495	 * Sets the character data handler function for the XML parser.
24496	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24497	 * @param $data (string) The second parameter, data, contains the character data as a string.
24498	 * @author Nicola Asuni
24499	 * @since 5.0.000 (2010-05-02)
24500	 * @protected
24501	 */
24502	protected function segSVGContentHandler($parser, $data) {
24503		$this->svgtext .= $data;
24504	}
24505
24506	// --- END SVG METHODS -----------------------------------------------------
24507
24508} // END OF TCPDF CLASS
24509
24510//============================================================+
24511// END OF FILE
24512//============================================================+
24513