Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
//============================================================+
3
// File name   : tcpdf.php
4
// Version     : 6.6.5
5
// Begin       : 2002-08-03
6
// Last Update : 2023-09-06
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-2023 Nicola Asuni - Tecnick.com LTD
11
//
12
// This file is part of TCPDF software library.
13
//
14
// TCPDF is free software: you can redistribute it and/or modify it
15
// under the terms of the GNU Lesser General Public License as
16
// published by the Free Software Foundation, either version 3 of the
17
// License, or (at your option) any later version.
18
//
19
// TCPDF is distributed in the hope that it will be useful, but
20
// WITHOUT ANY WARRANTY; without even the implied warranty of
21
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
// See the GNU Lesser General Public License for more details.
23
//
24
// You should have received a copy of the License
25
// along with TCPDF. If not, see
26
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27
//
28
// See LICENSE.TXT file for more information.
29
// -------------------------------------------------------------------
30
//
31
// Description :
32
//   This is a PHP class for generating PDF documents without requiring external extensions.
33
//
34
// NOTE:
35
//   This class was originally derived in 2002 from the Public
36
//   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37
//   but now is almost entirely rewritten and contains thousands of
38
//   new lines of code and hundreds new features.
39
//
40
// Main features:
41
//  * no external libraries are required for the basic functions;
42
//  * all standard page formats, custom page formats, custom margins and units of measure;
43
//  * UTF-8 Unicode and Right-To-Left languages;
44
//  * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45
//  * font subsetting;
46
//  * methods to publish some XHTML + CSS code, Javascript and Forms;
47
//  * images, graphic (geometric figures) and transformation methods;
48
//  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)
49
//  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50
//  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51
//  * automatic page header and footer management;
52
//  * document encryption up to 256 bit and digital signature certifications;
53
//  * transactions to UNDO commands;
54
//  * PDF annotations, including links, text and file attachments;
55
//  * text rendering modes (fill, stroke and clipping);
56
//  * multiple columns mode;
57
//  * no-write page regions;
58
//  * bookmarks, named destinations and table of content;
59
//  * text hyphenation;
60
//  * text stretching and spacing (tracking);
61
//  * automatic page break, line break and text alignments including justification;
62
//  * automatic page numbering and page groups;
63
//  * move and delete pages;
64
//  * page compression (requires php-zlib extension);
65
//  * XOBject Templates;
66
//  * Layers and object visibility.
67
//	* PDF/A-1b support
68
//============================================================+
69
 
70
/**
71
 * @file
72
 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73
 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74
 * <h3>TCPDF main features are:</h3>
75
 * <ul>
76
 * <li>no external libraries are required for the basic functions;</li>
77
 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78
 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79
 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80
 * <li>font subsetting;</li>
81
 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82
 * <li>images, graphic (geometric figures) and transformation methods;
83
 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)</li>
84
 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85
 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86
 * <li>automatic page header and footer management;</li>
87
 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88
 * <li>transactions to UNDO commands;</li>
89
 * <li>PDF annotations, including links, text and file attachments;</li>
90
 * <li>text rendering modes (fill, stroke and clipping);</li>
91
 * <li>multiple columns mode;</li>
92
 * <li>no-write page regions;</li>
93
 * <li>bookmarks, named destinations and table of content;</li>
94
 * <li>text hyphenation;</li>
95
 * <li>text stretching and spacing (tracking);</li>
96
 * <li>automatic page break, line break and text alignments including justification;</li>
97
 * <li>automatic page numbering and page groups;</li>
98
 * <li>move and delete pages;</li>
99
 * <li>page compression (requires php-zlib extension);</li>
100
 * <li>XOBject Templates;</li>
101
 * <li>Layers and object visibility;</li>
102
 * <li>PDF/A-1b support.</li>
103
 * </ul>
104
 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105
 * @package com.tecnick.tcpdf
106
 * @author Nicola Asuni
107
 * @version 6.6.5
108
 */
109
 
110
// TCPDF configuration
111
require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112
// TCPDF static font methods and data
113
require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114
// TCPDF static font methods and data
115
require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116
// TCPDF static color methods and data
117
require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118
// TCPDF static image methods and data
119
require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120
// TCPDF static methods and data
121
require_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.6.5
132
 * @author Nicola Asuni - info@tecnick.com
133
 * @IgnoreAnnotation("protected")
134
 * @IgnoreAnnotation("public")
135
 * @IgnoreAnnotation("pre")
136
 */
137
class 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
	 * @var float
578
	 */
579
	protected $header_margin;
580
 
581
	/**
582
	 * Minimum distance between footer and bottom page margin.
583
	 * @protected
584
	 * @var float
585
	 */
586
	protected $footer_margin;
587
 
588
	/**
589
	 * Original left margin value.
590
	 * @protected
591
	 * @since 1.53.0.TC013
592
	 */
593
	protected $original_lMargin;
594
 
595
	/**
596
	 * Original right margin value.
597
	 * @protected
598
	 * @since 1.53.0.TC013
599
	 */
600
	protected $original_rMargin;
601
 
602
	/**
603
	 * Default font used on page header.
604
	 * @protected
605
	 * @var array<int,string|float|null>
606
	 * @phpstan-var array{0: string, 1: string, 2: float|null}
607
	 */
608
	protected $header_font;
609
 
610
	/**
611
	 * Default font used on page footer.
612
	 * @protected
613
	 * @var array<int,string|float|null>
614
	 * @phpstan-var array{0: string, 1: string, 2: float|null}
615
	 */
616
	protected $footer_font;
617
 
618
	/**
619
	 * Language templates.
620
	 * @protected
621
	 */
622
	protected $l;
623
 
624
	/**
625
	 * Barcode to print on page footer (only if set).
626
	 * @protected
627
	 */
628
	protected $barcode = false;
629
 
630
	/**
631
	 * Boolean flag to print/hide page header.
632
	 * @protected
633
	 */
634
	protected $print_header = true;
635
 
636
	/**
637
	 * Boolean flag to print/hide page footer.
638
	 * @protected
639
	 */
640
	protected $print_footer = true;
641
 
642
	/**
643
	 * Header image logo.
644
	 * @protected
645
	 */
646
	protected $header_logo = '';
647
 
648
	/**
649
	 * Width of header image logo in user units.
650
	 * @protected
651
	 */
652
	protected $header_logo_width = 30;
653
 
654
	/**
655
	 * Title to be printed on default page header.
656
	 * @protected
657
	 */
658
	protected $header_title = '';
659
 
660
	/**
661
	 * String to print on page header after title.
662
	 * @protected
663
	 */
664
	protected $header_string = '';
665
 
666
	/**
667
	 * Color for header text (RGB array).
668
	 * @since 5.9.174 (2012-07-25)
669
	 * @protected
670
	 * @var int[]
671
	 * @phpstan-var array{0: int, 1: int, 2: int}
672
	 */
673
	protected $header_text_color = array(0,0,0);
674
 
675
	/**
676
	 * Color for header line (RGB array).
677
	 * @since 5.9.174 (2012-07-25)
678
	 * @protected
679
	 * @var int[]
680
	 * @phpstan-var array{0: int, 1: int, 2: int}
681
	 */
682
	protected $header_line_color = array(0,0,0);
683
 
684
	/**
685
	 * Color for footer text (RGB array).
686
	 * @since 5.9.174 (2012-07-25)
687
	 * @protected
688
	 * @var int[]
689
	 * @phpstan-var array{0: int, 1: int, 2: int}
690
	 */
691
	protected $footer_text_color = array(0,0,0);
692
 
693
	/**
694
	 * Color for footer line (RGB array).
695
	 * @since 5.9.174 (2012-07-25)
696
	 * @protected
697
	 * @var int[]
698
	 * @phpstan-var array{0: int, 1: int, 2: int}
699
	 */
700
	protected $footer_line_color = array(0,0,0);
701
 
702
	/**
703
	 * Text shadow data array.
704
	 * @since 5.9.174 (2012-07-25)
705
	 * @protected
706
	 */
707
	protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
708
 
709
	/**
710
	 * Default number of columns for html table.
711
	 * @protected
712
	 */
713
	protected $default_table_columns = 4;
714
 
715
	// variables for html parser
716
 
717
	/**
718
	 * HTML PARSER: array to store current link and rendering styles.
719
	 * @protected
720
	 */
721
	protected $HREF = array();
722
 
723
	/**
724
	 * List of available fonts on filesystem.
725
	 * @protected
726
	 */
727
	protected $fontlist = array();
728
 
729
	/**
730
	 * Current foreground color.
731
	 * @protected
732
	 */
733
	protected $fgcolor;
734
 
735
	/**
736
	 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
737
	 * @protected
738
	 */
739
	protected $listordered = array();
740
 
741
	/**
742
	 * HTML PARSER: array count list items on nested lists.
743
	 * @protected
744
	 */
745
	protected $listcount = array();
746
 
747
	/**
748
	 * HTML PARSER: current list nesting level.
749
	 * @protected
750
	 */
751
	protected $listnum = 0;
752
 
753
	/**
754
	 * HTML PARSER: indent amount for lists.
755
	 * @protected
756
	 */
757
	protected $listindent = 0;
758
 
759
	/**
760
	 * HTML PARSER: current list indententation level.
761
	 * @protected
762
	 */
763
	protected $listindentlevel = 0;
764
 
765
	/**
766
	 * Current background color.
767
	 * @protected
768
	 */
769
	protected $bgcolor;
770
 
771
	/**
772
	 * Temporary font size in points.
773
	 * @protected
774
	 */
775
	protected $tempfontsize = 10;
776
 
777
	/**
778
	 * Spacer string for LI tags.
779
	 * @protected
780
	 */
781
	protected $lispacer = '';
782
 
783
	/**
784
	 * Default encoding.
785
	 * @protected
786
	 * @since 1.53.0.TC010
787
	 */
788
	protected $encoding = 'UTF-8';
789
 
790
	/**
791
	 * Boolean flag to indicate if the document language is Right-To-Left.
792
	 * @protected
793
	 * @since 2.0.000
794
	 */
795
	protected $rtl = false;
796
 
797
	/**
798
	 * Boolean flag used to force RTL or LTR string direction.
799
	 * @protected
800
	 * @since 2.0.000
801
	 */
802
	protected $tmprtl = false;
803
 
804
	// --- Variables used for document encryption:
805
 
806
	/**
807
	 * IBoolean flag indicating whether document is protected.
808
	 * @protected
809
	 * @since 2.0.000 (2008-01-02)
810
	 */
811
	protected $encrypted;
812
 
813
	/**
814
	 * Array containing encryption settings.
815
	 * @protected
816
	 * @since 5.0.005 (2010-05-11)
817
	 */
818
	protected $encryptdata = array();
819
 
820
	/**
821
	 * Last RC4 key encrypted (cached for optimisation).
822
	 * @protected
823
	 * @since 2.0.000 (2008-01-02)
824
	 */
825
	protected $last_enc_key;
826
 
827
	/**
828
	 * Last RC4 computed key.
829
	 * @protected
830
	 * @since 2.0.000 (2008-01-02)
831
	 */
832
	protected $last_enc_key_c;
833
 
834
	/**
835
	 * File ID (used on document trailer).
836
	 * @protected
837
	 * @since 5.0.005 (2010-05-12)
838
	 */
839
	protected $file_id;
840
 
841
	// --- bookmark ---
842
 
843
	/**
844
	 * Outlines for bookmark.
845
	 * @protected
846
	 * @since 2.1.002 (2008-02-12)
847
	 */
848
	protected $outlines = array();
849
 
850
	/**
851
	 * Outline root for bookmark.
852
	 * @protected
853
	 * @since 2.1.002 (2008-02-12)
854
	 */
855
	protected $OutlineRoot;
856
 
857
	// --- javascript and form ---
858
 
859
	/**
860
	 * Javascript code.
861
	 * @protected
862
	 * @since 2.1.002 (2008-02-12)
863
	 */
864
	protected $javascript = '';
865
 
866
	/**
867
	 * Javascript counter.
868
	 * @protected
869
	 * @since 2.1.002 (2008-02-12)
870
	 */
871
	protected $n_js;
872
 
873
	/**
874
	 * line through state
875
	 * @protected
876
	 * @since 2.8.000 (2008-03-19)
877
	 */
878
	protected $linethrough;
879
 
880
	/**
881
	 * Array with additional document-wide usage rights for the document.
882
	 * @protected
883
	 * @since 5.8.014 (2010-08-23)
884
	 */
885
	protected $ur = array();
886
 
887
	/**
888
	 * DPI (Dot Per Inch) Document Resolution (do not change).
889
	 * @protected
890
	 * @since 3.0.000 (2008-03-27)
891
	 */
892
	protected $dpi = 72;
893
 
894
	/**
895
	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
896
	 * @protected
897
	 * @since 3.0.000 (2008-03-27)
898
	 */
899
	protected $newpagegroup = array();
900
 
901
	/**
902
	 * Array that contains the number of pages in each page group.
903
	 * @protected
904
	 * @since 3.0.000 (2008-03-27)
905
	 */
906
	protected $pagegroups = array();
907
 
908
	/**
909
	 * Current page group number.
910
	 * @protected
911
	 * @since 3.0.000 (2008-03-27)
912
	 */
913
	protected $currpagegroup = 0;
914
 
915
	/**
916
	 * Array of transparency objects and parameters.
917
	 * @protected
918
	 * @since 3.0.000 (2008-03-27)
919
	 */
920
	protected $extgstates;
921
 
922
	/**
923
	 * Set the default JPEG compression quality (1-100).
924
	 * @protected
925
	 * @since 3.0.000 (2008-03-27)
926
	 */
927
	protected $jpeg_quality;
928
 
929
	/**
930
	 * Default cell height ratio.
931
	 * @protected
932
	 * @since 3.0.014 (2008-05-23)
933
	 * @var float
934
	 */
935
	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
936
 
937
	/**
938
	 * PDF viewer preferences.
939
	 * @protected
940
	 * @since 3.1.000 (2008-06-09)
941
	 */
942
	protected $viewer_preferences;
943
 
944
	/**
945
	 * A name object specifying how the document should be displayed when opened.
946
	 * @protected
947
	 * @since 3.1.000 (2008-06-09)
948
	 */
949
	protected $PageMode;
950
 
951
	/**
952
	 * Array for storing gradient information.
953
	 * @protected
954
	 * @since 3.1.000 (2008-06-09)
955
	 */
956
	protected $gradients = array();
957
 
958
	/**
959
	 * Array used to store positions inside the pages buffer (keys are the page numbers).
960
	 * @protected
961
	 * @since 3.2.000 (2008-06-26)
962
	 */
963
	protected $intmrk = array();
964
 
965
	/**
966
	 * Array used to store positions inside the pages buffer (keys are the page numbers).
967
	 * @protected
968
	 * @since 5.7.000 (2010-08-03)
969
	 */
970
	protected $bordermrk = array();
971
 
972
	/**
973
	 * Array used to store page positions to track empty pages (keys are the page numbers).
974
	 * @protected
975
	 * @since 5.8.007 (2010-08-18)
976
	 */
977
	protected $emptypagemrk = array();
978
 
979
	/**
980
	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
981
	 * @protected
982
	 * @since 4.6.021 (2009-07-20)
983
	 */
984
	protected $cntmrk = array();
985
 
986
	/**
987
	 * Array used to store footer positions of each page.
988
	 * @protected
989
	 * @since 3.2.000 (2008-07-01)
990
	 */
991
	protected $footerpos = array();
992
 
993
	/**
994
	 * Array used to store footer length of each page.
995
	 * @protected
996
	 * @since 4.0.014 (2008-07-29)
997
	 */
998
	protected $footerlen = array();
999
 
1000
	/**
1001
	 * Boolean flag to indicate if a new line is created.
1002
	 * @protected
1003
	 * @since 3.2.000 (2008-07-01)
1004
	 */
1005
	protected $newline = true;
1006
 
1007
	/**
1008
	 * End position of the latest inserted line.
1009
	 * @protected
1010
	 * @since 3.2.000 (2008-07-01)
1011
	 */
1012
	protected $endlinex = 0;
1013
 
1014
	/**
1015
	 * PDF string for width value of the last line.
1016
	 * @protected
1017
	 * @since 4.0.006 (2008-07-16)
1018
	 */
1019
	protected $linestyleWidth = '';
1020
 
1021
	/**
1022
	 * PDF string for CAP value of the last line.
1023
	 * @protected
1024
	 * @since 4.0.006 (2008-07-16)
1025
	 */
1026
	protected $linestyleCap = '0 J';
1027
 
1028
	/**
1029
	 * PDF string for join value of the last line.
1030
	 * @protected
1031
	 * @since 4.0.006 (2008-07-16)
1032
	 */
1033
	protected $linestyleJoin = '0 j';
1034
 
1035
	/**
1036
	 * PDF string for dash value of the last line.
1037
	 * @protected
1038
	 * @since 4.0.006 (2008-07-16)
1039
	 */
1040
	protected $linestyleDash = '[] 0 d';
1041
 
1042
	/**
1043
	 * Boolean flag to indicate if marked-content sequence is open.
1044
	 * @protected
1045
	 * @since 4.0.013 (2008-07-28)
1046
	 */
1047
	protected $openMarkedContent = false;
1048
 
1049
	/**
1050
	 * Count the latest inserted vertical spaces on HTML.
1051
	 * @protected
1052
	 * @since 4.0.021 (2008-08-24)
1053
	 */
1054
	protected $htmlvspace = 0;
1055
 
1056
	/**
1057
	 * Array of Spot colors.
1058
	 * @protected
1059
	 * @since 4.0.024 (2008-09-12)
1060
	 */
1061
	protected $spot_colors = array();
1062
 
1063
	/**
1064
	 * Symbol used for HTML unordered list items.
1065
	 * @protected
1066
	 * @since 4.0.028 (2008-09-26)
1067
	 */
1068
	protected $lisymbol = '';
1069
 
1070
	/**
1071
	 * String used to mark the beginning and end of EPS image blocks.
1072
	 * @protected
1073
	 * @since 4.1.000 (2008-10-18)
1074
	 */
1075
	protected $epsmarker = 'x#!#EPS#!#x';
1076
 
1077
	/**
1078
	 * Array of transformation matrix.
1079
	 * @protected
1080
	 * @since 4.2.000 (2008-10-29)
1081
	 */
1082
	protected $transfmatrix = array();
1083
 
1084
	/**
1085
	 * Current key for transformation matrix.
1086
	 * @protected
1087
	 * @since 4.8.005 (2009-09-17)
1088
	 */
1089
	protected $transfmatrix_key = 0;
1090
 
1091
	/**
1092
	 * Booklet mode for double-sided pages.
1093
	 * @protected
1094
	 * @since 4.2.000 (2008-10-29)
1095
	 */
1096
	protected $booklet = false;
1097
 
1098
	/**
1099
	 * Epsilon value used for float calculations.
1100
	 * @protected
1101
	 * @since 4.2.000 (2008-10-29)
1102
	 */
1103
	protected $feps = 0.005;
1104
 
1105
	/**
1106
	 * Array used for custom vertical spaces for HTML tags.
1107
	 * @protected
1108
	 * @since 4.2.001 (2008-10-30)
1109
	 */
1110
	protected $tagvspaces = array();
1111
 
1112
	/**
1113
	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1114
	 * @protected
1115
	 * @since 4.2.007 (2008-11-12)
1116
	 */
1117
	protected $customlistindent = -1;
1118
 
1119
	/**
1120
	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1121
	 * @protected
1122
	 * @since 4.2.010 (2008-11-14)
1123
	 */
1124
	protected $opencell = true;
1125
 
1126
	/**
1127
	 * Array of files to embedd.
1128
	 * @protected
1129
	 * @since 4.4.000 (2008-12-07)
1130
	 */
1131
	protected $embeddedfiles = array();
1132
 
1133
	/**
1134
	 * Boolean flag to indicate if we are inside a PRE tag.
1135
	 * @protected
1136
	 * @since 4.4.001 (2008-12-08)
1137
	 */
1138
	protected $premode = false;
1139
 
1140
	/**
1141
	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1142
	 * keys are the page numbers
1143
	 * @protected
1144
	 * @since 4.4.002 (2008-12-09)
1145
	 */
1146
	protected $transfmrk = array();
1147
 
1148
	/**
1149
	 * Default color for html links.
1150
	 * @protected
1151
	 * @since 4.4.003 (2008-12-09)
1152
	 */
1153
	protected $htmlLinkColorArray = array(0, 0, 255);
1154
 
1155
	/**
1156
	 * Default font style to add to html links.
1157
	 * @protected
1158
	 * @since 4.4.003 (2008-12-09)
1159
	 */
1160
	protected $htmlLinkFontStyle = 'U';
1161
 
1162
	/**
1163
	 * Counts the number of pages.
1164
	 * @protected
1165
	 * @since 4.5.000 (2008-12-31)
1166
	 */
1167
	protected $numpages = 0;
1168
 
1169
	/**
1170
	 * Array containing page lengths in bytes.
1171
	 * @protected
1172
	 * @since 4.5.000 (2008-12-31)
1173
	 */
1174
	protected $pagelen = array();
1175
 
1176
	/**
1177
	 * Counts the number of pages.
1178
	 * @protected
1179
	 * @since 4.5.000 (2008-12-31)
1180
	 */
1181
	protected $numimages = 0;
1182
 
1183
	/**
1184
	 * Store the image keys.
1185
	 * @protected
1186
	 * @since 4.5.000 (2008-12-31)
1187
	 */
1188
	protected $imagekeys = array();
1189
 
1190
	/**
1191
	 * Length of the buffer in bytes.
1192
	 * @protected
1193
	 * @since 4.5.000 (2008-12-31)
1194
	 */
1195
	protected $bufferlen = 0;
1196
 
1197
	/**
1198
	 * Counts the number of fonts.
1199
	 * @protected
1200
	 * @since 4.5.000 (2009-01-02)
1201
	 */
1202
	protected $numfonts = 0;
1203
 
1204
	/**
1205
	 * Store the font keys.
1206
	 * @protected
1207
	 * @since 4.5.000 (2009-01-02)
1208
	 */
1209
	protected $fontkeys = array();
1210
 
1211
	/**
1212
	 * Store the font object IDs.
1213
	 * @protected
1214
	 * @since 4.8.001 (2009-09-09)
1215
	 */
1216
	protected $font_obj_ids = array();
1217
 
1218
	/**
1219
	 * Store the fage status (true when opened, false when closed).
1220
	 * @protected
1221
	 * @since 4.5.000 (2009-01-02)
1222
	 */
1223
	protected $pageopen = array();
1224
 
1225
	/**
1226
	 * Default monospace font.
1227
	 * @protected
1228
	 * @since 4.5.025 (2009-03-10)
1229
	 */
1230
	protected $default_monospaced_font = 'courier';
1231
 
1232
	/**
1233
	 * Cloned copy of the current class object.
1234
	 * @protected
1235
	 * @since 4.5.029 (2009-03-19)
1236
	 */
1237
	protected $objcopy;
1238
 
1239
	/**
1240
	 * Array used to store the lengths of cache files.
1241
	 * @protected
1242
	 * @since 4.5.029 (2009-03-19)
1243
	 */
1244
	protected $cache_file_length = array();
1245
 
1246
	/**
1247
	 * Table header content to be repeated on each new page.
1248
	 * @protected
1249
	 * @since 4.5.030 (2009-03-20)
1250
	 */
1251
	protected $thead = '';
1252
 
1253
	/**
1254
	 * Margins used for table header.
1255
	 * @protected
1256
	 * @since 4.5.030 (2009-03-20)
1257
	 */
1258
	protected $theadMargins = array();
1259
 
1260
	/**
1261
	 * Boolean flag to enable document digital signature.
1262
	 * @protected
1263
	 * @since 4.6.005 (2009-04-24)
1264
	 */
1265
	protected $sign = false;
1266
 
1267
	/**
1268
	 * Digital signature data.
1269
	 * @protected
1270
	 * @since 4.6.005 (2009-04-24)
1271
	 */
1272
	protected $signature_data = array();
1273
 
1274
	/**
1275
	 * Digital signature max length.
1276
	 * @protected
1277
	 * @since 4.6.005 (2009-04-24)
1278
	 */
1279
	protected $signature_max_length = 11742;
1280
 
1281
	/**
1282
	 * Data for digital signature appearance.
1283
	 * @protected
1284
	 * @since 5.3.011 (2010-06-16)
1285
	 */
1286
	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1287
 
1288
	/**
1289
	 * Array of empty digital signature appearances.
1290
	 * @protected
1291
	 * @since 5.9.101 (2011-07-06)
1292
	 */
1293
	protected $empty_signature_appearance = array();
1294
 
1295
	/**
1296
	 * Boolean flag to enable document timestamping with TSA.
1297
	 * @protected
1298
	 * @since 6.0.085 (2014-06-19)
1299
	 */
1300
	protected $tsa_timestamp = false;
1301
 
1302
	/**
1303
	 * Timestamping data.
1304
	 * @protected
1305
	 * @since 6.0.085 (2014-06-19)
1306
	 */
1307
	protected $tsa_data = array();
1308
 
1309
	/**
1310
	 * Regular expression used to find blank characters (required for word-wrapping).
1311
	 * @protected
1312
	 * @since 4.6.006 (2009-04-28)
1313
	 */
1314
	protected $re_spaces = '/[^\S\xa0]/';
1315
 
1316
	/**
1317
	 * Array of $re_spaces parts.
1318
	 * @protected
1319
	 * @since 5.5.011 (2010-07-09)
1320
	 */
1321
	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1322
 
1323
	/**
1324
	 * Digital signature object ID.
1325
	 * @protected
1326
	 * @since 4.6.022 (2009-06-23)
1327
	 */
1328
	protected $sig_obj_id = 0;
1329
 
1330
	/**
1331
	 * ID of page objects.
1332
	 * @protected
1333
	 * @since 4.7.000 (2009-08-29)
1334
	 */
1335
	protected $page_obj_id = array();
1336
 
1337
	/**
1338
	 * List of form annotations IDs.
1339
	 * @protected
1340
	 * @since 4.8.000 (2009-09-07)
1341
	 */
1342
	protected $form_obj_id = array();
1343
 
1344
	/**
1345
	 * 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.
1346
	 * @protected
1347
	 * @since 4.8.000 (2009-09-07)
1348
	 */
1349
	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1350
 
1351
	/**
1352
	 * Javascript objects array.
1353
	 * @protected
1354
	 * @since 4.8.000 (2009-09-07)
1355
	 */
1356
	protected $js_objects = array();
1357
 
1358
	/**
1359
	 * Current form action (used during XHTML rendering).
1360
	 * @protected
1361
	 * @since 4.8.000 (2009-09-07)
1362
	 */
1363
	protected $form_action = '';
1364
 
1365
	/**
1366
	 * Current form encryption type (used during XHTML rendering).
1367
	 * @protected
1368
	 * @since 4.8.000 (2009-09-07)
1369
	 */
1370
	protected $form_enctype = 'application/x-www-form-urlencoded';
1371
 
1372
	/**
1373
	 * Current method to submit forms.
1374
	 * @protected
1375
	 * @since 4.8.000 (2009-09-07)
1376
	 */
1377
	protected $form_mode = 'post';
1378
 
1379
	/**
1380
	 * List of fonts used on form fields (fontname => fontkey).
1381
	 * @protected
1382
	 * @since 4.8.001 (2009-09-09)
1383
	 */
1384
	protected $annotation_fonts = array();
1385
 
1386
	/**
1387
	 * List of radio buttons parent objects.
1388
	 * @protected
1389
	 * @since 4.8.001 (2009-09-09)
1390
	 */
1391
	protected $radiobutton_groups = array();
1392
 
1393
	/**
1394
	 * List of radio group objects IDs.
1395
	 * @protected
1396
	 * @since 4.8.001 (2009-09-09)
1397
	 */
1398
	protected $radio_groups = array();
1399
 
1400
	/**
1401
	 * Text indentation value (used for text-indent CSS attribute).
1402
	 * @protected
1403
	 * @since 4.8.006 (2009-09-23)
1404
	 */
1405
	protected $textindent = 0;
1406
 
1407
	/**
1408
	 * Store page number when startTransaction() is called.
1409
	 * @protected
1410
	 * @since 4.8.006 (2009-09-23)
1411
	 */
1412
	protected $start_transaction_page = 0;
1413
 
1414
	/**
1415
	 * Store Y position when startTransaction() is called.
1416
	 * @protected
1417
	 * @since 4.9.001 (2010-03-28)
1418
	 */
1419
	protected $start_transaction_y = 0;
1420
 
1421
	/**
1422
	 * True when we are printing the thead section on a new page.
1423
	 * @protected
1424
	 * @since 4.8.027 (2010-01-25)
1425
	 */
1426
	protected $inthead = false;
1427
 
1428
	/**
1429
	 * Array of column measures (width, space, starting Y position).
1430
	 * @protected
1431
	 * @since 4.9.001 (2010-03-28)
1432
	 */
1433
	protected $columns = array();
1434
 
1435
	/**
1436
	 * Number of colums.
1437
	 * @protected
1438
	 * @since 4.9.001 (2010-03-28)
1439
	 */
1440
	protected $num_columns = 1;
1441
 
1442
	/**
1443
	 * Current column number.
1444
	 * @protected
1445
	 * @since 4.9.001 (2010-03-28)
1446
	 */
1447
	protected $current_column = 0;
1448
 
1449
	/**
1450
	 * Starting page for columns.
1451
	 * @protected
1452
	 * @since 4.9.001 (2010-03-28)
1453
	 */
1454
	protected $column_start_page = 0;
1455
 
1456
	/**
1457
	 * Maximum page and column selected.
1458
	 * @protected
1459
	 * @since 5.8.000 (2010-08-11)
1460
	 */
1461
	protected $maxselcol = array('page' => 0, 'column' => 0);
1462
 
1463
	/**
1464
	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1465
	 * @protected
1466
	 * @since 5.8.000 (2010-08-11)
1467
	 */
1468
	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1469
 
1470
	/**
1471
	 * 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.
1472
	 * @protected
1473
	 * @since 4.9.008 (2010-04-03)
1474
	 */
1475
	protected $textrendermode = 0;
1476
 
1477
	/**
1478
	 * Text stroke width in doc units.
1479
	 * @protected
1480
	 * @since 4.9.008 (2010-04-03)
1481
	 */
1482
	protected $textstrokewidth = 0;
1483
 
1484
	/**
1485
	 * Current stroke color.
1486
	 * @protected
1487
	 * @since 4.9.008 (2010-04-03)
1488
	 */
1489
	protected $strokecolor;
1490
 
1491
	/**
1492
	 * Default unit of measure for document.
1493
	 * @protected
1494
	 * @since 5.0.000 (2010-04-22)
1495
	 */
1496
	protected $pdfunit = 'mm';
1497
 
1498
	/**
1499
	 * Boolean flag true when we are on TOC (Table Of Content) page.
1500
	 * @protected
1501
	 */
1502
	protected $tocpage = false;
1503
 
1504
	/**
1505
	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1506
	 * @protected
1507
	 * @since 5.0.000 (2010-04-26)
1508
	 */
1509
	protected $rasterize_vector_images = false;
1510
 
1511
	/**
1512
	 * Boolean flag: if true enables font subsetting by default.
1513
	 * @protected
1514
	 * @since 5.3.002 (2010-06-07)
1515
	 */
1516
	protected $font_subsetting = true;
1517
 
1518
	/**
1519
	 * Array of default graphic settings.
1520
	 * @protected
1521
	 * @since 5.5.008 (2010-07-02)
1522
	 */
1523
	protected $default_graphic_vars = array();
1524
 
1525
	/**
1526
	 * Array of XObjects.
1527
	 * @protected
1528
	 * @since 5.8.014 (2010-08-23)
1529
	 */
1530
	protected $xobjects = array();
1531
 
1532
	/**
1533
	 * Boolean value true when we are inside an XObject.
1534
	 * @protected
1535
	 * @since 5.8.017 (2010-08-24)
1536
	 */
1537
	protected $inxobj = false;
1538
 
1539
	/**
1540
	 * Current XObject ID.
1541
	 * @protected
1542
	 * @since 5.8.017 (2010-08-24)
1543
	 */
1544
	protected $xobjid = '';
1545
 
1546
	/**
1547
	 * Percentage of character stretching.
1548
	 * @protected
1549
	 * @since 5.9.000 (2010-09-29)
1550
	 */
1551
	protected $font_stretching = 100;
1552
 
1553
	/**
1554
	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1555
	 * @protected
1556
	 * @since 5.9.000 (2010-09-29)
1557
	 */
1558
	protected $font_spacing = 0;
1559
 
1560
	/**
1561
	 * Array of no-write regions.
1562
	 * ('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)
1563
	 * @protected
1564
	 * @since 5.9.003 (2010-10-14)
1565
	 */
1566
	protected $page_regions = array();
1567
 
1568
	/**
1569
	 * Boolean value true when page region check is active.
1570
	 * @protected
1571
	 */
1572
	protected $check_page_regions = true;
1573
 
1574
	/**
1575
	 * Array of PDF layers data.
1576
	 * @protected
1577
	 * @since 5.9.102 (2011-07-13)
1578
	 */
1579
	protected $pdflayers = array();
1580
 
1581
	/**
1582
	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1583
	 * @protected
1584
	 * @since 5.9.097 (2011-06-23)
1585
	 */
1586
	protected $dests = array();
1587
 
1588
	/**
1589
	 * Object ID for Named Destinations
1590
	 * @protected
1591
	 * @since 5.9.097 (2011-06-23)
1592
	 */
1593
	protected $n_dests;
1594
 
1595
	/**
1596
	 * Embedded Files Names
1597
	 * @protected
1598
	 * @since 5.9.204 (2013-01-23)
1599
	 */
1600
	protected $efnames = array();
1601
 
1602
	/**
1603
	 * Directory used for the last SVG image.
1604
	 * @protected
1605
	 * @since 5.0.000 (2010-05-05)
1606
	 */
1607
	protected $svgdir = '';
1608
 
1609
	/**
1610
	 *  Deafult unit of measure for SVG.
1611
	 * @protected
1612
	 * @since 5.0.000 (2010-05-02)
1613
	 */
1614
	protected $svgunit = 'px';
1615
 
1616
	/**
1617
	 * Array of SVG gradients.
1618
	 * @protected
1619
	 * @since 5.0.000 (2010-05-02)
1620
	 */
1621
	protected $svggradients = array();
1622
 
1623
	/**
1624
	 * ID of last SVG gradient.
1625
	 * @protected
1626
	 * @since 5.0.000 (2010-05-02)
1627
	 */
1628
	protected $svggradientid = 0;
1629
 
1630
	/**
1631
	 * Boolean value true when in SVG defs group.
1632
	 * @protected
1633
	 * @since 5.0.000 (2010-05-02)
1634
	 */
1635
	protected $svgdefsmode = false;
1636
 
1637
	/**
1638
	 * Array of SVG defs.
1639
	 * @protected
1640
	 * @since 5.0.000 (2010-05-02)
1641
	 */
1642
	protected $svgdefs = array();
1643
 
1644
	/**
1645
	 * Boolean value true when in SVG clipPath tag.
1646
	 * @protected
1647
	 * @since 5.0.000 (2010-04-26)
1648
	 */
1649
	protected $svgclipmode = false;
1650
 
1651
	/**
1652
	 * Array of SVG clipPath commands.
1653
	 * @protected
1654
	 * @since 5.0.000 (2010-05-02)
1655
	 */
1656
	protected $svgclippaths = array();
1657
 
1658
	/**
1659
	 * Array of SVG clipPath tranformation matrix.
1660
	 * @protected
1661
	 * @since 5.8.022 (2010-08-31)
1662
	 */
1663
	protected $svgcliptm = array();
1664
 
1665
	/**
1666
	 * ID of last SVG clipPath.
1667
	 * @protected
1668
	 * @since 5.0.000 (2010-05-02)
1669
	 */
1670
	protected $svgclipid = 0;
1671
 
1672
	/**
1673
	 * SVG text.
1674
	 * @protected
1675
	 * @since 5.0.000 (2010-05-02)
1676
	 */
1677
	protected $svgtext = '';
1678
 
1679
	/**
1680
	 * SVG text properties.
1681
	 * @protected
1682
	 * @since 5.8.013 (2010-08-23)
1683
	 */
1684
	protected $svgtextmode = array();
1685
 
1686
	/**
1687
	 * Array of SVG properties.
1688
	 * @protected
1689
	 * @since 5.0.000 (2010-05-02)
1690
	 */
1691
	protected $svgstyles = array(array(
1692
		'alignment-baseline' => 'auto',
1693
		'baseline-shift' => 'baseline',
1694
		'clip' => 'auto',
1695
		'clip-path' => 'none',
1696
		'clip-rule' => 'nonzero',
1697
		'color' => 'black',
1698
		'color-interpolation' => 'sRGB',
1699
		'color-interpolation-filters' => 'linearRGB',
1700
		'color-profile' => 'auto',
1701
		'color-rendering' => 'auto',
1702
		'cursor' => 'auto',
1703
		'direction' => 'ltr',
1704
		'display' => 'inline',
1705
		'dominant-baseline' => 'auto',
1706
		'enable-background' => 'accumulate',
1707
		'fill' => 'black',
1708
		'fill-opacity' => 1,
1709
		'fill-rule' => 'nonzero',
1710
		'filter' => 'none',
1711
		'flood-color' => 'black',
1712
		'flood-opacity' => 1,
1713
		'font' => '',
1714
		'font-family' => 'helvetica',
1715
		'font-size' => 'medium',
1716
		'font-size-adjust' => 'none',
1717
		'font-stretch' => 'normal',
1718
		'font-style' => 'normal',
1719
		'font-variant' => 'normal',
1720
		'font-weight' => 'normal',
1721
		'glyph-orientation-horizontal' => '0deg',
1722
		'glyph-orientation-vertical' => 'auto',
1723
		'image-rendering' => 'auto',
1724
		'kerning' => 'auto',
1725
		'letter-spacing' => 'normal',
1726
		'lighting-color' => 'white',
1727
		'marker' => '',
1728
		'marker-end' => 'none',
1729
		'marker-mid' => 'none',
1730
		'marker-start' => 'none',
1731
		'mask' => 'none',
1732
		'opacity' => 1,
1733
		'overflow' => 'auto',
1734
		'pointer-events' => 'visiblePainted',
1735
		'shape-rendering' => 'auto',
1736
		'stop-color' => 'black',
1737
		'stop-opacity' => 1,
1738
		'stroke' => 'none',
1739
		'stroke-dasharray' => 'none',
1740
		'stroke-dashoffset' => 0,
1741
		'stroke-linecap' => 'butt',
1742
		'stroke-linejoin' => 'miter',
1743
		'stroke-miterlimit' => 4,
1744
		'stroke-opacity' => 1,
1745
		'stroke-width' => 1,
1746
		'text-anchor' => 'start',
1747
		'text-decoration' => 'none',
1748
		'text-rendering' => 'auto',
1749
		'unicode-bidi' => 'normal',
1750
		'visibility' => 'visible',
1751
		'word-spacing' => 'normal',
1752
		'writing-mode' => 'lr-tb',
1753
		'text-color' => 'black',
1754
		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1755
		));
1756
 
1757
	/**
1758
	 * If true force sRGB color profile for all document.
1759
	 * @protected
1760
	 * @since 5.9.121 (2011-09-28)
1761
	 */
1762
	protected $force_srgb = false;
1763
 
1764
	/**
1765
	 * If true set the document to PDF/A mode.
1766
	 * @protected
1767
	 * @since 5.9.121 (2011-09-27)
1768
	 */
1769
	protected $pdfa_mode = false;
1770
 
1771
	/**
1772
	 * version of PDF/A mode (1 - 3).
1773
	 * @protected
1774
	 * @since 6.2.26 (2019-03-12)
1775
	 */
1776
	protected $pdfa_version = 1;
1777
 
1778
	/**
1779
	 * Document creation date-time
1780
	 * @protected
1781
	 * @since 5.9.152 (2012-03-22)
1782
	 */
1783
	protected $doc_creation_timestamp;
1784
 
1785
	/**
1786
	 * Document modification date-time
1787
	 * @protected
1788
	 * @since 5.9.152 (2012-03-22)
1789
	 */
1790
	protected $doc_modification_timestamp;
1791
 
1792
	/**
1793
	 * Custom XMP data.
1794
	 * @protected
1795
	 * @since 5.9.128 (2011-10-06)
1796
	 */
1797
	protected $custom_xmp = '';
1798
 
1799
	/**
1800
	 * Custom XMP RDF data.
1801
	 * @protected
1802
	 * @since 6.3.0 (2019-09-19)
1803
	 */
1804
	protected $custom_xmp_rdf = '';
1805
 
1806
	/**
1807
	 * Overprint mode array.
1808
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1809
	 * @protected
1810
	 * @since 5.9.152 (2012-03-23)
1811
	 * @var array<string,bool|int>
1812
	 */
1813
	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1814
 
1815
	/**
1816
	 * Alpha mode array.
1817
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1818
	 * @protected
1819
	 * @since 5.9.152 (2012-03-23)
1820
	 */
1821
	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1822
 
1823
	/**
1824
	 * Define the page boundaries boxes to be set on document.
1825
	 * @protected
1826
	 * @since 5.9.152 (2012-03-23)
1827
	 */
1828
	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1829
 
1830
	/**
1831
	 * If true print TCPDF meta link.
1832
	 * @protected
1833
	 * @since 5.9.152 (2012-03-23)
1834
	 */
1835
	protected $tcpdflink = true;
1836
 
1837
	/**
1838
	 * Cache array for computed GD gamma values.
1839
	 * @protected
1840
	 * @since 5.9.1632 (2012-06-05)
1841
	 */
1842
	protected $gdgammacache = array();
1843
 
1844
    /**
1845
     * Cache array for file content
1846
     * @protected
1847
     * @var array
1848
     * @since 6.3.5 (2020-09-28)
1849
     */
1850
	protected $fileContentCache = array();
1851
 
1852
	/**
1853
	 * Whether to allow local file path in image html tags, when prefixed with file://
1854
	 *
1855
	 * @var bool
1856
	 * @protected
1857
	 * @since 6.4 (2020-07-23)
1858
	 */
1859
	protected $allowLocalFiles = false;
1860
 
1861
	//------------------------------------------------------------
1862
	// METHODS
1863
	//------------------------------------------------------------
1864
 
1865
	/**
1866
	 * This is the class constructor.
1867
	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1868
	 *
1869
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1870
	 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1871
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1872
	 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1873
	 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1874
	 * @param boolean $diskcache DEPRECATED FEATURE
1875
	 * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1876
	 * @public
1877
	 * @see getPageSizeFromFormat(), setPageFormat()
1878
	 */
1879
	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1880
		// set file ID for trailer
1881
		$serformat = (is_array($format) ? json_encode($format) : $format);
1882
		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1883
		$this->font_obj_ids = array();
1884
		$this->page_obj_id = array();
1885
		$this->form_obj_id = array();
1886
 
1887
		// set pdf/a mode
1888
		if ($pdfa != false) {
1889
			$this->pdfa_mode = true;
1890
			$this->pdfa_version = $pdfa;  // 1 or 3
1891
		} else
1892
			$this->pdfa_mode = false;
1893
 
1894
		$this->force_srgb = false;
1895
		// set language direction
1896
		$this->rtl = false;
1897
		$this->tmprtl = false;
1898
		// some checks
1899
		$this->_dochecks();
1900
		// initialization of properties
1901
		$this->isunicode = $unicode;
1902
		$this->page = 0;
1903
		$this->transfmrk[0] = array();
1904
		$this->pagedim = array();
1905
		$this->n = 2;
1906
		$this->buffer = '';
1907
		$this->pages = array();
1908
		$this->state = 0;
1909
		$this->fonts = array();
1910
		$this->FontFiles = array();
1911
		$this->diffs = array();
1912
		$this->images = array();
1913
		$this->links = array();
1914
		$this->gradients = array();
1915
		$this->InFooter = false;
1916
		$this->lasth = 0;
1917
		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1918
		$this->FontStyle = '';
1919
		$this->FontSizePt = 12;
1920
		$this->underline = false;
1921
		$this->overline = false;
1922
		$this->linethrough = false;
1923
		$this->DrawColor = '0 G';
1924
		$this->FillColor = '0 g';
1925
		$this->TextColor = '0 g';
1926
		$this->ColorFlag = false;
1927
		$this->pdflayers = array();
1928
		// encryption values
1929
		$this->encrypted = false;
1930
		$this->last_enc_key = '';
1931
		// standard Unicode fonts
1932
		$this->CoreFonts = array(
1933
			'courier'=>'Courier',
1934
			'courierB'=>'Courier-Bold',
1935
			'courierI'=>'Courier-Oblique',
1936
			'courierBI'=>'Courier-BoldOblique',
1937
			'helvetica'=>'Helvetica',
1938
			'helveticaB'=>'Helvetica-Bold',
1939
			'helveticaI'=>'Helvetica-Oblique',
1940
			'helveticaBI'=>'Helvetica-BoldOblique',
1941
			'times'=>'Times-Roman',
1942
			'timesB'=>'Times-Bold',
1943
			'timesI'=>'Times-Italic',
1944
			'timesBI'=>'Times-BoldItalic',
1945
			'symbol'=>'Symbol',
1946
			'zapfdingbats'=>'ZapfDingbats'
1947
		);
1948
		// set scale factor
1949
		$this->setPageUnit($unit);
1950
		// set page format and orientation
1951
		$this->setPageFormat($format, $orientation);
1952
		// page margins (1 cm)
1953
		$margin = 28.35 / $this->k;
1954
		$this->setMargins($margin, $margin);
1955
		$this->clMargin = $this->lMargin;
1956
		$this->crMargin = $this->rMargin;
1957
		// internal cell padding
1958
		$cpadding = $margin / 10;
1959
		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1960
		// cell margins
1961
		$this->setCellMargins(0, 0, 0, 0);
1962
		// line width (0.2 mm)
1963
		$this->LineWidth = 0.57 / $this->k;
1964
		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1965
		$this->linestyleCap = '0 J';
1966
		$this->linestyleJoin = '0 j';
1967
		$this->linestyleDash = '[] 0 d';
1968
		// automatic page break
1969
		$this->setAutoPageBreak(true, (2 * $margin));
1970
		// full width display mode
1971
		$this->setDisplayMode('fullwidth');
1972
		// compression
1973
		$this->setCompression();
1974
		// set default PDF version number
1975
		$this->setPDFVersion();
1976
		$this->tcpdflink = true;
1977
		$this->encoding = $encoding;
1978
		$this->HREF = array();
1979
		$this->getFontsList();
1980
		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1981
		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1982
		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1983
		$this->extgstates = array();
1984
		$this->setTextShadow();
1985
		// signature
1986
		$this->sign = false;
1987
		$this->tsa_timestamp = false;
1988
		$this->tsa_data = array();
1989
		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1990
		$this->empty_signature_appearance = array();
1991
		// user's rights
1992
		$this->ur['enabled'] = false;
1993
		$this->ur['document'] = '/FullSave';
1994
		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1995
		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1996
		$this->ur['signature'] = '/Modify';
1997
		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1998
		$this->ur['formex'] = '';
1999
		// set default JPEG quality
2000
		$this->jpeg_quality = 75;
2001
		// initialize some settings
2002
		TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
2003
		// set default font
2004
		$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2005
		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2006
		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2007
		// check if PCRE Unicode support is enabled
2008
		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2009
			// PCRE unicode support is turned ON
2010
			// \s     : any whitespace character
2011
			// \p{Z}  : any separator
2012
			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2013
			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2014
			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2015
			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2016
		} else {
2017
			// PCRE unicode support is turned OFF
2018
			$this->setSpacesRE('/[^\S\xa0]/');
2019
		}
2020
		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2021
		// set document creation and modification timestamp
2022
		$this->doc_creation_timestamp = time();
2023
		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
2024
		// get default graphic vars
2025
		$this->default_graphic_vars = $this->getGraphicVars();
2026
		$this->header_xobj_autoreset = false;
2027
		$this->custom_xmp = '';
2028
		$this->custom_xmp_rdf = '';
2029
	}
2030
 
2031
	/**
2032
	 * Default destructor.
2033
	 * @public
2034
	 * @since 1.53.0.TC016
2035
	 */
2036
	public function __destruct() {
2037
		// cleanup
2038
		$this->_destroy(true);
2039
	}
2040
 
2041
	/**
2042
	 * Set the units of measure for the document.
2043
	 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2044
	 * @public
2045
	 * @since 3.0.015 (2008-06-06)
2046
	 */
2047
	public function setPageUnit($unit) {
2048
		$unit = strtolower($unit);
2049
		//Set scale factor
2050
		switch ($unit) {
2051
			// points
2052
			case 'px':
2053
			case 'pt': {
2054
				$this->k = 1;
2055
				break;
2056
			}
2057
			// millimeters
2058
			case 'mm': {
2059
				$this->k = $this->dpi / 25.4;
2060
				break;
2061
			}
2062
			// centimeters
2063
			case 'cm': {
2064
				$this->k = $this->dpi / 2.54;
2065
				break;
2066
			}
2067
			// inches
2068
			case 'in': {
2069
				$this->k = $this->dpi;
2070
				break;
2071
			}
2072
			// unsupported unit
2073
			default : {
2074
				$this->Error('Incorrect unit: '.$unit);
2075
				break;
2076
			}
2077
		}
2078
		$this->pdfunit = $unit;
2079
		if (isset($this->CurOrientation)) {
2080
			$this->setPageOrientation($this->CurOrientation);
2081
		}
2082
	}
2083
 
2084
	/**
2085
	 * Change the format of the current page
2086
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2087
	 * <li>['format'] = page format name (one of the above);</li>
2088
	 * <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>
2089
	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2090
	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2091
	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2092
	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2093
	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2094
	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2095
	 * <li>['CropBox'] : the visible region of default user space:</li>
2096
	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2097
	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2098
	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2099
	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2100
	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2101
	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2102
	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2103
	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2104
	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2105
	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2106
	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2107
	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2108
	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2109
	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2110
	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2111
	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2112
	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2113
	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2114
	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2115
	 * <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>
2116
	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2117
	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2118
	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2119
	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2120
	 * <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>
2121
	 * <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>
2122
	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2123
	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2124
	 * <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>
2125
	 * <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>
2126
	 * <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>
2127
	 * <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>
2128
	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2129
	 * </ul>
2130
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2131
	 * <li>P or Portrait (default)</li>
2132
	 * <li>L or Landscape</li>
2133
	 * <li>'' (empty string) for automatic orientation</li>
2134
	 * </ul>
2135
	 * @protected
2136
	 * @since 3.0.015 (2008-06-06)
2137
	 * @see getPageSizeFromFormat()
2138
	 */
2139
	protected function setPageFormat($format, $orientation='P') {
2140
		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2141
			// remove inherited values
2142
			unset($this->pagedim[$this->page]);
2143
		}
2144
		if (is_string($format)) {
2145
			// get page measures from format name
2146
			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2147
			$this->fwPt = $pf[0];
2148
			$this->fhPt = $pf[1];
2149
		} else {
2150
			// the boundaries of the physical medium on which the page shall be displayed or printed
2151
			if (isset($format['MediaBox'])) {
2152
				$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);
2153
				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2154
				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2155
			} else {
2156
				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2157
					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2158
				} else {
2159
					if (!isset($format['format'])) {
2160
						// default value
2161
						$format['format'] = 'A4';
2162
					}
2163
					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2164
				}
2165
				$this->fwPt = $pf[0];
2166
				$this->fhPt = $pf[1];
2167
				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2168
			}
2169
			// the visible region of default user space
2170
			if (isset($format['CropBox'])) {
2171
				$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);
2172
			}
2173
			// the region to which the contents of the page shall be clipped when output in a production environment
2174
			if (isset($format['BleedBox'])) {
2175
				$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);
2176
			}
2177
			// the intended dimensions of the finished page after trimming
2178
			if (isset($format['TrimBox'])) {
2179
				$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);
2180
			}
2181
			// the page's meaningful content (including potential white space)
2182
			if (isset($format['ArtBox'])) {
2183
				$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);
2184
			}
2185
			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2186
			if (isset($format['BoxColorInfo'])) {
2187
				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2188
			}
2189
			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2190
				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2191
				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2192
			}
2193
			if (isset($format['PZ'])) {
2194
				// The page's preferred zoom (magnification) factor
2195
				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2196
			}
2197
			if (isset($format['trans'])) {
2198
				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2199
				if (isset($format['trans']['Dur'])) {
2200
					// The page's display duration
2201
					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2202
				}
2203
				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2204
				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2205
					// The transition style that shall be used when moving to this page from another during a presentation
2206
					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2207
					$valid_effect = array('Split', 'Blinds');
2208
					$valid_vals = array('H', 'V');
2209
					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2210
						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2211
					}
2212
					$valid_effect = array('Split', 'Box', 'Fly');
2213
					$valid_vals = array('I', 'O');
2214
					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2215
						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2216
					}
2217
					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2218
					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2219
						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2220
							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2221
							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2222
							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2223
						}
2224
					}
2225
					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2226
						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2227
					}
2228
					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2229
						$this->pagedim[$this->page]['trans']['B'] = 'true';
2230
					}
2231
				} else {
2232
					$this->pagedim[$this->page]['trans']['S'] = 'R';
2233
				}
2234
				if (isset($format['trans']['D'])) {
2235
					// The duration of the transition effect, in seconds
2236
					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2237
				} else {
2238
					$this->pagedim[$this->page]['trans']['D'] = 1;
2239
				}
2240
			}
2241
		}
2242
		$this->setPageOrientation($orientation);
2243
	}
2244
 
2245
	/**
2246
	 * Set page orientation.
2247
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2248
	 * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2249
	 * @param float|null $bottommargin bottom margin of the page.
2250
	 * @public
2251
	 * @since 3.0.015 (2008-06-06)
2252
	 */
2253
	public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) {
2254
		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2255
			// the boundaries of the physical medium on which the page shall be displayed or printed
2256
			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2257
		}
2258
		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2259
			// the visible region of default user space
2260
			$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);
2261
		}
2262
		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2263
			// the region to which the contents of the page shall be clipped when output in a production environment
2264
			$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);
2265
		}
2266
		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2267
			// the intended dimensions of the finished page after trimming
2268
			$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);
2269
		}
2270
		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2271
			// the page's meaningful content (including potential white space)
2272
			$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);
2273
		}
2274
		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2275
			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2276
			$this->pagedim[$this->page]['Rotate'] = 0;
2277
		}
2278
		if (!isset($this->pagedim[$this->page]['PZ'])) {
2279
			// The page's preferred zoom (magnification) factor
2280
			$this->pagedim[$this->page]['PZ'] = 1;
2281
		}
2282
		if ($this->fwPt > $this->fhPt) {
2283
			// landscape
2284
			$default_orientation = 'L';
2285
		} else {
2286
			// portrait
2287
			$default_orientation = 'P';
2288
		}
2289
		$valid_orientations = array('P', 'L');
2290
		if (empty($orientation)) {
2291
			$orientation = $default_orientation;
2292
		} else {
2293
			$orientation = strtoupper($orientation[0]);
2294
		}
2295
		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2296
			$this->CurOrientation = $orientation;
2297
			$this->wPt = $this->fhPt;
2298
			$this->hPt = $this->fwPt;
2299
		} else {
2300
			$this->CurOrientation = $default_orientation;
2301
			$this->wPt = $this->fwPt;
2302
			$this->hPt = $this->fhPt;
2303
		}
2304
		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2305
			// swap X and Y coordinates (change page orientation)
2306
			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2307
		}
2308
		$this->w = ($this->wPt / $this->k);
2309
		$this->h = ($this->hPt / $this->k);
2310
		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2311
			if (isset($this->AutoPageBreak)) {
2312
				$autopagebreak = $this->AutoPageBreak;
2313
			} else {
2314
				$autopagebreak = true;
2315
			}
2316
		}
2317
		if (TCPDF_STATIC::empty_string($bottommargin)) {
2318
			if (isset($this->bMargin)) {
2319
				$bottommargin = $this->bMargin;
2320
			} else {
2321
				// default value = 2 cm
2322
				$bottommargin = 2 * 28.35 / $this->k;
2323
			}
2324
		}
2325
		$this->setAutoPageBreak($autopagebreak, $bottommargin);
2326
		// store page dimensions
2327
		$this->pagedim[$this->page]['w'] = $this->wPt;
2328
		$this->pagedim[$this->page]['h'] = $this->hPt;
2329
		$this->pagedim[$this->page]['wk'] = $this->w;
2330
		$this->pagedim[$this->page]['hk'] = $this->h;
2331
		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2332
		$this->pagedim[$this->page]['bm'] = $bottommargin;
2333
		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2334
		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2335
		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2336
		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2337
		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2338
		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2339
	}
2340
 
2341
	/**
2342
	 * Set regular expression to detect withespaces or word separators.
2343
	 * The pattern delimiter must be the forward-slash character "/".
2344
	 * Some example patterns are:
2345
	 * <pre>
2346
	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2347
	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2348
	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2349
	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2350
	 *      \s     : any whitespace character
2351
	 *      \p{Z}  : any separator
2352
	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2353
	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2354
	 * </pre>
2355
	 * @param string $re regular expression (leave empty for default).
2356
	 * @public
2357
	 * @since 4.6.016 (2009-06-15)
2358
	 */
2359
	public function setSpacesRE($re='/[^\S\xa0]/') {
2360
		$this->re_spaces = $re;
2361
		$re_parts = explode('/', $re);
2362
		// get pattern parts
2363
		$this->re_space = array();
2364
		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2365
			$this->re_space['p'] = $re_parts[1];
2366
		} else {
2367
			$this->re_space['p'] = '[\s]';
2368
		}
2369
		// set pattern modifiers
2370
		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2371
			$this->re_space['m'] = $re_parts[2];
2372
		} else {
2373
			$this->re_space['m'] = '';
2374
		}
2375
	}
2376
 
2377
	/**
2378
	 * Enable or disable Right-To-Left language mode
2379
	 * @param boolean $enable if true enable Right-To-Left language mode.
2380
	 * @param boolean $resetx if true reset the X position on direction change.
2381
	 * @public
2382
	 * @since 2.0.000 (2008-01-03)
2383
	 */
2384
	public function setRTL($enable, $resetx=true) {
2385
		$enable = $enable ? true : false;
2386
		$resetx = ($resetx AND ($enable != $this->rtl));
2387
		$this->rtl = $enable;
2388
		$this->tmprtl = false;
2389
		if ($resetx) {
2390
			$this->Ln(0);
2391
		}
2392
	}
2393
 
2394
	/**
2395
	 * Return the RTL status
2396
	 * @return bool
2397
	 * @public
2398
	 * @since 4.0.012 (2008-07-24)
2399
	 */
2400
	public function getRTL() {
2401
		return $this->rtl;
2402
	}
2403
 
2404
	/**
2405
	 * Force temporary RTL language direction
2406
	 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2407
	 * @public
2408
	 * @since 2.1.000 (2008-01-09)
2409
	 */
2410
	public function setTempRTL($mode) {
2411
		$newmode = false;
2412
		switch (strtoupper($mode)) {
2413
			case 'LTR':
2414
			case 'L': {
2415
				if ($this->rtl) {
2416
					$newmode = 'L';
2417
				}
2418
				break;
2419
			}
2420
			case 'RTL':
2421
			case 'R': {
2422
				if (!$this->rtl) {
2423
					$newmode = 'R';
2424
				}
2425
				break;
2426
			}
2427
			case false:
2428
			default: {
2429
				$newmode = false;
2430
				break;
2431
			}
2432
		}
2433
		$this->tmprtl = $newmode;
2434
	}
2435
 
2436
	/**
2437
	 * Return the current temporary RTL status
2438
	 * @return bool
2439
	 * @public
2440
	 * @since 4.8.014 (2009-11-04)
2441
	 */
2442
	public function isRTLTextDir() {
2443
		return ($this->rtl OR ($this->tmprtl == 'R'));
2444
	}
2445
 
2446
	/**
2447
	 * Set the last cell height.
2448
	 * @param float $h cell height.
2449
	 * @author Nicola Asuni
2450
	 * @public
2451
	 * @since 1.53.0.TC034
2452
	 */
2453
	public function setLastH($h) {
2454
		$this->lasth = $h;
2455
	}
2456
 
2457
	/**
2458
	 * Return the cell height
2459
	 * @param int $fontsize Font size in internal units
2460
	 * @param boolean $padding If true add cell padding
2461
	 * @public
2462
	 * @return float
2463
	 */
2464
	public function getCellHeight($fontsize, $padding=TRUE) {
2465
		$height = ($fontsize * $this->cell_height_ratio);
2466
		if ($padding && !empty($this->cell_padding)) {
2467
			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2468
		}
2469
		return round($height, 6);
2470
	}
2471
 
2472
	/**
2473
	 * Reset the last cell height.
2474
	 * @public
2475
	 * @since 5.9.000 (2010-10-03)
2476
	 */
2477
	public function resetLastH() {
2478
		$this->lasth = $this->getCellHeight($this->FontSize);
2479
	}
2480
 
2481
	/**
2482
	 * Get the last cell height.
2483
	 * @return float last cell height
2484
	 * @public
2485
	 * @since 4.0.017 (2008-08-05)
2486
	 */
2487
	public function getLastH() {
2488
		return $this->lasth;
2489
	}
2490
 
2491
	/**
2492
	 * Set the adjusting factor to convert pixels to user units.
2493
	 * @param float $scale adjusting factor to convert pixels to user units.
2494
	 * @author Nicola Asuni
2495
	 * @public
2496
	 * @since 1.5.2
2497
	 */
2498
	public function setImageScale($scale) {
2499
		$this->imgscale = $scale;
2500
	}
2501
 
2502
	/**
2503
	 * Returns the adjusting factor to convert pixels to user units.
2504
	 * @return float adjusting factor to convert pixels to user units.
2505
	 * @author Nicola Asuni
2506
	 * @public
2507
	 * @since 1.5.2
2508
	 */
2509
	public function getImageScale() {
2510
		return $this->imgscale;
2511
	}
2512
 
2513
	/**
2514
	 * Returns an array of page dimensions:
2515
	 * <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>
2516
	 * @param int|null $pagenum page number (empty = current page)
2517
	 * @return array of page dimensions.
2518
	 * @author Nicola Asuni
2519
	 * @public
2520
	 * @since 4.5.027 (2009-03-16)
2521
	 */
2522
	public function getPageDimensions($pagenum=null) {
2523
		if (empty($pagenum)) {
2524
			$pagenum = $this->page;
2525
		}
2526
		return $this->pagedim[$pagenum];
2527
	}
2528
 
2529
	/**
2530
	 * Returns the page width in units.
2531
	 * @param int|null $pagenum page number (empty = current page)
2532
	 * @return int|float page width.
2533
	 * @author Nicola Asuni
2534
	 * @public
2535
	 * @since 1.5.2
2536
	 * @see getPageDimensions()
2537
	 */
2538
	public function getPageWidth($pagenum=null) {
2539
		if (empty($pagenum)) {
2540
			return $this->w;
2541
		}
2542
		return $this->pagedim[$pagenum]['w'];
2543
	}
2544
 
2545
	/**
2546
	 * Returns the page height in units.
2547
	 * @param int|null $pagenum page number (empty = current page)
2548
	 * @return int|float page height.
2549
	 * @author Nicola Asuni
2550
	 * @public
2551
	 * @since 1.5.2
2552
	 * @see getPageDimensions()
2553
	 */
2554
	public function getPageHeight($pagenum=null) {
2555
		if (empty($pagenum)) {
2556
			return $this->h;
2557
		}
2558
		return $this->pagedim[$pagenum]['h'];
2559
	}
2560
 
2561
	/**
2562
	 * Returns the page break margin.
2563
	 * @param int|null $pagenum page number (empty = current page)
2564
	 * @return int|float page break margin.
2565
	 * @author Nicola Asuni
2566
	 * @public
2567
	 * @since 1.5.2
2568
	 * @see getPageDimensions()
2569
	 */
2570
	public function getBreakMargin($pagenum=null) {
2571
		if (empty($pagenum)) {
2572
			return $this->bMargin;
2573
		}
2574
		return $this->pagedim[$pagenum]['bm'];
2575
	}
2576
 
2577
	/**
2578
	 * Returns the scale factor (number of points in user unit).
2579
	 * @return int scale factor.
2580
	 * @author Nicola Asuni
2581
	 * @public
2582
	 * @since 1.5.2
2583
	 */
2584
	public function getScaleFactor() {
2585
		return $this->k;
2586
	}
2587
 
2588
	/**
2589
	 * Defines the left, top and right margins.
2590
	 * @param int|float $left Left margin.
2591
	 * @param int|float $top Top margin.
2592
	 * @param int|float|null $right Right margin. Default value is the left one.
2593
	 * @param boolean $keepmargins if true overwrites the default page margins
2594
	 * @public
2595
	 * @since 1.0
2596
	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2597
	 */
2598
	public function setMargins($left, $top, $right=null, $keepmargins=false) {
2599
		//Set left, top and right margins
2600
		$this->lMargin = $left;
2601
		$this->tMargin = $top;
2602
		if ($right == -1 OR $right === null) {
2603
			$right = $left;
2604
		}
2605
		$this->rMargin = $right;
2606
		if ($keepmargins) {
2607
			// overwrite original values
2608
			$this->original_lMargin = $this->lMargin;
2609
			$this->original_rMargin = $this->rMargin;
2610
		}
2611
	}
2612
 
2613
	/**
2614
	 * 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.
2615
	 * @param int|float $margin The margin.
2616
	 * @public
2617
	 * @since 1.4
2618
	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2619
	 */
2620
	public function setLeftMargin($margin) {
2621
		//Set left margin
2622
		$this->lMargin = $margin;
2623
		if (($this->page > 0) AND ($this->x < $margin)) {
2624
			$this->x = $margin;
2625
		}
2626
	}
2627
 
2628
	/**
2629
	 * Defines the top margin. The method can be called before creating the first page.
2630
	 * @param int|float $margin The margin.
2631
	 * @public
2632
	 * @since 1.5
2633
	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2634
	 */
2635
	public function setTopMargin($margin) {
2636
		//Set top margin
2637
		$this->tMargin = $margin;
2638
		if (($this->page > 0) AND ($this->y < $margin)) {
2639
			$this->y = $margin;
2640
		}
2641
	}
2642
 
2643
	/**
2644
	 * Defines the right margin. The method can be called before creating the first page.
2645
	 * @param int|float $margin The margin.
2646
	 * @public
2647
	 * @since 1.5
2648
	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2649
	 */
2650
	public function setRightMargin($margin) {
2651
		$this->rMargin = $margin;
2652
		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2653
			$this->x = $this->w - $margin;
2654
		}
2655
	}
2656
 
2657
	/**
2658
	 * Set the same internal Cell padding for top, right, bottom, left-
2659
	 * @param int|float $pad internal padding.
2660
	 * @public
2661
	 * @since 2.1.000 (2008-01-09)
2662
	 * @see getCellPaddings(), setCellPaddings()
2663
	 */
2664
	public function setCellPadding($pad) {
2665
		if ($pad >= 0) {
2666
			$this->cell_padding['L'] = $pad;
2667
			$this->cell_padding['T'] = $pad;
2668
			$this->cell_padding['R'] = $pad;
2669
			$this->cell_padding['B'] = $pad;
2670
		}
2671
	}
2672
 
2673
	/**
2674
	 * Set the internal Cell paddings.
2675
	 * @param int|float|null $left left padding
2676
	 * @param int|float|null $top top padding
2677
	 * @param int|float|null $right right padding
2678
	 * @param int|float|null $bottom bottom padding
2679
	 * @public
2680
	 * @since 5.9.000 (2010-10-03)
2681
	 * @see getCellPaddings(), SetCellPadding()
2682
	 */
2683
	public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) {
2684
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2685
			$this->cell_padding['L'] = $left;
2686
		}
2687
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2688
			$this->cell_padding['T'] = $top;
2689
		}
2690
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2691
			$this->cell_padding['R'] = $right;
2692
		}
2693
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2694
			$this->cell_padding['B'] = $bottom;
2695
		}
2696
	}
2697
 
2698
	/**
2699
	 * Get the internal Cell padding array.
2700
	 * @return array of padding values
2701
	 * @public
2702
	 * @since 5.9.000 (2010-10-03)
2703
	 * @see setCellPaddings(), SetCellPadding()
2704
	 */
2705
	public function getCellPaddings() {
2706
		return $this->cell_padding;
2707
	}
2708
 
2709
	/**
2710
	 * Set the internal Cell margins.
2711
	 * @param int|float|null $left left margin
2712
	 * @param int|float|null $top top margin
2713
	 * @param int|float|null $right right margin
2714
	 * @param int|float|null $bottom bottom margin
2715
	 * @public
2716
	 * @since 5.9.000 (2010-10-03)
2717
	 * @see getCellMargins()
2718
	 */
2719
	public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) {
2720
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2721
			$this->cell_margin['L'] = $left;
2722
		}
2723
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2724
			$this->cell_margin['T'] = $top;
2725
		}
2726
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2727
			$this->cell_margin['R'] = $right;
2728
		}
2729
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2730
			$this->cell_margin['B'] = $bottom;
2731
		}
2732
	}
2733
 
2734
	/**
2735
	 * Get the internal Cell margin array.
2736
	 * @return array of margin values
2737
	 * @public
2738
	 * @since 5.9.000 (2010-10-03)
2739
	 * @see setCellMargins()
2740
	 */
2741
	public function getCellMargins() {
2742
		return $this->cell_margin;
2743
	}
2744
 
2745
	/**
2746
	 * Adjust the internal Cell padding array to take account of the line width.
2747
	 * @param string|array|int|bool $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2748
	 * @return void|array array of adjustments
2749
	 * @public
2750
	 * @since 5.9.000 (2010-10-03)
2751
	 */
2752
	protected function adjustCellPadding($brd=0) {
2753
		if (empty($brd)) {
2754
			return;
2755
		}
2756
		if (is_string($brd)) {
2757
			// convert string to array
2758
			$slen = strlen($brd);
2759
			$newbrd = array();
2760
			for ($i = 0; $i < $slen; ++$i) {
2761
				$newbrd[$brd[$i]] = true;
2762
			}
2763
			$brd = $newbrd;
2764
		} elseif (
2765
			($brd === 1)
2766
			|| ($brd === true)
2767
			|| (is_numeric($brd) && ((int)$brd > 0))
2768
		) {
2769
			$brd = array('LRTB' => true);
2770
		}
2771
		if (!is_array($brd)) {
2772
			return;
2773
		}
2774
		// store current cell padding
2775
		$cp = $this->cell_padding;
2776
		// select border mode
2777
		if (isset($brd['mode'])) {
2778
			$mode = $brd['mode'];
2779
			unset($brd['mode']);
2780
		} else {
2781
			$mode = 'normal';
2782
		}
2783
		// process borders
2784
		foreach ($brd as $border => $style) {
2785
			$line_width = $this->LineWidth;
2786
			if (is_array($style) && isset($style['width'])) {
2787
				// get border width
2788
				$line_width = $style['width'];
2789
			}
2790
			$adj = 0; // line width inside the cell
2791
			switch ($mode) {
2792
				case 'ext': {
2793
					$adj = 0;
2794
					break;
2795
				}
2796
				case 'int': {
2797
					$adj = $line_width;
2798
					break;
2799
				}
2800
				case 'normal':
2801
				default: {
2802
					$adj = ($line_width / 2);
2803
					break;
2804
				}
2805
			}
2806
			// correct internal cell padding if required to avoid overlap between text and lines
2807
			if (
2808
				is_numeric($this->cell_padding['T'])
2809
				&& ($this->cell_padding['T'] < $adj)
2810
				&& (strpos($border, 'T') !== false)
2811
			) {
2812
				$this->cell_padding['T'] = $adj;
2813
			}
2814
			if (
2815
				is_numeric($this->cell_padding['R'])
2816
				&& ($this->cell_padding['R'] < $adj)
2817
				&& (strpos($border, 'R') !== false)
2818
			) {
2819
				$this->cell_padding['R'] = $adj;
2820
			}
2821
			if (
2822
				is_numeric($this->cell_padding['B'])
2823
				&& ($this->cell_padding['B'] < $adj)
2824
				&& (strpos($border, 'B') !== false)
2825
			) {
2826
				$this->cell_padding['B'] = $adj;
2827
			}
2828
			if (
2829
				is_numeric($this->cell_padding['L'])
2830
				&& ($this->cell_padding['L'] < $adj)
2831
				&& (strpos($border, 'L') !== false)
2832
			) {
2833
				$this->cell_padding['L'] = $adj;
2834
			}
2835
 
2836
		}
2837
 
2838
		return array(
2839
			'T' => ($this->cell_padding['T'] - $cp['T']),
2840
			'R' => ($this->cell_padding['R'] - $cp['R']),
2841
			'B' => ($this->cell_padding['B'] - $cp['B']),
2842
			'L' => ($this->cell_padding['L'] - $cp['L']),
2843
		);
2844
	}
2845
 
2846
	/**
2847
	 * 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.
2848
	 * @param boolean $auto Boolean indicating if mode should be on or off.
2849
	 * @param float $margin Distance from the bottom of the page.
2850
	 * @public
2851
	 * @since 1.0
2852
	 * @see Cell(), MultiCell(), AcceptPageBreak()
2853
	 */
2854
	public function setAutoPageBreak($auto, $margin=0) {
2855
		$this->AutoPageBreak = $auto ? true : false;
2856
		$this->bMargin = $margin;
2857
		$this->PageBreakTrigger = $this->h - $margin;
2858
	}
2859
 
2860
	/**
2861
	 * Return the auto-page-break mode (true or false).
2862
	 * @return bool auto-page-break mode
2863
	 * @public
2864
	 * @since 5.9.088
2865
	 */
2866
	public function getAutoPageBreak() {
2867
		return $this->AutoPageBreak;
2868
	}
2869
 
2870
	/**
2871
	 * Defines the way the document is to be displayed by the viewer.
2872
	 * @param mixed $zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2873
	 * @param string $layout The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2874
	 * @param string $mode A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2875
	 * @public
2876
	 * @since 1.2
2877
	 */
2878
	public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2879
		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2880
			$this->ZoomMode = $zoom;
2881
		} else {
2882
			$this->Error('Incorrect zoom display mode: '.$zoom);
2883
		}
2884
		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2885
		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2886
	}
2887
 
2888
	/**
2889
	 * 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.
2890
	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2891
	 * @param boolean $compress Boolean indicating if compression must be enabled.
2892
	 * @public
2893
	 * @since 1.4
2894
	 */
2895
	public function setCompression($compress=true) {
2896
		$this->compress = false;
2897
		if (function_exists('gzcompress')) {
2898
			if ($compress) {
2899
				if ( !$this->pdfa_mode) {
2900
					$this->compress = true;
2901
				}
2902
			}
2903
		}
2904
	}
2905
 
2906
	/**
2907
	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2908
	 * @param boolean $mode If true force sRGB output intent.
2909
	 * @public
2910
	 * @since 5.9.121 (2011-09-28)
2911
	 */
2912
	public function setSRGBmode($mode=false) {
2913
		$this->force_srgb = $mode ? true : false;
2914
	}
2915
 
2916
	/**
2917
	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2918
	 * This has effect only when unicode mode is set to false.
2919
	 * @param boolean $unicode if true set the meta information in Unicode
2920
	 * @since 5.9.027 (2010-12-01)
2921
	 * @public
2922
	 */
2923
	public function setDocInfoUnicode($unicode=true) {
2924
		$this->docinfounicode = $unicode ? true : false;
2925
	}
2926
 
2927
	/**
2928
	 * Defines the title of the document.
2929
	 * @param string $title The title.
2930
	 * @public
2931
	 * @since 1.2
2932
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2933
	 */
2934
	public function setTitle($title) {
2935
		$this->title = $title;
2936
	}
2937
 
2938
	/**
2939
	 * Defines the subject of the document.
2940
	 * @param string $subject The subject.
2941
	 * @public
2942
	 * @since 1.2
2943
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2944
	 */
2945
	public function setSubject($subject) {
2946
		$this->subject = $subject;
2947
	}
2948
 
2949
	/**
2950
	 * Defines the author of the document.
2951
	 * @param string $author The name of the author.
2952
	 * @public
2953
	 * @since 1.2
2954
	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2955
	 */
2956
	public function setAuthor($author) {
2957
		$this->author = $author;
2958
	}
2959
 
2960
	/**
2961
	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2962
	 * @param string $keywords The list of keywords.
2963
	 * @public
2964
	 * @since 1.2
2965
	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2966
	 */
2967
	public function setKeywords($keywords) {
2968
		$this->keywords = $keywords;
2969
	}
2970
 
2971
	/**
2972
	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2973
	 * @param string $creator The name of the creator.
2974
	 * @public
2975
	 * @since 1.2
2976
	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2977
	 */
2978
	public function setCreator($creator) {
2979
		$this->creator = $creator;
2980
	}
2981
 
2982
	/**
2983
	 * Whether to allow local file path in image html tags, when prefixed with file://
2984
	 *
2985
	 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
2986
	 * @public
2987
	 * @since 6.4
2988
	 */
2989
	public function setAllowLocalFiles($allowLocalFiles) {
2990
		$this->allowLocalFiles = (bool) $allowLocalFiles;
2991
	}
2992
 
2993
 
2994
	/**
2995
	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2996
	 * @param string $msg The error message
2997
	 * @public
2998
	 * @since 1.0
2999
	 */
3000
	public function Error($msg) {
3001
		// unset all class variables
3002
		$this->_destroy(true);
3003
		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
3004
			die('<strong>TCPDF ERROR: </strong>'.$msg);
3005
		} else {
3006
			throw new Exception('TCPDF ERROR: '.$msg);
3007
		}
3008
	}
3009
 
3010
	/**
3011
	 * This method begins the generation of the PDF document.
3012
	 * It is not necessary to call it explicitly because AddPage() does it automatically.
3013
	 * Note: no page is created by this method
3014
	 * @public
3015
	 * @since 1.0
3016
	 * @see AddPage(), Close()
3017
	 */
3018
	public function Open() {
3019
		$this->state = 1;
3020
	}
3021
 
3022
	/**
3023
	 * Terminates the PDF document.
3024
	 * It is not necessary to call this method explicitly because Output() does it automatically.
3025
	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3026
	 * @public
3027
	 * @since 1.0
3028
	 * @see Open(), Output()
3029
	 */
3030
	public function Close() {
3031
		if ($this->state == 3) {
3032
			return;
3033
		}
3034
		if ($this->page == 0) {
3035
			$this->AddPage();
3036
		}
3037
		$this->endLayer();
3038
		if ($this->tcpdflink) {
3039
			// save current graphic settings
3040
			$gvars = $this->getGraphicVars();
3041
			$this->setEqualColumns();
3042
			$this->lastpage(true);
3043
			$this->setAutoPageBreak(false);
3044
			$this->x = 0;
3045
			$this->y = $this->h - (1 / $this->k);
3046
			$this->lMargin = 0;
3047
			$this->_outSaveGraphicsState();
3048
			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3049
			$this->setFont($font, '', 1);
3050
			$this->setTextRenderingMode(0, false, false);
3051
			$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";
3052
			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3053
			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3054
			$this->_outRestoreGraphicsState();
3055
			// restore graphic settings
3056
			$this->setGraphicVars($gvars);
3057
		}
3058
		// close page
3059
		$this->endPage();
3060
		// close document
3061
		$this->_enddoc();
3062
		// unset all class variables (except critical ones)
3063
		$this->_destroy(false);
3064
	}
3065
 
3066
	/**
3067
	 * Move pointer at the specified document page and update page dimensions.
3068
	 * @param int $pnum page number (1 ... numpages)
3069
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3070
	 * @public
3071
	 * @since 2.1.000 (2008-01-07)
3072
	 * @see getPage(), lastpage(), getNumPages()
3073
	 */
3074
	public function setPage($pnum, $resetmargins=false) {
3075
		if (($pnum == $this->page) AND ($this->state == 2)) {
3076
			return;
3077
		}
3078
		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3079
			$this->state = 2;
3080
			// save current graphic settings
3081
			//$gvars = $this->getGraphicVars();
3082
			$oldpage = $this->page;
3083
			$this->page = $pnum;
3084
			$this->wPt = $this->pagedim[$this->page]['w'];
3085
			$this->hPt = $this->pagedim[$this->page]['h'];
3086
			$this->w = $this->pagedim[$this->page]['wk'];
3087
			$this->h = $this->pagedim[$this->page]['hk'];
3088
			$this->tMargin = $this->pagedim[$this->page]['tm'];
3089
			$this->bMargin = $this->pagedim[$this->page]['bm'];
3090
			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3091
			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3092
			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3093
			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3094
			$this->setAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3095
			// restore graphic settings
3096
			//$this->setGraphicVars($gvars);
3097
			if ($resetmargins) {
3098
				$this->lMargin = $this->pagedim[$this->page]['olm'];
3099
				$this->rMargin = $this->pagedim[$this->page]['orm'];
3100
				$this->setY($this->tMargin);
3101
			} else {
3102
				// account for booklet mode
3103
				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3104
					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3105
					$this->lMargin += $deltam;
3106
					$this->rMargin -= $deltam;
3107
				}
3108
			}
3109
		} else {
3110
			$this->Error('Wrong page number on setPage() function: '.$pnum);
3111
		}
3112
	}
3113
 
3114
	/**
3115
	 * Reset pointer to the last document page.
3116
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3117
	 * @public
3118
	 * @since 2.0.000 (2008-01-04)
3119
	 * @see setPage(), getPage(), getNumPages()
3120
	 */
3121
	public function lastPage($resetmargins=false) {
3122
		$this->setPage($this->getNumPages(), $resetmargins);
3123
	}
3124
 
3125
	/**
3126
	 * Get current document page number.
3127
	 * @return int page number
3128
	 * @public
3129
	 * @since 2.1.000 (2008-01-07)
3130
	 * @see setPage(), lastpage(), getNumPages()
3131
	 */
3132
	public function getPage() {
3133
		return $this->page;
3134
	}
3135
 
3136
	/**
3137
	 * Get the total number of insered pages.
3138
	 * @return int number of pages
3139
	 * @public
3140
	 * @since 2.1.000 (2008-01-07)
3141
	 * @see setPage(), getPage(), lastpage()
3142
	 */
3143
	public function getNumPages() {
3144
		return $this->numpages;
3145
	}
3146
 
3147
	/**
3148
	 * Adds a new TOC (Table Of Content) page to the document.
3149
	 * @param string $orientation page orientation.
3150
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3151
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3152
	 * @public
3153
	 * @since 5.0.001 (2010-05-06)
3154
	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3155
	 */
3156
	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3157
		$this->AddPage($orientation, $format, $keepmargins, true);
3158
	}
3159
 
3160
	/**
3161
	 * Terminate the current TOC (Table Of Content) page
3162
	 * @public
3163
	 * @since 5.0.001 (2010-05-06)
3164
	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3165
	 */
3166
	public function endTOCPage() {
3167
		$this->endPage(true);
3168
	}
3169
 
3170
	/**
3171
	 * 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).
3172
	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3173
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3174
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3175
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3176
	 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3177
	 * @public
3178
	 * @since 1.0
3179
	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3180
	 */
3181
	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3182
		if ($this->inxobj) {
3183
			// we are inside an XObject template
3184
			return;
3185
		}
3186
		if (!isset($this->original_lMargin) OR $keepmargins) {
3187
			$this->original_lMargin = $this->lMargin;
3188
		}
3189
		if (!isset($this->original_rMargin) OR $keepmargins) {
3190
			$this->original_rMargin = $this->rMargin;
3191
		}
3192
		// terminate previous page
3193
		$this->endPage();
3194
		// start new page
3195
		$this->startPage($orientation, $format, $tocpage);
3196
	}
3197
 
3198
	/**
3199
	 * Terminate the current page
3200
	 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3201
	 * @public
3202
	 * @since 4.2.010 (2008-11-14)
3203
	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3204
	 */
3205
	public function endPage($tocpage=false) {
3206
		// check if page is already closed
3207
		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3208
			return;
3209
		}
3210
		// print page footer
3211
		$this->setFooter();
3212
		// close page
3213
		$this->_endpage();
3214
		// mark page as closed
3215
		$this->pageopen[$this->page] = false;
3216
		if ($tocpage) {
3217
			$this->tocpage = false;
3218
		}
3219
	}
3220
 
3221
	/**
3222
	 * Starts a new page to the document. The page must be closed using the endPage() function.
3223
	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3224
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3225
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3226
	 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3227
	 * @since 4.2.010 (2008-11-14)
3228
	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3229
	 * @public
3230
	 */
3231
	public function startPage($orientation='', $format='', $tocpage=false) {
3232
		if ($tocpage) {
3233
			$this->tocpage = true;
3234
		}
3235
		// move page numbers of documents to be attached
3236
		if ($this->tocpage) {
3237
			// move reference to unexistent pages (used for page attachments)
3238
			// adjust outlines
3239
			$tmpoutlines = $this->outlines;
3240
			foreach ($tmpoutlines as $key => $outline) {
3241
				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3242
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3243
				}
3244
			}
3245
			// adjust dests
3246
			$tmpdests = $this->dests;
3247
			foreach ($tmpdests as $key => $dest) {
3248
				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3249
					$this->dests[$key]['p'] = ($dest['p'] + 1);
3250
				}
3251
			}
3252
			// adjust links
3253
			$tmplinks = $this->links;
3254
			foreach ($tmplinks as $key => $link) {
3255
				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3256
					$this->links[$key]['p'] = ($link['p'] + 1);
3257
				}
3258
			}
3259
		}
3260
		if ($this->numpages > $this->page) {
3261
			// this page has been already added
3262
			$this->setPage($this->page + 1);
3263
			$this->setY($this->tMargin);
3264
			return;
3265
		}
3266
		// start a new page
3267
		if ($this->state == 0) {
3268
			$this->Open();
3269
		}
3270
		++$this->numpages;
3271
		$this->swapMargins($this->booklet);
3272
		// save current graphic settings
3273
		$gvars = $this->getGraphicVars();
3274
		// start new page
3275
		$this->_beginpage($orientation, $format);
3276
		// mark page as open
3277
		$this->pageopen[$this->page] = true;
3278
		// restore graphic settings
3279
		$this->setGraphicVars($gvars);
3280
		// mark this point
3281
		$this->setPageMark();
3282
		// print page header
3283
		$this->setHeader();
3284
		// restore graphic settings
3285
		$this->setGraphicVars($gvars);
3286
		// mark this point
3287
		$this->setPageMark();
3288
		// print table header (if any)
3289
		$this->setTableHeader();
3290
		// set mark for empty page check
3291
		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3292
	}
3293
 
3294
	/**
3295
	 * Set start-writing mark on current page stream used to put borders and fills.
3296
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3297
	 * This function must be called after calling Image() function for a background image.
3298
	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3299
	 * @public
3300
	 * @since 4.0.016 (2008-07-30)
3301
	 */
3302
	public function setPageMark() {
3303
		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3304
		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3305
		$this->setContentMark();
3306
	}
3307
 
3308
	/**
3309
	 * Set start-writing mark on selected page.
3310
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3311
	 * @param int $page page number (default is the current page)
3312
	 * @protected
3313
	 * @since 4.6.021 (2009-07-20)
3314
	 */
3315
	protected function setContentMark($page=0) {
3316
		if ($page <= 0) {
3317
			$page = $this->page;
3318
		}
3319
		if (isset($this->footerlen[$page])) {
3320
			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3321
		} else {
3322
			$this->cntmrk[$page] = $this->pagelen[$page];
3323
		}
3324
	}
3325
 
3326
	/**
3327
	 * Set header data.
3328
	 * @param string $ln header image logo
3329
	 * @param int $lw header image logo width in mm
3330
	 * @param string $ht string to print as title on document header
3331
	 * @param string $hs string to print on document header
3332
	 * @param int[] $tc RGB array color for text.
3333
	 * @param int[] $lc RGB array color for line.
3334
	 * @public
3335
	 */
3336
	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3337
		$this->header_logo = $ln;
3338
		$this->header_logo_width = $lw;
3339
		$this->header_title = $ht;
3340
		$this->header_string = $hs;
3341
		$this->header_text_color = $tc;
3342
		$this->header_line_color = $lc;
3343
	}
3344
 
3345
	/**
3346
	 * Set footer data.
3347
	 * @param int[] $tc RGB array color for text.
3348
	 * @param int[] $lc RGB array color for line.
3349
	 * @public
3350
	 */
3351
	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3352
		$this->footer_text_color = $tc;
3353
		$this->footer_line_color = $lc;
3354
	}
3355
 
3356
	/**
3357
	 * Returns header data:
3358
	 * <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>
3359
	 * @return array<string,mixed>
3360
	 * @public
3361
	 * @since 4.0.012 (2008-07-24)
3362
	 */
3363
	public function getHeaderData() {
3364
		$ret = array();
3365
		$ret['logo'] = $this->header_logo;
3366
		$ret['logo_width'] = $this->header_logo_width;
3367
		$ret['title'] = $this->header_title;
3368
		$ret['string'] = $this->header_string;
3369
		$ret['text_color'] = $this->header_text_color;
3370
		$ret['line_color'] = $this->header_line_color;
3371
		return $ret;
3372
	}
3373
 
3374
	/**
3375
	 * Set header margin.
3376
	 * (minimum distance between header and top page margin)
3377
	 * @param float $hm distance in user units
3378
	 * @public
3379
	 */
3380
	public function setHeaderMargin($hm=10) {
3381
		$this->header_margin = $hm;
3382
	}
3383
 
3384
	/**
3385
	 * Returns header margin in user units.
3386
	 * @return float
3387
	 * @since 4.0.012 (2008-07-24)
3388
	 * @public
3389
	 */
3390
	public function getHeaderMargin() {
3391
		return $this->header_margin;
3392
	}
3393
 
3394
	/**
3395
	 * Set footer margin.
3396
	 * (minimum distance between footer and bottom page margin)
3397
	 * @param float $fm distance in user units
3398
	 * @public
3399
	 */
3400
	public function setFooterMargin($fm=10) {
3401
		$this->footer_margin = $fm;
3402
	}
3403
 
3404
	/**
3405
	 * Returns footer margin in user units.
3406
	 * @return float
3407
	 * @since 4.0.012 (2008-07-24)
3408
	 * @public
3409
	 */
3410
	public function getFooterMargin() {
3411
		return $this->footer_margin;
3412
	}
3413
	/**
3414
	 * Set a flag to print page header.
3415
	 * @param boolean $val set to true to print the page header (default), false otherwise.
3416
	 * @public
3417
	 */
3418
	public function setPrintHeader($val=true) {
3419
		$this->print_header = $val ? true : false;
3420
	}
3421
 
3422
	/**
3423
	 * Set a flag to print page footer.
3424
	 * @param boolean $val set to true to print the page footer (default), false otherwise.
3425
	 * @public
3426
	 */
3427
	public function setPrintFooter($val=true) {
3428
		$this->print_footer = $val ? true : false;
3429
	}
3430
 
3431
	/**
3432
	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3433
	 * @return float
3434
	 * @public
3435
	 */
3436
	public function getImageRBX() {
3437
		return $this->img_rb_x;
3438
	}
3439
 
3440
	/**
3441
	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3442
	 * @return float
3443
	 * @public
3444
	 */
3445
	public function getImageRBY() {
3446
		return $this->img_rb_y;
3447
	}
3448
 
3449
	/**
3450
	 * Reset the xobject template used by Header() method.
3451
	 * @public
3452
	 */
3453
	public function resetHeaderTemplate() {
3454
		$this->header_xobjid = false;
3455
	}
3456
 
3457
	/**
3458
	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3459
	 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3460
	 * @public
3461
	 */
3462
	public function setHeaderTemplateAutoreset($val=true) {
3463
		$this->header_xobj_autoreset = $val ? true : false;
3464
	}
3465
 
3466
	/**
3467
	 * This method is used to render the page header.
3468
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3469
	 * @public
3470
	 */
3471
	public function Header() {
3472
		if ($this->header_xobjid === false) {
3473
			// start a new XObject Template
3474
			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3475
			$headerfont = $this->getHeaderFont();
3476
			$headerdata = $this->getHeaderData();
3477
			$this->y = $this->header_margin;
3478
			if ($this->rtl) {
3479
				$this->x = $this->w - $this->original_rMargin;
3480
			} else {
3481
				$this->x = $this->original_lMargin;
3482
			}
3483
			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3484
				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3485
				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3486
					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3487
				} elseif ($imgtype == 'svg') {
3488
					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3489
				} else {
3490
					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3491
				}
3492
				$imgy = $this->getImageRBY();
3493
			} else {
3494
				$imgy = $this->y;
3495
			}
3496
			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3497
			// set starting margin for text data cell
3498
			if ($this->getRTL()) {
3499
				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3500
			} else {
3501
				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3502
			}
3503
			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3504
			$this->setTextColorArray($this->header_text_color);
3505
			// header title
3506
			$this->setFont($headerfont[0], 'B', $headerfont[2] + 1);
3507
			$this->setX($header_x);
3508
			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3509
			// header string
3510
			$this->setFont($headerfont[0], $headerfont[1], $headerfont[2]);
3511
			$this->setX($header_x);
3512
			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3513
			// print an ending header line
3514
			$this->setLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3515
			$this->setY((2.835 / $this->k) + max($imgy, $this->y));
3516
			if ($this->rtl) {
3517
				$this->setX($this->original_rMargin);
3518
			} else {
3519
				$this->setX($this->original_lMargin);
3520
			}
3521
			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3522
			$this->endTemplate();
3523
		}
3524
		// print header template
3525
		$x = 0;
3526
		$dx = 0;
3527
		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3528
			// adjust margins for booklet mode
3529
			$dx = ($this->original_lMargin - $this->original_rMargin);
3530
		}
3531
		if ($this->rtl) {
3532
			$x = $this->w + $dx;
3533
		} else {
3534
			$x = 0 + $dx;
3535
		}
3536
		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3537
		if ($this->header_xobj_autoreset) {
3538
			// reset header xobject template at each page
3539
			$this->header_xobjid = false;
3540
		}
3541
	}
3542
 
3543
	/**
3544
	 * This method is used to render the page footer.
3545
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3546
	 * @public
3547
	 */
3548
	public function Footer() {
3549
		$cur_y = $this->y;
3550
		$this->setTextColorArray($this->footer_text_color);
3551
		//set style for cell border
3552
		$line_width = (0.85 / $this->k);
3553
		$this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3554
		//print document barcode
3555
		$barcode = $this->getBarcode();
3556
		if (!empty($barcode)) {
3557
			$this->Ln($line_width);
3558
			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3559
			$style = array(
3560
				'position' => $this->rtl?'R':'L',
3561
				'align' => $this->rtl?'R':'L',
3562
				'stretch' => false,
3563
				'fitwidth' => true,
3564
				'cellfitalign' => '',
3565
				'border' => false,
3566
				'padding' => 0,
3567
				'fgcolor' => array(0,0,0),
3568
				'bgcolor' => false,
3569
				'text' => false
3570
			);
3571
			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3572
		}
3573
		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3574
		if (empty($this->pagegroups)) {
3575
			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3576
		} else {
3577
			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3578
		}
3579
		$this->setY($cur_y);
3580
		//Print page number
3581
		if ($this->getRTL()) {
3582
			$this->setX($this->original_rMargin);
3583
			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3584
		} else {
3585
			$this->setX($this->original_lMargin);
3586
			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3587
		}
3588
	}
3589
 
3590
	/**
3591
	 * This method is used to render the page header.
3592
	 * @protected
3593
	 * @since 4.0.012 (2008-07-24)
3594
	 */
3595
	protected function setHeader() {
3596
		if (!$this->print_header OR ($this->state != 2)) {
3597
			return;
3598
		}
3599
		$this->InHeader = true;
3600
		$this->setGraphicVars($this->default_graphic_vars);
3601
		$temp_thead = $this->thead;
3602
		$temp_theadMargins = $this->theadMargins;
3603
		$lasth = $this->lasth;
3604
		$newline = $this->newline;
3605
		$this->_outSaveGraphicsState();
3606
		$this->rMargin = $this->original_rMargin;
3607
		$this->lMargin = $this->original_lMargin;
3608
		$this->setCellPadding(0);
3609
		//set current position
3610
		if ($this->rtl) {
3611
			$this->setXY($this->original_rMargin, $this->header_margin);
3612
		} else {
3613
			$this->setXY($this->original_lMargin, $this->header_margin);
3614
		}
3615
		$this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3616
		$this->Header();
3617
		//restore position
3618
		if ($this->rtl) {
3619
			$this->setXY($this->original_rMargin, $this->tMargin);
3620
		} else {
3621
			$this->setXY($this->original_lMargin, $this->tMargin);
3622
		}
3623
		$this->_outRestoreGraphicsState();
3624
		$this->lasth = $lasth;
3625
		$this->thead = $temp_thead;
3626
		$this->theadMargins = $temp_theadMargins;
3627
		$this->newline = $newline;
3628
		$this->InHeader = false;
3629
	}
3630
 
3631
	/**
3632
	 * This method is used to render the page footer.
3633
	 * @protected
3634
	 * @since 4.0.012 (2008-07-24)
3635
	 */
3636
	protected function setFooter() {
3637
		if ($this->state != 2) {
3638
			return;
3639
		}
3640
		$this->InFooter = true;
3641
		// save current graphic settings
3642
		$gvars = $this->getGraphicVars();
3643
		// mark this point
3644
		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3645
		$this->_out("\n");
3646
		if ($this->print_footer) {
3647
			$this->setGraphicVars($this->default_graphic_vars);
3648
			$this->current_column = 0;
3649
			$this->num_columns = 1;
3650
			$temp_thead = $this->thead;
3651
			$temp_theadMargins = $this->theadMargins;
3652
			$lasth = $this->lasth;
3653
			$this->_outSaveGraphicsState();
3654
			$this->rMargin = $this->original_rMargin;
3655
			$this->lMargin = $this->original_lMargin;
3656
			$this->setCellPadding(0);
3657
			//set current position
3658
			$footer_y = $this->h - $this->footer_margin;
3659
			if ($this->rtl) {
3660
				$this->setXY($this->original_rMargin, $footer_y);
3661
			} else {
3662
				$this->setXY($this->original_lMargin, $footer_y);
3663
			}
3664
			$this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3665
			$this->Footer();
3666
			//restore position
3667
			if ($this->rtl) {
3668
				$this->setXY($this->original_rMargin, $this->tMargin);
3669
			} else {
3670
				$this->setXY($this->original_lMargin, $this->tMargin);
3671
			}
3672
			$this->_outRestoreGraphicsState();
3673
			$this->lasth = $lasth;
3674
			$this->thead = $temp_thead;
3675
			$this->theadMargins = $temp_theadMargins;
3676
		}
3677
		// restore graphic settings
3678
		$this->setGraphicVars($gvars);
3679
		$this->current_column = $gvars['current_column'];
3680
		$this->num_columns = $gvars['num_columns'];
3681
		// calculate footer length
3682
		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3683
		$this->InFooter = false;
3684
	}
3685
 
3686
	/**
3687
	 * Check if we are on the page body (excluding page header and footer).
3688
	 * @return bool true if we are not in page header nor in page footer, false otherwise.
3689
	 * @protected
3690
	 * @since 5.9.091 (2011-06-15)
3691
	 */
3692
	protected function inPageBody() {
3693
		return (($this->InHeader === false) AND ($this->InFooter === false));
3694
	}
3695
 
3696
	/**
3697
	 * This method is used to render the table header on new page (if any).
3698
	 * @protected
3699
	 * @since 4.5.030 (2009-03-25)
3700
	 */
3701
	protected function setTableHeader() {
3702
		if ($this->num_columns > 1) {
3703
			// multi column mode
3704
			return;
3705
		}
3706
		if (isset($this->theadMargins['top'])) {
3707
			// restore the original top-margin
3708
			$this->tMargin = $this->theadMargins['top'];
3709
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3710
			$this->y = $this->tMargin;
3711
		}
3712
		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3713
			// set margins
3714
			$prev_lMargin = $this->lMargin;
3715
			$prev_rMargin = $this->rMargin;
3716
			$prev_cell_padding = $this->cell_padding;
3717
			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3718
			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3719
			$this->cell_padding = $this->theadMargins['cell_padding'];
3720
			if ($this->rtl) {
3721
				$this->x = $this->w - $this->rMargin;
3722
			} else {
3723
				$this->x = $this->lMargin;
3724
			}
3725
			// account for special "cell" mode
3726
			if ($this->theadMargins['cell']) {
3727
				if ($this->rtl) {
3728
					$this->x -= $this->cell_padding['R'];
3729
				} else {
3730
					$this->x += $this->cell_padding['L'];
3731
				}
3732
			}
3733
			$gvars = $this->getGraphicVars();
3734
			if (!empty($this->theadMargins['gvars'])) {
3735
				// set the correct graphic style
3736
				$this->setGraphicVars($this->theadMargins['gvars']);
3737
				$this->rMargin = $gvars['rMargin'];
3738
				$this->lMargin = $gvars['lMargin'];
3739
			}
3740
			// print table header
3741
			$this->writeHTML($this->thead, false, false, false, false, '');
3742
			$this->setGraphicVars($gvars);
3743
			// set new top margin to skip the table headers
3744
			if (!isset($this->theadMargins['top'])) {
3745
				$this->theadMargins['top'] = $this->tMargin;
3746
			}
3747
			// store end of header position
3748
			if (!isset($this->columns[0]['th'])) {
3749
				$this->columns[0]['th'] = array();
3750
			}
3751
			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3752
			$this->tMargin = $this->y;
3753
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3754
			$this->lasth = 0;
3755
			$this->lMargin = $prev_lMargin;
3756
			$this->rMargin = $prev_rMargin;
3757
			$this->cell_padding = $prev_cell_padding;
3758
		}
3759
	}
3760
 
3761
	/**
3762
	 * Returns the current page number.
3763
	 * @return int page number
3764
	 * @public
3765
	 * @since 1.0
3766
	 * @see getAliasNbPages()
3767
	 */
3768
	public function PageNo() {
3769
		return $this->page;
3770
	}
3771
 
3772
	/**
3773
	 * Returns the array of spot colors.
3774
	 * @return array Spot colors array.
3775
	 * @public
3776
	 * @since 6.0.038 (2013-09-30)
3777
	 */
3778
	public function getAllSpotColors() {
3779
		return $this->spot_colors;
3780
	}
3781
 
3782
	/**
3783
	 * Defines a new spot color.
3784
	 * It can be expressed in RGB components or gray scale.
3785
	 * The method can be called before the first page is created and the value is retained from page to page.
3786
	 * @param string $name Full name of the spot color.
3787
	 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3788
	 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3789
	 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3790
	 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3791
	 * @public
3792
	 * @since 4.0.024 (2008-09-12)
3793
	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3794
	 */
3795
	public function AddSpotColor($name, $c, $m, $y, $k) {
3796
		if (!isset($this->spot_colors[$name])) {
3797
			$i = (1 + count($this->spot_colors));
3798
			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3799
		}
3800
	}
3801
 
3802
	/**
3803
	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3804
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3805
	 * @param string $name Name of the spot color.
3806
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3807
	 * @return string PDF color command.
3808
	 * @public
3809
	 * @since 5.9.125 (2011-10-03)
3810
	 */
3811
	public function setSpotColor($type, $name, $tint=100) {
3812
		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3813
		if ($spotcolor === false) {
3814
			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3815
		}
3816
		$tint = (max(0, min(100, $tint)) / 100);
3817
		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3818
		switch ($type) {
3819
			case 'draw': {
3820
				$pdfcolor .= sprintf('CS %F SCN', $tint);
3821
				$this->DrawColor = $pdfcolor;
3822
				$this->strokecolor = $spotcolor;
3823
				break;
3824
			}
3825
			case 'fill': {
3826
				$pdfcolor .= sprintf('cs %F scn', $tint);
3827
				$this->FillColor = $pdfcolor;
3828
				$this->bgcolor = $spotcolor;
3829
				break;
3830
			}
3831
			case 'text': {
3832
				$pdfcolor .= sprintf('cs %F scn', $tint);
3833
				$this->TextColor = $pdfcolor;
3834
				$this->fgcolor = $spotcolor;
3835
				break;
3836
			}
3837
		}
3838
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3839
		if ($this->state == 2) {
3840
			$this->_out($pdfcolor);
3841
		}
3842
		if ($this->inxobj) {
3843
			// we are inside an XObject template
3844
			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3845
		}
3846
		return $pdfcolor;
3847
	}
3848
 
3849
	/**
3850
	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3851
	 * @param string $name Name of the spot color.
3852
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3853
	 * @public
3854
	 * @since 4.0.024 (2008-09-12)
3855
	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3856
	 */
3857
	public function setDrawSpotColor($name, $tint=100) {
3858
		$this->setSpotColor('draw', $name, $tint);
3859
	}
3860
 
3861
	/**
3862
	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3863
	 * @param string $name Name of the spot color.
3864
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3865
	 * @public
3866
	 * @since 4.0.024 (2008-09-12)
3867
	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3868
	 */
3869
	public function setFillSpotColor($name, $tint=100) {
3870
		$this->setSpotColor('fill', $name, $tint);
3871
	}
3872
 
3873
	/**
3874
	 * Defines the spot color used for text.
3875
	 * @param string $name Name of the spot color.
3876
	 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3877
	 * @public
3878
	 * @since 4.0.024 (2008-09-12)
3879
	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3880
	 */
3881
	public function setTextSpotColor($name, $tint=100) {
3882
		$this->setSpotColor('text', $name, $tint);
3883
	}
3884
 
3885
	/**
3886
	 * Set the color array for the specified type ('draw', 'fill', 'text').
3887
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3888
	 * The method can be called before the first page is created and the value is retained from page to page.
3889
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3890
	 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3891
	 * @param boolean $ret If true do not send the PDF command.
3892
	 * @return string The PDF command or empty string.
3893
	 * @public
3894
	 * @since 3.1.000 (2008-06-11)
3895
	 */
3896
	public function setColorArray($type, $color, $ret=false) {
3897
		if (is_array($color)) {
3898
			$color = array_values($color);
3899
			// component: grey, RGB red or CMYK cyan
3900
			$c = isset($color[0]) ? $color[0] : -1;
3901
			// component: RGB green or CMYK magenta
3902
			$m = isset($color[1]) ? $color[1] : -1;
3903
			// component: RGB blue or CMYK yellow
3904
			$y = isset($color[2]) ? $color[2] : -1;
3905
			// component: CMYK black
3906
			$k = isset($color[3]) ? $color[3] : -1;
3907
			// color name
3908
			$name = isset($color[4]) ? $color[4] : '';
3909
			if ($c >= 0) {
3910
				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3911
			}
3912
		}
3913
		return '';
3914
	}
3915
 
3916
	/**
3917
	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3918
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3919
	 * The method can be called before the first page is created and the value is retained from page to page.
3920
	 * @param array $color Array of colors (1, 3 or 4 values).
3921
	 * @param boolean $ret If true do not send the PDF command.
3922
	 * @return string the PDF command
3923
	 * @public
3924
	 * @since 3.1.000 (2008-06-11)
3925
	 * @see SetDrawColor()
3926
	 */
3927
	public function setDrawColorArray($color, $ret=false) {
3928
		return $this->setColorArray('draw', $color, $ret);
3929
	}
3930
 
3931
	/**
3932
	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3933
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3934
	 * The method can be called before the first page is created and the value is retained from page to page.
3935
	 * @param array $color Array of colors (1, 3 or 4 values).
3936
	 * @param boolean $ret If true do not send the PDF command.
3937
	 * @public
3938
	 * @since 3.1.000 (2008-6-11)
3939
	 * @see SetFillColor()
3940
	 */
3941
	public function setFillColorArray($color, $ret=false) {
3942
		return $this->setColorArray('fill', $color, $ret);
3943
	}
3944
 
3945
	/**
3946
	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3947
	 * The method can be called before the first page is created and the value is retained from page to page.
3948
	 * @param array $color Array of colors (1, 3 or 4 values).
3949
	 * @param boolean $ret If true do not send the PDF command.
3950
	 * @public
3951
	 * @since 3.1.000 (2008-6-11)
3952
	 * @see SetFillColor()
3953
	 */
3954
	public function setTextColorArray($color, $ret=false) {
3955
		return $this->setColorArray('text', $color, $ret);
3956
	}
3957
 
3958
	/**
3959
	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3960
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3961
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3962
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3963
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3964
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3965
	 * @param boolean $ret If true do not send the command.
3966
	 * @param string $name spot color name (if any)
3967
	 * @return string The PDF command or empty string.
3968
	 * @public
3969
	 * @since 5.9.125 (2011-10-03)
3970
	 */
3971
	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3972
		// set default values
3973
		if (!is_numeric($col1)) {
3974
			$col1 = 0;
3975
		}
3976
		if (!is_numeric($col2)) {
3977
			$col2 = -1;
3978
		}
3979
		if (!is_numeric($col3)) {
3980
			$col3 = -1;
3981
		}
3982
		if (!is_numeric($col4)) {
3983
			$col4 = -1;
3984
		}
3985
		// set color by case
3986
		$suffix = '';
3987
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3988
			// Grey scale
3989
			$col1 = max(0, min(255, $col1));
3990
			$intcolor = array('G' => $col1);
3991
			$pdfcolor = sprintf('%F ', ($col1 / 255));
3992
			$suffix = 'g';
3993
		} elseif ($col4 == -1) {
3994
			// RGB
3995
			$col1 = max(0, min(255, $col1));
3996
			$col2 = max(0, min(255, $col2));
3997
			$col3 = max(0, min(255, $col3));
3998
			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3999
			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4000
			$suffix = 'rg';
4001
		} else {
4002
			$col1 = max(0, min(100, $col1));
4003
			$col2 = max(0, min(100, $col2));
4004
			$col3 = max(0, min(100, $col3));
4005
			$col4 = max(0, min(100, $col4));
4006
			if (empty($name)) {
4007
				// CMYK
4008
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4009
				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4010
				$suffix = 'k';
4011
			} else {
4012
				// SPOT COLOR
4013
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4014
				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4015
				$pdfcolor = $this->setSpotColor($type, $name, 100);
4016
			}
4017
		}
4018
		switch ($type) {
4019
			case 'draw': {
4020
				$pdfcolor .= strtoupper($suffix);
4021
				$this->DrawColor = $pdfcolor;
4022
				$this->strokecolor = $intcolor;
4023
				break;
4024
			}
4025
			case 'fill': {
4026
				$pdfcolor .= $suffix;
4027
				$this->FillColor = $pdfcolor;
4028
				$this->bgcolor = $intcolor;
4029
				break;
4030
			}
4031
			case 'text': {
4032
				$pdfcolor .= $suffix;
4033
				$this->TextColor = $pdfcolor;
4034
				$this->fgcolor = $intcolor;
4035
				break;
4036
			}
4037
		}
4038
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4039
		if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4040
			if (!$ret) {
4041
				$this->_out($pdfcolor);
4042
			}
4043
			return $pdfcolor;
4044
		}
4045
		return '';
4046
	}
4047
 
4048
	/**
4049
	 * 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.
4050
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4051
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4052
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4053
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4054
	 * @param boolean $ret If true do not send the command.
4055
	 * @param string $name spot color name (if any)
4056
	 * @return string the PDF command
4057
	 * @public
4058
	 * @since 1.3
4059
	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4060
	 */
4061
	public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4062
		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4063
	}
4064
 
4065
	/**
4066
	 * 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.
4067
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4068
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4069
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4070
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4071
	 * @param boolean $ret If true do not send the command.
4072
	 * @param string $name Spot color name (if any).
4073
	 * @return string The PDF command.
4074
	 * @public
4075
	 * @since 1.3
4076
	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4077
	 */
4078
	public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4079
		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4080
	}
4081
 
4082
	/**
4083
	 * 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.
4084
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4085
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4086
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4087
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4088
	 * @param boolean $ret If true do not send the command.
4089
	 * @param string $name Spot color name (if any).
4090
	 * @return string Empty string.
4091
	 * @public
4092
	 * @since 1.3
4093
	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4094
	 */
4095
	public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4096
		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4097
	}
4098
 
4099
	/**
4100
	 * Returns the length of a string in user unit. A font must be selected.<br>
4101
	 * @param string $s The string whose length is to be computed
4102
	 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4103
	 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4104
	 * @param float $fontsize Font size in points. The default value is the current size.
4105
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4106
	 * @return float[]|float total string length or array of characted widths
4107
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4108
	 * @author Nicola Asuni
4109
	 * @public
4110
	 * @since 1.2
4111
	 */
4112
	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4113
		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);
4114
	}
4115
 
4116
	/**
4117
	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4118
	 * @param array $sa The array of chars whose total length is to be computed
4119
	 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4120
	 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4121
	 * @param float $fontsize Font size in points. The default value is the current size.
4122
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4123
	 * @return float[]|float total string length or array of characted widths
4124
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4125
	 * @author Nicola Asuni
4126
	 * @public
4127
	 * @since 2.4.000 (2008-03-06)
4128
	 */
4129
	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4130
		// store current values
4131
		if (!TCPDF_STATIC::empty_string($fontname)) {
4132
			$prev_FontFamily = $this->FontFamily;
4133
			$prev_FontStyle = $this->FontStyle;
4134
			$prev_FontSizePt = $this->FontSizePt;
4135
			$this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4136
		}
4137
		// convert UTF-8 array to Latin1 if required
4138
		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4139
			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4140
		}
4141
		$w = 0; // total width
4142
		$wa = array(); // array of characters widths
4143
		foreach ($sa as $ck => $char) {
4144
			// character width
4145
			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4146
			$wa[] = $cw;
4147
			$w += $cw;
4148
		}
4149
		// restore previous values
4150
		if (!TCPDF_STATIC::empty_string($fontname)) {
4151
			$this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4152
		}
4153
		if ($getarray) {
4154
			return $wa;
4155
		}
4156
		return $w;
4157
	}
4158
 
4159
	/**
4160
	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4161
	 * @param int $char The char code whose length is to be returned
4162
	 * @param boolean $notlast If false ignore the font-spacing.
4163
	 * @return float char width
4164
	 * @author Nicola Asuni
4165
	 * @public
4166
	 * @since 2.4.000 (2008-03-06)
4167
	 */
4168
	public function GetCharWidth($char, $notlast=true) {
4169
		// get raw width
4170
		$chw = $this->getRawCharWidth($char);
4171
		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4172
			// increase/decrease font spacing
4173
			$chw += $this->font_spacing;
4174
		}
4175
		if ($this->font_stretching != 100) {
4176
			// fixed stretching mode
4177
			$chw *= ($this->font_stretching / 100);
4178
		}
4179
		return $chw;
4180
	}
4181
 
4182
	/**
4183
	 * Returns the length of the char in user unit for the current font.
4184
	 * @param int $char The char code whose length is to be returned
4185
	 * @return float char width
4186
	 * @author Nicola Asuni
4187
	 * @public
4188
	 * @since 5.9.000 (2010-09-28)
4189
	 */
4190
	public function getRawCharWidth($char) {
4191
		if ($char == 173) {
4192
			// SHY character will not be printed
4193
			return (0);
4194
		}
4195
		if (isset($this->CurrentFont['cw'][intval($char)])) {
4196
			$w = $this->CurrentFont['cw'][intval($char)];
4197
		} elseif (isset($this->CurrentFont['dw'])) {
4198
			// default width
4199
			$w = $this->CurrentFont['dw'];
4200
		} elseif (isset($this->CurrentFont['cw'][32])) {
4201
			// default width
4202
			$w = $this->CurrentFont['cw'][32];
4203
		} else {
4204
			$w = 600;
4205
		}
4206
		return $this->getAbsFontMeasure($w);
4207
	}
4208
 
4209
	/**
4210
	 * Returns the numbero of characters in a string.
4211
	 * @param string $s The input string.
4212
	 * @return int number of characters
4213
	 * @public
4214
	 * @since 2.0.0001 (2008-01-07)
4215
	 */
4216
	public function GetNumChars($s) {
4217
		if ($this->isUnicodeFont()) {
4218
			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4219
		}
4220
		return strlen($s);
4221
	}
4222
 
4223
	/**
4224
	 * Fill the list of available fonts ($this->fontlist).
4225
	 * @protected
4226
	 * @since 4.0.013 (2008-07-28)
4227
	 */
4228
	protected function getFontsList() {
4229
		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4230
			while (($file = readdir($fontsdir)) !== false) {
4231
				if (substr($file, -4) == '.php') {
4232
					array_push($this->fontlist, strtolower(basename($file, '.php')));
4233
				}
4234
			}
4235
			closedir($fontsdir);
4236
		}
4237
	}
4238
 
4239
	/**
4240
	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4241
	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4242
	 * 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.
4243
	 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4244
	 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4245
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4246
	 * @return array|false array containing the font data, or false in case of error.
4247
	 * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4248
	 * @public
4249
	 * @since 1.5
4250
	 * @see SetFont(), setFontSubsetting()
4251
	 */
4252
	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4253
		if ($subset === 'default') {
4254
			$subset = $this->font_subsetting;
4255
		}
4256
		if ($this->pdfa_mode) {
4257
			$subset = false;
4258
		}
4259
		if (TCPDF_STATIC::empty_string($family)) {
4260
			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4261
				$family = $this->FontFamily;
4262
			} else {
4263
				$this->Error('Empty font family');
4264
			}
4265
		}
4266
		// move embedded styles on $style
4267
		if (substr($family, -1) == 'I') {
4268
			$style .= 'I';
4269
			$family = substr($family, 0, -1);
4270
		}
4271
		if (substr($family, -1) == 'B') {
4272
			$style .= 'B';
4273
			$family = substr($family, 0, -1);
4274
		}
4275
		// normalize family name
4276
		$family = strtolower($family);
4277
		if ((!$this->isunicode) AND ($family == 'arial')) {
4278
			$family = 'helvetica';
4279
		}
4280
		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4281
			$style = '';
4282
		}
4283
		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4284
			// all fonts must be embedded
4285
			$family = 'pdfa'.$family;
4286
		}
4287
		$tempstyle = strtoupper($style === null ? '' : $style);
4288
		$style = '';
4289
		// underline
4290
		if (strpos($tempstyle, 'U') !== false) {
4291
			$this->underline = true;
4292
		} else {
4293
			$this->underline = false;
4294
		}
4295
		// line-through (deleted)
4296
		if (strpos($tempstyle, 'D') !== false) {
4297
			$this->linethrough = true;
4298
		} else {
4299
			$this->linethrough = false;
4300
		}
4301
		// overline
4302
		if (strpos($tempstyle, 'O') !== false) {
4303
			$this->overline = true;
4304
		} else {
4305
			$this->overline = false;
4306
		}
4307
		// bold
4308
		if (strpos($tempstyle, 'B') !== false) {
4309
			$style .= 'B';
4310
		}
4311
		// oblique
4312
		if (strpos($tempstyle, 'I') !== false) {
4313
			$style .= 'I';
4314
		}
4315
		$bistyle = $style;
4316
		$fontkey = $family.$style;
4317
		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4318
		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4319
		// check if the font has been already added
4320
		$fb = $this->getFontBuffer($fontkey);
4321
		if ($fb !== false) {
4322
			if ($this->inxobj) {
4323
				// we are inside an XObject template
4324
				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4325
			}
4326
			return $fontdata;
4327
		}
4328
		// get specified font directory (if any)
4329
		$fontdir = false;
4330
		if (!TCPDF_STATIC::empty_string($fontfile)) {
4331
			$fontdir = dirname($fontfile);
4332
			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4333
				$fontdir = '';
4334
			} else {
4335
				$fontdir .= '/';
4336
			}
4337
		}
4338
		// true when the font style variation is missing
4339
		$missing_style = false;
4340
		// search and include font file
4341
		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4342
			// build a standard filenames for specified font
4343
			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4344
			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4345
			if (TCPDF_STATIC::empty_string($fontfile)) {
4346
				$missing_style = true;
4347
				// try to remove the style part
4348
				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4349
				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4350
			}
4351
		}
4352
		// include font file
4353
		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4354
			$type=null;
4355
			$name=null;
4356
			$desc=null;
4357
			$up=-null;
4358
			$ut=null;
4359
			$cw=null;
4360
			$cbbox=null;
4361
			$dw=null;
4362
			$enc=null;
4363
			$cidinfo=null;
4364
			$file=null;
4365
			$ctg=null;
4366
			$diff=null;
4367
			$originalsize=null;
4368
			$size1=null;
4369
			$size2=null;
4370
			include($fontfile);
4371
		} else {
4372
			$this->Error('Could not include font definition file: '.$family.'');
4373
		}
4374
		// check font parameters
4375
		if ((!isset($type)) OR (!isset($cw))) {
4376
			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4377
		}
4378
		// SET default parameters
4379
		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4380
			$file = '';
4381
		}
4382
		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4383
			$enc = '';
4384
		}
4385
		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4386
			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4387
			$cidinfo['uni2cid'] = array();
4388
		}
4389
		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4390
			$ctg = '';
4391
		}
4392
		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4393
			$desc = array();
4394
		}
4395
		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4396
			$up = -100;
4397
		}
4398
		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4399
			$ut = 50;
4400
		}
4401
		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4402
			$cw = array();
4403
		}
4404
		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4405
			// set default width
4406
			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4407
				$dw = $desc['MissingWidth'];
4408
			} elseif (isset($cw[32])) {
4409
				$dw = $cw[32];
4410
			} else {
4411
				$dw = 600;
4412
			}
4413
		}
4414
		++$this->numfonts;
4415
		if ($type == 'core') {
4416
			$name = $this->CoreFonts[$fontkey];
4417
			$subset = false;
4418
		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4419
			$subset = false;
4420
		} elseif ($type == 'TrueTypeUnicode') {
4421
			$enc = 'Identity-H';
4422
		} elseif ($type == 'cidfont0') {
4423
			if ($this->pdfa_mode) {
4424
				$this->Error('All fonts must be embedded in PDF/A mode!');
4425
			}
4426
		} else {
4427
			$this->Error('Unknow font type: '.$type.'');
4428
		}
4429
		// set name if unset
4430
		if (!isset($name) OR empty($name)) {
4431
			$name = $fontkey;
4432
		}
4433
		// create artificial font style variations if missing (only works with non-embedded fonts)
4434
		if (($type != 'core') AND $missing_style) {
4435
			// style variations
4436
			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4437
			$name .= $styles[$bistyle];
4438
			// artificial bold
4439
			if (strpos($bistyle, 'B') !== false) {
4440
				if (isset($desc['StemV'])) {
4441
					// from normal to bold
4442
					$desc['StemV'] = round($desc['StemV'] * 1.75);
4443
				} else {
4444
					// bold
4445
					$desc['StemV'] = 123;
4446
				}
4447
			}
4448
			// artificial italic
4449
			if (strpos($bistyle, 'I') !== false) {
4450
				if (isset($desc['ItalicAngle'])) {
4451
					$desc['ItalicAngle'] -= 11;
4452
				} else {
4453
					$desc['ItalicAngle'] = -11;
4454
				}
4455
				if (isset($desc['Flags'])) {
4456
					$desc['Flags'] |= 64; //bit 7
4457
				} else {
4458
					$desc['Flags'] = 64;
4459
				}
4460
			}
4461
		}
4462
		// check if the array of characters bounding boxes is defined
4463
		if (!isset($cbbox)) {
4464
			$cbbox = array();
4465
		}
4466
		// initialize subsetchars
4467
		$subsetchars = array_fill(0, 255, true);
4468
		$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));
4469
		if ($this->inxobj) {
4470
			// we are inside an XObject template
4471
			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4472
		}
4473
		if (isset($diff) AND (!empty($diff))) {
4474
			//Search existing encodings
4475
			$d = 0;
4476
			$nb = count($this->diffs);
4477
			for ($i=1; $i <= $nb; ++$i) {
4478
				if ($this->diffs[$i] == $diff) {
4479
					$d = $i;
4480
					break;
4481
				}
4482
			}
4483
			if ($d == 0) {
4484
				$d = $nb + 1;
4485
				$this->diffs[$d] = $diff;
4486
			}
4487
			$this->setFontSubBuffer($fontkey, 'diff', $d);
4488
		}
4489
		if (!TCPDF_STATIC::empty_string($file)) {
4490
			if (!isset($this->FontFiles[$file])) {
4491
				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4492
					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4493
				} elseif ($type != 'core') {
4494
					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4495
				}
4496
			} else {
4497
				// update fontkeys that are sharing this font file
4498
				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4499
				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4500
					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4501
				}
4502
			}
4503
		}
4504
		return $fontdata;
4505
	}
4506
 
4507
	/**
4508
	 * Sets the font used to print character strings.
4509
	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4510
	 * The method can be called before the first page is created and the font is retained from page to page.
4511
	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4512
	 * 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 />
4513
	 * @param string $family Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4514
	 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4515
	 * @param float|null $size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4516
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4517
	 * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4518
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4519
	 * @author Nicola Asuni
4520
	 * @public
4521
	 * @since 1.0
4522
	 * @see AddFont(), SetFontSize()
4523
	 */
4524
	public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4525
		//Select a font; size given in points
4526
		if ($size === null) {
4527
			$size = $this->FontSizePt;
4528
		}
4529
		if ($size < 0) {
4530
			$size = 0;
4531
		}
4532
		// try to add font (if not already added)
4533
		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4534
		$this->FontFamily = $fontdata['family'];
4535
		$this->FontStyle = $fontdata['style'];
4536
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4537
			// save subset chars of the previous font
4538
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4539
		}
4540
		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4541
		$this->setFontSize($size, $out);
4542
	}
4543
 
4544
	/**
4545
	 * Defines the size of the current font.
4546
	 * @param float $size The font size in points.
4547
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4548
	 * @public
4549
	 * @since 1.0
4550
	 * @see SetFont()
4551
	 */
4552
	public function setFontSize($size, $out=true) {
4553
		$size = (float)$size;
4554
		// font size in points
4555
		$this->FontSizePt = $size;
4556
		// font size in user units
4557
		$this->FontSize = $size / $this->k;
4558
		// calculate some font metrics
4559
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4560
			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4561
			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4562
		} else {
4563
			$font_height = $size * 1.219;
4564
		}
4565
		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4566
			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4567
		}
4568
		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4569
			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4570
		}
4571
		if (!isset($font_ascent) AND !isset($font_descent)) {
4572
			// core font
4573
			$font_ascent = 0.76 * $font_height;
4574
			$font_descent = $font_height - $font_ascent;
4575
		} elseif (!isset($font_descent)) {
4576
			$font_descent = $font_height - $font_ascent;
4577
		} elseif (!isset($font_ascent)) {
4578
			$font_ascent = $font_height - $font_descent;
4579
		}
4580
		$this->FontAscent = ($font_ascent / $this->k);
4581
		$this->FontDescent = ($font_descent / $this->k);
4582
		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4583
			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4584
		}
4585
	}
4586
 
4587
	/**
4588
	 * Returns the bounding box of the current font in user units.
4589
	 * @return array
4590
	 * @public
4591
	 * @since 5.9.152 (2012-03-23)
4592
	 */
4593
	public function getFontBBox() {
4594
		$fbbox = array();
4595
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4596
			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4597
			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4598
		} else {
4599
			// Find max width
4600
			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4601
				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4602
			} else {
4603
				$maxw = 0;
4604
				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4605
					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4606
				}
4607
				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4608
					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4609
				}
4610
				if (isset($this->CurrentFont['dw'])) {
4611
					$maxw = max($maxw, $this->CurrentFont['dw']);
4612
				}
4613
				foreach ($this->CurrentFont['cw'] as $char => $w) {
4614
					$maxw = max($maxw, $w);
4615
				}
4616
				if ($maxw == 0) {
4617
					$maxw = 600;
4618
				}
4619
				$maxw = $this->getAbsFontMeasure($maxw);
4620
			}
4621
			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4622
		}
4623
		return $fbbox;
4624
	}
4625
 
4626
	/**
4627
	 * Convert a relative font measure into absolute value.
4628
	 * @param int $s Font measure.
4629
	 * @return float Absolute measure.
4630
	 * @since 5.9.186 (2012-09-13)
4631
	 */
4632
	public function getAbsFontMeasure($s) {
4633
		return ($s * $this->FontSize / 1000);
4634
	}
4635
 
4636
	/**
4637
	 * Returns the glyph bounding box of the specified character in the current font in user units.
4638
	 * @param int $char Input character code.
4639
	 * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4640
	 * @since 5.9.186 (2012-09-13)
4641
	 */
4642
	public function getCharBBox($char) {
4643
		$c = intval($char);
4644
		if (isset($this->CurrentFont['cw'][$c])) {
4645
			// glyph is defined ... use zero width & height for glyphs without outlines
4646
			$result = array(0,0,0,0);
4647
			if (isset($this->CurrentFont['cbbox'][$c])) {
4648
				$result = $this->CurrentFont['cbbox'][$c];
4649
			}
4650
			return array_map(array($this,'getAbsFontMeasure'), $result);
4651
		}
4652
		return false;
4653
	}
4654
 
4655
	/**
4656
	 * Return the font descent value
4657
	 * @param string $font font name
4658
	 * @param string $style font style
4659
	 * @param float $size The size (in points)
4660
	 * @return int font descent
4661
	 * @public
4662
	 * @author Nicola Asuni
4663
	 * @since 4.9.003 (2010-03-30)
4664
	 */
4665
	public function getFontDescent($font, $style='', $size=0) {
4666
		$fontdata = $this->AddFont($font, $style);
4667
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4668
		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4669
			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4670
		} else {
4671
			$descent = (1.219 * 0.24 * $size);
4672
		}
4673
		return ($descent / $this->k);
4674
	}
4675
 
4676
	/**
4677
	 * Return the font ascent value.
4678
	 * @param string $font font name
4679
	 * @param string $style font style
4680
	 * @param float $size The size (in points)
4681
	 * @return int font ascent
4682
	 * @public
4683
	 * @author Nicola Asuni
4684
	 * @since 4.9.003 (2010-03-30)
4685
	 */
4686
	public function getFontAscent($font, $style='', $size=0) {
4687
		$fontdata = $this->AddFont($font, $style);
4688
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4689
		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4690
			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4691
		} else {
4692
			$ascent = 1.219 * 0.76 * $size;
4693
		}
4694
		return ($ascent / $this->k);
4695
	}
4696
 
4697
	/**
4698
	 * Return true in the character is present in the specified font.
4699
	 * @param mixed $char Character to check (integer value or string)
4700
	 * @param string $font Font name (family name).
4701
	 * @param string $style Font style.
4702
	 * @return bool true if the char is defined, false otherwise.
4703
	 * @public
4704
	 * @since 5.9.153 (2012-03-28)
4705
	 */
4706
	public function isCharDefined($char, $font='', $style='') {
4707
		if (is_string($char)) {
4708
			// get character code
4709
			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4710
			$char = $char[0];
4711
		}
4712
		if (TCPDF_STATIC::empty_string($font)) {
4713
			if (TCPDF_STATIC::empty_string($style)) {
4714
				return (isset($this->CurrentFont['cw'][intval($char)]));
4715
			}
4716
			$font = $this->FontFamily;
4717
		}
4718
		$fontdata = $this->AddFont($font, $style);
4719
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4720
		return (isset($fontinfo['cw'][intval($char)]));
4721
	}
4722
 
4723
	/**
4724
	 * Replace missing font characters on selected font with specified substitutions.
4725
	 * @param string $text Text to process.
4726
	 * @param string $font Font name (family name).
4727
	 * @param string $style Font style.
4728
	 * @param array $subs Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4729
	 * @return string Processed text.
4730
	 * @public
4731
	 * @since 5.9.153 (2012-03-28)
4732
	 */
4733
	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4734
		if (empty($subs)) {
4735
			return $text;
4736
		}
4737
		if (TCPDF_STATIC::empty_string($font)) {
4738
			$font = $this->FontFamily;
4739
		}
4740
		$fontdata = $this->AddFont($font, $style);
4741
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4742
		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4743
		foreach ($uniarr as $k => $chr) {
4744
			if (!isset($fontinfo['cw'][$chr])) {
4745
				// this character is missing on the selected font
4746
				if (isset($subs[$chr])) {
4747
					// we have available substitutions
4748
					if (is_array($subs[$chr])) {
4749
						foreach($subs[$chr] as $s) {
4750
							if (isset($fontinfo['cw'][$s])) {
4751
								$uniarr[$k] = $s;
4752
								break;
4753
							}
4754
						}
4755
					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4756
						$uniarr[$k] = $subs[$chr];
4757
					}
4758
				}
4759
			}
4760
		}
4761
		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4762
	}
4763
 
4764
	/**
4765
	 * Defines the default monospaced font.
4766
	 * @param string $font Font name.
4767
	 * @public
4768
	 * @since 4.5.025
4769
	 */
4770
	public function setDefaultMonospacedFont($font) {
4771
		$this->default_monospaced_font = $font;
4772
	}
4773
 
4774
	/**
4775
	 * 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 />
4776
	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4777
	 * @public
4778
	 * @since 1.5
4779
	 * @see Cell(), Write(), Image(), Link(), SetLink()
4780
	 */
4781
	public function AddLink() {
4782
		// create a new internal link
4783
		$n = count($this->links) + 1;
4784
		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4785
		return $n;
4786
	}
4787
 
4788
	/**
4789
	 * Defines the page and position a link points to.
4790
	 * @param int $link The link identifier returned by AddLink()
4791
	 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4792
	 * @param int|string $page Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4793
	 * @public
4794
	 * @since 1.5
4795
	 * @see AddLink()
4796
	 */
4797
	public function setLink($link, $y=0, $page=-1) {
4798
		$fixed = false;
4799
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4800
			$page = intval(substr($page, 1));
4801
			// this page number will not be changed when moving/add/deleting pages
4802
			$fixed = true;
4803
		}
4804
		if ($page < 0) {
4805
			$page = $this->page;
4806
		}
4807
		if ($y == -1) {
4808
			$y = $this->y;
4809
		}
4810
		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4811
	}
4812
 
4813
	/**
4814
	 * Puts a link on a rectangular area of the page.
4815
	 * 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.
4816
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4817
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4818
	 * @param float $w Width of the rectangle
4819
	 * @param float $h Height of the rectangle
4820
	 * @param mixed $link URL or identifier returned by AddLink()
4821
	 * @param int $spaces number of spaces on the text to link
4822
	 * @public
4823
	 * @since 1.5
4824
	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4825
	 */
4826
	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4827
		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4828
	}
4829
 
4830
	/**
4831
	 * Puts a markup annotation on a rectangular area of the page.
4832
	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4833
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4834
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4835
	 * @param float $w Width of the rectangle
4836
	 * @param float $h Height of the rectangle
4837
	 * @param string $text annotation text or alternate content
4838
	 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4839
	 * @param int $spaces number of spaces on the text to link
4840
	 * @public
4841
	 * @since 4.0.018 (2008-08-06)
4842
	 */
4843
	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4844
		if ($this->inxobj) {
4845
			// store parameters for later use on template
4846
			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4847
			return;
4848
		}
4849
		if ($x === '') {
4850
			$x = $this->x;
4851
		}
4852
		if ($y === '') {
4853
			$y = $this->y;
4854
		}
4855
		// check page for no-write regions and adapt page margins if necessary
4856
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4857
		// recalculate coordinates to account for graphic transformations
4858
		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4859
			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4860
				$maxid = count($this->transfmatrix[$i]) - 1;
4861
				for ($j=$maxid; $j >= 0; --$j) {
4862
					$ctm = $this->transfmatrix[$i][$j];
4863
					if (isset($ctm['a'])) {
4864
						$x = $x * $this->k;
4865
						$y = ($this->h - $y) * $this->k;
4866
						$w = $w * $this->k;
4867
						$h = $h * $this->k;
4868
						// top left
4869
						$xt = $x;
4870
						$yt = $y;
4871
						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4872
						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4873
						// top right
4874
						$xt = $x + $w;
4875
						$yt = $y;
4876
						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4877
						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4878
						// bottom left
4879
						$xt = $x;
4880
						$yt = $y - $h;
4881
						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4882
						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4883
						// bottom right
4884
						$xt = $x + $w;
4885
						$yt = $y - $h;
4886
						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4887
						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4888
						// new coordinates (rectangle area)
4889
						$x = min($x1, $x2, $x3, $x4);
4890
						$y = max($y1, $y2, $y3, $y4);
4891
						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4892
						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4893
						$x = $x / $this->k;
4894
						$y = $this->h - ($y / $this->k);
4895
					}
4896
				}
4897
			}
4898
		}
4899
		if ($this->page <= 0) {
4900
			$page = 1;
4901
		} else {
4902
			$page = $this->page;
4903
		}
4904
		if (!isset($this->PageAnnots[$page])) {
4905
			$this->PageAnnots[$page] = array();
4906
		}
4907
		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4908
		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4909
			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4910
				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4911
				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4912
				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4913
			}
4914
		}
4915
		// Add widgets annotation's icons
4916
		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4917
			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4918
		}
4919
		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4920
			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4921
		}
4922
		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4923
			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4924
		}
4925
	}
4926
 
4927
	/**
4928
	 * Embedd the attached files.
4929
	 * @since 4.4.000 (2008-12-07)
4930
	 * @protected
4931
	 * @see Annotation()
4932
	 */
4933
	protected function _putEmbeddedFiles() {
4934
		if ($this->pdfa_mode && $this->pdfa_version != 3)  {
4935
			// embedded files are not allowed in PDF/A mode version 1 and 2
4936
			return;
4937
		}
4938
		reset($this->embeddedfiles);
4939
		foreach ($this->embeddedfiles as $filename => $filedata) {
4940
		    $data = $this->getCachedFileContents($filedata['file']);
4941
			if ($data !== FALSE) {
4942
				$rawsize = strlen($data);
4943
				if ($rawsize > 0) {
4944
					// update name tree
4945
					$this->efnames[$filename] = $filedata['f'].' 0 R';
4946
					// embedded file specification object
4947
					$out = $this->_getobj($filedata['f'])."\n";
4948
					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4949
					$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4950
					$out .= ' /AFRelationship /Source';
4951
					$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4952
					$out .= "\n".'endobj';
4953
					$this->_out($out);
4954
					// embedded file object
4955
					$filter = '';
4956
					if ($this->compress) {
4957
						$data = gzcompress($data);
4958
						$filter = ' /Filter /FlateDecode';
4959
					}
4960
 
4961
					if ($this->pdfa_version == 3) {
4962
						$filter = ' /Subtype /text#2Fxml';
4963
					}
4964
 
4965
					$stream = $this->_getrawstream($data, $filedata['n']);
4966
					$out = $this->_getobj($filedata['n'])."\n";
4967
					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4968
					$out .= ' stream'."\n".$stream."\n".'endstream';
4969
					$out .= "\n".'endobj';
4970
					$this->_out($out);
4971
				}
4972
			}
4973
		}
4974
	}
4975
 
4976
	/**
4977
	 * Prints a text cell at the specified position.
4978
	 * This method allows to place a string precisely on the page.
4979
	 * @param float $x Abscissa of the cell origin
4980
	 * @param float $y Ordinate of the cell origin
4981
	 * @param string $txt String to print
4982
	 * @param int $fstroke outline size in user units (0 = disable)
4983
	 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4984
	 * @param boolean $ffill if true fills the text
4985
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4986
	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4987
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4988
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
4989
	 * @param mixed $link URL or identifier returned by AddLink().
4990
	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4991
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
4992
	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4993
	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4994
	 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
4995
	 * @public
4996
	 * @since 1.0
4997
	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4998
	 */
4999
	public function Text($x, $y, $txt, $fstroke=0, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5000
		$textrendermode = $this->textrendermode;
5001
		$textstrokewidth = $this->textstrokewidth;
5002
		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
5003
		$this->setXY($x, $y, $rtloff);
5004
		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5005
		// restore previous rendering mode
5006
		$this->textrendermode = $textrendermode;
5007
		$this->textstrokewidth = $textstrokewidth;
5008
	}
5009
 
5010
	/**
5011
	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5012
	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5013
	 * This method is called automatically and should not be called directly by the application.
5014
	 * @return bool
5015
	 * @public
5016
	 * @since 1.4
5017
	 * @see SetAutoPageBreak()
5018
	 */
5019
	public function AcceptPageBreak() {
5020
		if ($this->num_columns > 1) {
5021
			// multi column mode
5022
			if ($this->current_column < ($this->num_columns - 1)) {
5023
				// go to next column
5024
				$this->selectColumn($this->current_column + 1);
5025
			} elseif ($this->AutoPageBreak) {
5026
				// add a new page
5027
				$this->AddPage();
5028
				// set first column
5029
				$this->selectColumn(0);
5030
			}
5031
			// avoid page breaking from checkPageBreak()
5032
			return false;
5033
		}
5034
		return $this->AutoPageBreak;
5035
	}
5036
 
5037
	/**
5038
	 * Add page if needed.
5039
	 * @param float $h Cell height. Default value: 0.
5040
	 * @param float|null $y starting y position, leave empty for current position.
5041
	 * @param bool  $addpage if true add a page, otherwise only return the true/false state
5042
	 * @return bool true in case of page break, false otherwise.
5043
	 * @since 3.2.000 (2008-07-01)
5044
	 * @protected
5045
	 */
5046
	protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5047
		if (TCPDF_STATIC::empty_string($y)) {
5048
			$y = $this->y;
5049
		}
5050
		$current_page = $this->page;
5051
		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5052
			if ($addpage) {
5053
				//Automatic page break
5054
				$x = $this->x;
5055
				$this->AddPage($this->CurOrientation);
5056
				$this->y = $this->tMargin;
5057
				$oldpage = $this->page - 1;
5058
				if ($this->rtl) {
5059
					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5060
						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5061
					} else {
5062
						$this->x = $x;
5063
					}
5064
				} else {
5065
					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5066
						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5067
					} else {
5068
						$this->x = $x;
5069
					}
5070
				}
5071
			}
5072
			return true;
5073
		}
5074
		if ($current_page != $this->page) {
5075
			// account for columns mode
5076
			return true;
5077
		}
5078
		return false;
5079
	}
5080
 
5081
	/**
5082
	 * 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 />
5083
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5084
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5085
	 * @param float $h Cell height. Default value: 0.
5086
	 * @param string $txt String to print. Default value: empty string.
5087
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5088
	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5089
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5090
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5091
	 * @param mixed $link URL or identifier returned by AddLink().
5092
	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5093
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5094
	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5095
	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5096
	 * @public
5097
	 * @since 1.0
5098
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5099
	 */
5100
	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') {
5101
		$prev_cell_margin = $this->cell_margin;
5102
		$prev_cell_padding = $this->cell_padding;
5103
		$this->adjustCellPadding($border);
5104
		if (!$ignore_min_height) {
5105
			$min_cell_height = $this->getCellHeight($this->FontSize);
5106
			if ($h < $min_cell_height) {
5107
				$h = $min_cell_height;
5108
			}
5109
		}
5110
		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5111
		// apply text shadow if enabled
5112
		if ($this->txtshadow['enabled']) {
5113
			// save data
5114
			$x = $this->x;
5115
			$y = $this->y;
5116
			$bc = $this->bgcolor;
5117
			$fc = $this->fgcolor;
5118
			$sc = $this->strokecolor;
5119
			$alpha = $this->alpha;
5120
			// print shadow
5121
			$this->x += $this->txtshadow['depth_w'];
5122
			$this->y += $this->txtshadow['depth_h'];
5123
			$this->setFillColorArray($this->txtshadow['color']);
5124
			$this->setTextColorArray($this->txtshadow['color']);
5125
			$this->setDrawColorArray($this->txtshadow['color']);
5126
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5127
				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5128
			}
5129
			if ($this->state == 2) {
5130
				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5131
			}
5132
			//restore data
5133
			$this->x = $x;
5134
			$this->y = $y;
5135
			$this->setFillColorArray($bc);
5136
			$this->setTextColorArray($fc);
5137
			$this->setDrawColorArray($sc);
5138
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5139
				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5140
			}
5141
		}
5142
		if ($this->state == 2) {
5143
			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5144
		}
5145
		$this->cell_padding = $prev_cell_padding;
5146
		$this->cell_margin = $prev_cell_margin;
5147
	}
5148
 
5149
	/**
5150
	 * 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 />
5151
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5152
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5153
	 * @param float $h Cell height. Default value: 0.
5154
	 * @param string $txt String to print. Default value: empty string.
5155
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5156
	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5157
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5158
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5159
	 * @param mixed $link URL or identifier returned by AddLink().
5160
	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5161
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5162
	 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5163
	 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5164
	 * @return string containing cell code
5165
	 * @protected
5166
	 * @since 1.0
5167
	 * @see Cell()
5168
	 */
5169
	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') {
5170
		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5171
		$txt = is_null($txt) ? '' : $txt;
5172
		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5173
		$prev_cell_margin = $this->cell_margin;
5174
		$prev_cell_padding = $this->cell_padding;
5175
		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5176
		$rs = ''; //string to be returned
5177
		$this->adjustCellPadding($border);
5178
		if (!$ignore_min_height) {
5179
			$min_cell_height = $this->getCellHeight($this->FontSize);
5180
			if ($h < $min_cell_height) {
5181
				$h = $min_cell_height;
5182
			}
5183
		}
5184
		$k = $this->k;
5185
		// check page for no-write regions and adapt page margins if necessary
5186
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5187
		if ($this->rtl) {
5188
			$x = $this->x - $this->cell_margin['R'];
5189
		} else {
5190
			$x = $this->x + $this->cell_margin['L'];
5191
		}
5192
		$y = $this->y + $this->cell_margin['T'];
5193
		$prev_font_stretching = $this->font_stretching;
5194
		$prev_font_spacing = $this->font_spacing;
5195
		// cell vertical alignment
5196
		switch ($calign) {
5197
			case 'A': {
5198
				// font top
5199
				switch ($valign) {
5200
					case 'T': {
5201
						// top
5202
						$y -= $this->cell_padding['T'];
5203
						break;
5204
					}
5205
					case 'B': {
5206
						// bottom
5207
						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5208
						break;
5209
					}
5210
					default:
5211
					case 'C':
5212
					case 'M': {
5213
						// center
5214
						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5215
						break;
5216
					}
5217
				}
5218
				break;
5219
			}
5220
			case 'L': {
5221
				// font baseline
5222
				switch ($valign) {
5223
					case 'T': {
5224
						// top
5225
						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5226
						break;
5227
					}
5228
					case 'B': {
5229
						// bottom
5230
						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5231
						break;
5232
					}
5233
					default:
5234
					case 'C':
5235
					case 'M': {
5236
						// center
5237
						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5238
						break;
5239
					}
5240
				}
5241
				break;
5242
			}
5243
			case 'D': {
5244
				// font bottom
5245
				switch ($valign) {
5246
					case 'T': {
5247
						// top
5248
						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5249
						break;
5250
					}
5251
					case 'B': {
5252
						// bottom
5253
						$y -= ($h - $this->cell_padding['B']);
5254
						break;
5255
					}
5256
					default:
5257
					case 'C':
5258
					case 'M': {
5259
						// center
5260
						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5261
						break;
5262
					}
5263
				}
5264
				break;
5265
			}
5266
			case 'B': {
5267
				// cell bottom
5268
				$y -= $h;
5269
				break;
5270
			}
5271
			case 'C':
5272
			case 'M': {
5273
				// cell center
5274
				$y -= ($h / 2);
5275
				break;
5276
			}
5277
			default:
5278
			case 'T': {
5279
				// cell top
5280
				break;
5281
			}
5282
		}
5283
		// text vertical alignment
5284
		switch ($valign) {
5285
			case 'T': {
5286
				// top
5287
				$yt = $y + $this->cell_padding['T'];
5288
				break;
5289
			}
5290
			case 'B': {
5291
				// bottom
5292
				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5293
				break;
5294
			}
5295
			default:
5296
			case 'C':
5297
			case 'M': {
5298
				// center
5299
				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5300
				break;
5301
			}
5302
		}
5303
		$basefonty = $yt + $this->FontAscent;
5304
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5305
			if ($this->rtl) {
5306
				$w = $x - $this->lMargin;
5307
			} else {
5308
				$w = $this->w - $this->rMargin - $x;
5309
			}
5310
		}
5311
		$s = '';
5312
		// fill and borders
5313
		if (is_string($border) AND (strlen($border) == 4)) {
5314
			// full border
5315
			$border = 1;
5316
		}
5317
		if ($fill OR ($border == 1)) {
5318
			if ($fill) {
5319
				$op = ($border == 1) ? 'B' : 'f';
5320
			} else {
5321
				$op = 'S';
5322
			}
5323
			if ($this->rtl) {
5324
				$xk = (($x - $w) * $k);
5325
			} else {
5326
				$xk = ($x * $k);
5327
			}
5328
			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5329
		}
5330
		// draw borders
5331
		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5332
		if ($txt != '') {
5333
			$txt2 = $txt;
5334
			if ($this->isunicode) {
5335
				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5336
					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5337
				} else {
5338
					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5339
					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5340
					// replace thai chars (if any)
5341
					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5342
						// number of chars
5343
						$numchars = count($unicode);
5344
						// po pla, for far, for fan
5345
						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5346
						// do chada, to patak
5347
						$lowtail = array(0x0e0e, 0x0e0f);
5348
						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5349
						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5350
						// mai ek, mai tho, mai tri, mai chattawa, karan
5351
						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5352
						// sara u, sara uu, pinthu
5353
						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5354
						$output = array();
5355
						for ($i = 0; $i < $numchars; $i++) {
5356
							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5357
								$ch0 = $unicode[$i];
5358
								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5359
								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5360
								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5361
								if (in_array($ch0, $tonemark)) {
5362
									if ($chn == 0x0e33) {
5363
										// sara um
5364
										if (in_array($ch1, $longtail)) {
5365
											// tonemark at upper left
5366
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5367
										} else {
5368
											// tonemark at upper right (normal position)
5369
											$output[] = $ch0;
5370
										}
5371
									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5372
										// tonemark at lower left
5373
										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5374
									} elseif (in_array($ch1, $upvowel)) {
5375
										if (in_array($ch2, $longtail)) {
5376
											// tonemark at upper left
5377
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5378
										} else {
5379
											// tonemark at upper right (normal position)
5380
											$output[] = $ch0;
5381
										}
5382
									} else {
5383
										// tonemark at lower right
5384
										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5385
									}
5386
								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5387
									// add lower left nikhahit and sara aa
5388
									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5389
										$output[] = 0xf711;
5390
										$this->CurrentFont['subsetchars'][0xf711] = true;
5391
										$output[] = 0x0e32;
5392
										$this->CurrentFont['subsetchars'][0x0e32] = true;
5393
									} else {
5394
										$output[] = $ch0;
5395
									}
5396
								} elseif (in_array($ch1, $longtail)) {
5397
									if ($ch0 == 0x0e31) {
5398
										// lower left mai hun arkad
5399
										$output[] = $this->replaceChar($ch0, 0xf710);
5400
									} elseif (in_array($ch0, $upvowel)) {
5401
										// lower left
5402
										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5403
									} elseif ($ch0 == 0x0e47) {
5404
										// lower left mai tai koo
5405
										$output[] = $this->replaceChar($ch0, 0xf712);
5406
									} else {
5407
										// normal character
5408
										$output[] = $ch0;
5409
									}
5410
								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5411
									// lower vowel
5412
									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5413
								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5414
									// yo ying without lower part
5415
									$output[] = $this->replaceChar($ch0, 0xf70f);
5416
								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5417
									// tho santan without lower part
5418
									$output[] = $this->replaceChar($ch0, 0xf700);
5419
								} else {
5420
									$output[] = $ch0;
5421
								}
5422
							} else {
5423
								// non-thai character
5424
								$output[] = $unicode[$i];
5425
							}
5426
						}
5427
						$unicode = $output;
5428
						// update font subsetchars
5429
						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5430
					} // end of K_THAI_TOPCHARS
5431
					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5432
				}
5433
			}
5434
			$txt2 = TCPDF_STATIC::_escape($txt2);
5435
			// get current text width (considering general font stretching and spacing)
5436
			$txwidth = $this->GetStringWidth($txt);
5437
			$width = $txwidth;
5438
			// check for stretch mode
5439
			if ($stretch > 0) {
5440
				// calculate ratio between cell width and text width
5441
				if ($width <= 0) {
5442
					$ratio = 1;
5443
				} else {
5444
					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5445
				}
5446
				// check if stretching is required
5447
				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5448
					// the text will be stretched to fit cell width
5449
					if ($stretch > 2) {
5450
						// set new character spacing
5451
						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5452
					} else {
5453
						// set new horizontal stretching
5454
						$this->font_stretching *= $ratio;
5455
					}
5456
					// recalculate text width (the text fills the entire cell)
5457
					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5458
					// reset alignment
5459
					$align = '';
5460
				}
5461
			}
5462
			if ($this->font_stretching != 100) {
5463
				// apply font stretching
5464
				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5465
			}
5466
			if ($this->font_spacing != 0) {
5467
				// increase/decrease font spacing
5468
				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5469
			}
5470
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5471
				$s .= 'q '.$this->TextColor.' ';
5472
			}
5473
			// rendering mode
5474
			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5475
			// count number of spaces
5476
			$ns = substr_count($txt, chr(32));
5477
			// Justification
5478
			$spacewidth = 0;
5479
			if (($align == 'J') AND ($ns > 0)) {
5480
				if ($this->isUnicodeFont()) {
5481
					// get string width without spaces
5482
					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5483
					// calculate average space width
5484
					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5485
					if ($this->font_stretching != 100) {
5486
						// word spacing is affected by stretching
5487
						$spacewidth /= ($this->font_stretching / 100);
5488
					}
5489
					// set word position to be used with TJ operator
5490
					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5491
					$unicode_justification = true;
5492
				} else {
5493
					// get string width
5494
					$width = $txwidth;
5495
					// new space width
5496
					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5497
					if ($this->font_stretching != 100) {
5498
						// word spacing (Tw) is affected by stretching
5499
						$spacewidth /= ($this->font_stretching / 100);
5500
					}
5501
					// set word spacing
5502
					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5503
				}
5504
				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5505
			}
5506
			// replace carriage return characters
5507
			$txt2 = str_replace("\r", ' ', $txt2);
5508
			switch ($align) {
5509
				case 'C': {
5510
					$dx = ($w - $width) / 2;
5511
					break;
5512
				}
5513
				case 'R': {
5514
					if ($this->rtl) {
5515
						$dx = $this->cell_padding['R'];
5516
					} else {
5517
						$dx = $w - $width - $this->cell_padding['R'];
5518
					}
5519
					break;
5520
				}
5521
				case 'L': {
5522
					if ($this->rtl) {
5523
						$dx = $w - $width - $this->cell_padding['L'];
5524
					} else {
5525
						$dx = $this->cell_padding['L'];
5526
					}
5527
					break;
5528
				}
5529
				case 'J':
5530
				default: {
5531
					if ($this->rtl) {
5532
						$dx = $this->cell_padding['R'];
5533
					} else {
5534
						$dx = $this->cell_padding['L'];
5535
					}
5536
					break;
5537
				}
5538
			}
5539
			if ($this->rtl) {
5540
				$xdx = $x - $dx - $width;
5541
			} else {
5542
				$xdx = $x + $dx;
5543
			}
5544
			$xdk = $xdx * $k;
5545
			// print text
5546
			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5547
			if (isset($uniblock)) { // @phpstan-ignore-line
5548
				// print overlapping characters as separate string
5549
				$xshift = 0; // horizontal shift
5550
				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5551
				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5552
				foreach ($uniblock as $uk => $uniarr) { // @phpstan-ignore-line
5553
					if (($uk % 2) == 0) {
5554
						// x space to skip
5555
						if ($spacewidth != 0) {
5556
							// justification shift
5557
							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5558
						}
5559
						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5560
					} else {
5561
						// character to print
5562
						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5563
						$topchr = TCPDF_STATIC::_escape($topchr);
5564
						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5565
					}
5566
				}
5567
			}
5568
			if ($this->underline) {
5569
				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5570
			}
5571
			if ($this->linethrough) {
5572
				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5573
			}
5574
			if ($this->overline) {
5575
				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5576
			}
5577
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5578
				$s .= ' Q';
5579
			}
5580
			if ($link) {
5581
				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5582
			}
5583
		}
5584
		// output cell
5585
		if ($s) {
5586
			// output cell
5587
			$rs .= $s;
5588
			if ($this->font_spacing != 0) {
5589
				// reset font spacing mode
5590
				$rs .= ' BT 0 Tc ET';
5591
			}
5592
			if ($this->font_stretching != 100) {
5593
				// reset font stretching mode
5594
				$rs .= ' BT 100 Tz ET';
5595
			}
5596
		}
5597
		// reset word spacing
5598
		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5599
			$rs .= ' BT 0 Tw ET';
5600
		}
5601
		// reset stretching and spacing
5602
		$this->font_stretching = $prev_font_stretching;
5603
		$this->font_spacing = $prev_font_spacing;
5604
		$this->lasth = $h;
5605
		if ($ln > 0) {
5606
			//Go to the beginning of the next line
5607
			$this->y = $y + $h + $this->cell_margin['B'];
5608
			if ($ln == 1) {
5609
				if ($this->rtl) {
5610
					$this->x = $this->w - $this->rMargin;
5611
				} else {
5612
					$this->x = $this->lMargin;
5613
				}
5614
			}
5615
		} else {
5616
			// go left or right by case
5617
			if ($this->rtl) {
5618
				$this->x = $x - $w - $this->cell_margin['L'];
5619
			} else {
5620
				$this->x = $x + $w + $this->cell_margin['R'];
5621
			}
5622
		}
5623
		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5624
		$rs = $gstyles.$rs;
5625
		$this->cell_padding = $prev_cell_padding;
5626
		$this->cell_margin = $prev_cell_margin;
5627
		return $rs;
5628
	}
5629
 
5630
	/**
5631
	 * Replace a char if is defined on the current font.
5632
	 * @param int $oldchar Integer code (unicode) of the character to replace.
5633
	 * @param int $newchar Integer code (unicode) of the new character.
5634
	 * @return int the replaced char or the old char in case the new char i not defined
5635
	 * @protected
5636
	 * @since 5.9.167 (2012-06-22)
5637
	 */
5638
	protected function replaceChar($oldchar, $newchar) {
5639
		if ($this->isCharDefined($newchar)) {
5640
			// add the new char on the subset list
5641
			$this->CurrentFont['subsetchars'][$newchar] = true;
5642
			// return the new character
5643
			return $newchar;
5644
		}
5645
		// return the old char
5646
		return $oldchar;
5647
	}
5648
 
5649
	/**
5650
	 * Returns the code to draw the cell border
5651
	 * @param float $x X coordinate.
5652
	 * @param float $y Y coordinate.
5653
	 * @param float $w Cell width.
5654
	 * @param float $h Cell height.
5655
	 * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5656
	 * @return string containing cell border code
5657
	 * @protected
5658
	 * @see SetLineStyle()
5659
	 * @since 5.7.000 (2010-08-02)
5660
	 */
5661
	protected function getCellBorder($x, $y, $w, $h, $brd) {
5662
		$s = ''; // string to be returned
5663
		if (empty($brd)) {
5664
			return $s;
5665
		}
5666
		if ($brd == 1) {
5667
			$brd = array('LRTB' => true);
5668
		}
5669
		// calculate coordinates for border
5670
		$k = $this->k;
5671
		if ($this->rtl) {
5672
			$xeL = ($x - $w) * $k;
5673
			$xeR = $x * $k;
5674
		} else {
5675
			$xeL = $x * $k;
5676
			$xeR = ($x + $w) * $k;
5677
		}
5678
		$yeL = (($this->h - ($y + $h)) * $k);
5679
		$yeT = (($this->h - $y) * $k);
5680
		$xeT = $xeL;
5681
		$xeB = $xeR;
5682
		$yeR = $yeT;
5683
		$yeB = $yeL;
5684
		if (is_string($brd)) {
5685
			// convert string to array
5686
			$slen = strlen($brd);
5687
			$newbrd = array();
5688
			for ($i = 0; $i < $slen; ++$i) {
5689
				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5690
			}
5691
			$brd = $newbrd;
5692
		}
5693
		if (isset($brd['mode'])) {
5694
			$mode = $brd['mode'];
5695
			unset($brd['mode']);
5696
		} else {
5697
			$mode = 'normal';
5698
		}
5699
		foreach ($brd as $border => $style) {
5700
			if (is_array($style) AND !empty($style)) {
5701
				// apply border style
5702
				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5703
				$s .= $this->setLineStyle($style, true)."\n";
5704
			}
5705
			switch ($mode) {
5706
				case 'ext': {
5707
					$off = (($this->LineWidth / 2) * $k);
5708
					$xL = $xeL - $off;
5709
					$xR = $xeR + $off;
5710
					$yT = $yeT + $off;
5711
					$yL = $yeL - $off;
5712
					$xT = $xL;
5713
					$xB = $xR;
5714
					$yR = $yT;
5715
					$yB = $yL;
5716
					$w += $this->LineWidth;
5717
					$h += $this->LineWidth;
5718
					break;
5719
				}
5720
				case 'int': {
5721
					$off = ($this->LineWidth / 2) * $k;
5722
					$xL = $xeL + $off;
5723
					$xR = $xeR - $off;
5724
					$yT = $yeT - $off;
5725
					$yL = $yeL + $off;
5726
					$xT = $xL;
5727
					$xB = $xR;
5728
					$yR = $yT;
5729
					$yB = $yL;
5730
					$w -= $this->LineWidth;
5731
					$h -= $this->LineWidth;
5732
					break;
5733
				}
5734
				case 'normal':
5735
				default: {
5736
					$xL = $xeL;
5737
					$xT = $xeT;
5738
					$xB = $xeB;
5739
					$xR = $xeR;
5740
					$yL = $yeL;
5741
					$yT = $yeT;
5742
					$yB = $yeB;
5743
					$yR = $yeR;
5744
					break;
5745
				}
5746
			}
5747
			// draw borders by case
5748
			if (strlen($border) == 4) {
5749
				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5750
			} elseif (strlen($border) == 3) {
5751
				if (strpos($border,'B') === false) { // LTR
5752
					$s .= sprintf('%F %F m ', $xL, $yL);
5753
					$s .= sprintf('%F %F l ', $xT, $yT);
5754
					$s .= sprintf('%F %F l ', $xR, $yR);
5755
					$s .= sprintf('%F %F l ', $xB, $yB);
5756
					$s .= 'S ';
5757
				} elseif (strpos($border,'L') === false) { // TRB
5758
					$s .= sprintf('%F %F m ', $xT, $yT);
5759
					$s .= sprintf('%F %F l ', $xR, $yR);
5760
					$s .= sprintf('%F %F l ', $xB, $yB);
5761
					$s .= sprintf('%F %F l ', $xL, $yL);
5762
					$s .= 'S ';
5763
				} elseif (strpos($border,'T') === false) { // RBL
5764
					$s .= sprintf('%F %F m ', $xR, $yR);
5765
					$s .= sprintf('%F %F l ', $xB, $yB);
5766
					$s .= sprintf('%F %F l ', $xL, $yL);
5767
					$s .= sprintf('%F %F l ', $xT, $yT);
5768
					$s .= 'S ';
5769
				} elseif (strpos($border,'R') === false) { // BLT
5770
					$s .= sprintf('%F %F m ', $xB, $yB);
5771
					$s .= sprintf('%F %F l ', $xL, $yL);
5772
					$s .= sprintf('%F %F l ', $xT, $yT);
5773
					$s .= sprintf('%F %F l ', $xR, $yR);
5774
					$s .= 'S ';
5775
				}
5776
			} elseif (strlen($border) == 2) {
5777
				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5778
					$s .= sprintf('%F %F m ', $xL, $yL);
5779
					$s .= sprintf('%F %F l ', $xT, $yT);
5780
					$s .= sprintf('%F %F l ', $xR, $yR);
5781
					$s .= 'S ';
5782
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5783
					$s .= sprintf('%F %F m ', $xT, $yT);
5784
					$s .= sprintf('%F %F l ', $xR, $yR);
5785
					$s .= sprintf('%F %F l ', $xB, $yB);
5786
					$s .= 'S ';
5787
				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5788
					$s .= sprintf('%F %F m ', $xR, $yR);
5789
					$s .= sprintf('%F %F l ', $xB, $yB);
5790
					$s .= sprintf('%F %F l ', $xL, $yL);
5791
					$s .= 'S ';
5792
				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5793
					$s .= sprintf('%F %F m ', $xB, $yB);
5794
					$s .= sprintf('%F %F l ', $xL, $yL);
5795
					$s .= sprintf('%F %F l ', $xT, $yT);
5796
					$s .= 'S ';
5797
				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5798
					$s .= sprintf('%F %F m ', $xL, $yL);
5799
					$s .= sprintf('%F %F l ', $xT, $yT);
5800
					$s .= 'S ';
5801
					$s .= sprintf('%F %F m ', $xR, $yR);
5802
					$s .= sprintf('%F %F l ', $xB, $yB);
5803
					$s .= 'S ';
5804
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5805
					$s .= sprintf('%F %F m ', $xT, $yT);
5806
					$s .= sprintf('%F %F l ', $xR, $yR);
5807
					$s .= 'S ';
5808
					$s .= sprintf('%F %F m ', $xB, $yB);
5809
					$s .= sprintf('%F %F l ', $xL, $yL);
5810
					$s .= 'S ';
5811
				}
5812
			} else { // strlen($border) == 1
5813
				if (strpos($border,'L') !== false) { // L
5814
					$s .= sprintf('%F %F m ', $xL, $yL);
5815
					$s .= sprintf('%F %F l ', $xT, $yT);
5816
					$s .= 'S ';
5817
				} elseif (strpos($border,'T') !== false) { // T
5818
					$s .= sprintf('%F %F m ', $xT, $yT);
5819
					$s .= sprintf('%F %F l ', $xR, $yR);
5820
					$s .= 'S ';
5821
				} elseif (strpos($border,'R') !== false) { // R
5822
					$s .= sprintf('%F %F m ', $xR, $yR);
5823
					$s .= sprintf('%F %F l ', $xB, $yB);
5824
					$s .= 'S ';
5825
				} elseif (strpos($border,'B') !== false) { // B
5826
					$s .= sprintf('%F %F m ', $xB, $yB);
5827
					$s .= sprintf('%F %F l ', $xL, $yL);
5828
					$s .= 'S ';
5829
				}
5830
			}
5831
			if (is_array($style) AND !empty($style)) {
5832
				// reset border style to previous value
5833
				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5834
			}
5835
		}
5836
		return $s;
5837
	}
5838
 
5839
	/**
5840
	 * This method allows printing text with line breaks.
5841
	 * 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 />
5842
	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5843
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5844
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
5845
	 * @param string $txt String to print
5846
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5847
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5848
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5849
	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5850
	 * @param float|null $x x position in user units
5851
	 * @param float|null $y y position in user units
5852
	 * @param boolean $reseth if true reset the last cell height (default true).
5853
	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5854
	 * @param boolean $ishtml INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5855
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5856
	 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5857
	 * @param string $valign Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5858
	 * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5859
	 * @return int Return the number of cells or 1 for html mode.
5860
	 * @public
5861
	 * @since 1.3
5862
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5863
	 */
5864
	public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5865
		$prev_cell_margin = $this->cell_margin;
5866
		$prev_cell_padding = $this->cell_padding;
5867
		// adjust internal padding
5868
		$this->adjustCellPadding($border);
5869
		$mc_padding = $this->cell_padding;
5870
		$mc_margin = $this->cell_margin;
5871
		$this->cell_padding['T'] = 0;
5872
		$this->cell_padding['B'] = 0;
5873
		$this->setCellMargins(0, 0, 0, 0);
5874
		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5875
			// reset row height
5876
			$this->resetLastH();
5877
		}
5878
		if (!TCPDF_STATIC::empty_string($y)) {
5879
			$this->setY($y); // set y in order to convert negative y values to positive ones
5880
		}
5881
		$y = $this->GetY();
5882
		$resth = 0;
5883
		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5884
			// spit cell in more pages/columns
5885
			$newh = ($this->PageBreakTrigger - $y);
5886
			$resth = ($h - $newh); // cell to be printed on the next page/column
5887
			$h = $newh;
5888
		}
5889
		// get current page number
5890
		$startpage = $this->page;
5891
		// get current column
5892
		$startcolumn = $this->current_column;
5893
		if (!TCPDF_STATIC::empty_string($x)) {
5894
			$this->setX($x);
5895
		} else {
5896
			$x = $this->GetX();
5897
		}
5898
		// check page for no-write regions and adapt page margins if necessary
5899
		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5900
		// apply margins
5901
		$oy = $y + $mc_margin['T'];
5902
		if ($this->rtl) {
5903
			$ox = ($this->w - $x - $mc_margin['R']);
5904
		} else {
5905
			$ox = ($x + $mc_margin['L']);
5906
		}
5907
		$this->x = $ox;
5908
		$this->y = $oy;
5909
		// set width
5910
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5911
			if ($this->rtl) {
5912
				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5913
			} else {
5914
				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5915
			}
5916
		}
5917
		// store original margin values
5918
		$lMargin = $this->lMargin;
5919
		$rMargin = $this->rMargin;
5920
		if ($this->rtl) {
5921
			$this->rMargin = ($this->w - $this->x);
5922
			$this->lMargin = ($this->x - $w);
5923
		} else {
5924
			$this->lMargin = ($this->x);
5925
			$this->rMargin = ($this->w - $this->x - $w);
5926
		}
5927
		$this->clMargin = $this->lMargin;
5928
		$this->crMargin = $this->rMargin;
5929
		if ($autopadding) {
5930
			// add top padding
5931
			$this->y += $mc_padding['T'];
5932
		}
5933
		if ($ishtml) { // ******* Write HTML text
5934
			$this->writeHTML($txt, true, false, $reseth, true, $align);
5935
			$nl = 1;
5936
		} else { // ******* Write simple text
5937
			$prev_FontSizePt = $this->FontSizePt;
5938
			if ($fitcell) {
5939
				// ajust height values
5940
				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5941
				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5942
			}
5943
			// vertical alignment
5944
			if ($maxh > 0) {
5945
				// get text height
5946
				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5947
				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5948
					// try to reduce font size to fit text on cell (use a quick search algorithm)
5949
					$fmin = 1;
5950
					$fmax = $this->FontSizePt;
5951
					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5952
					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5953
					while ($maxit >= 0) {
5954
						$fmid = (($fmax + $fmin) / 2);
5955
						$this->setFontSize($fmid, false);
5956
						$this->resetLastH();
5957
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5958
						$diff = ($maxh - $text_height);
5959
						if ($diff >= 0) {
5960
							if ($diff <= $diff_epsilon) {
5961
								break;
5962
							}
5963
							$fmin = $fmid;
5964
						} else {
5965
							$fmax = $fmid;
5966
						}
5967
						--$maxit;
5968
					}
5969
					if ($maxit < 0) {
5970
						// premature exit, we get the minimum font value to fit the cell
5971
						$this->setFontSize($fmin);
5972
						$this->resetLastH();
5973
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5974
					} else {
5975
						$this->setFontSize($fmid);
5976
						$this->resetLastH();
5977
					}
5978
				}
5979
				if ($text_height < $maxh) {
5980
					if ($valign == 'M') {
5981
						// text vertically centered
5982
						$this->y += (($maxh - $text_height) / 2);
5983
					} elseif ($valign == 'B') {
5984
						// text vertically aligned on bottom
5985
						$this->y += ($maxh - $text_height);
5986
					}
5987
				}
5988
			}
5989
			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5990
			if ($fitcell) {
5991
				// restore font size
5992
				$this->setFontSize($prev_FontSizePt);
5993
			}
5994
		}
5995
		if ($autopadding) {
5996
			// add bottom padding
5997
			$this->y += $mc_padding['B'];
5998
		}
5999
		// Get end-of-text Y position
6000
		$currentY = $this->y;
6001
		// get latest page number
6002
		$endpage = $this->page;
6003
		if ($resth > 0) {
6004
			$skip = ($endpage - $startpage);
6005
			$tmpresth = $resth;
6006
			while ($tmpresth > 0) {
6007
				if ($skip <= 0) {
6008
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
6009
					$this->checkPageBreak($this->PageBreakTrigger + 1);
6010
				}
6011
				if ($this->num_columns > 1) {
6012
					$tmpresth -= ($this->h - $this->y - $this->bMargin);
6013
				} else {
6014
					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6015
				}
6016
				--$skip;
6017
			}
6018
			$currentY = $this->y;
6019
			$endpage = $this->page;
6020
		}
6021
		// get latest column
6022
		$endcolumn = $this->current_column;
6023
		if ($this->num_columns == 0) {
6024
			$this->num_columns = 1;
6025
		}
6026
		// disable page regions check
6027
		$check_page_regions = $this->check_page_regions;
6028
		$this->check_page_regions = false;
6029
		// get border modes
6030
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
6031
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
6032
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6033
		// design borders around HTML cells.
6034
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6035
			$ccode = '';
6036
			$this->setPage($page);
6037
			if ($this->num_columns < 2) {
6038
				// single-column mode
6039
				$this->setX($x);
6040
				$this->y = $this->tMargin;
6041
			}
6042
			// account for margin changes
6043
			if ($page > $startpage) {
6044
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6045
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6046
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6047
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6048
				}
6049
			}
6050
			if ($startpage == $endpage) {
6051
				// single page
6052
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6053
					if ($column != $this->current_column) {
6054
						$this->selectColumn($column);
6055
					}
6056
					if ($this->rtl) {
6057
						$this->x -= $mc_margin['R'];
6058
					} else {
6059
						$this->x += $mc_margin['L'];
6060
					}
6061
					if ($startcolumn == $endcolumn) { // single column
6062
						$cborder = $border;
6063
						$h = max($h, ($currentY - $oy));
6064
						$this->y = $oy;
6065
					} elseif ($column == $startcolumn) { // first column
6066
						$cborder = $border_start;
6067
						$this->y = $oy;
6068
						$h = $this->h - $this->y - $this->bMargin;
6069
					} elseif ($column == $endcolumn) { // end column
6070
						$cborder = $border_end;
6071
						$h = $currentY - $this->y;
6072
						if ($resth > $h) {
6073
							$h = $resth;
6074
						}
6075
					} else { // middle column
6076
						$cborder = $border_middle;
6077
						$h = $this->h - $this->y - $this->bMargin;
6078
						$resth -= $h;
6079
					}
6080
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6081
				} // end for each column
6082
			} elseif ($page == $startpage) { // first page
6083
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6084
					if ($column != $this->current_column) {
6085
						$this->selectColumn($column);
6086
					}
6087
					if ($this->rtl) {
6088
						$this->x -= $mc_margin['R'];
6089
					} else {
6090
						$this->x += $mc_margin['L'];
6091
					}
6092
					if ($column == $startcolumn) { // first column
6093
						$cborder = $border_start;
6094
						$this->y = $oy;
6095
						$h = $this->h - $this->y - $this->bMargin;
6096
					} else { // middle column
6097
						$cborder = $border_middle;
6098
						$h = $this->h - $this->y - $this->bMargin;
6099
						$resth -= $h;
6100
					}
6101
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6102
				} // end for each column
6103
			} elseif ($page == $endpage) { // last page
6104
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6105
					if ($column != $this->current_column) {
6106
						$this->selectColumn($column);
6107
					}
6108
					if ($this->rtl) {
6109
						$this->x -= $mc_margin['R'];
6110
					} else {
6111
						$this->x += $mc_margin['L'];
6112
					}
6113
					if ($column == $endcolumn) {
6114
						// end column
6115
						$cborder = $border_end;
6116
						$h = $currentY - $this->y;
6117
						if ($resth > $h) {
6118
							$h = $resth;
6119
						}
6120
					} else {
6121
						// middle column
6122
						$cborder = $border_middle;
6123
						$h = $this->h - $this->y - $this->bMargin;
6124
						$resth -= $h;
6125
					}
6126
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6127
				} // end for each column
6128
			} else { // middle page
6129
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6130
					$this->selectColumn($column);
6131
					if ($this->rtl) {
6132
						$this->x -= $mc_margin['R'];
6133
					} else {
6134
						$this->x += $mc_margin['L'];
6135
					}
6136
					$cborder = $border_middle;
6137
					$h = $this->h - $this->y - $this->bMargin;
6138
					$resth -= $h;
6139
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6140
				} // end for each column
6141
			}
6142
			if ($cborder OR $fill) {
6143
				$offsetlen = strlen($ccode);
6144
				// draw border and fill
6145
				if ($this->inxobj) {
6146
					// we are inside an XObject template
6147
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6148
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6149
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6150
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6151
					} else {
6152
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6153
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6154
					}
6155
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6156
					$pstart = substr($pagebuff, 0, $pagemark);
6157
					$pend = substr($pagebuff, $pagemark);
6158
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6159
				} else {
6160
					if (end($this->transfmrk[$this->page]) !== false) {
6161
						$pagemarkkey = key($this->transfmrk[$this->page]);
6162
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6163
						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6164
					} elseif ($this->InFooter) {
6165
						$pagemark = $this->footerpos[$this->page];
6166
						$this->footerpos[$this->page] += $offsetlen;
6167
					} else {
6168
						$pagemark = $this->intmrk[$this->page];
6169
						$this->intmrk[$this->page] += $offsetlen;
6170
					}
6171
					$pagebuff = $this->getPageBuffer($this->page);
6172
					$pstart = substr($pagebuff, 0, $pagemark);
6173
					$pend = substr($pagebuff, $pagemark);
6174
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6175
				}
6176
			}
6177
		} // end for each page
6178
		// restore page regions check
6179
		$this->check_page_regions = $check_page_regions;
6180
		// Get end-of-cell Y position
6181
		$currentY = $this->GetY();
6182
		// restore previous values
6183
		if ($this->num_columns > 1) {
6184
			$this->selectColumn();
6185
		} else {
6186
			// restore original margins
6187
			$this->lMargin = $lMargin;
6188
			$this->rMargin = $rMargin;
6189
			if ($this->page > $startpage) {
6190
				// check for margin variations between pages (i.e. booklet mode)
6191
				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6192
				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6193
				if (($dl != 0) OR ($dr != 0)) {
6194
					$this->lMargin += $dl;
6195
					$this->rMargin += $dr;
6196
				}
6197
			}
6198
		}
6199
		if ($ln > 0) {
6200
			//Go to the beginning of the next line
6201
			$this->setY($currentY + $mc_margin['B']);
6202
			if ($ln == 2) {
6203
				$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6204
			}
6205
		} else {
6206
			// go left or right by case
6207
			$this->setPage($startpage);
6208
			$this->y = $y;
6209
			$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6210
		}
6211
		$this->setContentMark();
6212
		$this->cell_padding = $prev_cell_padding;
6213
		$this->cell_margin = $prev_cell_margin;
6214
		$this->clMargin = $this->lMargin;
6215
		$this->crMargin = $this->rMargin;
6216
		return $nl;
6217
	}
6218
 
6219
	/**
6220
	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6221
	 * @param string $txt String for calculating his height
6222
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6223
	 * @param boolean $reseth if true reset the last cell height (default false).
6224
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6225
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6226
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6227
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6228
	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6229
	 * @public
6230
	 * @since 4.5.011
6231
	 */
6232
	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6233
		if ($txt === NULL) {
6234
			return 0;
6235
		}
6236
		if ($txt === '') {
6237
			// empty string
6238
			return 1;
6239
		}
6240
		// adjust internal padding
6241
		$prev_cell_padding = $this->cell_padding;
6242
		$prev_lasth = $this->lasth;
6243
		if (is_array($cellpadding)) {
6244
			$this->cell_padding = $cellpadding;
6245
		}
6246
		$this->adjustCellPadding($border);
6247
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6248
			if ($this->rtl) {
6249
				$w = $this->x - $this->lMargin;
6250
			} else {
6251
				$w = $this->w - $this->rMargin - $this->x;
6252
			}
6253
		}
6254
		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6255
		if ($reseth) {
6256
			// reset row height
6257
			$this->resetLastH();
6258
		}
6259
		$lines = 1;
6260
		$sum = 0;
6261
		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6262
		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6263
		$length = count($chars);
6264
		$lastSeparator = -1;
6265
		for ($i = 0; $i < $length; ++$i) {
6266
			$c = $chars[$i];
6267
			$charWidth = $charsWidth[$i];
6268
			if (($c != 160)
6269
					AND (($c == 173)
6270
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6271
						OR (($c == 45)
6272
							AND ($i > 0) AND ($i < ($length - 1))
6273
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6274
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6275
						)
6276
					)
6277
				) {
6278
				$lastSeparator = $i;
6279
			}
6280
			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6281
				++$lines;
6282
				if ($c == 10) {
6283
					$lastSeparator = -1;
6284
					$sum = 0;
6285
				} elseif ($lastSeparator != -1) {
6286
					$i = $lastSeparator;
6287
					$lastSeparator = -1;
6288
					$sum = 0;
6289
				} else {
6290
					$sum = $charWidth;
6291
				}
6292
			} else {
6293
				$sum += $charWidth;
6294
			}
6295
		}
6296
		if ($chars[($length - 1)] == 10) {
6297
			--$lines;
6298
		}
6299
		$this->cell_padding = $prev_cell_padding;
6300
		$this->lasth = $prev_lasth;
6301
		return $lines;
6302
	}
6303
 
6304
	/**
6305
	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6306
	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6307
	 * @pre
6308
	 *  // store current object
6309
	 *  $pdf->startTransaction();
6310
	 *  // store starting values
6311
	 *  $start_y = $pdf->GetY();
6312
	 *  $start_page = $pdf->getPage();
6313
	 *  // call your printing functions with your parameters
6314
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6315
	 *  $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6316
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6317
	 *  // get the new Y
6318
	 *  $end_y = $pdf->GetY();
6319
	 *  $end_page = $pdf->getPage();
6320
	 *  // calculate height
6321
	 *  $height = 0;
6322
	 *  if ($end_page == $start_page) {
6323
	 *  	$height = $end_y - $start_y;
6324
	 *  } else {
6325
	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6326
	 *  		$this->setPage($page);
6327
	 *  		if ($page == $start_page) {
6328
	 *  			// first page
6329
	 *  			$height += $this->h - $start_y - $this->bMargin;
6330
	 *  		} elseif ($page == $end_page) {
6331
	 *  			// last page
6332
	 *  			$height += $end_y - $this->tMargin;
6333
	 *  		} else {
6334
	 *  			$height += $this->h - $this->tMargin - $this->bMargin;
6335
	 *  		}
6336
	 *  	}
6337
	 *  }
6338
	 *  // restore previous object
6339
	 *  $pdf = $pdf->rollbackTransaction();
6340
	 *
6341
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6342
	 * @param string $txt String for calculating his height
6343
	 * @param boolean $reseth if true reset the last cell height (default false).
6344
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6345
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6346
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6347
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6348
	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6349
	 * @public
6350
	 */
6351
	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6352
		// adjust internal padding
6353
		$prev_cell_padding = $this->cell_padding;
6354
		$prev_lasth = $this->lasth;
6355
		if (is_array($cellpadding)) {
6356
			$this->cell_padding = $cellpadding;
6357
		}
6358
		$this->adjustCellPadding($border);
6359
		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6360
		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6361
		$this->cell_padding = $prev_cell_padding;
6362
		$this->lasth = $prev_lasth;
6363
		return $height;
6364
	}
6365
 
6366
	/**
6367
	 * This method prints text from the current position.<br />
6368
	 * @param float $h Line height
6369
	 * @param string $txt String to print
6370
	 * @param mixed $link URL or identifier returned by AddLink()
6371
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6372
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6373
	 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6374
	 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6375
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
6376
	 * @param boolean $firstblock if true the string is the starting of a line.
6377
	 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6378
	 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6379
	 * @param array|null $margin margin array of the parent container
6380
	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6381
	 * @public
6382
	 * @since 1.5
6383
	 */
6384
	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) {
6385
		// check page for no-write regions and adapt page margins if necessary
6386
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6387
		if (strlen($txt) == 0) {
6388
			// fix empty text
6389
			$txt = ' ';
6390
		}
6391
		if (!is_array($margin)) {
6392
			// set default margins
6393
			$margin = $this->cell_margin;
6394
		}
6395
		// remove carriage returns
6396
		$s = str_replace("\r", '', $txt);
6397
		// check if string contains arabic text
6398
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6399
			$arabic = true;
6400
		} else {
6401
			$arabic = false;
6402
		}
6403
		// check if string contains RTL text
6404
		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6405
			$rtlmode = true;
6406
		} else {
6407
			$rtlmode = false;
6408
		}
6409
		// get a char width
6410
		$chrwidth = $this->GetCharWidth(46); // dot character
6411
		// get array of unicode values
6412
		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6413
		// calculate maximum width for a single character on string
6414
		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6415
		array_walk($chrw, array($this, 'getRawCharWidth'));
6416
		$maxchwidth = ((is_array($chrw) || $chrw instanceof Countable) && count($chrw) > 0) ? max($chrw) : 0;
6417
		// get array of chars
6418
		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6419
		// get the number of characters
6420
		$nb = count($chars);
6421
		// replacement for SHY character (minus symbol)
6422
		$shy_replacement = 45;
6423
		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6424
		// widht for SHY replacement
6425
		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6426
		// page width
6427
		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6428
		// calculate remaining line width ($w)
6429
		if ($this->rtl) {
6430
			$w = $this->x - $this->lMargin;
6431
		} else {
6432
			$w = $this->w - $this->rMargin - $this->x;
6433
		}
6434
		// max column width
6435
		$wmax = ($w - $wadj);
6436
		if (!$firstline) {
6437
			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6438
		}
6439
		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6440
			// the maximum width character do not fit on column
6441
			return '';
6442
		}
6443
		// minimum row height
6444
		$row_height = max($h, $this->getCellHeight($this->FontSize));
6445
		// max Y
6446
		$maxy = $this->y + $maxh - max($row_height, $h);
6447
		$start_page = $this->page;
6448
		$i = 0; // character position
6449
		$j = 0; // current starting position
6450
		$sep = -1; // position of the last blank space
6451
		$prevsep = $sep; // previous separator
6452
		$shy = false; // true if the last blank is a soft hypen (SHY)
6453
		$prevshy = $shy; // previous shy mode
6454
		$l = 0; // current string length
6455
		$nl = 0; //number of lines
6456
		$linebreak = false;
6457
		$pc = 0; // previous character
6458
		// for each character
6459
		while ($i < $nb) {
6460
			if (($maxh > 0) AND ($this->y > $maxy) ) {
6461
				break;
6462
			}
6463
			//Get the current character
6464
			$c = $chars[$i];
6465
			if ($c == 10) { // 10 = "\n" = new line
6466
				//Explicit line break
6467
				if ($align == 'J') {
6468
					if ($this->rtl) {
6469
						$talign = 'R';
6470
					} else {
6471
						$talign = 'L';
6472
					}
6473
				} else {
6474
					$talign = $align;
6475
				}
6476
				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6477
				if ($firstline) {
6478
					$startx = $this->x;
6479
					$tmparr = array_slice($chars, $j, ($i - $j));
6480
					if ($rtlmode) {
6481
						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6482
					}
6483
					$linew = $this->GetArrStringWidth($tmparr);
6484
					unset($tmparr);
6485
					if ($this->rtl) {
6486
						$this->endlinex = $startx - $linew;
6487
					} else {
6488
						$this->endlinex = $startx + $linew;
6489
					}
6490
					$w = $linew;
6491
					$tmpcellpadding = $this->cell_padding;
6492
					if ($maxh == 0) {
6493
						$this->setCellPadding(0);
6494
					}
6495
				}
6496
				if ($firstblock AND $this->isRTLTextDir()) {
6497
					$tmpstr = $this->stringRightTrim($tmpstr);
6498
				}
6499
				// Skip newlines at the beginning of a page or column
6500
				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6501
					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6502
				}
6503
				unset($tmpstr);
6504
				if ($firstline) {
6505
					$this->cell_padding = $tmpcellpadding;
6506
					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6507
				}
6508
				++$nl;
6509
				$j = $i + 1;
6510
				$l = 0;
6511
				$sep = -1;
6512
				$prevsep = $sep;
6513
				$shy = false;
6514
				// account for margin changes
6515
				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6516
					if ($this->AcceptPageBreak())
6517
					{
6518
						if ($this->rtl) {
6519
							$this->x -= $margin['R'];
6520
						} else {
6521
							$this->x += $margin['L'];
6522
						}
6523
						$this->lMargin += $margin['L'];
6524
						$this->rMargin += $margin['R'];
6525
					}
6526
				}
6527
				$w = $this->getRemainingWidth();
6528
				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6529
			} else {
6530
				// 160 is the non-breaking space.
6531
				// 173 is SHY (Soft Hypen).
6532
				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6533
				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6534
				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6535
				if (($c != 160)
6536
					AND (($c == 173)
6537
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6538
						OR (($c == 45)
6539
							AND ($i < ($nb - 1))
6540
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6541
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6542
						)
6543
					)
6544
				) {
6545
					// update last blank space position
6546
					$prevsep = $sep;
6547
					$sep = $i;
6548
					// check if is a SHY
6549
					if (($c == 173) OR ($c == 45)) {
6550
						$prevshy = $shy;
6551
						$shy = true;
6552
						if ($pc == 45) {
6553
							$tmp_shy_replacement_width = 0;
6554
							$tmp_shy_replacement_char = '';
6555
						} else {
6556
							$tmp_shy_replacement_width = $shy_replacement_width;
6557
							$tmp_shy_replacement_char = $shy_replacement_char;
6558
						}
6559
					} else {
6560
						$shy = false;
6561
					}
6562
				}
6563
				// update string length
6564
				if ($this->isUnicodeFont() AND ($arabic)) {
6565
					// with bidirectional algorithm some chars may be changed affecting the line length
6566
					// *** very slow ***
6567
					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6568
				} else {
6569
					$l += $this->GetCharWidth($c, ($i+1 < $nb));
6570
				}
6571
				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6572
					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6573
						$sep = $prevsep;
6574
						$shy = $prevshy;
6575
					}
6576
					// we have reached the end of column
6577
					if ($sep == -1) {
6578
						// check if the line was already started
6579
						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6580
							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6581
							// print a void cell and go to next line
6582
							$this->Cell($w, $h, '', 0, 1);
6583
							$linebreak = true;
6584
							if ($firstline) {
6585
								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6586
							}
6587
						} else {
6588
							// truncate the word because do not fit on column
6589
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6590
							if ($firstline) {
6591
								$startx = $this->x;
6592
								$tmparr = array_slice($chars, $j, ($i - $j));
6593
								if ($rtlmode) {
6594
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6595
								}
6596
								$linew = $this->GetArrStringWidth($tmparr);
6597
								unset($tmparr);
6598
								if ($this->rtl) {
6599
									$this->endlinex = $startx - $linew;
6600
								} else {
6601
									$this->endlinex = $startx + $linew;
6602
								}
6603
								$w = $linew;
6604
								$tmpcellpadding = $this->cell_padding;
6605
								if ($maxh == 0) {
6606
									$this->setCellPadding(0);
6607
								}
6608
							}
6609
							if ($firstblock AND $this->isRTLTextDir()) {
6610
								$tmpstr = $this->stringRightTrim($tmpstr);
6611
							}
6612
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6613
							unset($tmpstr);
6614
							if ($firstline) {
6615
								$this->cell_padding = $tmpcellpadding;
6616
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6617
							}
6618
							$j = $i;
6619
							--$i;
6620
						}
6621
					} else {
6622
						// word wrapping
6623
						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6624
							$endspace = 1;
6625
						} else {
6626
							$endspace = 0;
6627
						}
6628
						// check the length of the next string
6629
						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6630
						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6631
						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6632
							// truncate the word because do not fit on a full page width
6633
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6634
							if ($firstline) {
6635
								$startx = $this->x;
6636
								$tmparr = array_slice($chars, $j, ($i - $j));
6637
								if ($rtlmode) {
6638
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6639
								}
6640
								$linew = $this->GetArrStringWidth($tmparr);
6641
								unset($tmparr);
6642
								if ($this->rtl) {
6643
									$this->endlinex = ($startx - $linew);
6644
								} else {
6645
									$this->endlinex = ($startx + $linew);
6646
								}
6647
								$w = $linew;
6648
								$tmpcellpadding = $this->cell_padding;
6649
								if ($maxh == 0) {
6650
									$this->setCellPadding(0);
6651
								}
6652
							}
6653
							if ($firstblock AND $this->isRTLTextDir()) {
6654
								$tmpstr = $this->stringRightTrim($tmpstr);
6655
							}
6656
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6657
							unset($tmpstr);
6658
							if ($firstline) {
6659
								$this->cell_padding = $tmpcellpadding;
6660
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6661
							}
6662
							$j = $i;
6663
							--$i;
6664
						} else {
6665
							// word wrapping
6666
							if ($shy) {
6667
								// add hypen (minus symbol) at the end of the line
6668
								$shy_width = $tmp_shy_replacement_width;
6669
								if ($this->rtl) {
6670
									$shy_char_left = $tmp_shy_replacement_char;
6671
									$shy_char_right = '';
6672
								} else {
6673
									$shy_char_left = '';
6674
									$shy_char_right = $tmp_shy_replacement_char;
6675
								}
6676
							} else {
6677
								$shy_width = 0;
6678
								$shy_char_left = '';
6679
								$shy_char_right = '';
6680
							}
6681
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6682
							if ($firstline) {
6683
								$startx = $this->x;
6684
								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6685
								if ($rtlmode) {
6686
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6687
								}
6688
								$linew = $this->GetArrStringWidth($tmparr);
6689
								unset($tmparr);
6690
								if ($this->rtl) {
6691
									$this->endlinex = $startx - $linew - $shy_width;
6692
								} else {
6693
									$this->endlinex = $startx + $linew + $shy_width;
6694
								}
6695
								$w = $linew;
6696
								$tmpcellpadding = $this->cell_padding;
6697
								if ($maxh == 0) {
6698
									$this->setCellPadding(0);
6699
								}
6700
							}
6701
							// print the line
6702
							if ($firstblock AND $this->isRTLTextDir()) {
6703
								$tmpstr = $this->stringRightTrim($tmpstr);
6704
							}
6705
							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6706
							unset($tmpstr);
6707
							if ($firstline) {
6708
								if ($chars[$sep] == 45) {
6709
									$endspace += 1;
6710
								}
6711
								// return the remaining text
6712
								$this->cell_padding = $tmpcellpadding;
6713
								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6714
							}
6715
							$i = $sep;
6716
							$sep = -1;
6717
							$shy = false;
6718
							$j = ($i + 1);
6719
						}
6720
					}
6721
					// account for margin changes
6722
					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6723
						if ($this->AcceptPageBreak())
6724
						{
6725
							if ($this->rtl) {
6726
								$this->x -= $margin['R'];
6727
							} else {
6728
								$this->x += $margin['L'];
6729
							}
6730
							$this->lMargin += $margin['L'];
6731
							$this->rMargin += $margin['R'];
6732
						}
6733
					}
6734
					$w = $this->getRemainingWidth();
6735
					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6736
					if ($linebreak) {
6737
						$linebreak = false;
6738
					} else {
6739
						++$nl;
6740
						$l = 0;
6741
					}
6742
				}
6743
			}
6744
			// save last character
6745
			$pc = $c;
6746
			++$i;
6747
		} // end while i < nb
6748
		// print last substring (if any)
6749
		if ($l > 0) {
6750
			switch ($align) {
6751
				case 'J':
6752
				case 'C': {
6753
					break;
6754
				}
6755
				case 'L': {
6756
					if (!$this->rtl) {
6757
						$w = $l;
6758
					}
6759
					break;
6760
				}
6761
				case 'R': {
6762
					if ($this->rtl) {
6763
						$w = $l;
6764
					}
6765
					break;
6766
				}
6767
				default: {
6768
					$w = $l;
6769
					break;
6770
				}
6771
			}
6772
			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6773
			if ($firstline) {
6774
				$startx = $this->x;
6775
				$tmparr = array_slice($chars, $j, ($nb - $j));
6776
				if ($rtlmode) {
6777
					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6778
				}
6779
				$linew = $this->GetArrStringWidth($tmparr);
6780
				unset($tmparr);
6781
				if ($this->rtl) {
6782
					$this->endlinex = $startx - $linew;
6783
				} else {
6784
					$this->endlinex = $startx + $linew;
6785
				}
6786
				$w = $linew;
6787
				$tmpcellpadding = $this->cell_padding;
6788
				if ($maxh == 0) {
6789
					$this->setCellPadding(0);
6790
				}
6791
			}
6792
			if ($firstblock AND $this->isRTLTextDir()) {
6793
				$tmpstr = $this->stringRightTrim($tmpstr);
6794
			}
6795
			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6796
			unset($tmpstr);
6797
			if ($firstline) {
6798
				$this->cell_padding = $tmpcellpadding;
6799
				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6800
			}
6801
			++$nl;
6802
		}
6803
		if ($firstline) {
6804
			return '';
6805
		}
6806
		return $nl;
6807
	}
6808
 
6809
	/**
6810
	 * Returns the remaining width between the current position and margins.
6811
	 * @return float Return the remaining width
6812
	 * @protected
6813
	 */
6814
	protected function getRemainingWidth() {
6815
		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6816
		if ($this->rtl) {
6817
			return ($this->x - $this->lMargin);
6818
		} else {
6819
			return ($this->w - $this->rMargin - $this->x);
6820
		}
6821
	}
6822
 
6823
	/**
6824
	 * Set the block dimensions accounting for page breaks and page/column fitting
6825
	 * @param float $w width
6826
	 * @param float $h height
6827
	 * @param float $x X coordinate
6828
	 * @param float $y Y coodiante
6829
	 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6830
	 * @return array array($w, $h, $x, $y)
6831
	 * @protected
6832
	 * @since 5.5.009 (2010-07-05)
6833
	 */
6834
	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6835
		if ($w <= 0) {
6836
			// set maximum width
6837
			$w = ($this->w - $this->lMargin - $this->rMargin);
6838
			if ($w <= 0) {
6839
				$w = 1;
6840
			}
6841
		}
6842
		if ($h <= 0) {
6843
			// set maximum height
6844
			$h = ($this->PageBreakTrigger - $this->tMargin);
6845
			if ($h <= 0) {
6846
				$h = 1;
6847
			}
6848
		}
6849
		// resize the block to be vertically contained on a single page or single column
6850
		if ($fitonpage OR $this->AutoPageBreak) {
6851
			$ratio_wh = ($w / $h);
6852
			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6853
				$h = $this->PageBreakTrigger - $this->tMargin;
6854
				$w = ($h * $ratio_wh);
6855
			}
6856
			// resize the block to be horizontally contained on a single page or single column
6857
			if ($fitonpage) {
6858
				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6859
				if ($w > $maxw) {
6860
					$w = $maxw;
6861
					$h = ($w / $ratio_wh);
6862
				}
6863
			}
6864
		}
6865
		// Check whether we need a new page or new column first as this does not fit
6866
		$prev_x = $this->x;
6867
		$prev_y = $this->y;
6868
		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6869
			$y = $this->y;
6870
			if ($this->rtl) {
6871
				$x += ($prev_x - $this->x);
6872
			} else {
6873
				$x += ($this->x - $prev_x);
6874
			}
6875
			$this->newline = true;
6876
		}
6877
		// resize the block to be contained on the remaining available page or column space
6878
		if ($fitonpage) {
6879
			// fallback to avoid division by zero
6880
			$h = $h == 0 ? 1 : $h;
6881
			$ratio_wh = ($w / $h);
6882
			if (($y + $h) > $this->PageBreakTrigger) {
6883
				$h = $this->PageBreakTrigger - $y;
6884
				$w = ($h * $ratio_wh);
6885
			}
6886
			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6887
				$w = $this->w - $this->rMargin - $x;
6888
				$h = ($w / $ratio_wh);
6889
			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6890
				$w = $x - $this->lMargin;
6891
				$h = ($w / $ratio_wh);
6892
			}
6893
		}
6894
		return array($w, $h, $x, $y);
6895
	}
6896
 
6897
	/**
6898
	 * Puts an image in the page.
6899
	 * The upper-left corner must be given.
6900
	 * The dimensions can be specified in different ways:<ul>
6901
	 * <li>explicit width and height (expressed in user unit)</li>
6902
	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6903
	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6904
	 * 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;
6905
	 * The format can be specified explicitly or inferred from the file extension.<br />
6906
	 * It is possible to put a link on the image.<br />
6907
	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6908
	 * @param string $file Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6909
	 * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6910
	 * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6911
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6912
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6913
	 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6914
	 * @param mixed $link URL or identifier returned by AddLink().
6915
	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6916
	 * @param mixed $resize If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6917
	 * @param int $dpi dot-per-inch resolution used on resize
6918
	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6919
	 * @param boolean $ismask true if this image is a mask, false otherwise
6920
	 * @param mixed $imgmask image object returned by this function or false
6921
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6922
	 * @param mixed $fitbox If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6923
	 * @param boolean $hidden If true do not display the image.
6924
	 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6925
	 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6926
	 * @param array $altimgs Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6927
	 * @return mixed|false image information
6928
	 * @public
6929
	 * @since 1.1
6930
	 */
6931
	public function Image($file, $x=null, $y=null, $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()) {
6932
		if ($this->state != 2) {
6933
			return false;
6934
		}
6935
		if (TCPDF_STATIC::empty_string($x)) {
6936
			$x = $this->x;
6937
		}
6938
		if (TCPDF_STATIC::empty_string($y)) {
6939
			$y = $this->y;
6940
		}
6941
		// check page for no-write regions and adapt page margins if necessary
6942
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6943
		$exurl = ''; // external streams
6944
		$imsize = FALSE;
6945
 
6946
        // Make sure the file variable is not empty or null because accessing $file[0] later
6947
        // results in error when running PHP 7.4
6948
        if (empty($file)) {
6949
            return false;
6950
        }
6951
		// check if we are passing an image as file or string
6952
		if ($file[0] === '@') {
6953
			// image from string
6954
			$imgdata = substr($file, 1);
6955
		} else { // image file
6956
			if ($file[0] === '*') {
6957
				// image as external stream
6958
				$file = substr($file, 1);
6959
				$exurl = $file;
6960
			}
6961
			// check if file exist and it is valid
6962
			if (!@$this->fileExists($file)) {
6963
				return false;
6964
			}
6965
            if (false !== $info = $this->getImageBuffer($file)) {
6966
                $imsize = array($info['w'], $info['h']);
6967
            } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
6968
                $imgdata = $this->getCachedFileContents($file);
6969
            }
6970
		}
6971
		if (!empty($imgdata)) {
6972
			// copy image to cache
6973
			$original_file = $file;
6974
			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6975
			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6976
			if (!$fp) {
6977
				$this->Error('Unable to write file: '.$file);
6978
			}
6979
			fwrite($fp, $imgdata);
6980
			fclose($fp);
6981
			unset($imgdata);
6982
			$imsize = @getimagesize($file);
6983
			if ($imsize === FALSE) {
6984
				unlink($file);
6985
				$file = $original_file;
6986
			}
6987
		}
6988
		if ($imsize === FALSE) {
6989
			if (($w > 0) AND ($h > 0)) {
6990
				// get measures from specified data
6991
				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6992
				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6993
				$imsize = array($pw, $ph);
6994
			} else {
6995
				$this->Error('[Image] Unable to get the size of the image: '.$file);
6996
			}
6997
		}
6998
		// file hash
6999
		$filehash = md5($file);
7000
		// get original image width and height in pixels
7001
		list($pixw, $pixh) = $imsize;
7002
		// calculate image width and height on document
7003
		if (($w <= 0) AND ($h <= 0)) {
7004
			// convert image size to document unit
7005
			$w = $this->pixelsToUnits($pixw);
7006
			$h = $this->pixelsToUnits($pixh);
7007
		} elseif ($w <= 0) {
7008
			$w = $h * $pixw / $pixh;
7009
		} elseif ($h <= 0) {
7010
			$h = $w * $pixh / $pixw;
7011
		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7012
			if (strlen($fitbox) !== 2) {
7013
				// set default alignment
7014
				$fitbox = '--';
7015
			}
7016
			// scale image dimensions proportionally to fit within the ($w, $h) box
7017
			if ((($w * $pixh) / ($h * $pixw)) < 1) {
7018
				// store current height
7019
				$oldh = $h;
7020
				// calculate new height
7021
				$h = $w * $pixh / $pixw;
7022
				// height difference
7023
				$hdiff = ($oldh - $h);
7024
				// vertical alignment
7025
				switch (strtoupper($fitbox[1])) {
7026
					case 'T': {
7027
						break;
7028
					}
7029
					case 'M': {
7030
						$y += ($hdiff / 2);
7031
						break;
7032
					}
7033
					case 'B': {
7034
						$y += $hdiff;
7035
						break;
7036
					}
7037
				}
7038
			} else {
7039
				// store current width
7040
				$oldw = $w;
7041
				// calculate new width
7042
				$w = $h * $pixw / $pixh;
7043
				// width difference
7044
				$wdiff = ($oldw - $w);
7045
				// horizontal alignment
7046
				switch (strtoupper($fitbox[0])) {
7047
					case 'L': {
7048
						if ($this->rtl) {
7049
							$x -= $wdiff;
7050
						}
7051
						break;
7052
					}
7053
					case 'C': {
7054
						if ($this->rtl) {
7055
							$x -= ($wdiff / 2);
7056
						} else {
7057
							$x += ($wdiff / 2);
7058
						}
7059
						break;
7060
					}
7061
					case 'R': {
7062
						if (!$this->rtl) {
7063
							$x += $wdiff;
7064
						}
7065
						break;
7066
					}
7067
				}
7068
			}
7069
		}
7070
		// fit the image on available space
7071
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7072
		// calculate new minimum dimensions in pixels
7073
		$neww = round($w * $this->k * $dpi / $this->dpi);
7074
		$newh = round($h * $this->k * $dpi / $this->dpi);
7075
		// check if resize is necessary (resize is used only to reduce the image)
7076
		$newsize = ($neww * $newh);
7077
		$pixsize = ($pixw * $pixh);
7078
		if (intval($resize) == 2) {
7079
			$resize = true;
7080
		} elseif ($newsize >= $pixsize) {
7081
			$resize = false;
7082
		}
7083
		// check if image has been already added on document
7084
		$newimage = true;
7085
		if (in_array($file, $this->imagekeys)) {
7086
			$newimage = false;
7087
			// get existing image data
7088
			$info = $this->getImageBuffer($file);
7089
			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7090
				// check if the newer image is larger
7091
				$oldsize = ($info['w'] * $info['h']);
7092
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7093
					$newimage = true;
7094
				}
7095
			}
7096
		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7097
			// create temp image file (without alpha channel)
7098
			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7099
			// create temp alpha file
7100
			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7101
			// check for cached images
7102
			if (in_array($tempfile_plain, $this->imagekeys)) {
7103
				// get existing image data
7104
				$info = $this->getImageBuffer($tempfile_plain);
7105
				// check if the newer image is larger
7106
				$oldsize = ($info['w'] * $info['h']);
7107
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7108
					$newimage = true;
7109
				} else {
7110
					$newimage = false;
7111
					// embed mask image
7112
					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7113
					// embed image, masked with previously embedded mask
7114
					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7115
				}
7116
			}
7117
		}
7118
		if ($newimage) {
7119
			//First use of image, get info
7120
			$type = strtolower($type);
7121
			if ($type == '') {
7122
				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7123
			} elseif ($type == 'jpg') {
7124
				$type = 'jpeg';
7125
			}
7126
			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7127
			$mtd = '_parse'.$type;
7128
			// GD image handler function
7129
			$gdfunction = 'imagecreatefrom'.$type;
7130
			$info = false;
7131
			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7132
				// TCPDF image functions
7133
				$info = TCPDF_IMAGES::$mtd($file);
7134
				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7135
					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7136
					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7137
				}
7138
			}
7139
			if (($info === false) AND function_exists($gdfunction)) {
7140
				try {
7141
					// GD library
7142
					$img = $gdfunction($file);
7143
					if ($img !== false) {
7144
						if ($resize) {
7145
							$imgr = imagecreatetruecolor($neww, $newh);
7146
							if (($type == 'gif') OR ($type == 'png')) {
7147
								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7148
							}
7149
							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7150
							$img = $imgr;
7151
						}
7152
						if (($type == 'gif') OR ($type == 'png')) {
7153
							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7154
						} else {
7155
							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7156
						}
7157
					}
7158
				} catch(Exception $e) {
7159
					$info = false;
7160
				}
7161
			}
7162
			if (($info === false) AND extension_loaded('imagick')) {
7163
				try {
7164
					// ImageMagick library
7165
					$img = new Imagick();
7166
					if ($type == 'svg') {
7167
						if ($file[0] === '@') {
7168
							// image from string
7169
							$svgimg = substr($file, 1);
7170
						} else {
7171
							// get SVG file content
7172
                            $svgimg = $this->getCachedFileContents($file);
7173
						}
7174
						if ($svgimg !== FALSE) {
7175
							// get width and height
7176
							$regs = array();
7177
							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7178
								$svgtag = $regs[1];
7179
								$tmp = array();
7180
								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7181
									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7182
									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7183
									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7184
								} else {
7185
									$ow = $w;
7186
								}
7187
								$tmp = array();
7188
								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7189
									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7190
									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7191
									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7192
								} else {
7193
									$oh = $h;
7194
								}
7195
								$tmp = array();
7196
								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7197
									$vbw = ($ow * $this->imgscale * $this->k);
7198
									$vbh = ($oh * $this->imgscale * $this->k);
7199
									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7200
									$svgtag = $vbox.$svgtag;
7201
								}
7202
								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7203
							}
7204
							$img->readImageBlob($svgimg);
7205
						}
7206
					} else {
7207
						$img->readImage($file);
7208
					}
7209
					if ($resize) {
7210
						$img->resizeImage($neww, $newh, 10, 1, false);
7211
					}
7212
					$img->setCompressionQuality($this->jpeg_quality);
7213
					$img->setImageFormat('jpeg');
7214
					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7215
					$img->writeImage($tempname);
7216
					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7217
					unlink($tempname);
7218
					$img->destroy();
7219
				} catch(Exception $e) {
7220
					$info = false;
7221
				}
7222
			}
7223
			if ($info === false) {
7224
				// unable to process image
7225
				return false;
7226
			}
7227
			if ($ismask) {
7228
				// force grayscale
7229
				$info['cs'] = 'DeviceGray';
7230
			}
7231
			if ($imgmask !== false) {
7232
				$info['masked'] = $imgmask;
7233
			}
7234
			if (!empty($exurl)) {
7235
				$info['exurl'] = $exurl;
7236
			}
7237
			// array of alternative images
7238
			$info['altimgs'] = $altimgs;
7239
			// add image to document
7240
			$info['i'] = $this->setImageBuffer($file, $info);
7241
		}
7242
		// set alignment
7243
		$this->img_rb_x = $x + $w;
7244
		$this->img_rb_y = $y + $h;
7245
 
7246
		// set alignment
7247
		if ($palign == 'L') {
7248
			$ximg = $this->lMargin;
7249
		} elseif ($palign == 'C') {
7250
			$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7251
		} elseif ($palign == 'R') {
7252
			$ximg = $this->w - $this->rMargin - $w;
7253
		} else {
7254
			$ximg = $this->rtl ? $x - $w : $x;
7255
		}
7256
 
7257
		if ($ismask OR $hidden) {
7258
			// image is not displayed
7259
			return $info['i'];
7260
		}
7261
		$xkimg = $ximg * $this->k;
7262
		if (!$alt) {
7263
			// only non-alternative immages will be set
7264
			$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']));
7265
		}
7266
		if (!empty($border)) {
7267
			$bx = $this->x;
7268
			$by = $this->y;
7269
			$this->x = $ximg;
7270
			if ($this->rtl) {
7271
				$this->x += $w;
7272
			}
7273
			$this->y = $y;
7274
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7275
			$this->x = $bx;
7276
			$this->y = $by;
7277
		}
7278
		if ($link) {
7279
			$this->Link($ximg, $y, $w, $h, $link, 0);
7280
		}
7281
		// set pointer to align the next text/objects
7282
		switch($align) {
7283
			case 'T': {
7284
				$this->y = $y;
7285
				$this->x = $this->img_rb_x;
7286
				break;
7287
			}
7288
			case 'M': {
7289
				$this->y = $y + round($h/2);
7290
				$this->x = $this->img_rb_x;
7291
				break;
7292
			}
7293
			case 'B': {
7294
				$this->y = $this->img_rb_y;
7295
				$this->x = $this->img_rb_x;
7296
				break;
7297
			}
7298
			case 'N': {
7299
				$this->setY($this->img_rb_y);
7300
				break;
7301
			}
7302
			default:{
7303
				break;
7304
			}
7305
		}
7306
		$this->endlinex = $this->img_rb_x;
7307
		if ($this->inxobj) {
7308
			// we are inside an XObject template
7309
			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7310
		}
7311
		return $info['i'];
7312
	}
7313
 
7314
	/**
7315
	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7316
	 * @param string $file Name of the file containing the image.
7317
	 * @param float $x Abscissa of the upper-left corner.
7318
	 * @param float $y Ordinate of the upper-left corner.
7319
	 * @param float $wpx Original width of the image in pixels.
7320
	 * @param float $hpx original height of the image in pixels.
7321
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7322
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7323
	 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7324
	 * @param mixed $link URL or identifier returned by AddLink().
7325
	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7326
	 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7327
	 * @param int $dpi dot-per-inch resolution used on resize
7328
	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7329
	 * @param string $filehash File hash used to build unique file names.
7330
	 * @author Nicola Asuni
7331
	 * @protected
7332
	 * @since 4.3.007 (2008-12-04)
7333
	 * @see Image()
7334
	 */
7335
	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7336
		// create temp images
7337
		if (empty($filehash)) {
7338
			$filehash = md5($file);
7339
		}
7340
		// create temp image file (without alpha channel)
7341
		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7342
		// create temp alpha file
7343
		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7344
		$parsed = false;
7345
		$parse_error = '';
7346
		// ImageMagick extension
7347
		if (($parsed === false) AND extension_loaded('imagick')) {
7348
			try {
7349
				// ImageMagick library
7350
				$img = new Imagick();
7351
				$img->readImage($file);
7352
				// clone image object
7353
				$imga = TCPDF_STATIC::objclone($img);
7354
				// extract alpha channel
7355
				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7356
					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7357
				} else {
7358
					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7359
					$img->negateImage(true);
7360
				}
7361
				$img->setImageFormat('png');
7362
				$img->writeImage($tempfile_alpha);
7363
				// remove alpha channel
7364
				if (method_exists($imga, 'setImageMatte')) {
7365
					$imga->setImageMatte(false);
7366
				} else {
7367
					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7368
				}
7369
				$imga->setImageFormat('png');
7370
				$imga->writeImage($tempfile_plain);
7371
				$parsed = true;
7372
			} catch (Exception $e) {
7373
				// Imagemagick fails, try with GD
7374
				$parse_error = 'Imagick library error: '.$e->getMessage();
7375
			}
7376
		}
7377
		// GD extension
7378
		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7379
			try {
7380
				// generate images
7381
				$img = imagecreatefrompng($file);
7382
				$imgalpha = imagecreate($wpx, $hpx);
7383
				// generate gray scale palette (0 -> 255)
7384
				for ($c = 0; $c < 256; ++$c) {
7385
					ImageColorAllocate($imgalpha, $c, $c, $c);
7386
				}
7387
				// extract alpha channel
7388
				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7389
					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7390
						$color = imagecolorat($img, $xpx, $ypx);
7391
						// get and correct gamma color
7392
						$alpha = $this->getGDgamma($img, $color);
7393
						imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha);
7394
					}
7395
				}
7396
				imagepng($imgalpha, $tempfile_alpha);
7397
				imagedestroy($imgalpha);
7398
				// extract image without alpha channel
7399
				$imgplain = imagecreatetruecolor($wpx, $hpx);
7400
				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7401
				imagepng($imgplain, $tempfile_plain);
7402
				imagedestroy($imgplain);
7403
				$parsed = true;
7404
			} catch (Exception $e) {
7405
				// GD fails
7406
				$parse_error = 'GD library error: '.$e->getMessage();
7407
			}
7408
		}
7409
		if ($parsed === false) {
7410
			if (empty($parse_error)) {
7411
				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7412
			} else {
7413
				$this->Error($parse_error);
7414
			}
7415
		}
7416
		// embed mask image
7417
		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7418
		// embed image, masked with previously embedded mask
7419
		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7420
	}
7421
 
7422
	/**
7423
	 * Get the GD-corrected PNG gamma value from alpha color
7424
	 * @param resource $img GD image Resource ID.
7425
	 * @param int $c alpha color
7426
	 * @protected
7427
	 * @since 4.3.007 (2008-12-04)
7428
	 */
7429
	protected function getGDgamma($img, $c) {
7430
		if (!isset($this->gdgammacache['#'.$c])) {
7431
			$colors = imagecolorsforindex($img, $c);
7432
			// GD alpha is only 7 bit (0 -> 127)
7433
			$this->gdgammacache['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255);
7434
			// correct gamma
7435
			$this->gdgammacache['#'.$c] = (int) (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7436
			// store the latest values on cache to improve performances
7437
			if (count($this->gdgammacache) > 8) {
7438
				// remove one element from the cache array
7439
				array_shift($this->gdgammacache);
7440
			}
7441
		}
7442
		return $this->gdgammacache['#'.$c];
7443
	}
7444
 
7445
	/**
7446
	 * Performs a line break.
7447
	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7448
	 * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell.
7449
	 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7450
	 * @public
7451
	 * @since 1.0
7452
	 * @see Cell()
7453
	 */
7454
	public function Ln($h=null, $cell=false) {
7455
		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'])) {
7456
			// revove vertical space from the top of the column
7457
			return;
7458
		}
7459
		if ($cell) {
7460
			if ($this->rtl) {
7461
				$cellpadding = $this->cell_padding['R'];
7462
			} else {
7463
				$cellpadding = $this->cell_padding['L'];
7464
			}
7465
		} else {
7466
			$cellpadding = 0;
7467
		}
7468
		if ($this->rtl) {
7469
			$this->x = $this->w - $this->rMargin - $cellpadding;
7470
		} else {
7471
			$this->x = $this->lMargin + $cellpadding;
7472
		}
7473
		if (TCPDF_STATIC::empty_string($h)) {
7474
			$h = $this->lasth;
7475
		}
7476
		$this->y += $h;
7477
		$this->newline = true;
7478
	}
7479
 
7480
	/**
7481
	 * Returns the relative X value of current position.
7482
	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7483
	 * @return float
7484
	 * @public
7485
	 * @since 1.2
7486
	 * @see SetX(), GetY(), SetY()
7487
	 */
7488
	public function GetX() {
7489
		//Get x position
7490
		if ($this->rtl) {
7491
			return ($this->w - $this->x);
7492
		} else {
7493
			return $this->x;
7494
		}
7495
	}
7496
 
7497
	/**
7498
	 * Returns the absolute X value of current position.
7499
	 * @return float
7500
	 * @public
7501
	 * @since 1.2
7502
	 * @see SetX(), GetY(), SetY()
7503
	 */
7504
	public function GetAbsX() {
7505
		return $this->x;
7506
	}
7507
 
7508
	/**
7509
	 * Returns the ordinate of the current position.
7510
	 * @return float
7511
	 * @public
7512
	 * @since 1.0
7513
	 * @see SetY(), GetX(), SetX()
7514
	 */
7515
	public function GetY() {
7516
		return $this->y;
7517
	}
7518
 
7519
	/**
7520
	 * Defines the abscissa of the current position.
7521
	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7522
	 * @param float $x The value of the abscissa in user units.
7523
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7524
	 * @public
7525
	 * @since 1.2
7526
	 * @see GetX(), GetY(), SetY(), SetXY()
7527
	 */
7528
	public function setX($x, $rtloff=false) {
7529
		$x = floatval($x);
7530
		if (!$rtloff AND $this->rtl) {
7531
			if ($x >= 0) {
7532
				$this->x = $this->w - $x;
7533
			} else {
7534
				$this->x = abs($x);
7535
			}
7536
		} else {
7537
			if ($x >= 0) {
7538
				$this->x = $x;
7539
			} else {
7540
				$this->x = $this->w + $x;
7541
			}
7542
		}
7543
		if ($this->x < 0) {
7544
			$this->x = 0;
7545
		}
7546
		if ($this->x > $this->w) {
7547
			$this->x = $this->w;
7548
		}
7549
	}
7550
 
7551
	/**
7552
	 * Moves the current abscissa back to the left margin and sets the ordinate.
7553
	 * If the passed value is negative, it is relative to the bottom of the page.
7554
	 * @param float $y The value of the ordinate in user units.
7555
	 * @param bool $resetx if true (default) reset the X position.
7556
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7557
	 * @public
7558
	 * @since 1.0
7559
	 * @see GetX(), GetY(), SetY(), SetXY()
7560
	 */
7561
	public function setY($y, $resetx=true, $rtloff=false) {
7562
		$y = floatval($y);
7563
		if ($resetx) {
7564
			//reset x
7565
			if (!$rtloff AND $this->rtl) {
7566
				$this->x = $this->w - $this->rMargin;
7567
			} else {
7568
				$this->x = $this->lMargin;
7569
			}
7570
		}
7571
		if ($y >= 0) {
7572
			$this->y = $y;
7573
		} else {
7574
			$this->y = $this->h + $y;
7575
		}
7576
		if ($this->y < 0) {
7577
			$this->y = 0;
7578
		}
7579
		if ($this->y > $this->h) {
7580
			$this->y = $this->h;
7581
		}
7582
	}
7583
 
7584
	/**
7585
	 * Defines the abscissa and ordinate of the current position.
7586
	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7587
	 * @param float $x The value of the abscissa.
7588
	 * @param float $y The value of the ordinate.
7589
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7590
	 * @public
7591
	 * @since 1.2
7592
	 * @see SetX(), SetY()
7593
	 */
7594
	public function setXY($x, $y, $rtloff=false) {
7595
		$this->setY($y, false, $rtloff);
7596
		$this->setX($x, $rtloff);
7597
	}
7598
 
7599
	/**
7600
	 * Set the absolute X coordinate of the current pointer.
7601
	 * @param float $x The value of the abscissa in user units.
7602
	 * @public
7603
	 * @since 5.9.186 (2012-09-13)
7604
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7605
	 */
7606
	public function setAbsX($x) {
7607
		$this->x = floatval($x);
7608
	}
7609
 
7610
	/**
7611
	 * Set the absolute Y coordinate of the current pointer.
7612
	 * @param float $y (float) The value of the ordinate in user units.
7613
	 * @public
7614
	 * @since 5.9.186 (2012-09-13)
7615
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7616
	 */
7617
	public function setAbsY($y) {
7618
		$this->y = floatval($y);
7619
	}
7620
 
7621
	/**
7622
	 * Set the absolute X and Y coordinates of the current pointer.
7623
	 * @param float $x The value of the abscissa in user units.
7624
	 * @param float $y (float) The value of the ordinate in user units.
7625
	 * @public
7626
	 * @since 5.9.186 (2012-09-13)
7627
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7628
	 */
7629
	public function setAbsXY($x, $y) {
7630
		$this->setAbsX($x);
7631
		$this->setAbsY($y);
7632
	}
7633
 
7634
	/**
7635
	 * Send the document to a given destination: string, local file or browser.
7636
	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7637
	 * The method first calls Close() if necessary to terminate the document.
7638
	 * @param string $name The name of the file when saved
7639
	 * @param string $dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7640
	 * @return string
7641
	 * @public
7642
	 * @since 1.0
7643
	 * @see Close()
7644
	 */
7645
	public function Output($name='doc.pdf', $dest='I') {
7646
		//Output PDF to some destination
7647
		//Finish document if necessary
7648
		if ($this->state < 3) {
7649
			$this->Close();
7650
		}
7651
		//Normalize parameters
7652
		if (is_bool($dest)) {
7653
			$dest = $dest ? 'D' : 'F';
7654
		}
7655
		$dest = strtoupper($dest);
7656
 
7657
		if ($this->sign) {
7658
			// *** apply digital signature to the document ***
7659
			// get the document content
7660
			$pdfdoc = $this->getBuffer();
7661
			// remove last newline
7662
			$pdfdoc = substr($pdfdoc, 0, -1);
7663
			// remove filler space
7664
			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7665
			// define the ByteRange
7666
			$byte_range = array();
7667
			$byte_range[0] = 0;
7668
			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7669
			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7670
			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7671
			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7672
			// replace the ByteRange
7673
			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7674
			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7675
			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7676
			// write the document to a temporary folder
7677
			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7678
			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7679
			if (!$f) {
7680
				$this->Error('Unable to create temporary file: '.$tempdoc);
7681
			}
7682
			$pdfdoc_length = strlen($pdfdoc);
7683
			fwrite($f, $pdfdoc, $pdfdoc_length);
7684
			fclose($f);
7685
			// get digital signature via openssl library
7686
			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7687
			if (empty($this->signature_data['extracerts'])) {
7688
				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7689
			} else {
7690
				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']);
7691
			}
7692
			// read signature
7693
			$signature = file_get_contents($tempsign);
7694
			// extract signature
7695
			$signature = substr($signature, $pdfdoc_length);
7696
			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7697
			$tmparr = explode("\n\n", $signature);
7698
			$signature = $tmparr[1];
7699
			// decode signature
7700
			$signature = base64_decode(trim($signature));
7701
			// add TSA timestamp to signature
7702
			$signature = $this->applyTSA($signature);
7703
			// convert signature to hex
7704
			$signature = current(unpack('H*', $signature));
7705
			$signature = str_pad($signature, $this->signature_max_length, '0');
7706
			// Add signature to the document
7707
			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7708
			$this->bufferlen = strlen($this->buffer);
7709
		}
7710
		switch($dest) {
7711
			case 'I': {
7712
				// Send PDF to the standard output
7713
				if (ob_get_contents()) {
7714
					$this->Error('Some data has already been output, can\'t send PDF file');
7715
				}
7716
				if (php_sapi_name() != 'cli') {
7717
					// send output to a browser
7718
					header('Content-Type: application/pdf');
7719
					if (headers_sent()) {
7720
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7721
					}
7722
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7723
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7724
					header('Pragma: public');
7725
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7726
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7727
					header('Content-Disposition: inline; filename="' . rawurlencode(basename($name)) . '"; ' .
7728
						'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7729
					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7730
				} else {
7731
					echo $this->getBuffer();
7732
				}
7733
				break;
7734
			}
7735
			case 'D': {
7736
				// download PDF as file
7737
				if (ob_get_contents()) {
7738
					$this->Error('Some data has already been output, can\'t send PDF file');
7739
				}
7740
				header('Content-Description: File Transfer');
7741
				if (headers_sent()) {
7742
					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7743
				}
7744
				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7745
				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7746
				header('Pragma: public');
7747
				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7748
				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7749
				// force download dialog
7750
				if (strpos(php_sapi_name(), 'cgi') === false) {
7751
					header('Content-Type: application/force-download');
7752
					header('Content-Type: application/octet-stream', false);
7753
					header('Content-Type: application/download', false);
7754
					header('Content-Type: application/pdf', false);
7755
				} else {
7756
					header('Content-Type: application/pdf');
7757
				}
7758
				// use the Content-Disposition header to supply a recommended filename
7759
				header('Content-Disposition: attachment; filename="' . rawurlencode(basename($name)) . '"; ' .
7760
					'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7761
				header('Content-Transfer-Encoding: binary');
7762
				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7763
				break;
7764
			}
7765
			case 'F':
7766
			case 'FI':
7767
			case 'FD': {
7768
				// save PDF to a local file
7769
				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7770
				if (!$f) {
7771
					$this->Error('Unable to create output file: '.$name);
7772
				}
7773
				fwrite($f, $this->getBuffer(), $this->bufferlen);
7774
				fclose($f);
7775
				if ($dest == 'FI') {
7776
					// send headers to browser
7777
					header('Content-Type: application/pdf');
7778
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7779
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7780
					header('Pragma: public');
7781
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7782
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7783
					header('Content-Disposition: inline; filename="'.basename($name).'"');
7784
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7785
				} elseif ($dest == 'FD') {
7786
					// send headers to browser
7787
					if (ob_get_contents()) {
7788
						$this->Error('Some data has already been output, can\'t send PDF file');
7789
					}
7790
					header('Content-Description: File Transfer');
7791
					if (headers_sent()) {
7792
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7793
					}
7794
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7795
					header('Pragma: public');
7796
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7797
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7798
					// force download dialog
7799
					if (strpos(php_sapi_name(), 'cgi') === false) {
7800
						header('Content-Type: application/force-download');
7801
						header('Content-Type: application/octet-stream', false);
7802
						header('Content-Type: application/download', false);
7803
						header('Content-Type: application/pdf', false);
7804
					} else {
7805
						header('Content-Type: application/pdf');
7806
					}
7807
					// use the Content-Disposition header to supply a recommended filename
7808
					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7809
					header('Content-Transfer-Encoding: binary');
7810
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7811
				}
7812
				break;
7813
			}
7814
			case 'E': {
7815
				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7816
				$retval = 'Content-Type: application/pdf;'."\r\n";
7817
				$retval .= ' name="'.$name.'"'."\r\n";
7818
				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7819
				$retval .= 'Content-Disposition: attachment;'."\r\n";
7820
				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7821
				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7822
				return $retval;
7823
			}
7824
			case 'S': {
7825
				// returns PDF as a string
7826
				return $this->getBuffer();
7827
			}
7828
			default: {
7829
				$this->Error('Incorrect output destination: '.$dest);
7830
			}
7831
		}
7832
		return '';
7833
	}
7834
 
7835
	protected static $cleaned_ids = array();
7836
	/**
7837
	 * Unset all class variables except the following critical variables.
7838
	 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7839
	 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7840
	 * @public
7841
	 * @since 4.5.016 (2009-02-24)
7842
	 */
7843
	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7844
		if (isset(self::$cleaned_ids[$this->file_id])) {
7845
			$destroyall = false;
7846
		}
7847
		if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7848
			self::$cleaned_ids[$this->file_id] = true;
7849
			// remove all temporary files
7850
			if ($handle = @opendir(K_PATH_CACHE)) {
7851
				while ( false !== ( $file_name = readdir( $handle ) ) ) {
7852
					if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7853
						unlink(K_PATH_CACHE.$file_name);
7854
					}
7855
				}
7856
				closedir($handle);
7857
			}
7858
			if (isset($this->imagekeys)) {
7859
				foreach($this->imagekeys as $file) {
7860
					if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
7861
						@unlink($file);
7862
					}
7863
				}
7864
			}
7865
		}
7866
		$preserve = array(
7867
			'file_id',
7868
			'state',
7869
			'bufferlen',
7870
			'buffer',
7871
			'cached_files',
7872
			'imagekeys',
7873
			'sign',
7874
			'signature_data',
7875
			'signature_max_length',
7876
			'byterange_string',
7877
			'tsa_timestamp',
7878
			'tsa_data'
7879
		);
7880
		foreach (array_keys(get_object_vars($this)) as $val) {
7881
			if ($destroyall OR !in_array($val, $preserve)) {
7882
				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7883
					unset($this->$val);
7884
				}
7885
			}
7886
		}
7887
	}
7888
 
7889
	/**
7890
	 * Check for locale-related bug
7891
	 * @protected
7892
	 */
7893
	protected function _dochecks() {
7894
		//Check for locale-related bug
7895
		if (1.1 == 1) {
7896
			$this->Error('Don\'t alter the locale before including class file');
7897
		}
7898
		//Check for decimal separator
7899
		if (sprintf('%.1F', 1.0) != '1.0') {
7900
			setlocale(LC_NUMERIC, 'C');
7901
		}
7902
	}
7903
 
7904
	/**
7905
	 * Return an array containing variations for the basic page number alias.
7906
	 * @param string $a Base alias.
7907
	 * @return array of page number aliases
7908
	 * @protected
7909
	 */
7910
	protected function getInternalPageNumberAliases($a= '') {
7911
		$alias = array();
7912
		// build array of Unicode + ASCII variants (the order is important)
7913
		$alias = array('u' => array(), 'a' => array());
7914
		$u = '{'.$a.'}';
7915
		$alias['u'][] = TCPDF_STATIC::_escape($u);
7916
		if ($this->isunicode) {
7917
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7918
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7919
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7920
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7921
		}
7922
		$alias['a'][] = TCPDF_STATIC::_escape($a);
7923
		return $alias;
7924
	}
7925
 
7926
	/**
7927
	 * Return an array containing all internal page aliases.
7928
	 * @return array of page number aliases
7929
	 * @protected
7930
	 */
7931
	protected function getAllInternalPageNumberAliases() {
7932
		$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);
7933
		$pnalias = array();
7934
		foreach($basic_alias as $k => $a) {
7935
			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7936
		}
7937
		return $pnalias;
7938
	}
7939
 
7940
	/**
7941
	 * Replace right shift page number aliases with spaces to correct right alignment.
7942
	 * This works perfectly only when using monospaced fonts.
7943
	 * @param string $page Page content.
7944
	 * @param array $aliases Array of page aliases.
7945
	 * @param int $diff initial difference to add.
7946
	 * @return string replaced page content.
7947
	 * @protected
7948
	 */
7949
	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7950
		foreach ($aliases as $type => $alias) {
7951
			foreach ($alias as $a) {
7952
				// find position of compensation factor
7953
				$startnum = (strpos($a, ':') + 1);
7954
				$a = substr($a, 0, $startnum);
7955
				if (($pos = strpos($page, $a)) !== false) {
7956
					// end of alias
7957
					$endnum = strpos($page, '}', $pos);
7958
					// string to be replaced
7959
					$aa = substr($page, $pos, ($endnum - $pos + 1));
7960
					// get compensation factor
7961
					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7962
					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7963
					$ratio = floatval($ratio);
7964
					if ($type == 'u') {
7965
						$chrdiff = floor(($diff + 12) * $ratio);
7966
						$shift = str_repeat(' ', $chrdiff);
7967
						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7968
					} else {
7969
						$chrdiff = floor(($diff + 11) * $ratio);
7970
						$shift = str_repeat(' ', $chrdiff);
7971
					}
7972
					$page = str_replace($aa, $shift, $page);
7973
				}
7974
			}
7975
		}
7976
		return $page;
7977
	}
7978
 
7979
	/**
7980
	 * Set page boxes to be included on page descriptions.
7981
	 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7982
	 * @protected
7983
	 */
7984
	protected function setPageBoxTypes($boxes) {
7985
		$this->page_boxes = array();
7986
		foreach ($boxes as $box) {
7987
			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7988
				$this->page_boxes[] = $box;
7989
			}
7990
		}
7991
	}
7992
 
7993
	/**
7994
	 * Output pages (and replace page number aliases).
7995
	 * @protected
7996
	 */
7997
	protected function _putpages() {
7998
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7999
		// get internal aliases for page numbers
8000
		$pnalias = $this->getAllInternalPageNumberAliases();
8001
		$num_pages = $this->numpages;
8002
		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
8003
		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
8004
		$ptp_num_chars = $this->GetNumChars($ptpa);
8005
		$pagegroupnum = 0;
8006
		$groupnum = 0;
8007
		$ptgu = 1;
8008
		$ptga = 1;
8009
		$ptg_num_chars = 1;
8010
		for ($n = 1; $n <= $num_pages; ++$n) {
8011
			// get current page
8012
			$temppage = $this->getPageBuffer($n);
8013
			$pagelen = strlen($temppage);
8014
			// set replacements for total pages number
8015
			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
8016
			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
8017
			$pnp_num_chars = $this->GetNumChars($pnpa);
8018
			$pdiff = 0; // difference used for right shift alignment of page numbers
8019
			$gdiff = 0; // difference used for right shift alignment of page group numbers
8020
			if (!empty($this->pagegroups)) {
8021
				if (isset($this->newpagegroup[$n])) {
8022
					$pagegroupnum = 0;
8023
					++$groupnum;
8024
					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
8025
					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
8026
					$ptg_num_chars = $this->GetNumChars($ptga);
8027
				}
8028
				++$pagegroupnum;
8029
				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
8030
				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
8031
				$png_num_chars = $this->GetNumChars($pnga);
8032
				// replace page numbers
8033
				$replace = array();
8034
				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8035
				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8036
				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8037
				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8038
				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8039
			}
8040
			// replace page numbers
8041
			$replace = array();
8042
			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8043
			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8044
			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8045
			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8046
			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8047
			// replace right shift alias
8048
			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8049
			// replace EPS marker
8050
			$temppage = str_replace($this->epsmarker, '', $temppage);
8051
			//Page
8052
			$this->page_obj_id[$n] = $this->_newobj();
8053
			$out = '<<';
8054
			$out .= ' /Type /Page';
8055
			$out .= ' /Parent 1 0 R';
8056
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8057
				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8058
			}
8059
			$out .= ' /Resources 2 0 R';
8060
			foreach ($this->page_boxes as $box) {
8061
				$out .= ' /'.$box;
8062
				$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']);
8063
			}
8064
			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8065
				$out .= ' /BoxColorInfo <<';
8066
				foreach ($this->page_boxes as $box) {
8067
					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8068
						$out .= ' /'.$box.' <<';
8069
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8070
							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8071
							$out .= ' /C [';
8072
							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8073
							$out .= ' ]';
8074
						}
8075
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8076
							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8077
						}
8078
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8079
							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8080
						}
8081
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8082
							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8083
							$out .= ' /D [';
8084
							foreach ($dashes as $dash) {
8085
								$out .= sprintf(' %F', ($dash * $this->k));
8086
							}
8087
							$out .= ' ]';
8088
						}
8089
						$out .= ' >>';
8090
					}
8091
				}
8092
				$out .= ' >>';
8093
			}
8094
			$out .= ' /Contents '.($this->n + 1).' 0 R';
8095
			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8096
			if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
8097
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8098
			}
8099
			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8100
				// page transitions
8101
				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8102
					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8103
				}
8104
				$out .= ' /Trans <<';
8105
				$out .= ' /Type /Trans';
8106
				if (isset($this->pagedim[$n]['trans']['S'])) {
8107
					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8108
				}
8109
				if (isset($this->pagedim[$n]['trans']['D'])) {
8110
					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8111
				}
8112
				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8113
					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8114
				}
8115
				if (isset($this->pagedim[$n]['trans']['M'])) {
8116
					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8117
				}
8118
				if (isset($this->pagedim[$n]['trans']['Di'])) {
8119
					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8120
				}
8121
				if (isset($this->pagedim[$n]['trans']['SS'])) {
8122
					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8123
				}
8124
				if (isset($this->pagedim[$n]['trans']['B'])) {
8125
					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8126
				}
8127
				$out .= ' >>';
8128
			}
8129
			$out .= $this->_getannotsrefs($n);
8130
			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8131
			$out .= ' >>';
8132
			$out .= "\n".'endobj';
8133
			$this->_out($out);
8134
			//Page content
8135
			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8136
			$this->_newobj();
8137
			$p = $this->_getrawstream($p);
8138
			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8139
		}
8140
		//Pages root
8141
		$out = $this->_getobj(1)."\n";
8142
		$out .= '<< /Type /Pages /Kids [';
8143
		foreach($this->page_obj_id as $page_obj) {
8144
			$out .= ' '.$page_obj.' 0 R';
8145
		}
8146
		$out .= ' ] /Count '.$num_pages.' >>';
8147
		$out .= "\n".'endobj';
8148
		$this->_out($out);
8149
	}
8150
 
8151
	/**
8152
	 * Get references to page annotations.
8153
	 * @param int $n page number
8154
	 * @return string
8155
	 * @protected
8156
	 * @author Nicola Asuni
8157
	 * @since 5.0.010 (2010-05-17)
8158
	 */
8159
	protected function _getannotsrefs($n) {
8160
		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8161
			return '';
8162
		}
8163
		$out = ' /Annots [';
8164
		if (isset($this->PageAnnots[$n])) {
8165
			foreach ($this->PageAnnots[$n] as $key => $val) {
8166
				if (!in_array($val['n'], $this->radio_groups)) {
8167
					$out .= ' '.$val['n'].' 0 R';
8168
				}
8169
			}
8170
			// add radiobutton groups
8171
			if (isset($this->radiobutton_groups[$n])) {
8172
				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8173
					if (isset($data['n'])) {
8174
						$out .= ' '.$data['n'].' 0 R';
8175
					}
8176
				}
8177
			}
8178
		}
8179
		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8180
			// set reference for signature object
8181
			$out .= ' '.$this->sig_obj_id.' 0 R';
8182
		}
8183
		if (!empty($this->empty_signature_appearance)) {
8184
			foreach ($this->empty_signature_appearance as $esa) {
8185
				if ($esa['page'] == $n) {
8186
					// set reference for empty signature objects
8187
					$out .= ' '.$esa['objid'].' 0 R';
8188
				}
8189
			}
8190
		}
8191
		$out .= ' ]';
8192
		return $out;
8193
	}
8194
 
8195
	/**
8196
	 * Output annotations objects for all pages.
8197
	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8198
	 * See section 12.5 of PDF 32000_2008 reference.
8199
	 * @protected
8200
	 * @author Nicola Asuni
8201
	 * @since 4.0.018 (2008-08-06)
8202
	 */
8203
	protected function _putannotsobjs() {
8204
		// reset object counter
8205
		for ($n=1; $n <= $this->numpages; ++$n) {
8206
			if (isset($this->PageAnnots[$n])) {
8207
				// set page annotations
8208
				foreach ($this->PageAnnots[$n] as $key => $pl) {
8209
					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8210
					// create annotation object for grouping radiobuttons
8211
					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8212
						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8213
						$annots = '<<';
8214
						$annots .= ' /Type /Annot';
8215
						$annots .= ' /Subtype /Widget';
8216
						$annots .= ' /Rect [0 0 0 0]';
8217
						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8218
							// read only
8219
							$annots .= ' /F 68';
8220
							$annots .= ' /Ff 49153';
8221
						} else {
8222
							$annots .= ' /F 4'; // default print for PDF/A
8223
							$annots .= ' /Ff 49152';
8224
						}
8225
						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8226
						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8227
							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8228
						}
8229
						$annots .= ' /FT /Btn';
8230
						$annots .= ' /Kids [';
8231
						$defval = '';
8232
						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8233
							if (isset($data['kid'])) {
8234
								$annots .= ' '.$data['kid'].' 0 R';
8235
								if ($data['def'] !== 'Off') {
8236
									$defval = $data['def'];
8237
								}
8238
							}
8239
						}
8240
						$annots .= ' ]';
8241
						if (!empty($defval)) {
8242
							$annots .= ' /V /'.$defval;
8243
						}
8244
						$annots .= ' >>';
8245
						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8246
						$this->form_obj_id[] = $radio_button_obj_id;
8247
						// store object id to be used on Parent entry of Kids
8248
						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8249
					}
8250
					$formfield = false;
8251
					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8252
					$a = $pl['x'] * $this->k;
8253
					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8254
					$c = $pl['w'] * $this->k;
8255
					$d = $pl['h'] * $this->k;
8256
					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8257
					// create new annotation object
8258
					$annots = '<</Type /Annot';
8259
					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8260
					$annots .= ' /Rect ['.$rect.']';
8261
					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8262
					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8263
						$annots .= ' /FT /'.$pl['opt']['ft'];
8264
						$formfield = true;
8265
					}
8266
					if ($pl['opt']['subtype'] !== 'Link') {
8267
						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8268
					}
8269
					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8270
					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8271
					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8272
					if (isset($pl['opt']['f'])) {
8273
						$fval = 0;
8274
						if (is_array($pl['opt']['f'])) {
8275
							foreach ($pl['opt']['f'] as $f) {
8276
								switch (strtolower($f)) {
8277
									case 'invisible': {
8278
										$fval += 1 << 0;
8279
										break;
8280
									}
8281
									case 'hidden': {
8282
										$fval += 1 << 1;
8283
										break;
8284
									}
8285
									case 'print': {
8286
										$fval += 1 << 2;
8287
										break;
8288
									}
8289
									case 'nozoom': {
8290
										$fval += 1 << 3;
8291
										break;
8292
									}
8293
									case 'norotate': {
8294
										$fval += 1 << 4;
8295
										break;
8296
									}
8297
									case 'noview': {
8298
										$fval += 1 << 5;
8299
										break;
8300
									}
8301
									case 'readonly': {
8302
										$fval += 1 << 6;
8303
										break;
8304
									}
8305
									case 'locked': {
8306
										$fval += 1 << 8;
8307
										break;
8308
									}
8309
									case 'togglenoview': {
8310
										$fval += 1 << 9;
8311
										break;
8312
									}
8313
									case 'lockedcontents': {
8314
										$fval += 1 << 10;
8315
										break;
8316
									}
8317
									default: {
8318
										break;
8319
									}
8320
								}
8321
							}
8322
						} else {
8323
							$fval = intval($pl['opt']['f']);
8324
						}
8325
					} else {
8326
						$fval = 4;
8327
					}
8328
					if ($this->pdfa_mode) {
8329
						// force print flag for PDF/A mode
8330
						$fval |= 4;
8331
					}
8332
					$annots .= ' /F '.intval($fval);
8333
					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8334
						$annots .= ' /AS /'.$pl['opt']['as'];
8335
					}
8336
					if (isset($pl['opt']['ap'])) {
8337
						// appearance stream
8338
						$annots .= ' /AP <<';
8339
						if (is_array($pl['opt']['ap'])) {
8340
							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8341
								// $apmode can be: n = normal; r = rollover; d = down;
8342
								$annots .= ' /'.strtoupper($apmode);
8343
								if (is_array($apdef)) {
8344
									$annots .= ' <<';
8345
									foreach ($apdef as $apstate => $stream) {
8346
										// reference to XObject that define the appearance for this mode-state
8347
										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8348
										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8349
									}
8350
									$annots .= ' >>';
8351
								} else {
8352
									// reference to XObject that define the appearance for this mode
8353
									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8354
									$annots .= ' '.$apsobjid.' 0 R';
8355
								}
8356
							}
8357
						} else {
8358
							$annots .= $pl['opt']['ap'];
8359
						}
8360
						$annots .= ' >>';
8361
					}
8362
					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8363
						$annots .= ' /BS <<';
8364
						$annots .= ' /Type /Border';
8365
						if (isset($pl['opt']['bs']['w'])) {
8366
							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8367
						}
8368
						$bstyles = array('S', 'D', 'B', 'I', 'U');
8369
						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8370
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8371
						}
8372
						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8373
							$annots .= ' /D [';
8374
							foreach ($pl['opt']['bs']['d'] as $cord) {
8375
								$annots .= ' '.intval($cord);
8376
							}
8377
							$annots .= ']';
8378
						}
8379
						$annots .= ' >>';
8380
					} else {
8381
						$annots .= ' /Border [';
8382
						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8383
							$annots .= intval($pl['opt']['border'][0]).' ';
8384
							$annots .= intval($pl['opt']['border'][1]).' ';
8385
							$annots .= intval($pl['opt']['border'][2]);
8386
							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8387
								$annots .= ' [';
8388
								foreach ($pl['opt']['border'][3] as $dash) {
8389
									$annots .= intval($dash).' ';
8390
								}
8391
								$annots .= ']';
8392
							}
8393
						} else {
8394
							$annots .= '0 0 0';
8395
						}
8396
						$annots .= ']';
8397
					}
8398
					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8399
						$annots .= ' /BE <<';
8400
						$bstyles = array('S', 'C');
8401
						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8402
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8403
						} else {
8404
							$annots .= ' /S /S';
8405
						}
8406
						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8407
							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8408
						}
8409
						$annots .= '>>';
8410
					}
8411
					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8412
						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8413
					}
8414
					//$annots .= ' /StructParent ';
8415
					//$annots .= ' /OC ';
8416
					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8417
					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8418
						// this is a markup type
8419
						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8420
							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8421
						}
8422
						//$annots .= ' /Popup ';
8423
						if (isset($pl['opt']['ca'])) {
8424
							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8425
						}
8426
						if (isset($pl['opt']['rc'])) {
8427
							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8428
						}
8429
						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8430
						//$annots .= ' /IRT ';
8431
						if (isset($pl['opt']['subj'])) {
8432
							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8433
						}
8434
						//$annots .= ' /RT ';
8435
						//$annots .= ' /IT ';
8436
						//$annots .= ' /ExData ';
8437
					}
8438
					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8439
					// Annotation types
8440
					switch (strtolower($pl['opt']['subtype'])) {
8441
						case 'text': {
8442
							if (isset($pl['opt']['open'])) {
8443
								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8444
							}
8445
							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8446
							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8447
								$annots .= ' /Name /'.$pl['opt']['name'];
8448
							} else {
8449
								$annots .= ' /Name /Note';
8450
							}
8451
							$hasStateModel = isset($pl['opt']['statemodel']);
8452
							$hasState = isset($pl['opt']['state']);
8453
							$statemodels = array('Marked', 'Review');
8454
							if (!$hasStateModel && !$hasState) {
8455
								break;
8456
							}
8457
							if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8458
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8459
							} else {
8460
								$pl['opt']['statemodel'] = 'Marked';
8461
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8462
							}
8463
							if ($pl['opt']['statemodel'] == 'Marked') {
8464
								$states = array('Accepted', 'Unmarked');
8465
							} else {
8466
								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8467
							}
8468
							if ($hasState AND in_array($pl['opt']['state'], $states)) {
8469
								$annots .= ' /State /'.$pl['opt']['state'];
8470
							} else {
8471
								if ($pl['opt']['statemodel'] == 'Marked') {
8472
									$annots .= ' /State /Unmarked';
8473
								} else {
8474
									$annots .= ' /State /None';
8475
								}
8476
							}
8477
							break;
8478
						}
8479
						case 'link': {
8480
							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8481
								if ($pl['txt'][0] == '#') {
8482
									// internal destination
8483
									$annots .= ' /A <</S /GoTo /D /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8484
								} elseif ($pl['txt'][0] == '%') {
8485
									// embedded PDF file
8486
									$filename = basename(substr($pl['txt'], 1));
8487
									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8488
								} elseif ($pl['txt'][0] == '*') {
8489
									// embedded generic file
8490
									$filename = basename(substr($pl['txt'], 1));
8491
									$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});';
8492
									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8493
								} else {
8494
									$parsedUrl = parse_url($pl['txt']);
8495
									if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8496
										// relative link to a PDF file
8497
										$dest = '[0 /Fit]'; // default page 0
8498
										if (!empty($parsedUrl['fragment'])) {
8499
											// check for named destination
8500
											$tmp = explode('=', $parsedUrl['fragment']);
8501
											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8502
										}
8503
										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8504
									} else {
8505
										// external URI link
8506
										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8507
									}
8508
								}
8509
							} elseif (isset($this->links[$pl['txt']])) {
8510
								// internal link ID
8511
								$l = $this->links[$pl['txt']];
8512
								if (isset($this->page_obj_id[($l['p'])])) {
8513
									$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)));
8514
								}
8515
							}
8516
							$hmodes = array('N', 'I', 'O', 'P');
8517
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8518
								$annots .= ' /H /'.$pl['opt']['h'];
8519
							} else {
8520
								$annots .= ' /H /I';
8521
							}
8522
							//$annots .= ' /PA ';
8523
							//$annots .= ' /Quadpoints ';
8524
							break;
8525
						}
8526
						case 'freetext': {
8527
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8528
								$annots .= ' /DA ('.$pl['opt']['da'].')';
8529
							}
8530
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8531
								$annots .= ' /Q '.intval($pl['opt']['q']);
8532
							}
8533
							if (isset($pl['opt']['rc'])) {
8534
								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8535
							}
8536
							if (isset($pl['opt']['ds'])) {
8537
								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8538
							}
8539
							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8540
								$annots .= ' /CL [';
8541
								foreach ($pl['opt']['cl'] as $cl) {
8542
									$annots .= sprintf('%F ', $cl * $this->k);
8543
								}
8544
								$annots .= ']';
8545
							}
8546
							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8547
							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8548
								$annots .= ' /IT /'.$pl['opt']['it'];
8549
							}
8550
							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8551
								$l = $pl['opt']['rd'][0] * $this->k;
8552
								$r = $pl['opt']['rd'][1] * $this->k;
8553
								$t = $pl['opt']['rd'][2] * $this->k;
8554
								$b = $pl['opt']['rd'][3] * $this->k;
8555
								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8556
							}
8557
							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8558
								$annots .= ' /LE /'.$pl['opt']['le'];
8559
							}
8560
							break;
8561
						}
8562
						case 'line': {
8563
							break;
8564
						}
8565
						case 'square': {
8566
							break;
8567
						}
8568
						case 'circle': {
8569
							break;
8570
						}
8571
						case 'polygon': {
8572
							break;
8573
						}
8574
						case 'polyline': {
8575
							break;
8576
						}
8577
						case 'highlight': {
8578
							break;
8579
						}
8580
						case 'underline': {
8581
							break;
8582
						}
8583
						case 'squiggly': {
8584
							break;
8585
						}
8586
						case 'strikeout': {
8587
							break;
8588
						}
8589
						case 'stamp': {
8590
							break;
8591
						}
8592
						case 'caret': {
8593
							break;
8594
						}
8595
						case 'ink': {
8596
							break;
8597
						}
8598
						case 'popup': {
8599
							break;
8600
						}
8601
						case 'fileattachment': {
8602
							if ($this->pdfa_mode && $this->pdfa_version != 3) {
8603
								// embedded files are not allowed in PDF/A mode version 1 and 2
8604
								break;
8605
							}
8606
							if (!isset($pl['opt']['fs'])) {
8607
								break;
8608
							}
8609
							$filename = basename($pl['opt']['fs']);
8610
							if (isset($this->embeddedfiles[$filename]['f'])) {
8611
								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8612
								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8613
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8614
									$annots .= ' /Name /'.$pl['opt']['name'];
8615
								} else {
8616
									$annots .= ' /Name /PushPin';
8617
								}
8618
								// index (zero-based) of the annotation in the Annots array of this page
8619
								$this->embeddedfiles[$filename]['a'] = $key;
8620
							}
8621
							break;
8622
						}
8623
						case 'sound': {
8624
							if (!isset($pl['opt']['fs'])) {
8625
								break;
8626
							}
8627
							$filename = basename($pl['opt']['fs']);
8628
							if (isset($this->embeddedfiles[$filename]['f'])) {
8629
								// ... TO BE COMPLETED ...
8630
								// /R /C /B /E /CO /CP
8631
								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8632
								$iconsapp = array('Speaker', 'Mic');
8633
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8634
									$annots .= ' /Name /'.$pl['opt']['name'];
8635
								} else {
8636
									$annots .= ' /Name /Speaker';
8637
								}
8638
							}
8639
							break;
8640
						}
8641
						case 'movie': {
8642
							break;
8643
						}
8644
						case 'widget': {
8645
							$hmode = array('N', 'I', 'O', 'P', 'T');
8646
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8647
								$annots .= ' /H /'.$pl['opt']['h'];
8648
							}
8649
							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8650
								$annots .= ' /MK <<';
8651
								if (isset($pl['opt']['mk']['r'])) {
8652
									$annots .= ' /R '.$pl['opt']['mk']['r'];
8653
								}
8654
								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8655
									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8656
								}
8657
								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8658
									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8659
								}
8660
								if (isset($pl['opt']['mk']['ca'])) {
8661
									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8662
								}
8663
								if (isset($pl['opt']['mk']['rc'])) {
8664
									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8665
								}
8666
								if (isset($pl['opt']['mk']['ac'])) {
8667
									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8668
								}
8669
								if (isset($pl['opt']['mk']['i'])) {
8670
									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8671
									if ($info !== false) {
8672
										$annots .= ' /I '.$info['n'].' 0 R';
8673
									}
8674
								}
8675
								if (isset($pl['opt']['mk']['ri'])) {
8676
									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8677
									if ($info !== false) {
8678
										$annots .= ' /RI '.$info['n'].' 0 R';
8679
									}
8680
								}
8681
								if (isset($pl['opt']['mk']['ix'])) {
8682
									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8683
									if ($info !== false) {
8684
										$annots .= ' /IX '.$info['n'].' 0 R';
8685
									}
8686
								}
8687
								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8688
									$annots .= ' /IF <<';
8689
									$if_sw = array('A', 'B', 'S', 'N');
8690
									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8691
										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8692
									}
8693
									$if_s = array('A', 'P');
8694
									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8695
										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8696
									}
8697
									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8698
										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8699
									}
8700
									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8701
										$annots .= ' /FB true';
8702
									}
8703
									$annots .= '>>';
8704
								}
8705
								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8706
									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8707
								}
8708
								$annots .= '>>';
8709
							} // end MK
8710
							// --- Entries for field dictionaries ---
8711
							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8712
								// set parent
8713
								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8714
							}
8715
							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8716
								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8717
							}
8718
							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8719
								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8720
							}
8721
							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8722
								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8723
							}
8724
							if (isset($pl['opt']['ff'])) {
8725
								if (is_array($pl['opt']['ff'])) {
8726
									// array of bit settings
8727
									$flag = 0;
8728
									foreach($pl['opt']['ff'] as $val) {
8729
										$flag += 1 << ($val - 1);
8730
									}
8731
								} else {
8732
									$flag = intval($pl['opt']['ff']);
8733
								}
8734
								$annots .= ' /Ff '.$flag;
8735
							}
8736
							if (isset($pl['opt']['maxlen'])) {
8737
								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8738
							}
8739
							if (isset($pl['opt']['v'])) {
8740
								$annots .= ' /V';
8741
								if (is_array($pl['opt']['v'])) {
8742
									foreach ($pl['opt']['v'] AS $optval) {
8743
										if (is_float($optval)) {
8744
											$optval = sprintf('%F', $optval);
8745
										}
8746
										$annots .= ' '.$optval;
8747
									}
8748
								} else {
8749
									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8750
								}
8751
							}
8752
							if (isset($pl['opt']['dv'])) {
8753
								$annots .= ' /DV';
8754
								if (is_array($pl['opt']['dv'])) {
8755
									foreach ($pl['opt']['dv'] AS $optval) {
8756
										if (is_float($optval)) {
8757
											$optval = sprintf('%F', $optval);
8758
										}
8759
										$annots .= ' '.$optval;
8760
									}
8761
								} else {
8762
									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8763
								}
8764
							}
8765
							if (isset($pl['opt']['rv'])) {
8766
								$annots .= ' /RV';
8767
								if (is_array($pl['opt']['rv'])) {
8768
									foreach ($pl['opt']['rv'] AS $optval) {
8769
										if (is_float($optval)) {
8770
											$optval = sprintf('%F', $optval);
8771
										}
8772
										$annots .= ' '.$optval;
8773
									}
8774
								} else {
8775
									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8776
								}
8777
							}
8778
							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8779
								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8780
							}
8781
							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8782
								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8783
							}
8784
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8785
								$annots .= ' /DA ('.$pl['opt']['da'].')';
8786
							}
8787
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8788
								$annots .= ' /Q '.intval($pl['opt']['q']);
8789
							}
8790
							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8791
								$annots .= ' /Opt [';
8792
								foreach($pl['opt']['opt'] AS $copt) {
8793
									if (is_array($copt)) {
8794
										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8795
									} else {
8796
										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8797
									}
8798
								}
8799
								$annots .= ']';
8800
							}
8801
							if (isset($pl['opt']['ti'])) {
8802
								$annots .= ' /TI '.intval($pl['opt']['ti']);
8803
							}
8804
							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8805
								$annots .= ' /I [';
8806
								foreach($pl['opt']['i'] AS $copt) {
8807
									$annots .= intval($copt).' ';
8808
								}
8809
								$annots .= ']';
8810
							}
8811
							break;
8812
						}
8813
						case 'screen': {
8814
							break;
8815
						}
8816
						case 'printermark': {
8817
							break;
8818
						}
8819
						case 'trapnet': {
8820
							break;
8821
						}
8822
						case 'watermark': {
8823
							break;
8824
						}
8825
						case '3d': {
8826
							break;
8827
						}
8828
						default: {
8829
							break;
8830
						}
8831
					}
8832
					$annots .= '>>';
8833
					// create new annotation object
8834
					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8835
					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8836
						// store reference of form object
8837
						$this->form_obj_id[] = $annot_obj_id;
8838
					}
8839
				}
8840
			}
8841
		} // end for each page
8842
	}
8843
 
8844
	/**
8845
	 * Put appearance streams XObject used to define annotation's appearance states.
8846
	 * @param int $w annotation width
8847
	 * @param int $h annotation height
8848
	 * @param string $stream appearance stream
8849
	 * @return int object ID
8850
	 * @protected
8851
	 * @since 4.8.001 (2009-09-09)
8852
	 */
8853
	protected function _putAPXObject($w=0, $h=0, $stream='') {
8854
		$stream = trim($stream);
8855
		$out = $this->_getobj()."\n";
8856
		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8857
		$out .= '<<';
8858
		$out .= ' /Type /XObject';
8859
		$out .= ' /Subtype /Form';
8860
		$out .= ' /FormType 1';
8861
		if ($this->compress) {
8862
			$stream = gzcompress($stream);
8863
			$out .= ' /Filter /FlateDecode';
8864
		}
8865
		$rect = sprintf('%F %F', $w, $h);
8866
		$out .= ' /BBox [0 0 '.$rect.']';
8867
		$out .= ' /Matrix [1 0 0 1 0 0]';
8868
		$out .= ' /Resources 2 0 R';
8869
		$stream = $this->_getrawstream($stream);
8870
		$out .= ' /Length '.strlen($stream);
8871
		$out .= ' >>';
8872
		$out .= ' stream'."\n".$stream."\n".'endstream';
8873
		$out .= "\n".'endobj';
8874
		$this->_out($out);
8875
		return $this->n;
8876
	}
8877
 
8878
	/**
8879
	 * Output fonts.
8880
	 * @author Nicola Asuni
8881
	 * @protected
8882
	 */
8883
	protected function _putfonts() {
8884
		$nf = $this->n;
8885
		foreach ($this->diffs as $diff) {
8886
			//Encodings
8887
			$this->_newobj();
8888
			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8889
		}
8890
		foreach ($this->FontFiles as $file => $info) {
8891
			// search and get font file to embedd
8892
			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8893
			if (!TCPDF_STATIC::empty_string($fontfile)) {
8894
				$font = file_get_contents($fontfile);
8895
				$compressed = (substr($file, -2) == '.z');
8896
				if ((!$compressed) AND (isset($info['length2']))) {
8897
					$header = (ord($font[0]) == 128);
8898
					if ($header) {
8899
						// strip first binary header
8900
						$font = substr($font, 6);
8901
					}
8902
					if ($header AND (ord($font[$info['length1']]) == 128)) {
8903
						// strip second binary header
8904
						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8905
					}
8906
				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8907
					if ($compressed) {
8908
						// uncompress font
8909
						$font = gzuncompress($font);
8910
					}
8911
					// merge subset characters
8912
					$subsetchars = array(); // used chars
8913
					foreach ($info['fontkeys'] as $fontkey) {
8914
						$fontinfo = $this->getFontBuffer($fontkey);
8915
						$subsetchars += $fontinfo['subsetchars'];
8916
					}
8917
					// rebuild a font subset
8918
					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8919
					// calculate new font length
8920
					$info['length1'] = strlen($font);
8921
					if ($compressed) {
8922
						// recompress font
8923
						$font = gzcompress($font);
8924
					}
8925
				}
8926
				$this->_newobj();
8927
				$this->FontFiles[$file]['n'] = $this->n;
8928
				$stream = $this->_getrawstream($font);
8929
				$out = '<< /Length '.strlen($stream);
8930
				if ($compressed) {
8931
					$out .= ' /Filter /FlateDecode';
8932
				}
8933
				$out .= ' /Length1 '.$info['length1'];
8934
				if (isset($info['length2'])) {
8935
					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8936
				}
8937
				$out .= ' >>';
8938
				$out .= ' stream'."\n".$stream."\n".'endstream';
8939
				$out .= "\n".'endobj';
8940
				$this->_out($out);
8941
			}
8942
		}
8943
		foreach ($this->fontkeys as $k) {
8944
			//Font objects
8945
			$font = $this->getFontBuffer($k);
8946
			$type = $font['type'];
8947
			$name = $font['name'];
8948
			if ($type == 'core') {
8949
				// standard core font
8950
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8951
				$out .= '<</Type /Font';
8952
				$out .= ' /Subtype /Type1';
8953
				$out .= ' /BaseFont /'.$name;
8954
				$out .= ' /Name /F'.$font['i'];
8955
				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8956
					$out .= ' /Encoding /WinAnsiEncoding';
8957
				}
8958
				if ($k == 'helvetica') {
8959
					// add default font for annotations
8960
					$this->annotation_fonts[$k] = $font['i'];
8961
				}
8962
				$out .= ' >>';
8963
				$out .= "\n".'endobj';
8964
				$this->_out($out);
8965
			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8966
				// additional Type1 or TrueType font
8967
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8968
				$out .= '<</Type /Font';
8969
				$out .= ' /Subtype /'.$type;
8970
				$out .= ' /BaseFont /'.$name;
8971
				$out .= ' /Name /F'.$font['i'];
8972
				$out .= ' /FirstChar 32 /LastChar 255';
8973
				$out .= ' /Widths '.($this->n + 1).' 0 R';
8974
				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8975
				if ($font['enc']) {
8976
					if (isset($font['diff'])) {
8977
						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8978
					} else {
8979
						$out .= ' /Encoding /WinAnsiEncoding';
8980
					}
8981
				}
8982
				$out .= ' >>';
8983
				$out .= "\n".'endobj';
8984
				$this->_out($out);
8985
				// Widths
8986
				$this->_newobj();
8987
				$s = '[';
8988
				for ($i = 32; $i < 256; ++$i) {
8989
					if (isset($font['cw'][$i])) {
8990
						$s .= $font['cw'][$i].' ';
8991
					} else {
8992
						$s .= $font['dw'].' ';
8993
					}
8994
				}
8995
				$s .= ']';
8996
				$s .= "\n".'endobj';
8997
				$this->_out($s);
8998
				//Descriptor
8999
				$this->_newobj();
9000
				$s = '<</Type /FontDescriptor /FontName /'.$name;
9001
				foreach ($font['desc'] as $fdk => $fdv) {
9002
					if (is_float($fdv)) {
9003
						$fdv = sprintf('%F', $fdv);
9004
					}
9005
					$s .= ' /'.$fdk.' '.$fdv.'';
9006
				}
9007
				if (!TCPDF_STATIC::empty_string($font['file'])) {
9008
					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9009
				}
9010
				$s .= '>>';
9011
				$s .= "\n".'endobj';
9012
				$this->_out($s);
9013
			} else {
9014
				// additional types
9015
				$mtd = '_put'.strtolower($type);
9016
				if (!method_exists($this, $mtd)) {
9017
					$this->Error('Unsupported font type: '.$type);
9018
				}
9019
				$this->$mtd($font);
9020
			}
9021
		}
9022
	}
9023
 
9024
	/**
9025
	 * Adds unicode fonts.<br>
9026
	 * Based on PDF Reference 1.3 (section 5)
9027
	 * @param array $font font data
9028
	 * @protected
9029
	 * @author Nicola Asuni
9030
	 * @since 1.52.0.TC005 (2005-01-05)
9031
	 */
9032
	protected function _puttruetypeunicode($font) {
9033
		$fontname = '';
9034
		if ($font['subset']) {
9035
			// change name for font subsetting
9036
			$subtag = sprintf('%06u', $font['i']);
9037
			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9038
			$fontname .= $subtag.'+';
9039
		}
9040
		$fontname .= $font['name'];
9041
		// Type0 Font
9042
		// A composite font composed of other fonts, organized hierarchically
9043
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9044
		$out .= '<< /Type /Font';
9045
		$out .= ' /Subtype /Type0';
9046
		$out .= ' /BaseFont /'.$fontname;
9047
		$out .= ' /Name /F'.$font['i'];
9048
		$out .= ' /Encoding /'.$font['enc'];
9049
		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9050
		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9051
		$out .= ' >>';
9052
		$out .= "\n".'endobj';
9053
		$this->_out($out);
9054
		// ToUnicode map for Identity-H
9055
		$stream = TCPDF_FONT_DATA::$uni_identity_h;
9056
		// ToUnicode Object
9057
		$this->_newobj();
9058
		$stream = ($this->compress) ? gzcompress($stream) : $stream;
9059
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9060
		$stream = $this->_getrawstream($stream);
9061
		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9062
		// CIDFontType2
9063
		// A CIDFont whose glyph descriptions are based on TrueType font technology
9064
		$oid = $this->_newobj();
9065
		$out = '<< /Type /Font';
9066
		$out .= ' /Subtype /CIDFontType2';
9067
		$out .= ' /BaseFont /'.$fontname;
9068
		// A dictionary containing entries that define the character collection of the CIDFont.
9069
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9070
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9071
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9072
		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9073
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9074
		$out .= ' /DW '.$font['dw']; // default width
9075
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9076
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9077
			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9078
		}
9079
		$out .= ' >>';
9080
		$out .= "\n".'endobj';
9081
		$this->_out($out);
9082
		// Font descriptor
9083
		// A font descriptor describing the CIDFont default metrics other than its glyph widths
9084
		$this->_newobj();
9085
		$out = '<< /Type /FontDescriptor';
9086
		$out .= ' /FontName /'.$fontname;
9087
		foreach ($font['desc'] as $key => $value) {
9088
			if (is_float($value)) {
9089
				$value = sprintf('%F', $value);
9090
			}
9091
			$out .= ' /'.$key.' '.$value;
9092
		}
9093
		$fontdir = false;
9094
		if (!TCPDF_STATIC::empty_string($font['file'])) {
9095
			// A stream containing a TrueType font
9096
			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9097
			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
9098
		}
9099
		$out .= ' >>';
9100
		$out .= "\n".'endobj';
9101
		$this->_out($out);
9102
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9103
			$this->_newobj();
9104
			// Embed CIDToGIDMap
9105
			// A specification of the mapping from CIDs to glyph indices
9106
			// search and get CTG font file to embedd
9107
			$ctgfile = strtolower($font['ctg']);
9108
			// search and get ctg font file to embedd
9109
			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9110
			if (TCPDF_STATIC::empty_string($fontfile)) {
9111
				$this->Error('Font file not found: '.$ctgfile);
9112
			}
9113
			$stream = $this->_getrawstream(file_get_contents($fontfile));
9114
			$out = '<< /Length '.strlen($stream).'';
9115
			if (substr($fontfile, -2) == '.z') { // check file extension
9116
				// Decompresses data encoded using the public-domain
9117
				// zlib/deflate compression method, reproducing the
9118
				// original text or binary data
9119
				$out .= ' /Filter /FlateDecode';
9120
			}
9121
			$out .= ' >>';
9122
			$out .= ' stream'."\n".$stream."\n".'endstream';
9123
			$out .= "\n".'endobj';
9124
			$this->_out($out);
9125
		}
9126
	}
9127
 
9128
	/**
9129
	 * Output CID-0 fonts.
9130
	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9131
	 * @param array $font font data
9132
	 * @protected
9133
	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9134
	 * @since 3.2.000 (2008-06-23)
9135
	 */
9136
	protected function _putcidfont0($font) {
9137
		$cidoffset = 0;
9138
		if (!isset($font['cw'][1])) {
9139
			$cidoffset = 31;
9140
		}
9141
		if (isset($font['cidinfo']['uni2cid'])) {
9142
			// convert unicode to cid.
9143
			$uni2cid = $font['cidinfo']['uni2cid'];
9144
			$cw = array();
9145
			foreach ($font['cw'] as $uni => $width) {
9146
				if (isset($uni2cid[$uni])) {
9147
					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9148
				} elseif ($uni < 256) {
9149
					$cw[$uni] = $width;
9150
				} // else unknown character
9151
			}
9152
			$font = array_merge($font, array('cw' => $cw));
9153
		}
9154
		$name = $font['name'];
9155
		$enc = $font['enc'];
9156
		if ($enc) {
9157
			$longname = $name.'-'.$enc;
9158
		} else {
9159
			$longname = $name;
9160
		}
9161
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9162
		$out .= '<</Type /Font';
9163
		$out .= ' /Subtype /Type0';
9164
		$out .= ' /BaseFont /'.$longname;
9165
		$out .= ' /Name /F'.$font['i'];
9166
		if ($enc) {
9167
			$out .= ' /Encoding /'.$enc;
9168
		}
9169
		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9170
		$out .= ' >>';
9171
		$out .= "\n".'endobj';
9172
		$this->_out($out);
9173
		$oid = $this->_newobj();
9174
		$out = '<</Type /Font';
9175
		$out .= ' /Subtype /CIDFontType0';
9176
		$out .= ' /BaseFont /'.$name;
9177
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9178
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9179
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9180
		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9181
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9182
		$out .= ' /DW '.$font['dw'];
9183
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9184
		$out .= ' >>';
9185
		$out .= "\n".'endobj';
9186
		$this->_out($out);
9187
		$this->_newobj();
9188
		$s = '<</Type /FontDescriptor /FontName /'.$name;
9189
		foreach ($font['desc'] as $k => $v) {
9190
			if ($k != 'Style') {
9191
				if (is_float($v)) {
9192
					$v = sprintf('%F', $v);
9193
				}
9194
				$s .= ' /'.$k.' '.$v.'';
9195
			}
9196
		}
9197
		$s .= '>>';
9198
		$s .= "\n".'endobj';
9199
		$this->_out($s);
9200
	}
9201
 
9202
	/**
9203
	 * Output images.
9204
	 * @protected
9205
	 */
9206
	protected function _putimages() {
9207
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9208
		foreach ($this->imagekeys as $file) {
9209
			$info = $this->getImageBuffer($file);
9210
			// set object for alternate images array
9211
			$altoid = null;
9212
			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9213
				$altoid = $this->_newobj();
9214
				$out = '[';
9215
				foreach ($info['altimgs'] as $altimage) {
9216
					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9217
						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9218
						$out .= ' /DefaultForPrinting';
9219
						if ($altimage[1] === true) {
9220
							$out .= ' true';
9221
						} else {
9222
							$out .= ' false';
9223
						}
9224
						$out .= ' >>';
9225
					}
9226
				}
9227
				$out .= ' ]';
9228
				$out .= "\n".'endobj';
9229
				$this->_out($out);
9230
			}
9231
			// set image object
9232
			$oid = $this->_newobj();
9233
			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9234
			$this->setImageSubBuffer($file, 'n', $this->n);
9235
			$out = '<</Type /XObject';
9236
			$out .= ' /Subtype /Image';
9237
			$out .= ' /Width '.$info['w'];
9238
			$out .= ' /Height '.$info['h'];
9239
			if (array_key_exists('masked', $info)) {
9240
				$out .= ' /SMask '.($this->n - 1).' 0 R';
9241
			}
9242
			// set color space
9243
			$icc = false;
9244
			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9245
				// ICC Colour Space
9246
				$icc = true;
9247
				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9248
			} elseif ($info['cs'] == 'Indexed') {
9249
				// Indexed Colour Space
9250
				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9251
			} else {
9252
				// Device Colour Space
9253
				$out .= ' /ColorSpace /'.$info['cs'];
9254
			}
9255
			if ($info['cs'] == 'DeviceCMYK') {
9256
				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9257
			}
9258
			$out .= ' /BitsPerComponent '.$info['bpc'];
9259
			if ($altoid > 0) {
9260
				// reference to alternate images dictionary
9261
				$out .= ' /Alternates '.$altoid.' 0 R';
9262
			}
9263
			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9264
				// external stream
9265
				$out .= ' /Length 0';
9266
				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9267
				if (isset($info['f'])) {
9268
					$out .= ' /FFilter /'.$info['f'];
9269
				}
9270
				$out .= ' >>';
9271
				$out .= ' stream'."\n".'endstream';
9272
			} else {
9273
				if (isset($info['f'])) {
9274
					$out .= ' /Filter /'.$info['f'];
9275
				}
9276
				if (isset($info['parms'])) {
9277
					$out .= ' '.$info['parms'];
9278
				}
9279
				if (isset($info['trns']) AND is_array($info['trns'])) {
9280
					$trns = '';
9281
					$count_info = count($info['trns']);
9282
					if ($info['cs'] == 'Indexed') {
9283
						$maxval =(pow(2, $info['bpc']) - 1);
9284
						for ($i = 0; $i < $count_info; ++$i) {
9285
							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9286
								// this is not a binary type mask @TODO: create a SMask
9287
								$trns = '';
9288
								break;
9289
							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9290
								// store the first fully transparent value
9291
								$trns .= $i.' '.$i.' ';
9292
							}
9293
						}
9294
					} else {
9295
						// grayscale or RGB
9296
						for ($i = 0; $i < $count_info; ++$i) {
9297
							if ($info['trns'][$i] == 0) {
9298
								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9299
							}
9300
						}
9301
					}
9302
					// Colour Key Masking
9303
					if (!empty($trns)) {
9304
						$out .= ' /Mask ['.$trns.']';
9305
					}
9306
				}
9307
				$stream = $this->_getrawstream($info['data']);
9308
				$out .= ' /Length '.strlen($stream).' >>';
9309
				$out .= ' stream'."\n".$stream."\n".'endstream';
9310
			}
9311
			$out .= "\n".'endobj';
9312
			$this->_out($out);
9313
			if ($icc) {
9314
				// ICC colour profile
9315
				$this->_newobj();
9316
				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9317
				$icc = $this->_getrawstream($icc);
9318
				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9319
			} elseif ($info['cs'] == 'Indexed') {
9320
				// colour palette
9321
				$this->_newobj();
9322
				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9323
				$pal = $this->_getrawstream($pal);
9324
				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9325
			}
9326
		}
9327
	}
9328
 
9329
	/**
9330
	 * Output Form XObjects Templates.
9331
	 * @author Nicola Asuni
9332
	 * @since 5.8.017 (2010-08-24)
9333
	 * @protected
9334
	 * @see startTemplate(), endTemplate(), printTemplate()
9335
	 */
9336
	protected function _putxobjects() {
9337
		foreach ($this->xobjects as $key => $data) {
9338
			if (isset($data['outdata'])) {
9339
				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9340
				$out = $this->_getobj($data['n'])."\n";
9341
				$out .= '<<';
9342
				$out .= ' /Type /XObject';
9343
				$out .= ' /Subtype /Form';
9344
				$out .= ' /FormType 1';
9345
				if ($this->compress) {
9346
					$stream = gzcompress($stream);
9347
					$out .= ' /Filter /FlateDecode';
9348
				}
9349
				$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));
9350
				$out .= ' /Matrix [1 0 0 1 0 0]';
9351
				$out .= ' /Resources <<';
9352
				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9353
				if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9354
					// transparency
9355
					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9356
						$out .= ' /ExtGState <<';
9357
						foreach ($data['extgstates'] as $k => $extgstate) {
9358
							if (isset($this->extgstates[$k]['name'])) {
9359
								$out .= ' /'.$this->extgstates[$k]['name'];
9360
							} else {
9361
								$out .= ' /GS'.$k;
9362
							}
9363
							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9364
						}
9365
						$out .= ' >>';
9366
					}
9367
					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9368
						$gp = '';
9369
						$gs = '';
9370
						foreach ($data['gradients'] as $id => $grad) {
9371
							// gradient patterns
9372
							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9373
							// gradient shadings
9374
							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9375
						}
9376
						$out .= ' /Pattern <<'.$gp.' >>';
9377
						$out .= ' /Shading <<'.$gs.' >>';
9378
					}
9379
				}
9380
				// spot colors
9381
				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9382
					$out .= ' /ColorSpace <<';
9383
					foreach ($data['spot_colors'] as $name => $color) {
9384
						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9385
					}
9386
					$out .= ' >>';
9387
				}
9388
				// fonts
9389
				if (!empty($data['fonts'])) {
9390
					$out .= ' /Font <<';
9391
					foreach ($data['fonts'] as $fontkey => $fontid) {
9392
						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9393
					}
9394
					$out .= ' >>';
9395
				}
9396
				// images or nested xobjects
9397
				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9398
					$out .= ' /XObject <<';
9399
					foreach ($data['images'] as $imgid) {
9400
						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9401
					}
9402
					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9403
						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9404
					}
9405
					$out .= ' >>';
9406
				}
9407
				$out .= ' >>'; //end resources
9408
				if (isset($data['group']) AND ($data['group'] !== false)) {
9409
					// set transparency group
9410
					$out .= ' /Group << /Type /Group /S /Transparency';
9411
					if (is_array($data['group'])) {
9412
						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9413
							$out .= ' /CS /'.$data['group']['CS'];
9414
						}
9415
						if (isset($data['group']['I'])) {
9416
							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9417
						}
9418
						if (isset($data['group']['K'])) {
9419
							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9420
						}
9421
					}
9422
					$out .= ' >>';
9423
				}
9424
				$stream = $this->_getrawstream($stream, $data['n']);
9425
				$out .= ' /Length '.strlen($stream);
9426
				$out .= ' >>';
9427
				$out .= ' stream'."\n".$stream."\n".'endstream';
9428
				$out .= "\n".'endobj';
9429
				$this->_out($out);
9430
			}
9431
		}
9432
	}
9433
 
9434
	/**
9435
	 * Output Spot Colors Resources.
9436
	 * @protected
9437
	 * @since 4.0.024 (2008-09-12)
9438
	 */
9439
	protected function _putspotcolors() {
9440
		foreach ($this->spot_colors as $name => $color) {
9441
			$this->_newobj();
9442
			$this->spot_colors[$name]['n'] = $this->n;
9443
			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9444
			$out .= ' /DeviceCMYK <<';
9445
			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9446
			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9447
			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9448
			$out .= "\n".'endobj';
9449
			$this->_out($out);
9450
		}
9451
	}
9452
 
9453
	/**
9454
	 * Return XObjects Dictionary.
9455
	 * @return string XObjects dictionary
9456
	 * @protected
9457
	 * @since 5.8.014 (2010-08-23)
9458
	 */
9459
	protected function _getxobjectdict() {
9460
		$out = '';
9461
		foreach ($this->xobjects as $id => $objid) {
9462
			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9463
		}
9464
		return $out;
9465
	}
9466
 
9467
	/**
9468
	 * Output Resources Dictionary.
9469
	 * @protected
9470
	 */
9471
	protected function _putresourcedict() {
9472
		$out = $this->_getobj(2)."\n";
9473
		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9474
		$out .= ' /Font <<';
9475
		foreach ($this->fontkeys as $fontkey) {
9476
			$font = $this->getFontBuffer($fontkey);
9477
			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9478
		}
9479
		$out .= ' >>';
9480
		$out .= ' /XObject <<';
9481
		$out .= $this->_getxobjectdict();
9482
		$out .= ' >>';
9483
		// layers
9484
		if (!empty($this->pdflayers)) {
9485
			$out .= ' /Properties <<';
9486
			foreach ($this->pdflayers as $layer) {
9487
				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9488
			}
9489
			$out .= ' >>';
9490
		}
9491
		if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9492
			// transparency
9493
			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9494
				$out .= ' /ExtGState <<';
9495
				foreach ($this->extgstates as $k => $extgstate) {
9496
					if (isset($extgstate['name'])) {
9497
						$out .= ' /'.$extgstate['name'];
9498
					} else {
9499
						$out .= ' /GS'.$k;
9500
					}
9501
					$out .= ' '.$extgstate['n'].' 0 R';
9502
				}
9503
				$out .= ' >>';
9504
			}
9505
			if (isset($this->gradients) AND !empty($this->gradients)) {
9506
				$gp = '';
9507
				$gs = '';
9508
				foreach ($this->gradients as $id => $grad) {
9509
					// gradient patterns
9510
					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9511
					// gradient shadings
9512
					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9513
				}
9514
				$out .= ' /Pattern <<'.$gp.' >>';
9515
				$out .= ' /Shading <<'.$gs.' >>';
9516
			}
9517
		}
9518
		// spot colors
9519
		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9520
			$out .= ' /ColorSpace <<';
9521
			foreach ($this->spot_colors as $color) {
9522
				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9523
			}
9524
			$out .= ' >>';
9525
		}
9526
		$out .= ' >>';
9527
		$out .= "\n".'endobj';
9528
		$this->_out($out);
9529
	}
9530
 
9531
	/**
9532
	 * Output Resources.
9533
	 * @protected
9534
	 */
9535
	protected function _putresources() {
9536
		$this->_putextgstates();
9537
		$this->_putocg();
9538
		$this->_putfonts();
9539
		$this->_putimages();
9540
		$this->_putspotcolors();
9541
		$this->_putshaders();
9542
		$this->_putxobjects();
9543
		$this->_putresourcedict();
9544
		$this->_putdests();
9545
		$this->_putEmbeddedFiles();
9546
		$this->_putannotsobjs();
9547
		$this->_putjavascript();
9548
		$this->_putbookmarks();
9549
		$this->_putencryption();
9550
	}
9551
 
9552
	/**
9553
	 * Adds some Metadata information (Document Information Dictionary)
9554
	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9555
	 * @return int object id
9556
	 * @protected
9557
	 */
9558
	protected function _putinfo() {
9559
		$oid = $this->_newobj();
9560
		$out = '<<';
9561
		// store current isunicode value
9562
		$prev_isunicode = $this->isunicode;
9563
		if ($this->docinfounicode) {
9564
			$this->isunicode = true;
9565
		}
9566
		if (!TCPDF_STATIC::empty_string($this->title)) {
9567
			// The document's title.
9568
			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9569
		}
9570
		if (!TCPDF_STATIC::empty_string($this->author)) {
9571
			// The name of the person who created the document.
9572
			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9573
		}
9574
		if (!TCPDF_STATIC::empty_string($this->subject)) {
9575
			// The subject of the document.
9576
			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9577
		}
9578
		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9579
			// Keywords associated with the document.
9580
			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9581
		}
9582
		if (!TCPDF_STATIC::empty_string($this->creator)) {
9583
			// 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.
9584
			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9585
		}
9586
		// restore previous isunicode value
9587
		$this->isunicode = $prev_isunicode;
9588
		// default producer
9589
		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9590
		// The date and time the document was created, in human-readable form
9591
		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9592
		// The date and time the document was most recently modified, in human-readable form
9593
		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9594
		// A name object indicating whether the document has been modified to include trapping information
9595
		$out .= ' /Trapped /False';
9596
		$out .= ' >>';
9597
		$out .= "\n".'endobj';
9598
		$this->_out($out);
9599
		return $oid;
9600
	}
9601
 
9602
	/**
9603
	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9604
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9605
	 * @param string $xmp Custom XMP data.
9606
	 * @since 5.9.128 (2011-10-06)
9607
	 * @public
9608
	 */
9609
	public function setExtraXMP($xmp) {
9610
		$this->custom_xmp = $xmp;
9611
	}
9612
 
9613
	/**
9614
	 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9615
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9616
	 * @param string $xmp Custom XMP RDF data.
9617
	 * @since 6.3.0 (2019-09-19)
9618
	 * @public
9619
	 */
9620
	public function setExtraXMPRDF($xmp) {
9621
		$this->custom_xmp_rdf = $xmp;
9622
	}
9623
 
9624
	/**
9625
	 * Put XMP data object and return ID.
9626
	 * @return int The object ID.
9627
	 * @since 5.9.121 (2011-09-28)
9628
	 * @protected
9629
	 */
9630
	protected function _putXMP() {
9631
		$oid = $this->_newobj();
9632
		// store current isunicode value
9633
		$prev_isunicode = $this->isunicode;
9634
		$this->isunicode = true;
9635
		$prev_encrypted = $this->encrypted;
9636
		$this->encrypted = false;
9637
		// set XMP data
9638
		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9639
		$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";
9640
		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9641
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9642
		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9643
		$xmp .= "\t\t\t".'<dc:title>'."\n";
9644
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9645
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9646
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9647
		$xmp .= "\t\t\t".'</dc:title>'."\n";
9648
		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9649
		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9650
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9651
		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9652
		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9653
		$xmp .= "\t\t\t".'<dc:description>'."\n";
9654
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9655
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9656
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9657
		$xmp .= "\t\t\t".'</dc:description>'."\n";
9658
		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9659
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9660
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9661
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9662
		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9663
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9664
		// convert doc creation date format
9665
		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9666
		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9667
		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9668
		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9669
		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9670
		// convert doc modification date format
9671
		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9672
		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9673
		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9674
		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9675
		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9676
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9677
		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9678
		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9679
		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9680
		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9681
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9682
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9683
		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9684
		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9685
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9686
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9687
		$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);
9688
		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9689
		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9690
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9691
		if ($this->pdfa_mode) {
9692
			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9693
			$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9694
			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9695
			$xmp .= "\t\t".'</rdf:Description>'."\n";
9696
		}
9697
		// XMP extension schemas
9698
		$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";
9699
		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9700
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9701
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9702
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9703
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9704
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9705
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9706
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9707
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9708
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9709
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9710
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9711
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9712
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9713
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9714
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9715
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9716
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9717
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9718
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9719
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9720
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9721
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9722
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9723
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9724
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9725
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9726
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9727
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9728
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9729
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9730
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9731
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9732
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9733
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9734
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9735
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9736
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9737
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9738
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9739
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9740
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9741
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9742
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9743
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9744
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9745
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9746
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9747
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9748
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9749
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9750
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9751
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9752
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9753
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9754
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9755
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9756
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9757
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9758
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9759
		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9760
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9761
		$xmp .= $this->custom_xmp_rdf;
9762
		$xmp .= "\t".'</rdf:RDF>'."\n";
9763
		$xmp .= $this->custom_xmp;
9764
		$xmp .= '</x:xmpmeta>'."\n";
9765
		$xmp .= '<?xpacket end="w"?>';
9766
		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9767
		// restore previous isunicode value
9768
		$this->isunicode = $prev_isunicode;
9769
		$this->encrypted = $prev_encrypted;
9770
		$this->_out($out);
9771
		return $oid;
9772
	}
9773
 
9774
	/**
9775
	 * Output Catalog.
9776
	 * @return int object id
9777
	 * @protected
9778
	 */
9779
	protected function _putcatalog() {
9780
		// put XMP
9781
		$xmpobj = $this->_putXMP();
9782
		// if required, add standard sRGB ICC colour profile
9783
		if ($this->pdfa_mode OR $this->force_srgb) {
9784
			$iccobj = $this->_newobj();
9785
			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9786
			$filter = '';
9787
			if ($this->compress) {
9788
				$filter = ' /Filter /FlateDecode';
9789
				$icc = gzcompress($icc);
9790
			}
9791
			$icc = $this->_getrawstream($icc);
9792
			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9793
		}
9794
		// start catalog
9795
		$oid = $this->_newobj();
9796
		$out = '<< /Type /Catalog';
9797
		$out .= ' /Version /'.$this->PDFVersion;
9798
		//$out .= ' /Extensions <<>>';
9799
		$out .= ' /Pages 1 0 R';
9800
		//$out .= ' /PageLabels ' //...;
9801
		$out .= ' /Names <<';
9802
		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9803
			$out .= ' /JavaScript '.$this->n_js;
9804
		}
9805
		if (!empty($this->efnames)) {
9806
			$out .= ' /EmbeddedFiles <</Names [';
9807
			foreach ($this->efnames AS $fn => $fref) {
9808
				$out .= ' '.$this->_datastring($fn).' '.$fref;
9809
			}
9810
			$out .= ' ]>>';
9811
		}
9812
		$out .= ' >>';
9813
		if (!empty($this->dests)) {
9814
			$out .= ' /Dests '.($this->n_dests).' 0 R';
9815
		}
9816
		$out .= $this->_putviewerpreferences();
9817
		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9818
			$out .= ' /PageLayout /'.$this->LayoutMode;
9819
		}
9820
		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9821
			$out .= ' /PageMode /'.$this->PageMode;
9822
		}
9823
		if (count($this->outlines) > 0) {
9824
			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9825
			$out .= ' /PageMode /UseOutlines';
9826
		}
9827
		//$out .= ' /Threads []';
9828
		if ($this->ZoomMode == 'fullpage') {
9829
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9830
		} elseif ($this->ZoomMode == 'fullwidth') {
9831
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9832
		} elseif ($this->ZoomMode == 'real') {
9833
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9834
		} elseif (!is_string($this->ZoomMode)) {
9835
			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9836
		}
9837
		//$out .= ' /AA <<>>';
9838
		//$out .= ' /URI <<>>';
9839
		$out .= ' /Metadata '.$xmpobj.' 0 R';
9840
		//$out .= ' /StructTreeRoot <<>>';
9841
		//$out .= ' /MarkInfo <<>>';
9842
		if (isset($this->l['a_meta_language'])) {
9843
			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9844
		}
9845
		//$out .= ' /SpiderInfo <<>>';
9846
		// set OutputIntent to sRGB IEC61966-2.1 if required
9847
		if ($this->pdfa_mode OR $this->force_srgb) {
9848
			$out .= ' /OutputIntents [<<';
9849
			$out .= ' /Type /OutputIntent';
9850
			$out .= ' /S /GTS_PDFA1';
9851
			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9852
			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9853
			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9854
			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9855
			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9856
			$out .= ' >>]';
9857
		}
9858
		//$out .= ' /PieceInfo <<>>';
9859
		if (!empty($this->pdflayers)) {
9860
			$lyrobjs = '';
9861
			$lyrobjs_off = '';
9862
			$lyrobjs_lock = '';
9863
			foreach ($this->pdflayers as $layer) {
9864
				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9865
				$lyrobjs .= $layer_obj_ref;
9866
				if ($layer['view'] === false) {
9867
					$lyrobjs_off .= $layer_obj_ref;
9868
				}
9869
				if ($layer['lock']) {
9870
					$lyrobjs_lock .= $layer_obj_ref;
9871
				}
9872
			}
9873
			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9874
			$out .= ' /D <<';
9875
			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9876
			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9877
			$out .= ' /BaseState /ON';
9878
			$out .= ' /OFF ['.$lyrobjs_off.']';
9879
			$out .= ' /Locked ['.$lyrobjs_lock.']';
9880
			$out .= ' /Intent /View';
9881
			$out .= ' /AS [';
9882
			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9883
			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9884
			$out .= ' ]';
9885
			$out .= ' /Order ['.$lyrobjs.']';
9886
			$out .= ' /ListMode /AllPages';
9887
			//$out .= ' /RBGroups ['..']';
9888
			//$out .= ' /Locked ['..']';
9889
			$out .= ' >>';
9890
			$out .= ' >>';
9891
		}
9892
		// AcroForm
9893
		if (!empty($this->form_obj_id)
9894
			OR ($this->sign AND isset($this->signature_data['cert_type']))
9895
			OR !empty($this->empty_signature_appearance)) {
9896
			$out .= ' /AcroForm <<';
9897
			$objrefs = '';
9898
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9899
				// set reference for signature object
9900
				$objrefs .= $this->sig_obj_id.' 0 R';
9901
			}
9902
			if (!empty($this->empty_signature_appearance)) {
9903
				foreach ($this->empty_signature_appearance as $esa) {
9904
					// set reference for empty signature objects
9905
					$objrefs .= ' '.$esa['objid'].' 0 R';
9906
				}
9907
			}
9908
			if (!empty($this->form_obj_id)) {
9909
				foreach($this->form_obj_id as $objid) {
9910
					$objrefs .= ' '.$objid.' 0 R';
9911
				}
9912
			}
9913
			$out .= ' /Fields ['.$objrefs.']';
9914
			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9915
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9916
				$out .= ' /NeedAppearances false';
9917
			}
9918
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9919
				if ($this->signature_data['cert_type'] > 0) {
9920
					$out .= ' /SigFlags 3';
9921
				} else {
9922
					$out .= ' /SigFlags 1';
9923
				}
9924
			}
9925
			//$out .= ' /CO ';
9926
			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9927
				$out .= ' /DR <<';
9928
				$out .= ' /Font <<';
9929
				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9930
					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9931
				}
9932
				$out .= ' >> >>';
9933
			}
9934
			$font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
9935
			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9936
			$out .= ' /Q '.(($this->rtl)?'2':'0');
9937
			//$out .= ' /XFA ';
9938
			$out .= ' >>';
9939
			// signatures
9940
			if ($this->sign AND isset($this->signature_data['cert_type'])
9941
				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9942
				if ($this->signature_data['cert_type'] > 0) {
9943
					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9944
				} else {
9945
					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9946
				}
9947
			}
9948
		}
9949
		//$out .= ' /Legal <<>>';
9950
		//$out .= ' /Requirements []';
9951
		//$out .= ' /Collection <<>>';
9952
		//$out .= ' /NeedsRendering true';
9953
		$out .= ' >>';
9954
		$out .= "\n".'endobj';
9955
		$this->_out($out);
9956
		return $oid;
9957
	}
9958
 
9959
	/**
9960
	 * Output viewer preferences.
9961
	 * @return string for viewer preferences
9962
	 * @author Nicola asuni
9963
	 * @since 3.1.000 (2008-06-09)
9964
	 * @protected
9965
	 */
9966
	protected function _putviewerpreferences() {
9967
		$vp = $this->viewer_preferences;
9968
		$out = ' /ViewerPreferences <<';
9969
		if ($this->rtl) {
9970
			$out .= ' /Direction /R2L';
9971
		} else {
9972
			$out .= ' /Direction /L2R';
9973
		}
9974
		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9975
			$out .= ' /HideToolbar true';
9976
		}
9977
		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9978
			$out .= ' /HideMenubar true';
9979
		}
9980
		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9981
			$out .= ' /HideWindowUI true';
9982
		}
9983
		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9984
			$out .= ' /FitWindow true';
9985
		}
9986
		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9987
			$out .= ' /CenterWindow true';
9988
		}
9989
		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9990
			$out .= ' /DisplayDocTitle true';
9991
		}
9992
		if (isset($vp['NonFullScreenPageMode'])) {
9993
			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9994
		}
9995
		if (isset($vp['ViewArea'])) {
9996
			$out .= ' /ViewArea /'.$vp['ViewArea'];
9997
		}
9998
		if (isset($vp['ViewClip'])) {
9999
			$out .= ' /ViewClip /'.$vp['ViewClip'];
10000
		}
10001
		if (isset($vp['PrintArea'])) {
10002
			$out .= ' /PrintArea /'.$vp['PrintArea'];
10003
		}
10004
		if (isset($vp['PrintClip'])) {
10005
			$out .= ' /PrintClip /'.$vp['PrintClip'];
10006
		}
10007
		if (isset($vp['PrintScaling'])) {
10008
			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
10009
		}
10010
		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
10011
			$out .= ' /Duplex /'.$vp['Duplex'];
10012
		}
10013
		if (isset($vp['PickTrayByPDFSize'])) {
10014
			if ($vp['PickTrayByPDFSize']) {
10015
				$out .= ' /PickTrayByPDFSize true';
10016
			} else {
10017
				$out .= ' /PickTrayByPDFSize false';
10018
			}
10019
		}
10020
		if (isset($vp['PrintPageRange'])) {
10021
			$PrintPageRangeNum = '';
10022
			foreach ($vp['PrintPageRange'] as $k => $v) {
10023
				$PrintPageRangeNum .= ' '.($v - 1).'';
10024
			}
10025
			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10026
		}
10027
		if (isset($vp['NumCopies'])) {
10028
			$out .= ' /NumCopies '.intval($vp['NumCopies']);
10029
		}
10030
		$out .= ' >>';
10031
		return $out;
10032
	}
10033
 
10034
	/**
10035
	 * Output PDF File Header (7.5.2).
10036
	 * @protected
10037
	 */
10038
	protected function _putheader() {
10039
		$this->_out('%PDF-'.$this->PDFVersion);
10040
		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10041
	}
10042
 
10043
	/**
10044
	 * Output end of document (EOF).
10045
	 * @protected
10046
	 */
10047
	protected function _enddoc() {
10048
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10049
			// save subset chars of the previous font
10050
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10051
		}
10052
		$this->state = 1;
10053
		$this->_putheader();
10054
		$this->_putpages();
10055
		$this->_putresources();
10056
		// empty signature fields
10057
		if (!empty($this->empty_signature_appearance)) {
10058
			foreach ($this->empty_signature_appearance as $key => $esa) {
10059
				// widget annotation for empty signature
10060
				$out = $this->_getobj($esa['objid'])."\n";
10061
				$out .= '<< /Type /Annot';
10062
				$out .= ' /Subtype /Widget';
10063
				$out .= ' /Rect ['.$esa['rect'].']';
10064
				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10065
				$out .= ' /F 4';
10066
				$out .= ' /FT /Sig';
10067
				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10068
				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10069
				$out .= ' /Ff 0';
10070
				$out .= ' >>';
10071
				$out .= "\n".'endobj';
10072
				$this->_out($out);
10073
			}
10074
		}
10075
		// Signature
10076
		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10077
			// widget annotation for signature
10078
			$out = $this->_getobj($this->sig_obj_id)."\n";
10079
			$out .= '<< /Type /Annot';
10080
			$out .= ' /Subtype /Widget';
10081
			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10082
			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10083
			$out .= ' /F 4';
10084
			$out .= ' /FT /Sig';
10085
			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10086
			$out .= ' /Ff 0';
10087
			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10088
			$out .= ' >>';
10089
			$out .= "\n".'endobj';
10090
			$this->_out($out);
10091
			// signature
10092
			$this->_putsignature();
10093
		}
10094
		// Info
10095
		$objid_info = $this->_putinfo();
10096
		// Catalog
10097
		$objid_catalog = $this->_putcatalog();
10098
		// Cross-ref
10099
		$o = $this->bufferlen;
10100
		// XREF section
10101
		$this->_out('xref');
10102
		$this->_out('0 '.($this->n + 1));
10103
		$this->_out('0000000000 65535 f ');
10104
		$freegen = ($this->n + 2);
10105
		for ($i=1; $i <= $this->n; ++$i) {
10106
			if (!isset($this->offsets[$i]) AND ($i > 1)) {
10107
				$this->_out(sprintf('0000000000 %05d f ', $freegen));
10108
				++$freegen;
10109
			} else {
10110
				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10111
			}
10112
		}
10113
		// TRAILER
10114
		$out = 'trailer'."\n";
10115
		$out .= '<<';
10116
		$out .= ' /Size '.($this->n + 1);
10117
		$out .= ' /Root '.$objid_catalog.' 0 R';
10118
		$out .= ' /Info '.$objid_info.' 0 R';
10119
		if ($this->encrypted) {
10120
			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10121
		}
10122
		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10123
		$out .= ' >>';
10124
		$this->_out($out);
10125
		$this->_out('startxref');
10126
		$this->_out($o);
10127
		$this->_out('%%EOF');
10128
		$this->state = 3; // end-of-doc
10129
	}
10130
 
10131
	/**
10132
	 * Initialize a new page.
10133
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10134
	 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10135
	 * @protected
10136
	 * @see getPageSizeFromFormat(), setPageFormat()
10137
	 */
10138
	protected function _beginpage($orientation='', $format='') {
10139
		++$this->page;
10140
		$this->pageobjects[$this->page] = array();
10141
		$this->setPageBuffer($this->page, '');
10142
		// initialize array for graphics tranformation positions inside a page buffer
10143
		$this->transfmrk[$this->page] = array();
10144
		$this->state = 2;
10145
		if (TCPDF_STATIC::empty_string($orientation)) {
10146
			if (isset($this->CurOrientation)) {
10147
				$orientation = $this->CurOrientation;
10148
			} elseif ($this->fwPt > $this->fhPt) {
10149
				// landscape
10150
				$orientation = 'L';
10151
			} else {
10152
				// portrait
10153
				$orientation = 'P';
10154
			}
10155
		}
10156
		if (TCPDF_STATIC::empty_string($format)) {
10157
			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10158
			$this->setPageOrientation($orientation);
10159
		} else {
10160
			$this->setPageFormat($format, $orientation);
10161
		}
10162
		if ($this->rtl) {
10163
			$this->x = $this->w - $this->rMargin;
10164
		} else {
10165
			$this->x = $this->lMargin;
10166
		}
10167
		$this->y = $this->tMargin;
10168
		if (isset($this->newpagegroup[$this->page])) {
10169
			// start a new group
10170
			$this->currpagegroup = $this->newpagegroup[$this->page];
10171
			$this->pagegroups[$this->currpagegroup] = 1;
10172
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10173
			++$this->pagegroups[$this->currpagegroup];
10174
		}
10175
	}
10176
 
10177
	/**
10178
	 * Mark end of page.
10179
	 * @protected
10180
	 */
10181
	protected function _endpage() {
10182
		$this->setVisibility('all');
10183
		$this->state = 1;
10184
	}
10185
 
10186
	/**
10187
	 * Begin a new object and return the object number.
10188
	 * @return int object number
10189
	 * @protected
10190
	 */
10191
	protected function _newobj() {
10192
		$this->_out($this->_getobj());
10193
		return $this->n;
10194
	}
10195
 
10196
	/**
10197
	 * Return the starting object string for the selected object ID.
10198
	 * @param int|null $objid Object ID (leave empty to get a new ID).
10199
	 * @return string the starting object string
10200
	 * @protected
10201
	 * @since 5.8.009 (2010-08-20)
10202
	 */
10203
	protected function _getobj($objid=null) {
10204
		if (TCPDF_STATIC::empty_string($objid)) {
10205
			++$this->n;
10206
			$objid = $this->n;
10207
		}
10208
		$this->offsets[$objid] = $this->bufferlen;
10209
		$this->pageobjects[$this->page][] = $objid;
10210
		return $objid.' 0 obj';
10211
	}
10212
 
10213
	/**
10214
	 * Underline text.
10215
	 * @param int $x X coordinate
10216
	 * @param int $y Y coordinate
10217
	 * @param string $txt text to underline
10218
	 * @protected
10219
	 */
10220
	protected function _dounderline($x, $y, $txt) {
10221
		$w = $this->GetStringWidth($txt);
10222
		return $this->_dounderlinew($x, $y, $w);
10223
	}
10224
 
10225
	/**
10226
	 * Underline for rectangular text area.
10227
	 * @param int $x X coordinate
10228
	 * @param int $y Y coordinate
10229
	 * @param int $w width to underline
10230
	 * @protected
10231
	 * @since 4.8.008 (2009-09-29)
10232
	 */
10233
	protected function _dounderlinew($x, $y, $w) {
10234
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10235
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10236
	}
10237
 
10238
	/**
10239
	 * Line through text.
10240
	 * @param int $x X coordinate
10241
	 * @param int $y Y coordinate
10242
	 * @param string $txt text to linethrough
10243
	 * @protected
10244
	 */
10245
	protected function _dolinethrough($x, $y, $txt) {
10246
		$w = $this->GetStringWidth($txt);
10247
		return $this->_dolinethroughw($x, $y, $w);
10248
	}
10249
 
10250
	/**
10251
	 * Line through for rectangular text area.
10252
	 * @param int $x X coordinate
10253
	 * @param int $y Y coordinate
10254
	 * @param int $w line length (width)
10255
	 * @protected
10256
	 * @since 4.9.008 (2009-09-29)
10257
	 */
10258
	protected function _dolinethroughw($x, $y, $w) {
10259
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10260
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10261
	}
10262
 
10263
	/**
10264
	 * Overline text.
10265
	 * @param int $x X coordinate
10266
	 * @param int $y Y coordinate
10267
	 * @param string $txt text to overline
10268
	 * @protected
10269
	 * @since 4.9.015 (2010-04-19)
10270
	 */
10271
	protected function _dooverline($x, $y, $txt) {
10272
		$w = $this->GetStringWidth($txt);
10273
		return $this->_dooverlinew($x, $y, $w);
10274
	}
10275
 
10276
	/**
10277
	 * Overline for rectangular text area.
10278
	 * @param int $x X coordinate
10279
	 * @param int $y Y coordinate
10280
	 * @param int $w width to overline
10281
	 * @protected
10282
	 * @since 4.9.015 (2010-04-19)
10283
	 */
10284
	protected function _dooverlinew($x, $y, $w) {
10285
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10286
		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10287
 
10288
	}
10289
 
10290
	/**
10291
	 * Format a data string for meta information
10292
	 * @param string $s data string to escape.
10293
	 * @param int $n object ID
10294
	 * @return string escaped string.
10295
	 * @protected
10296
	 */
10297
	protected function _datastring($s, $n=0) {
10298
		if ($n == 0) {
10299
			$n = $this->n;
10300
		}
10301
		$s = $this->_encrypt_data($n, $s);
10302
		return '('. TCPDF_STATIC::_escape($s).')';
10303
	}
10304
 
10305
	/**
10306
	 * Set the document creation timestamp
10307
	 * @param mixed $time Document creation timestamp in seconds or date-time string.
10308
	 * @public
10309
	 * @since 5.9.152 (2012-03-23)
10310
	 */
10311
	public function setDocCreationTimestamp($time) {
10312
		if (is_string($time)) {
10313
			$time = TCPDF_STATIC::getTimestamp($time);
10314
		}
10315
		$this->doc_creation_timestamp = intval($time);
10316
	}
10317
 
10318
	/**
10319
	 * Set the document modification timestamp
10320
	 * @param mixed $time Document modification timestamp in seconds or date-time string.
10321
	 * @public
10322
	 * @since 5.9.152 (2012-03-23)
10323
	 */
10324
	public function setDocModificationTimestamp($time) {
10325
		if (is_string($time)) {
10326
			$time = TCPDF_STATIC::getTimestamp($time);
10327
		}
10328
		$this->doc_modification_timestamp = intval($time);
10329
	}
10330
 
10331
	/**
10332
	 * Returns document creation timestamp in seconds.
10333
	 * @return int Creation timestamp in seconds.
10334
	 * @public
10335
	 * @since 5.9.152 (2012-03-23)
10336
	 */
10337
	public function getDocCreationTimestamp() {
10338
		return $this->doc_creation_timestamp;
10339
	}
10340
 
10341
	/**
10342
	 * Returns document modification timestamp in seconds.
10343
	 * @return int Modfication timestamp in seconds.
10344
	 * @public
10345
	 * @since 5.9.152 (2012-03-23)
10346
	 */
10347
	public function getDocModificationTimestamp() {
10348
		return $this->doc_modification_timestamp;
10349
	}
10350
 
10351
	/**
10352
	 * Returns a formatted date for meta information
10353
	 * @param int $n Object ID.
10354
	 * @param int $timestamp Timestamp to convert.
10355
	 * @return string escaped date string.
10356
	 * @protected
10357
	 * @since 4.6.028 (2009-08-25)
10358
	 */
10359
	protected function _datestring($n=0, $timestamp=0) {
10360
		if ((empty($timestamp)) OR ($timestamp < 0)) {
10361
			$timestamp = $this->doc_creation_timestamp;
10362
		}
10363
		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10364
	}
10365
 
10366
	/**
10367
	 * Format a text string for meta information
10368
	 * @param string $s string to escape.
10369
	 * @param int $n object ID
10370
	 * @return string escaped string.
10371
	 * @protected
10372
	 */
10373
	protected function _textstring($s, $n=0) {
10374
		if ($this->isunicode) {
10375
			//Convert string to UTF-16BE
10376
			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10377
		}
10378
		return $this->_datastring($s, $n);
10379
	}
10380
 
10381
	/**
10382
	 * get raw output stream.
10383
	 * @param string $s string to output.
10384
	 * @param int $n object reference for encryption mode
10385
	 * @protected
10386
	 * @author Nicola Asuni
10387
	 * @since 5.5.000 (2010-06-22)
10388
	 */
10389
	protected function _getrawstream($s, $n=0) {
10390
		if ($n <= 0) {
10391
			// default to current object
10392
			$n = $this->n;
10393
		}
10394
		return $this->_encrypt_data($n, $s);
10395
	}
10396
 
10397
	/**
10398
	 * Output a string to the document.
10399
	 * @param string $s string to output.
10400
	 * @protected
10401
	 */
10402
	protected function _out($s) {
10403
		if ($this->state == 2) {
10404
			if ($this->inxobj) {
10405
				// we are inside an XObject template
10406
				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10407
			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10408
				// puts data before page footer
10409
				$pagebuff = $this->getPageBuffer($this->page);
10410
				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10411
				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10412
				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10413
				// update footer position
10414
				$this->footerpos[$this->page] += strlen($s."\n");
10415
			} else {
10416
				// set page data
10417
				$this->setPageBuffer($this->page, $s."\n", true);
10418
			}
10419
		} elseif ($this->state > 0) {
10420
			// set general data
10421
			$this->setBuffer($s."\n");
10422
		}
10423
	}
10424
 
10425
	/**
10426
	 * Set header font.
10427
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10428
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10429
	 * @public
10430
	 * @since 1.1
10431
	 */
10432
	public function setHeaderFont($font) {
10433
		$this->header_font = $font;
10434
	}
10435
 
10436
	/**
10437
	 * Get header font.
10438
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10439
	 * @phpstan-return array{0: string, 1: string, 2: float|null}
10440
	 * @public
10441
	 * @since 4.0.012 (2008-07-24)
10442
	 */
10443
	public function getHeaderFont() {
10444
		return $this->header_font;
10445
	}
10446
 
10447
	/**
10448
	 * Set footer font.
10449
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10450
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10451
	 * @public
10452
	 * @since 1.1
10453
	 */
10454
	public function setFooterFont($font) {
10455
		$this->footer_font = $font;
10456
	}
10457
 
10458
	/**
10459
	 * Get Footer font.
10460
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10461
	 * @phpstan-return array{0: string, 1: string, 2: float|null} $font
10462
	 * @public
10463
	 * @since 4.0.012 (2008-07-24)
10464
	 */
10465
	public function getFooterFont() {
10466
		return $this->footer_font;
10467
	}
10468
 
10469
	/**
10470
	 * Set language array.
10471
	 * @param array $language
10472
	 * @public
10473
	 * @since 1.1
10474
	 */
10475
	public function setLanguageArray($language) {
10476
		$this->l = $language;
10477
		if (isset($this->l['a_meta_dir'])) {
10478
			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10479
		} else {
10480
			$this->rtl = false;
10481
		}
10482
	}
10483
 
10484
	/**
10485
	 * Returns the PDF data.
10486
	 * @public
10487
	 */
10488
	public function getPDFData() {
10489
		if ($this->state < 3) {
10490
			$this->Close();
10491
		}
10492
		return $this->buffer;
10493
	}
10494
 
10495
	/**
10496
	 * Output anchor link.
10497
	 * @param string $url link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10498
	 * @param string $name link name
10499
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10500
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
10501
	 * @param array|null $color array of RGB text color
10502
	 * @param string $style font style (U, D, B, I)
10503
	 * @param boolean $firstblock if true the string is the starting of a line.
10504
	 * @return int the number of cells used or the remaining text if $firstline = true;
10505
	 * @public
10506
	 */
10507
	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) {
10508
		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10509
			// convert url to internal link
10510
			$lnkdata = explode(',', $url);
10511
			if (isset($lnkdata[0]) ) {
10512
				$page = substr($lnkdata[0], 1);
10513
				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10514
					$lnky = floatval($lnkdata[1]);
10515
				} else {
10516
					$lnky = 0;
10517
				}
10518
				$url = $this->AddLink();
10519
				$this->setLink($url, $lnky, $page);
10520
			}
10521
		}
10522
		// store current settings
10523
		$prevcolor = $this->fgcolor;
10524
		$prevstyle = $this->FontStyle;
10525
		if (empty($color)) {
10526
			$this->setTextColorArray($this->htmlLinkColorArray);
10527
		} else {
10528
			$this->setTextColorArray($color);
10529
		}
10530
		if ($style == -1) {
10531
			$this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10532
		} else {
10533
			$this->setFont('', $this->FontStyle.$style);
10534
		}
10535
		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10536
		// restore settings
10537
		$this->setFont('', $prevstyle);
10538
		$this->setTextColorArray($prevcolor);
10539
		return $ret;
10540
	}
10541
 
10542
	/**
10543
	 * Converts pixels to User's Units.
10544
	 * @param int $px pixels
10545
	 * @return float value in user's unit
10546
	 * @public
10547
	 * @see setImageScale(), getImageScale()
10548
	 */
10549
	public function pixelsToUnits($px) {
10550
		return ($px / ($this->imgscale * $this->k));
10551
	}
10552
 
10553
	/**
10554
	 * Reverse function for htmlentities.
10555
	 * Convert entities in UTF-8.
10556
	 * @param string $text_to_convert Text to convert.
10557
	 * @return string converted text string
10558
	 * @public
10559
	 */
10560
	public function unhtmlentities($text_to_convert) {
10561
		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10562
	}
10563
 
10564
	// ENCRYPTION METHODS ----------------------------------
10565
 
10566
	/**
10567
	 * Compute encryption key depending on object number where the encrypted data is stored.
10568
	 * This is used for all strings and streams without crypt filter specifier.
10569
	 * @param int $n object number
10570
	 * @return int object key
10571
	 * @protected
10572
	 * @author Nicola Asuni
10573
	 * @since 2.0.000 (2008-01-02)
10574
	 */
10575
	protected function _objectkey($n) {
10576
		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10577
		if ($this->encryptdata['mode'] == 2) { // AES-128
10578
			// AES padding
10579
			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10580
		}
10581
		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10582
		$objkey = substr($objkey, 0, 16);
10583
		return $objkey;
10584
	}
10585
 
10586
	/**
10587
	 * Encrypt the input string.
10588
	 * @param int $n object number
10589
	 * @param string $s data string to encrypt
10590
	 * @return string encrypted string
10591
	 * @protected
10592
	 * @author Nicola Asuni
10593
	 * @since 5.0.005 (2010-05-11)
10594
	 */
10595
	protected function _encrypt_data($n, $s) {
10596
		if (!$this->encrypted) {
10597
			return $s;
10598
		}
10599
		switch ($this->encryptdata['mode']) {
10600
			case 0:   // RC4-40
10601
			case 1: { // RC4-128
10602
				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10603
				break;
10604
			}
10605
			case 2: { // AES-128
10606
				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10607
				break;
10608
			}
10609
			case 3: { // AES-256
10610
				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10611
				break;
10612
			}
10613
		}
10614
		return $s;
10615
	}
10616
 
10617
	/**
10618
	 * Put encryption on PDF document.
10619
	 * @protected
10620
	 * @author Nicola Asuni
10621
	 * @since 2.0.000 (2008-01-02)
10622
	 */
10623
	protected function _putencryption() {
10624
		if (!$this->encrypted) {
10625
			return;
10626
		}
10627
		$this->encryptdata['objid'] = $this->_newobj();
10628
		$out = '<<';
10629
		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10630
			$this->encryptdata['Filter'] = 'Standard';
10631
		}
10632
		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10633
		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10634
			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10635
		}
10636
		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10637
			$this->encryptdata['V'] = 1;
10638
		}
10639
		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10640
		$out .= ' /V '.$this->encryptdata['V'];
10641
		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10642
			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10643
			$out .= ' /Length '.$this->encryptdata['Length'];
10644
		} else {
10645
			$out .= ' /Length 40';
10646
		}
10647
		if ($this->encryptdata['V'] >= 4) {
10648
			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10649
				$this->encryptdata['StmF'] = 'Identity';
10650
			}
10651
			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10652
				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10653
				$this->encryptdata['StrF'] = 'Identity';
10654
			}
10655
			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10656
			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10657
				$out .= ' /CF <<';
10658
				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10659
				$out .= ' /Type /CryptFilter';
10660
				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10661
					// The method used
10662
					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10663
					if ($this->encryptdata['pubkey']) {
10664
						$out .= ' /Recipients [';
10665
						foreach ($this->encryptdata['Recipients'] as $rec) {
10666
							$out .= ' <'.$rec.'>';
10667
						}
10668
						$out .= ' ]';
10669
						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10670
							$out .= ' /EncryptMetadata false';
10671
						} else {
10672
							$out .= ' /EncryptMetadata true';
10673
						}
10674
					}
10675
				} else {
10676
					$out .= ' /CFM /None';
10677
				}
10678
				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10679
					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10680
					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10681
				} else {
10682
					$out .= ' /AuthEvent /DocOpen';
10683
				}
10684
				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10685
					// The bit length of the encryption key.
10686
					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10687
				}
10688
				$out .= ' >> >>';
10689
			}
10690
			// The name of the crypt filter that shall be used by default when decrypting streams.
10691
			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10692
			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10693
			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10694
			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10695
				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10696
				$out .= ' /EFF /'.$this->encryptdata[''];
10697
			}
10698
		}
10699
		// Additional encryption dictionary entries for the standard security handler
10700
		if ($this->encryptdata['pubkey']) {
10701
			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10702
				$out .= ' /Recipients [';
10703
				foreach ($this->encryptdata['Recipients'] as $rec) {
10704
					$out .= ' <'.$rec.'>';
10705
				}
10706
				$out .= ' ]';
10707
			}
10708
		} else {
10709
			$out .= ' /R';
10710
			if ($this->encryptdata['V'] == 5) { // AES-256
10711
				$out .= ' 5';
10712
				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10713
				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10714
				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10715
			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10716
				$out .= ' 4';
10717
			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10718
				$out .= ' 2';
10719
			} else { // RC-128
10720
				$out .= ' 3';
10721
			}
10722
			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10723
			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10724
			$out .= ' /P '.$this->encryptdata['P'];
10725
			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10726
				$out .= ' /EncryptMetadata false';
10727
			} else {
10728
				$out .= ' /EncryptMetadata true';
10729
			}
10730
		}
10731
		$out .= ' >>';
10732
		$out .= "\n".'endobj';
10733
		$this->_out($out);
10734
	}
10735
 
10736
	/**
10737
	 * Compute U value (used for encryption)
10738
	 * @return string U value
10739
	 * @protected
10740
	 * @since 2.0.000 (2008-01-02)
10741
	 * @author Nicola Asuni
10742
	 */
10743
	protected function _Uvalue() {
10744
		if ($this->encryptdata['mode'] == 0) { // RC4-40
10745
			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10746
		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10747
			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10748
			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10749
			$len = strlen($tmp);
10750
			for ($i = 1; $i <= 19; ++$i) {
10751
				$ek = '';
10752
				for ($j = 0; $j < $len; ++$j) {
10753
					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10754
				}
10755
				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10756
			}
10757
			$enc .= str_repeat("\x00", 16);
10758
			return substr($enc, 0, 32);
10759
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10760
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10761
			// User Validation Salt
10762
			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10763
			// User Key Salt
10764
			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10765
			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10766
		}
10767
	}
10768
 
10769
	/**
10770
	 * Compute UE value (used for encryption)
10771
	 * @return string UE value
10772
	 * @protected
10773
	 * @since 5.9.006 (2010-10-19)
10774
	 * @author Nicola Asuni
10775
	 */
10776
	protected function _UEvalue() {
10777
		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10778
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10779
	}
10780
 
10781
	/**
10782
	 * Compute O value (used for encryption)
10783
	 * @return string O value
10784
	 * @protected
10785
	 * @since 2.0.000 (2008-01-02)
10786
	 * @author Nicola Asuni
10787
	 */
10788
	protected function _Ovalue() {
10789
		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10790
			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10791
			if ($this->encryptdata['mode'] > 0) {
10792
				for ($i = 0; $i < 50; ++$i) {
10793
					$tmp = TCPDF_STATIC::_md5_16($tmp);
10794
				}
10795
			}
10796
			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10797
			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10798
			if ($this->encryptdata['mode'] > 0) {
10799
				$len = strlen($owner_key);
10800
				for ($i = 1; $i <= 19; ++$i) {
10801
					$ek = '';
10802
					for ($j = 0; $j < $len; ++$j) {
10803
						$ek .= chr(ord($owner_key[$j]) ^ $i);
10804
					}
10805
					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10806
				}
10807
			}
10808
			return $enc;
10809
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10810
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10811
			// Owner Validation Salt
10812
			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10813
			// Owner Key Salt
10814
			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10815
			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10816
		}
10817
	}
10818
 
10819
	/**
10820
	 * Compute OE value (used for encryption)
10821
	 * @return string OE value
10822
	 * @protected
10823
	 * @since 5.9.006 (2010-10-19)
10824
	 * @author Nicola Asuni
10825
	 */
10826
	protected function _OEvalue() {
10827
		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10828
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10829
	}
10830
 
10831
	/**
10832
	 * Convert password for AES-256 encryption mode
10833
	 * @param string $password password
10834
	 * @return string password
10835
	 * @protected
10836
	 * @since 5.9.006 (2010-10-19)
10837
	 * @author Nicola Asuni
10838
	 */
10839
	protected function _fixAES256Password($password) {
10840
		$psw = ''; // password to be returned
10841
		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10842
		foreach ($psw_array as $c) {
10843
			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10844
		}
10845
		return substr($psw, 0, 127);
10846
	}
10847
 
10848
	/**
10849
	 * Compute encryption key
10850
	 * @protected
10851
	 * @since 2.0.000 (2008-01-02)
10852
	 * @author Nicola Asuni
10853
	 */
10854
	protected function _generateencryptionkey() {
10855
		$keybytelen = ($this->encryptdata['Length'] / 8);
10856
		if (!$this->encryptdata['pubkey']) { // standard mode
10857
			if ($this->encryptdata['mode'] == 3) { // AES-256
10858
				// generate 256 bit random key
10859
				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10860
				// truncate passwords
10861
				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10862
				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10863
				// Compute U value
10864
				$this->encryptdata['U'] = $this->_Uvalue();
10865
				// Compute UE value
10866
				$this->encryptdata['UE'] = $this->_UEvalue();
10867
				// Compute O value
10868
				$this->encryptdata['O'] = $this->_Ovalue();
10869
				// Compute OE value
10870
				$this->encryptdata['OE'] = $this->_OEvalue();
10871
				// Compute P value
10872
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10873
				// Computing the encryption dictionary's Perms (permissions) value
10874
				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10875
				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10876
				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10877
					$perms .= 'F';
10878
				} else {
10879
					$perms .= 'T';
10880
				}
10881
				$perms .= 'adb'; // bytes 9-11
10882
				$perms .= 'nick'; // bytes 12-15
10883
				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10884
			} else { // RC4-40, RC4-128, AES-128
10885
				// Pad passwords
10886
				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10887
				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10888
				// Compute O value
10889
				$this->encryptdata['O'] = $this->_Ovalue();
10890
				// get default permissions (reverse byte order)
10891
				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10892
				// Compute encryption key
10893
				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10894
				if ($this->encryptdata['mode'] > 0) {
10895
					for ($i = 0; $i < 50; ++$i) {
10896
						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10897
					}
10898
				}
10899
				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10900
				// Compute U value
10901
				$this->encryptdata['U'] = $this->_Uvalue();
10902
				// Compute P value
10903
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10904
			}
10905
		} else { // Public-Key mode
10906
			// random 20-byte seed
10907
			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10908
			$recipient_bytes = '';
10909
			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10910
				// for each public certificate
10911
				if (isset($pubkey['p'])) {
10912
					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10913
				} else {
10914
					$pkprotection = $this->encryptdata['protection'];
10915
				}
10916
				// get default permissions (reverse byte order)
10917
				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10918
				// envelope data
10919
				$envelope = $seed.$pkpermissions;
10920
				// write the envelope data to a temporary file
10921
				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10922
				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10923
				if (!$f) {
10924
					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10925
				}
10926
				$envelope_length = strlen($envelope);
10927
				fwrite($f, $envelope, $envelope_length);
10928
				fclose($f);
10929
				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10930
				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10931
					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10932
				}
10933
				// read encryption signature
10934
				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10935
				// extract signature
10936
				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10937
				$tmparr = explode("\n\n", $signature);
10938
				$signature = trim($tmparr[1]);
10939
				unset($tmparr);
10940
				// decode signature
10941
				$signature = base64_decode($signature);
10942
				// convert signature to hex
10943
				$hexsignature = current(unpack('H*', $signature));
10944
				// store signature on recipients array
10945
				$this->encryptdata['Recipients'][] = $hexsignature;
10946
				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10947
				$recipient_bytes .= $signature;
10948
			}
10949
			// calculate encryption key
10950
			if ($this->encryptdata['mode'] == 3) { // AES-256
10951
				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10952
			} else { // RC4-40, RC4-128, AES-128
10953
				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10954
			}
10955
		}
10956
	}
10957
 
10958
	/**
10959
	 * Set document protection
10960
	 * Remark: the protection against modification is for people who have the full Acrobat product.
10961
	 * 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.
10962
	 * 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.
10963
	 * @param array $permissions the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
10964
	 * @param string $user_pass user password. Empty by default.
10965
	 * @param string|null $owner_pass owner password. If not specified, a random value is used.
10966
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10967
	 * @param array|null $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10968
	 * @public
10969
	 * @since 2.0.000 (2008-01-02)
10970
	 * @author Nicola Asuni
10971
	 */
10972
	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) {
10973
		if ($this->pdfa_mode) {
10974
			// encryption is not allowed in PDF/A mode
10975
			return;
10976
		}
10977
		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10978
		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10979
			// public-key mode
10980
			$this->encryptdata['pubkeys'] = $pubkeys;
10981
			if ($mode == 0) {
10982
				// public-Key Security requires at least 128 bit
10983
				$mode = 1;
10984
			}
10985
			if (!function_exists('openssl_pkcs7_encrypt')) {
10986
				$this->Error('Public-Key Security requires openssl library.');
10987
			}
10988
			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10989
			$this->encryptdata['pubkey'] = true;
10990
			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10991
			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
10992
			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
10993
		} else {
10994
			// standard mode (password mode)
10995
			$this->encryptdata['pubkey'] = false;
10996
			$this->encryptdata['Filter'] = 'Standard';
10997
			$this->encryptdata['StmF'] = 'StdCF';
10998
			$this->encryptdata['StrF'] = 'StdCF';
10999
		}
11000
		if ($mode > 1) { // AES
11001
			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
11002
				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
11003
			}
11004
			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
11005
				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
11006
			}
11007
			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
11008
				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11009
			}
11010
			if (($mode == 3) AND !function_exists('hash')) {
11011
				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11012
				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11013
			}
11014
		}
11015
		if ($owner_pass === null) {
11016
			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11017
		}
11018
		$this->encryptdata['user_password'] = $user_pass;
11019
		$this->encryptdata['owner_password'] = $owner_pass;
11020
		$this->encryptdata['mode'] = $mode;
11021
		switch ($mode) {
11022
			case 0: { // RC4 40 bit
11023
				$this->encryptdata['V'] = 1;
11024
				$this->encryptdata['Length'] = 40;
11025
				$this->encryptdata['CF']['CFM'] = 'V2';
11026
				break;
11027
			}
11028
			case 1: { // RC4 128 bit
11029
				$this->encryptdata['V'] = 2;
11030
				$this->encryptdata['Length'] = 128;
11031
				$this->encryptdata['CF']['CFM'] = 'V2';
11032
				if ($this->encryptdata['pubkey']) {
11033
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11034
					$this->encryptdata['Recipients'] = array();
11035
				}
11036
				break;
11037
			}
11038
			case 2: { // AES 128 bit
11039
				$this->encryptdata['V'] = 4;
11040
				$this->encryptdata['Length'] = 128;
11041
				$this->encryptdata['CF']['CFM'] = 'AESV2';
11042
				$this->encryptdata['CF']['Length'] = 128;
11043
				if ($this->encryptdata['pubkey']) {
11044
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11045
					$this->encryptdata['Recipients'] = array();
11046
				}
11047
				break;
11048
			}
11049
			case 3: { // AES 256 bit
11050
				$this->encryptdata['V'] = 5;
11051
				$this->encryptdata['Length'] = 256;
11052
				$this->encryptdata['CF']['CFM'] = 'AESV3';
11053
				$this->encryptdata['CF']['Length'] = 256;
11054
				if ($this->encryptdata['pubkey']) {
11055
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11056
					$this->encryptdata['Recipients'] = array();
11057
				}
11058
				break;
11059
			}
11060
		}
11061
		$this->encrypted = true;
11062
		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11063
		$this->_generateencryptionkey();
11064
	}
11065
 
11066
	// END OF ENCRYPTION FUNCTIONS -------------------------
11067
 
11068
	// START TRANSFORMATIONS SECTION -----------------------
11069
 
11070
	/**
11071
	 * Starts a 2D tranformation saving current graphic state.
11072
	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11073
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11074
	 * @public
11075
	 * @since 2.1.000 (2008-01-07)
11076
	 * @see StartTransform(), StopTransform()
11077
	 */
11078
	public function StartTransform() {
11079
		if ($this->state != 2) {
11080
			return;
11081
		}
11082
		$this->_outSaveGraphicsState();
11083
		if ($this->inxobj) {
11084
			// we are inside an XObject template
11085
			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11086
		} else {
11087
			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11088
		}
11089
		++$this->transfmatrix_key;
11090
		$this->transfmatrix[$this->transfmatrix_key] = array();
11091
	}
11092
 
11093
	/**
11094
	 * Stops a 2D tranformation restoring previous graphic state.
11095
	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11096
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11097
	 * @public
11098
	 * @since 2.1.000 (2008-01-07)
11099
	 * @see StartTransform(), StopTransform()
11100
	 */
11101
	public function StopTransform() {
11102
		if ($this->state != 2) {
11103
			return;
11104
		}
11105
		$this->_outRestoreGraphicsState();
11106
		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11107
			array_pop($this->transfmatrix[$this->transfmatrix_key]);
11108
			--$this->transfmatrix_key;
11109
		}
11110
		if ($this->inxobj) {
11111
			// we are inside an XObject template
11112
			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11113
		} else {
11114
			array_pop($this->transfmrk[$this->page]);
11115
		}
11116
	}
11117
	/**
11118
	 * Horizontal Scaling.
11119
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11120
	 * @param int $x abscissa of the scaling center. Default is current x position
11121
	 * @param int $y ordinate of the scaling center. Default is current y position
11122
	 * @public
11123
	 * @since 2.1.000 (2008-01-07)
11124
	 * @see StartTransform(), StopTransform()
11125
	 */
11126
	public function ScaleX($s_x, $x='', $y='') {
11127
		$this->Scale($s_x, 100, $x, $y);
11128
	}
11129
 
11130
	/**
11131
	 * Vertical Scaling.
11132
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11133
	 * @param int $x abscissa of the scaling center. Default is current x position
11134
	 * @param int $y ordinate of the scaling center. Default is current y position
11135
	 * @public
11136
	 * @since 2.1.000 (2008-01-07)
11137
	 * @see StartTransform(), StopTransform()
11138
	 */
11139
	public function ScaleY($s_y, $x='', $y='') {
11140
		$this->Scale(100, $s_y, $x, $y);
11141
	}
11142
 
11143
	/**
11144
	 * Vertical and horizontal proportional Scaling.
11145
	 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11146
	 * @param int $x abscissa of the scaling center. Default is current x position
11147
	 * @param int $y ordinate of the scaling center. Default is current y position
11148
	 * @public
11149
	 * @since 2.1.000 (2008-01-07)
11150
	 * @see StartTransform(), StopTransform()
11151
	 */
11152
	public function ScaleXY($s, $x='', $y='') {
11153
		$this->Scale($s, $s, $x, $y);
11154
	}
11155
 
11156
	/**
11157
	 * Vertical and horizontal non-proportional Scaling.
11158
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11159
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11160
	 * @param float|null $x abscissa of the scaling center. Default is current x position
11161
	 * @param float|null $y ordinate of the scaling center. Default is current y position
11162
	 * @public
11163
	 * @since 2.1.000 (2008-01-07)
11164
	 * @see StartTransform(), StopTransform()
11165
	 */
11166
	public function Scale($s_x, $s_y, $x=null, $y=null) {
11167
		if (TCPDF_STATIC::empty_string($x)) {
11168
			$x = $this->x;
11169
		}
11170
		if (TCPDF_STATIC::empty_string($y)) {
11171
			$y = $this->y;
11172
		}
11173
		if (($s_x == 0) OR ($s_y == 0)) {
11174
			$this->Error('Please do not use values equal to zero for scaling');
11175
		}
11176
		$y = ($this->h - $y) * $this->k;
11177
		$x *= $this->k;
11178
		//calculate elements of transformation matrix
11179
		$s_x /= 100;
11180
		$s_y /= 100;
11181
		$tm = array();
11182
		$tm[0] = $s_x;
11183
		$tm[1] = 0;
11184
		$tm[2] = 0;
11185
		$tm[3] = $s_y;
11186
		$tm[4] = $x * (1 - $s_x);
11187
		$tm[5] = $y * (1 - $s_y);
11188
		//scale the coordinate system
11189
		$this->Transform($tm);
11190
	}
11191
 
11192
	/**
11193
	 * Horizontal Mirroring.
11194
	 * @param float|null $x abscissa of the point. Default is current x position
11195
	 * @public
11196
	 * @since 2.1.000 (2008-01-07)
11197
	 * @see StartTransform(), StopTransform()
11198
	 */
11199
	public function MirrorH($x=null) {
11200
		$this->Scale(-100, 100, $x);
11201
	}
11202
 
11203
	/**
11204
	 * Verical Mirroring.
11205
	 * @param float|null $y ordinate of the point. Default is current y position
11206
	 * @public
11207
	 * @since 2.1.000 (2008-01-07)
11208
	 * @see StartTransform(), StopTransform()
11209
	 */
11210
	public function MirrorV($y=null) {
11211
		$this->Scale(100, -100, null, $y);
11212
	}
11213
 
11214
	/**
11215
	 * Point reflection mirroring.
11216
	 * @param float|null $x abscissa of the point. Default is current x position
11217
	 * @param float|null $y ordinate of the point. Default is current y position
11218
	 * @public
11219
	 * @since 2.1.000 (2008-01-07)
11220
	 * @see StartTransform(), StopTransform()
11221
	 */
11222
	public function MirrorP($x=null,$y=null) {
11223
		$this->Scale(-100, -100, $x, $y);
11224
	}
11225
 
11226
	/**
11227
	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11228
	 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11229
	 * @param float|null $x abscissa of the point. Default is current x position
11230
	 * @param float|null $y ordinate of the point. Default is current y position
11231
	 * @public
11232
	 * @since 2.1.000 (2008-01-07)
11233
	 * @see StartTransform(), StopTransform()
11234
	 */
11235
	public function MirrorL($angle=0, $x=null,$y=null) {
11236
		$this->Scale(-100, 100, $x, $y);
11237
		$this->Rotate(-2*($angle-90), $x, $y);
11238
	}
11239
 
11240
	/**
11241
	 * Translate graphic object horizontally.
11242
	 * @param int $t_x movement to the right (or left for RTL)
11243
	 * @public
11244
	 * @since 2.1.000 (2008-01-07)
11245
	 * @see StartTransform(), StopTransform()
11246
	 */
11247
	public function TranslateX($t_x) {
11248
		$this->Translate($t_x, 0);
11249
	}
11250
 
11251
	/**
11252
	 * Translate graphic object vertically.
11253
	 * @param int $t_y movement to the bottom
11254
	 * @public
11255
	 * @since 2.1.000 (2008-01-07)
11256
	 * @see StartTransform(), StopTransform()
11257
	 */
11258
	public function TranslateY($t_y) {
11259
		$this->Translate(0, $t_y);
11260
	}
11261
 
11262
	/**
11263
	 * Translate graphic object horizontally and vertically.
11264
	 * @param int $t_x movement to the right
11265
	 * @param int $t_y movement to the bottom
11266
	 * @public
11267
	 * @since 2.1.000 (2008-01-07)
11268
	 * @see StartTransform(), StopTransform()
11269
	 */
11270
	public function Translate($t_x, $t_y) {
11271
		//calculate elements of transformation matrix
11272
		$tm = array();
11273
		$tm[0] = 1;
11274
		$tm[1] = 0;
11275
		$tm[2] = 0;
11276
		$tm[3] = 1;
11277
		$tm[4] = $t_x * $this->k;
11278
		$tm[5] = -$t_y * $this->k;
11279
		//translate the coordinate system
11280
		$this->Transform($tm);
11281
	}
11282
 
11283
	/**
11284
	 * Rotate object.
11285
	 * @param float $angle angle in degrees for counter-clockwise rotation
11286
	 * @param float|null $x abscissa of the rotation center. Default is current x position
11287
	 * @param float|null $y ordinate of the rotation center. Default is current y position
11288
	 * @public
11289
	 * @since 2.1.000 (2008-01-07)
11290
	 * @see StartTransform(), StopTransform()
11291
	 */
11292
	public function Rotate($angle, $x=null, $y=null) {
11293
		if (TCPDF_STATIC::empty_string($x)) {
11294
			$x = $this->x;
11295
		}
11296
		if (TCPDF_STATIC::empty_string($y)) {
11297
			$y = $this->y;
11298
		}
11299
		$y = ($this->h - $y) * $this->k;
11300
		$x *= $this->k;
11301
		//calculate elements of transformation matrix
11302
		$tm = array();
11303
		$tm[0] = cos(deg2rad($angle));
11304
		$tm[1] = sin(deg2rad($angle));
11305
		$tm[2] = -$tm[1];
11306
		$tm[3] = $tm[0];
11307
		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11308
		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11309
		//rotate the coordinate system around ($x,$y)
11310
		$this->Transform($tm);
11311
	}
11312
 
11313
	/**
11314
	 * Skew horizontally.
11315
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11316
	 * @param float|null $x abscissa of the skewing center. default is current x position
11317
	 * @param float|null $y ordinate of the skewing center. default is current y position
11318
	 * @public
11319
	 * @since 2.1.000 (2008-01-07)
11320
	 * @see StartTransform(), StopTransform()
11321
	 */
11322
	public function SkewX($angle_x, $x=null, $y=null) {
11323
		$this->Skew($angle_x, 0, $x, $y);
11324
	}
11325
 
11326
	/**
11327
	 * Skew vertically.
11328
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11329
	 * @param float|null $x abscissa of the skewing center. default is current x position
11330
	 * @param float|null $y ordinate of the skewing center. default is current y position
11331
	 * @public
11332
	 * @since 2.1.000 (2008-01-07)
11333
	 * @see StartTransform(), StopTransform()
11334
	 */
11335
	public function SkewY($angle_y, $x=null, $y=null) {
11336
		$this->Skew(0, $angle_y, $x, $y);
11337
	}
11338
 
11339
	/**
11340
	 * Skew.
11341
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11342
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11343
	 * @param float|null $x abscissa of the skewing center. default is current x position
11344
	 * @param float|null $y ordinate of the skewing center. default is current y position
11345
	 * @public
11346
	 * @since 2.1.000 (2008-01-07)
11347
	 * @see StartTransform(), StopTransform()
11348
	 */
11349
	public function Skew($angle_x, $angle_y, $x=null, $y=null) {
11350
		if (TCPDF_STATIC::empty_string($x)) {
11351
			$x = $this->x;
11352
		}
11353
		if (TCPDF_STATIC::empty_string($y)) {
11354
			$y = $this->y;
11355
		}
11356
		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11357
			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11358
		}
11359
		$x *= $this->k;
11360
		$y = ($this->h - $y) * $this->k;
11361
		//calculate elements of transformation matrix
11362
		$tm = array();
11363
		$tm[0] = 1;
11364
		$tm[1] = tan(deg2rad($angle_y));
11365
		$tm[2] = tan(deg2rad($angle_x));
11366
		$tm[3] = 1;
11367
		$tm[4] = -$tm[2] * $y;
11368
		$tm[5] = -$tm[1] * $x;
11369
		//skew the coordinate system
11370
		$this->Transform($tm);
11371
	}
11372
 
11373
	/**
11374
	 * Apply graphic transformations.
11375
	 * @param array $tm transformation matrix
11376
	 * @protected
11377
	 * @since 2.1.000 (2008-01-07)
11378
	 * @see StartTransform(), StopTransform()
11379
	 */
11380
	protected function Transform($tm) {
11381
		if ($this->state != 2) {
11382
			return;
11383
		}
11384
		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11385
		// add tranformation matrix
11386
		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11387
		// update transformation mark
11388
		if ($this->inxobj) {
11389
			// we are inside an XObject template
11390
			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11391
				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11392
				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11393
			}
11394
		} elseif (end($this->transfmrk[$this->page]) !== false) {
11395
			$key = key($this->transfmrk[$this->page]);
11396
			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11397
		}
11398
	}
11399
 
11400
	// END TRANSFORMATIONS SECTION -------------------------
11401
 
11402
	// START GRAPHIC FUNCTIONS SECTION ---------------------
11403
	// The following section is based on the code provided by David Hernandez Sanz
11404
 
11405
	/**
11406
	 * 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.
11407
	 * @param float $width The width.
11408
	 * @public
11409
	 * @since 1.0
11410
	 * @see Line(), Rect(), Cell(), MultiCell()
11411
	 */
11412
	public function setLineWidth($width) {
11413
		//Set line width
11414
		$this->LineWidth = $width;
11415
		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11416
		if ($this->state == 2) {
11417
			$this->_out($this->linestyleWidth);
11418
		}
11419
	}
11420
 
11421
	/**
11422
	 * Returns the current the line width.
11423
	 * @return int Line width
11424
	 * @public
11425
	 * @since 2.1.000 (2008-01-07)
11426
	 * @see Line(), SetLineWidth()
11427
	 */
11428
	public function GetLineWidth() {
11429
		return $this->LineWidth;
11430
	}
11431
 
11432
	/**
11433
	 * Set line style.
11434
	 * @param array $style Line style. Array with keys among the following:
11435
	 * <ul>
11436
	 *	 <li>width (float): Width of the line in user units.</li>
11437
	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11438
	 * butt, round, square. The difference between "square" and "butt" is that
11439
	 * "square" projects a flat end past the end of the line.</li>
11440
	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11441
	 * bevel.</li>
11442
	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11443
	 * series of length values, which are the lengths of the on and off dashes.
11444
	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11445
	 * 1 off, 2 on, 1 off, ...</li>
11446
	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11447
	 * the point at which the pattern starts.</li>
11448
	 *	 <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>
11449
	 * </ul>
11450
	 * @param boolean $ret if true do not send the command.
11451
	 * @return string the PDF command
11452
	 * @public
11453
	 * @since 2.1.000 (2008-01-08)
11454
	 */
11455
	public function setLineStyle($style, $ret=false) {
11456
		$s = ''; // string to be returned
11457
		if (!is_array($style)) {
11458
			return $s;
11459
		}
11460
		if (isset($style['width'])) {
11461
			$this->LineWidth = $style['width'];
11462
			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11463
			$s .= $this->linestyleWidth.' ';
11464
		}
11465
		if (isset($style['cap'])) {
11466
			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11467
			if (isset($ca[$style['cap']])) {
11468
				$this->linestyleCap = $ca[$style['cap']].' J';
11469
				$s .= $this->linestyleCap.' ';
11470
			}
11471
		}
11472
		if (isset($style['join'])) {
11473
			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11474
			if (isset($ja[$style['join']])) {
11475
				$this->linestyleJoin = $ja[$style['join']].' j';
11476
				$s .= $this->linestyleJoin.' ';
11477
			}
11478
		}
11479
		if (isset($style['dash'])) {
11480
			$dash_string = '';
11481
			if ($style['dash']) {
11482
				if (preg_match('/^.+,/', $style['dash']) > 0) {
11483
					$tab = explode(',', $style['dash']);
11484
				} else {
11485
					$tab = array($style['dash']);
11486
				}
11487
				$dash_string = '';
11488
				foreach ($tab as $i => $v) {
11489
					if ($i) {
11490
						$dash_string .= ' ';
11491
					}
11492
					$dash_string .= sprintf('%F', $v);
11493
				}
11494
			}
11495
			if (!isset($style['phase']) OR !$style['dash']) {
11496
				$style['phase'] = 0;
11497
			}
11498
			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11499
			$s .= $this->linestyleDash.' ';
11500
		}
11501
		if (isset($style['color'])) {
11502
			$s .= $this->setDrawColorArray($style['color'], true).' ';
11503
		}
11504
		if (!$ret AND ($this->state == 2)) {
11505
			$this->_out($s);
11506
		}
11507
		return $s;
11508
	}
11509
 
11510
	/**
11511
	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11512
	 * @param float $x Abscissa of point.
11513
	 * @param float $y Ordinate of point.
11514
	 * @protected
11515
	 * @since 2.1.000 (2008-01-08)
11516
	 */
11517
	protected function _outPoint($x, $y) {
11518
		if ($this->state == 2) {
11519
			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11520
		}
11521
	}
11522
 
11523
	/**
11524
	 * Append a straight line segment from the current point to the point (x, y).
11525
	 * The new current point shall be (x, y).
11526
	 * @param float $x Abscissa of end point.
11527
	 * @param float $y Ordinate of end point.
11528
	 * @protected
11529
	 * @since 2.1.000 (2008-01-08)
11530
	 */
11531
	protected function _outLine($x, $y) {
11532
		if ($this->state == 2) {
11533
			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11534
		}
11535
	}
11536
 
11537
	/**
11538
	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11539
	 * @param float $x Abscissa of upper-left corner.
11540
	 * @param float $y Ordinate of upper-left corner.
11541
	 * @param float $w Width.
11542
	 * @param float $h Height.
11543
	 * @param string $op options
11544
	 * @protected
11545
	 * @since 2.1.000 (2008-01-08)
11546
	 */
11547
	protected function _outRect($x, $y, $w, $h, $op) {
11548
		if ($this->state == 2) {
11549
			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11550
		}
11551
	}
11552
 
11553
	/**
11554
	 * 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.
11555
	 * The new current point shall be (x3, y3).
11556
	 * @param float $x1 Abscissa of control point 1.
11557
	 * @param float $y1 Ordinate of control point 1.
11558
	 * @param float $x2 Abscissa of control point 2.
11559
	 * @param float $y2 Ordinate of control point 2.
11560
	 * @param float $x3 Abscissa of end point.
11561
	 * @param float $y3 Ordinate of end point.
11562
	 * @protected
11563
	 * @since 2.1.000 (2008-01-08)
11564
	 */
11565
	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11566
		if ($this->state == 2) {
11567
			$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)));
11568
		}
11569
	}
11570
 
11571
	/**
11572
	 * 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.
11573
	 * The new current point shall be (x3, y3).
11574
	 * @param float $x2 Abscissa of control point 2.
11575
	 * @param float $y2 Ordinate of control point 2.
11576
	 * @param float $x3 Abscissa of end point.
11577
	 * @param float $y3 Ordinate of end point.
11578
	 * @protected
11579
	 * @since 4.9.019 (2010-04-26)
11580
	 */
11581
	protected function _outCurveV($x2, $y2, $x3, $y3) {
11582
		if ($this->state == 2) {
11583
			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11584
		}
11585
	}
11586
 
11587
	/**
11588
	 * 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.
11589
	 * The new current point shall be (x3, y3).
11590
	 * @param float $x1 Abscissa of control point 1.
11591
	 * @param float $y1 Ordinate of control point 1.
11592
	 * @param float $x3 Abscissa of end point.
11593
	 * @param float $y3 Ordinate of end point.
11594
	 * @protected
11595
	 * @since 2.1.000 (2008-01-08)
11596
	 */
11597
	protected function _outCurveY($x1, $y1, $x3, $y3) {
11598
		if ($this->state == 2) {
11599
			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11600
		}
11601
	}
11602
 
11603
	/**
11604
	 * Draws a line between two points.
11605
	 * @param float $x1 Abscissa of first point.
11606
	 * @param float $y1 Ordinate of first point.
11607
	 * @param float $x2 Abscissa of second point.
11608
	 * @param float $y2 Ordinate of second point.
11609
	 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11610
	 * @public
11611
	 * @since 1.0
11612
	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11613
	 */
11614
	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11615
		if ($this->state != 2) {
11616
			return;
11617
		}
11618
		if (is_array($style)) {
11619
			$this->setLineStyle($style);
11620
		}
11621
		$this->_outPoint($x1, $y1);
11622
		$this->_outLine($x2, $y2);
11623
		$this->_out('S');
11624
	}
11625
 
11626
	/**
11627
	 * Draws a rectangle.
11628
	 * @param float $x Abscissa of upper-left corner.
11629
	 * @param float $y Ordinate of upper-left corner.
11630
	 * @param float $w Width.
11631
	 * @param float $h Height.
11632
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11633
	 * @param array $border_style Border style of rectangle. Array with keys among the following:
11634
	 * <ul>
11635
	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11636
	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11637
	 * </ul>
11638
	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11639
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11640
	 * @public
11641
	 * @since 1.0
11642
	 * @see SetLineStyle()
11643
	 */
11644
	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11645
		if ($this->state != 2) {
11646
			return;
11647
		}
11648
		if (empty($style)) {
11649
			$style = 'S';
11650
		}
11651
		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11652
			// set background color
11653
			$this->setFillColorArray($fill_color);
11654
		}
11655
		if (!empty($border_style)) {
11656
			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11657
				//set global style for border
11658
				$this->setLineStyle($border_style['all']);
11659
				$border_style = array();
11660
			} else {
11661
				// remove stroke operator from style
11662
				$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*' );
11663
				if (isset($opnostroke[$style])) {
11664
					$style = $opnostroke[$style];
11665
				}
11666
			}
11667
		}
11668
		if (!empty($style)) {
11669
			$op = TCPDF_STATIC::getPathPaintOperator($style);
11670
			$this->_outRect($x, $y, $w, $h, $op);
11671
		}
11672
		if (!empty($border_style)) {
11673
			$border_style2 = array();
11674
			foreach ($border_style as $line => $value) {
11675
				$length = strlen($line);
11676
				for ($i = 0; $i < $length; ++$i) {
11677
					$border_style2[$line[$i]] = $value;
11678
				}
11679
			}
11680
			$border_style = $border_style2;
11681
			if (isset($border_style['L']) AND $border_style['L']) {
11682
				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11683
			}
11684
			if (isset($border_style['T']) AND $border_style['T']) {
11685
				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11686
			}
11687
			if (isset($border_style['R']) AND $border_style['R']) {
11688
				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11689
			}
11690
			if (isset($border_style['B']) AND $border_style['B']) {
11691
				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11692
			}
11693
		}
11694
	}
11695
 
11696
	/**
11697
	 * Draws a Bezier curve.
11698
	 * The Bezier curve is a tangent to the line between the control points at
11699
	 * either end of the curve.
11700
	 * @param float $x0 Abscissa of start point.
11701
	 * @param float $y0 Ordinate of start point.
11702
	 * @param float $x1 Abscissa of control point 1.
11703
	 * @param float $y1 Ordinate of control point 1.
11704
	 * @param float $x2 Abscissa of control point 2.
11705
	 * @param float $y2 Ordinate of control point 2.
11706
	 * @param float $x3 Abscissa of end point.
11707
	 * @param float $y3 Ordinate of end point.
11708
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11709
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11710
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11711
	 * @public
11712
	 * @see SetLineStyle()
11713
	 * @since 2.1.000 (2008-01-08)
11714
	 */
11715
	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11716
		if ($this->state != 2) {
11717
			return;
11718
		}
11719
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11720
			$this->setFillColorArray($fill_color);
11721
		}
11722
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11723
		if ($line_style) {
11724
			$this->setLineStyle($line_style);
11725
		}
11726
		$this->_outPoint($x0, $y0);
11727
		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11728
		$this->_out($op);
11729
	}
11730
 
11731
	/**
11732
	 * Draws a poly-Bezier curve.
11733
	 * Each Bezier curve segment is a tangent to the line between the control points at
11734
	 * either end of the curve.
11735
	 * @param float $x0 Abscissa of start point.
11736
	 * @param float $y0 Ordinate of start point.
11737
	 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11738
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11739
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11740
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11741
	 * @public
11742
	 * @see SetLineStyle()
11743
	 * @since 3.0008 (2008-05-12)
11744
	 */
11745
	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11746
		if ($this->state != 2) {
11747
			return;
11748
		}
11749
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11750
			$this->setFillColorArray($fill_color);
11751
		}
11752
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11753
		if ($op == 'f') {
11754
			$line_style = array();
11755
		}
11756
		if ($line_style) {
11757
			$this->setLineStyle($line_style);
11758
		}
11759
		$this->_outPoint($x0, $y0);
11760
		foreach ($segments as $segment) {
11761
			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11762
			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11763
		}
11764
		$this->_out($op);
11765
	}
11766
 
11767
	/**
11768
	 * Draws an ellipse.
11769
	 * An ellipse is formed from n Bezier curves.
11770
	 * @param float $x0 Abscissa of center point.
11771
	 * @param float $y0 Ordinate of center point.
11772
	 * @param float $rx Horizontal radius.
11773
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11774
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11775
	 * @param float $astart Angle start of draw line. Default value: 0.
11776
	 * @param float $afinish Angle finish of draw line. Default value: 360.
11777
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11778
	 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11779
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11780
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11781
	 * @author Nicola Asuni
11782
	 * @public
11783
	 * @since 2.1.000 (2008-01-08)
11784
	 */
11785
	public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11786
		if ($this->state != 2) {
11787
			return;
11788
		}
11789
		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11790
			$ry = $rx;
11791
		}
11792
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11793
			$this->setFillColorArray($fill_color);
11794
		}
11795
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11796
		if ($op == 'f') {
11797
			$line_style = array();
11798
		}
11799
		if ($line_style) {
11800
			$this->setLineStyle($line_style);
11801
		}
11802
		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11803
		$this->_out($op);
11804
	}
11805
 
11806
	/**
11807
	 * Append an elliptical arc to the current path.
11808
	 * An ellipse is formed from n Bezier curves.
11809
	 * @param float $xc Abscissa of center point.
11810
	 * @param float $yc Ordinate of center point.
11811
	 * @param float $rx Horizontal radius.
11812
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11813
	 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11814
	 * @param float $angs Angle start of draw line. Default value: 0.
11815
	 * @param float $angf Angle finish of draw line. Default value: 360.
11816
	 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11817
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11818
	 * @param boolean $startpoint if true output a starting point.
11819
	 * @param boolean $ccw if true draws in counter-clockwise.
11820
	 * @param boolean $svg if true the angles are in svg mode (already calculated).
11821
	 * @return array bounding box coordinates (x min, y min, x max, y max)
11822
	 * @author Nicola Asuni
11823
	 * @protected
11824
	 * @since 4.9.019 (2010-04-26)
11825
	 */
11826
	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11827
		if (($rx <= 0) OR ($ry < 0)) {
11828
			return;
11829
		}
11830
		$k = $this->k;
11831
		if ($nc < 2) {
11832
			$nc = 2;
11833
		}
11834
		$xmin = 2147483647;
11835
		$ymin = 2147483647;
11836
		$xmax = 0;
11837
		$ymax = 0;
11838
		if ($pie) {
11839
			// center of the arc
11840
			$this->_outPoint($xc, $yc);
11841
		}
11842
		$xang = deg2rad((float) $xang);
11843
		$angs = deg2rad((float) $angs);
11844
		$angf = deg2rad((float) $angf);
11845
		if ($svg) {
11846
			$as = $angs;
11847
			$af = $angf;
11848
		} else {
11849
			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11850
			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11851
		}
11852
		if ($as < 0) {
11853
			$as += (2 * M_PI);
11854
		}
11855
		if ($af < 0) {
11856
			$af += (2 * M_PI);
11857
		}
11858
		if ($ccw AND ($as > $af)) {
11859
			// reverse rotation
11860
			$as -= (2 * M_PI);
11861
		} elseif (!$ccw AND ($as < $af)) {
11862
			// reverse rotation
11863
			$af -= (2 * M_PI);
11864
		}
11865
		$total_angle = ($af - $as);
11866
		if ($nc < 2) {
11867
			$nc = 2;
11868
		}
11869
		// total arcs to draw
11870
		$nc *= (2 * abs($total_angle) / M_PI);
11871
		$nc = round($nc) + 1;
11872
		// angle of each arc
11873
		$arcang = ($total_angle / $nc);
11874
		// center point in PDF coordinates
11875
		$x0 = $xc;
11876
		$y0 = ($this->h - $yc);
11877
		// starting angle
11878
		$ang = $as;
11879
		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11880
		$cos_xang = cos($xang);
11881
		$sin_xang = sin($xang);
11882
		$cos_ang = cos($ang);
11883
		$sin_ang = sin($ang);
11884
		// first arc point
11885
		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11886
		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11887
		// first Bezier control point
11888
		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11889
		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11890
		if ($pie) {
11891
			// line from center to arc starting point
11892
			$this->_outLine($px1, $this->h - $py1);
11893
		} elseif ($startpoint) {
11894
			// arc starting point
11895
			$this->_outPoint($px1, $this->h - $py1);
11896
		}
11897
		// draw arcs
11898
		for ($i = 1; $i <= $nc; ++$i) {
11899
			// starting angle
11900
			$ang = $as + ($i * $arcang);
11901
			if ($i == $nc) {
11902
				$ang = $af;
11903
			}
11904
			$cos_ang = cos($ang);
11905
			$sin_ang = sin($ang);
11906
			// second arc point
11907
			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11908
			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11909
			// second Bezier control point
11910
			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11911
			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11912
			// draw arc
11913
			$cx1 = ($px1 + $qx1);
11914
			$cy1 = ($this->h - ($py1 + $qy1));
11915
			$cx2 = ($px2 - $qx2);
11916
			$cy2 = ($this->h - ($py2 - $qy2));
11917
			$cx3 = $px2;
11918
			$cy3 = ($this->h - $py2);
11919
			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11920
			// get bounding box coordinates
11921
			$xmin = min($xmin, $cx1, $cx2, $cx3);
11922
			$ymin = min($ymin, $cy1, $cy2, $cy3);
11923
			$xmax = max($xmax, $cx1, $cx2, $cx3);
11924
			$ymax = max($ymax, $cy1, $cy2, $cy3);
11925
			// move to next point
11926
			$px1 = $px2;
11927
			$py1 = $py2;
11928
			$qx1 = $qx2;
11929
			$qy1 = $qy2;
11930
		}
11931
		if ($pie) {
11932
			$this->_outLine($xc, $yc);
11933
			// get bounding box coordinates
11934
			$xmin = min($xmin, $xc);
11935
			$ymin = min($ymin, $yc);
11936
			$xmax = max($xmax, $xc);
11937
			$ymax = max($ymax, $yc);
11938
		}
11939
		return array($xmin, $ymin, $xmax, $ymax);
11940
	}
11941
 
11942
	/**
11943
	 * Draws a circle.
11944
	 * A circle is formed from n Bezier curves.
11945
	 * @param float $x0 Abscissa of center point.
11946
	 * @param float $y0 Ordinate of center point.
11947
	 * @param float $r Radius.
11948
	 * @param float $angstr Angle start of draw line. Default value: 0.
11949
	 * @param float $angend Angle finish of draw line. Default value: 360.
11950
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11951
	 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11952
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11953
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
11954
	 * @public
11955
	 * @since 2.1.000 (2008-01-08)
11956
	 */
11957
	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11958
		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11959
	}
11960
 
11961
	/**
11962
	 * Draws a polygonal line
11963
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11964
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11965
	 * @param array $line_style Line style of polygon. Array with keys among the following:
11966
	 * <ul>
11967
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11968
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11969
	 * </ul>
11970
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11971
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11972
	 * @since 4.8.003 (2009-09-15)
11973
	 * @public
11974
	 */
11975
	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11976
		$this->Polygon($p, $style, $line_style, $fill_color, false);
11977
	}
11978
 
11979
	/**
11980
	 * Draws a polygon.
11981
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11982
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11983
	 * @param array $line_style Line style of polygon. Array with keys among the following:
11984
	 * <ul>
11985
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11986
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11987
	 * </ul>
11988
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11989
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11990
	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
11991
	 * @public
11992
	 * @since 2.1.000 (2008-01-08)
11993
	 */
11994
	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11995
		if ($this->state != 2) {
11996
			return;
11997
		}
11998
		$nc = count($p); // number of coordinates
11999
		$np = $nc / 2; // number of points
12000
		if ($closed) {
12001
			// close polygon by adding the first 2 points at the end (one line)
12002
			for ($i = 0; $i < 4; ++$i) {
12003
				$p[$nc + $i] = $p[$i];
12004
			}
12005
			// copy style for the last added line
12006
			if (isset($line_style[0])) {
12007
				$line_style[$np] = $line_style[0];
12008
			}
12009
			$nc += 4;
12010
		}
12011
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12012
			$this->setFillColorArray($fill_color);
12013
		}
12014
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12015
		if ($op == 'f') {
12016
			$line_style = array();
12017
		}
12018
		$draw = true;
12019
		if ($line_style) {
12020
			if (isset($line_style['all'])) {
12021
				$this->setLineStyle($line_style['all']);
12022
			} else {
12023
				$draw = false;
12024
				if ($op == 'B') {
12025
					// draw fill
12026
					$op = 'f';
12027
					$this->_outPoint($p[0], $p[1]);
12028
					for ($i = 2; $i < $nc; $i = $i + 2) {
12029
						$this->_outLine($p[$i], $p[$i + 1]);
12030
					}
12031
					$this->_out($op);
12032
				}
12033
				// draw outline
12034
				$this->_outPoint($p[0], $p[1]);
12035
				for ($i = 2; $i < $nc; $i = $i + 2) {
12036
					$line_num = ($i / 2) - 1;
12037
					if (isset($line_style[$line_num])) {
12038
						if ($line_style[$line_num] != 0) {
12039
							if (is_array($line_style[$line_num])) {
12040
								$this->_out('S');
12041
								$this->setLineStyle($line_style[$line_num]);
12042
								$this->_outPoint($p[$i - 2], $p[$i - 1]);
12043
								$this->_outLine($p[$i], $p[$i + 1]);
12044
								$this->_out('S');
12045
								$this->_outPoint($p[$i], $p[$i + 1]);
12046
							} else {
12047
								$this->_outLine($p[$i], $p[$i + 1]);
12048
							}
12049
						}
12050
					} else {
12051
						$this->_outLine($p[$i], $p[$i + 1]);
12052
					}
12053
				}
12054
				$this->_out($op);
12055
			}
12056
		}
12057
		if ($draw) {
12058
			$this->_outPoint($p[0], $p[1]);
12059
			for ($i = 2; $i < $nc; $i = $i + 2) {
12060
				$this->_outLine($p[$i], $p[$i + 1]);
12061
			}
12062
			$this->_out($op);
12063
		}
12064
	}
12065
 
12066
	/**
12067
	 * Draws a regular polygon.
12068
	 * @param float $x0 Abscissa of center point.
12069
	 * @param float $y0 Ordinate of center point.
12070
	 * @param float $r Radius of inscribed circle.
12071
	 * @param integer $ns Number of sides.
12072
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12073
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12074
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12075
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12076
	 * <ul>
12077
	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12078
	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12079
	 * </ul>
12080
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12081
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12082
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12083
	 * <ul>
12084
	 *	 <li>D or empty string: Draw (default).</li>
12085
	 *	 <li>F: Fill.</li>
12086
	 *	 <li>DF or FD: Draw and fill.</li>
12087
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12088
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12089
	 * </ul>
12090
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12091
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12092
	 * @public
12093
	 * @since 2.1.000 (2008-01-08)
12094
	 */
12095
	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()) {
12096
		if (3 > $ns) {
12097
			$ns = 3;
12098
		}
12099
		if ($draw_circle) {
12100
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12101
		}
12102
		$p = array();
12103
		for ($i = 0; $i < $ns; ++$i) {
12104
			$a = $angle + ($i * 360 / $ns);
12105
			$a_rad = deg2rad((float) $a);
12106
			$p[] = $x0 + ($r * sin($a_rad));
12107
			$p[] = $y0 + ($r * cos($a_rad));
12108
		}
12109
		$this->Polygon($p, $style, $line_style, $fill_color);
12110
	}
12111
 
12112
	/**
12113
	 * Draws a star polygon
12114
	 * @param float $x0 Abscissa of center point.
12115
	 * @param float $y0 Ordinate of center point.
12116
	 * @param float $r Radius of inscribed circle.
12117
	 * @param integer $nv Number of vertices.
12118
	 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12119
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12120
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12121
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12122
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12123
	 * <ul>
12124
	 *	 <li>all: Line style of all sides. Array like for
12125
	 * SetLineStyle().</li>
12126
	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12127
	 * </ul>
12128
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12129
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12130
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12131
	 * <ul>
12132
	 *	 <li>D or empty string: Draw (default).</li>
12133
	 *	 <li>F: Fill.</li>
12134
	 *	 <li>DF or FD: Draw and fill.</li>
12135
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12136
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12137
	 * </ul>
12138
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12139
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12140
	 * @public
12141
	 * @since 2.1.000 (2008-01-08)
12142
	 */
12143
	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()) {
12144
		if ($nv < 2) {
12145
			$nv = 2;
12146
		}
12147
		if ($draw_circle) {
12148
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12149
		}
12150
		$p2 = array();
12151
		$visited = array();
12152
		for ($i = 0; $i < $nv; ++$i) {
12153
			$a = $angle + ($i * 360 / $nv);
12154
			$a_rad = deg2rad((float) $a);
12155
			$p2[] = $x0 + ($r * sin($a_rad));
12156
			$p2[] = $y0 + ($r * cos($a_rad));
12157
			$visited[] = false;
12158
		}
12159
		$p = array();
12160
		$i = 0;
12161
		do {
12162
			$p[] = $p2[$i * 2];
12163
			$p[] = $p2[($i * 2) + 1];
12164
			$visited[$i] = true;
12165
			$i += $ng;
12166
			$i %= $nv;
12167
		} while (!$visited[$i]);
12168
		$this->Polygon($p, $style, $line_style, $fill_color);
12169
	}
12170
 
12171
	/**
12172
	 * Draws a rounded rectangle.
12173
	 * @param float $x Abscissa of upper-left corner.
12174
	 * @param float $y Ordinate of upper-left corner.
12175
	 * @param float $w Width.
12176
	 * @param float $h Height.
12177
	 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12178
	 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12179
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12180
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12181
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12182
	 * @public
12183
	 * @since 2.1.000 (2008-01-08)
12184
	 */
12185
	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12186
		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12187
	}
12188
 
12189
	/**
12190
	 * Draws a rounded rectangle.
12191
	 * @param float $x Abscissa of upper-left corner.
12192
	 * @param float $y Ordinate of upper-left corner.
12193
	 * @param float $w Width.
12194
	 * @param float $h Height.
12195
	 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12196
	 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12197
	 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12198
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12199
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12200
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12201
	 * @public
12202
	 * @since 4.9.019 (2010-04-22)
12203
	 */
12204
	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12205
		if ($this->state != 2) {
12206
			return;
12207
		}
12208
		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12209
			// Not rounded
12210
			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12211
			return;
12212
		}
12213
		// Rounded
12214
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12215
			$this->setFillColorArray($fill_color);
12216
		}
12217
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12218
		if ($op == 'f') {
12219
			$border_style = array();
12220
		}
12221
		if ($border_style) {
12222
			$this->setLineStyle($border_style);
12223
		}
12224
		$MyArc = 4 / 3 * (sqrt(2) - 1);
12225
		$this->_outPoint($x + $rx, $y);
12226
		$xc = $x + $w - $rx;
12227
		$yc = $y + $ry;
12228
		$this->_outLine($xc, $y);
12229
		if ($round_corner[0]) {
12230
			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12231
		} else {
12232
			$this->_outLine($x + $w, $y);
12233
		}
12234
		$xc = $x + $w - $rx;
12235
		$yc = $y + $h - $ry;
12236
		$this->_outLine($x + $w, $yc);
12237
		if ($round_corner[1]) {
12238
			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12239
		} else {
12240
			$this->_outLine($x + $w, $y + $h);
12241
		}
12242
		$xc = $x + $rx;
12243
		$yc = $y + $h - $ry;
12244
		$this->_outLine($xc, $y + $h);
12245
		if ($round_corner[2]) {
12246
			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12247
		} else {
12248
			$this->_outLine($x, $y + $h);
12249
		}
12250
		$xc = $x + $rx;
12251
		$yc = $y + $ry;
12252
		$this->_outLine($x, $yc);
12253
		if ($round_corner[3]) {
12254
			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12255
		} else {
12256
			$this->_outLine($x, $y);
12257
			$this->_outLine($x + $rx, $y);
12258
		}
12259
		$this->_out($op);
12260
	}
12261
 
12262
	/**
12263
	 * Draws a grahic arrow.
12264
	 * @param float $x0 Abscissa of first point.
12265
	 * @param float $y0 Ordinate of first point.
12266
	 * @param float $x1 Abscissa of second point.
12267
	 * @param float $y1 Ordinate of second point.
12268
	 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12269
	 * @param float $arm_size length of arrowhead arms
12270
	 * @param int $arm_angle angle between an arm and the shaft
12271
	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12272
	 * @since 4.6.018 (2009-07-10)
12273
	 */
12274
	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12275
		// getting arrow direction angle
12276
		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12277
		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12278
		if ($dir_angle < 0) {
12279
			$dir_angle += (2 * M_PI);
12280
		}
12281
		$arm_angle = deg2rad($arm_angle);
12282
		$sx1 = $x1;
12283
		$sy1 = $y1;
12284
		if ($head_style > 0) {
12285
			// calculate the stopping point for the arrow shaft
12286
			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12287
			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12288
		}
12289
		// main arrow line / shaft
12290
		$this->Line($x0, $y0, $sx1, $sy1);
12291
		// left arrowhead arm tip
12292
		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12293
		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12294
		// right arrowhead arm tip
12295
		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12296
		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12297
		$mode = 'D';
12298
		$style = array();
12299
		switch ($head_style) {
12300
			case 0: {
12301
				// draw only arrowhead arms
12302
				$mode = 'D';
12303
				$style = array(1, 1, 0);
12304
				break;
12305
			}
12306
			case 1: {
12307
				// draw closed arrowhead, but no fill
12308
				$mode = 'D';
12309
				break;
12310
			}
12311
			case 2: {
12312
				// closed and filled arrowhead
12313
				$mode = 'DF';
12314
				break;
12315
			}
12316
			case 3: {
12317
				// filled arrowhead
12318
				$mode = 'F';
12319
				break;
12320
			}
12321
		}
12322
		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12323
	}
12324
 
12325
	// END GRAPHIC FUNCTIONS SECTION -----------------------
12326
 
12327
	/**
12328
	 * Add a Named Destination.
12329
	 * NOTE: destination names are unique, so only last entry will be saved.
12330
	 * @param string $name Destination name.
12331
	 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12332
	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12333
	 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12334
	 * @return string|false Stripped named destination identifier or false in case of error.
12335
	 * @public
12336
	 * @author Christian Deligant, Nicola Asuni
12337
	 * @since 5.9.097 (2011-06-23)
12338
	 */
12339
	public function setDestination($name, $y=-1, $page='', $x=-1) {
12340
		// remove unsupported characters
12341
		$name = TCPDF_STATIC::encodeNameObject($name);
12342
		if (TCPDF_STATIC::empty_string($name)) {
12343
			return false;
12344
		}
12345
		if ($y == -1) {
12346
			$y = $this->GetY();
12347
		} elseif ($y < 0) {
12348
			$y = 0;
12349
		} elseif ($y > $this->h) {
12350
			$y = $this->h;
12351
		}
12352
		if ($x == -1) {
12353
			$x = $this->GetX();
12354
		} elseif ($x < 0) {
12355
			$x = 0;
12356
		} elseif ($x > $this->w) {
12357
			$x = $this->w;
12358
		}
12359
		$fixed = false;
12360
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12361
			$page = intval(substr($page, 1));
12362
			// this page number will not be changed when moving/add/deleting pages
12363
			$fixed = true;
12364
		}
12365
		if (empty($page)) {
12366
			$page = $this->PageNo();
12367
			if (empty($page)) {
12368
				return;
12369
			}
12370
		}
12371
		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12372
		return $name;
12373
	}
12374
 
12375
	/**
12376
	 * Return the Named Destination array.
12377
	 * @return array Named Destination array.
12378
	 * @public
12379
	 * @author Nicola Asuni
12380
	 * @since 5.9.097 (2011-06-23)
12381
	 */
12382
	public function getDestination() {
12383
		return $this->dests;
12384
	}
12385
 
12386
	/**
12387
	 * Insert Named Destinations.
12388
	 * @protected
12389
	 * @author Johannes G\FCntert, Nicola Asuni
12390
	 * @since 5.9.098 (2011-06-23)
12391
	 */
12392
	protected function _putdests() {
12393
		if (empty($this->dests)) {
12394
			return;
12395
		}
12396
		$this->n_dests = $this->_newobj();
12397
		$out = ' <<';
12398
		foreach($this->dests as $name => $o) {
12399
			$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)));
12400
		}
12401
		$out .= ' >>';
12402
		$out .= "\n".'endobj';
12403
		$this->_out($out);
12404
	}
12405
 
12406
	/**
12407
	 * Adds a bookmark - alias for Bookmark().
12408
	 * @param string $txt Bookmark description.
12409
	 * @param int $level Bookmark level (minimum value is 0).
12410
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12411
	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12412
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12413
	 * @param array $color RGB color array (values from 0 to 255).
12414
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12415
	 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12416
	 * @public
12417
	 */
12418
	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12419
		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12420
	}
12421
 
12422
	/**
12423
	 * Adds a bookmark.
12424
	 * @param string $txt Bookmark description.
12425
	 * @param int $level Bookmark level (minimum value is 0).
12426
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12427
	 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12428
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12429
	 * @param array $color RGB color array (values from 0 to 255).
12430
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12431
	 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12432
	 * @public
12433
	 * @since 2.1.002 (2008-02-12)
12434
	 */
12435
	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12436
		if ($level < 0) {
12437
			$level = 0;
12438
		}
12439
		if (isset($this->outlines[0])) {
12440
			$lastoutline = end($this->outlines);
12441
			$maxlevel = $lastoutline['l'] + 1;
12442
		} else {
12443
			$maxlevel = 0;
12444
		}
12445
		if ($level > $maxlevel) {
12446
			$level = $maxlevel;
12447
		}
12448
		if ($y == -1) {
12449
			$y = $this->GetY();
12450
		} elseif ($y < 0) {
12451
			$y = 0;
12452
		} elseif ($y > $this->h) {
12453
			$y = $this->h;
12454
		}
12455
		if ($x == -1) {
12456
			$x = $this->GetX();
12457
		} elseif ($x < 0) {
12458
			$x = 0;
12459
		} elseif ($x > $this->w) {
12460
			$x = $this->w;
12461
		}
12462
		$fixed = false;
12463
		$pageAsString = (string) $page;
12464
		if ($pageAsString && $pageAsString[0] == '*') {
12465
			$page = intval(substr($page, 1));
12466
			// this page number will not be changed when moving/add/deleting pages
12467
			$fixed = true;
12468
		}
12469
		if (empty($page)) {
12470
			$page = $this->PageNo();
12471
			if (empty($page)) {
12472
				return;
12473
			}
12474
		}
12475
		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12476
	}
12477
 
12478
	/**
12479
	 * Sort bookmarks for page and key.
12480
	 * @protected
12481
	 * @since 5.9.119 (2011-09-19)
12482
	 */
12483
	protected function sortBookmarks() {
12484
		// get sorting columns
12485
		$outline_p = array();
12486
		$outline_y = array();
12487
		foreach ($this->outlines as $key => $row) {
12488
			$outline_p[$key] = $row['p'];
12489
			$outline_k[$key] = $key;
12490
		}
12491
		// sort outlines by page and original position
12492
		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12493
	}
12494
 
12495
	/**
12496
	 * Create a bookmark PDF string.
12497
	 * @protected
12498
	 * @author Olivier Plathey, Nicola Asuni
12499
	 * @since 2.1.002 (2008-02-12)
12500
	 */
12501
	protected function _putbookmarks() {
12502
		$nb = count($this->outlines);
12503
		if ($nb == 0) {
12504
			return;
12505
		}
12506
		// sort bookmarks
12507
		$this->sortBookmarks();
12508
		$lru = array();
12509
		$level = 0;
12510
		foreach ($this->outlines as $i => $o) {
12511
			if ($o['l'] > 0) {
12512
				$parent = $lru[($o['l'] - 1)];
12513
				//Set parent and last pointers
12514
				$this->outlines[$i]['parent'] = $parent;
12515
				$this->outlines[$parent]['last'] = $i;
12516
				if ($o['l'] > $level) {
12517
					//Level increasing: set first pointer
12518
					$this->outlines[$parent]['first'] = $i;
12519
				}
12520
			} else {
12521
				$this->outlines[$i]['parent'] = $nb;
12522
			}
12523
			if (($o['l'] <= $level) AND ($i > 0)) {
12524
				//Set prev and next pointers
12525
				$prev = $lru[$o['l']];
12526
				$this->outlines[$prev]['next'] = $i;
12527
				$this->outlines[$i]['prev'] = $prev;
12528
			}
12529
			$lru[$o['l']] = $i;
12530
			$level = $o['l'];
12531
		}
12532
		//Outline items
12533
		$n = $this->n + 1;
12534
		$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';
12535
		foreach ($this->outlines as $i => $o) {
12536
			$oid = $this->_newobj();
12537
			// covert HTML title to string
12538
			$title = preg_replace($nltags, "\n", $o['t']);
12539
			$title = preg_replace("/[\r]+/si", '', $title);
12540
			$title = preg_replace("/[\n]+/si", "\n", $title);
12541
			$title = strip_tags($title);
12542
			$title = $this->stringTrim($title);
12543
			$out = '<</Title '.$this->_textstring($title, $oid);
12544
			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12545
			if (isset($o['prev'])) {
12546
				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12547
			}
12548
			if (isset($o['next'])) {
12549
				$out .= ' /Next '.($n + $o['next']).' 0 R';
12550
			}
12551
			if (isset($o['first'])) {
12552
				$out .= ' /First '.($n + $o['first']).' 0 R';
12553
			}
12554
			if (isset($o['last'])) {
12555
				$out .= ' /Last '.($n + $o['last']).' 0 R';
12556
			}
12557
			if (isset($o['u']) AND !empty($o['u'])) {
12558
				// link
12559
				if (is_string($o['u'])) {
12560
					if ($o['u'][0] == '#') {
12561
						// internal destination
12562
						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12563
					} elseif ($o['u'][0] == '%') {
12564
						// embedded PDF file
12565
						$filename = basename(substr($o['u'], 1));
12566
						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12567
					} elseif ($o['u'][0] == '*') {
12568
						// embedded generic file
12569
						$filename = basename(substr($o['u'], 1));
12570
						$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});';
12571
						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12572
					} else {
12573
						// external URI link
12574
						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12575
					}
12576
				} elseif (isset($this->links[$o['u']])) {
12577
					// internal link ID
12578
					$l = $this->links[$o['u']];
12579
					if (isset($this->page_obj_id[($l['p'])])) {
12580
						$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)));
12581
					}
12582
				}
12583
			} elseif (isset($this->page_obj_id[($o['p'])])) {
12584
				// link to a page
12585
				$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)));
12586
			}
12587
			// set font style
12588
			$style = 0;
12589
			if (!empty($o['s'])) {
12590
				// bold
12591
				if (strpos($o['s'], 'B') !== false) {
12592
					$style |= 2;
12593
				}
12594
				// oblique
12595
				if (strpos($o['s'], 'I') !== false) {
12596
					$style |= 1;
12597
				}
12598
			}
12599
			$out .= sprintf(' /F %d', $style);
12600
			// set bookmark color
12601
			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12602
				$color = array_values($o['c']);
12603
				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12604
			} else {
12605
				// black
12606
				$out .= ' /C [0.0 0.0 0.0]';
12607
			}
12608
			$out .= ' /Count 0'; // normally closed item
12609
			$out .= ' >>';
12610
			$out .= "\n".'endobj';
12611
			$this->_out($out);
12612
		}
12613
		//Outline root
12614
		$this->OutlineRoot = $this->_newobj();
12615
		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12616
	}
12617
 
12618
	// --- JAVASCRIPT ------------------------------------------------------
12619
 
12620
	/**
12621
	 * Adds a javascript
12622
	 * @param string $script Javascript code
12623
	 * @public
12624
	 * @author Johannes G\FCntert, Nicola Asuni
12625
	 * @since 2.1.002 (2008-02-12)
12626
	 */
12627
	public function IncludeJS($script) {
12628
		$this->javascript .= $script;
12629
	}
12630
 
12631
	/**
12632
	 * Adds a javascript object and return object ID
12633
	 * @param string $script Javascript code
12634
	 * @param boolean $onload if true executes this object when opening the document
12635
	 * @return int internal object ID
12636
	 * @public
12637
	 * @author Nicola Asuni
12638
	 * @since 4.8.000 (2009-09-07)
12639
	 */
12640
	public function addJavascriptObject($script, $onload=false) {
12641
		if ($this->pdfa_mode) {
12642
			// javascript is not allowed in PDF/A mode
12643
			return false;
12644
		}
12645
		++$this->n;
12646
		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12647
		return $this->n;
12648
	}
12649
 
12650
	/**
12651
	 * Create a javascript PDF string.
12652
	 * @protected
12653
	 * @author Johannes G\FCntert, Nicola Asuni
12654
	 * @since 2.1.002 (2008-02-12)
12655
	 */
12656
	protected function _putjavascript() {
12657
		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12658
			return;
12659
		}
12660
		if (strpos($this->javascript, 'this.addField') > 0) {
12661
			if (!$this->ur['enabled']) {
12662
				//$this->setUserRights();
12663
			}
12664
			// the following two lines are used to avoid form fields duplication after saving
12665
			// The addField method only works when releasing user rights (UR3)
12666
			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12667
			$jsb = "getField('tcpdfdocsaved').value='saved';";
12668
			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12669
		}
12670
		// name tree for javascript
12671
		$this->n_js = '<< /Names [';
12672
		if (!empty($this->javascript)) {
12673
			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12674
		}
12675
		if (!empty($this->js_objects)) {
12676
			foreach ($this->js_objects as $key => $val) {
12677
				if ($val['onload']) {
12678
					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12679
				}
12680
			}
12681
		}
12682
		$this->n_js .= ' ] >>';
12683
		// default Javascript object
12684
		if (!empty($this->javascript)) {
12685
			$obj_id = $this->_newobj();
12686
			$out = '<< /S /JavaScript';
12687
			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12688
			$out .= ' >>';
12689
			$out .= "\n".'endobj';
12690
			$this->_out($out);
12691
		}
12692
		// additional Javascript objects
12693
		if (!empty($this->js_objects)) {
12694
			foreach ($this->js_objects as $key => $val) {
12695
				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12696
				$this->_out($out);
12697
			}
12698
		}
12699
	}
12700
 
12701
	/**
12702
	 * Adds a javascript form field.
12703
	 * @param string $type field type
12704
	 * @param string $name field name
12705
	 * @param int $x horizontal position
12706
	 * @param int $y vertical position
12707
	 * @param int $w width
12708
	 * @param int $h height
12709
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12710
	 * @protected
12711
	 * @author Denis Van Nuffelen, Nicola Asuni
12712
	 * @since 2.1.002 (2008-02-12)
12713
	 */
12714
	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12715
		if ($this->rtl) {
12716
			$x = $x - $w;
12717
		}
12718
		// the followind avoid fields duplication after saving the document
12719
		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12720
		$k = $this->k;
12721
		$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";
12722
		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12723
		foreach($prop as $key => $val) {
12724
			if (strcmp(substr($key, -5), 'Color') == 0) {
12725
				$val = TCPDF_COLORS::_JScolor($val);
12726
			} else {
12727
				$val = "'".$val."'";
12728
			}
12729
			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12730
		}
12731
		if ($this->rtl) {
12732
			$this->x -= $w;
12733
		} else {
12734
			$this->x += $w;
12735
		}
12736
		$this->javascript .= '}';
12737
	}
12738
 
12739
	// --- FORM FIELDS -----------------------------------------------------
12740
 
12741
 
12742
 
12743
	/**
12744
	 * Set default properties for form fields.
12745
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12746
	 * @public
12747
	 * @author Nicola Asuni
12748
	 * @since 4.8.000 (2009-09-06)
12749
	 */
12750
	public function setFormDefaultProp($prop=array()) {
12751
		$this->default_form_prop = $prop;
12752
	}
12753
 
12754
	/**
12755
	 * Return the default properties for form fields.
12756
	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12757
	 * @public
12758
	 * @author Nicola Asuni
12759
	 * @since 4.8.000 (2009-09-06)
12760
	 */
12761
	public function getFormDefaultProp() {
12762
		return $this->default_form_prop;
12763
	}
12764
 
12765
	/**
12766
	 * Creates a text field
12767
	 * @param string $name field name
12768
	 * @param float $w Width of the rectangle
12769
	 * @param float $h Height of the rectangle
12770
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12771
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12772
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12773
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12774
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12775
	 * @public
12776
	 * @author Nicola Asuni
12777
	 * @since 4.8.000 (2009-09-07)
12778
	 */
12779
	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
12780
		if (TCPDF_STATIC::empty_string($x)) {
12781
			$x = $this->x;
12782
		}
12783
		if (TCPDF_STATIC::empty_string($y)) {
12784
			$y = $this->y;
12785
		}
12786
		// check page for no-write regions and adapt page margins if necessary
12787
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12788
		if ($js) {
12789
			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12790
			return;
12791
		}
12792
		// get default style
12793
		$prop = array_merge($this->getFormDefaultProp(), $prop);
12794
		// get annotation data
12795
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12796
		// set default appearance stream
12797
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12798
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12799
		$popt['da'] = $fontstyle;
12800
		// build appearance stream
12801
		$popt['ap'] = array();
12802
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12803
		$text = '';
12804
		if (isset($prop['value']) AND !empty($prop['value'])) {
12805
			$text = $prop['value'];
12806
		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12807
			$text = $opt['v'];
12808
		}
12809
		$tmpid = $this->startTemplate($w, $h, false);
12810
		$align = '';
12811
		if (isset($popt['q'])) {
12812
			switch ($popt['q']) {
12813
				case 0: {
12814
					$align = 'L';
12815
					break;
12816
				}
12817
				case 1: {
12818
					$align = 'C';
12819
					break;
12820
				}
12821
				case 2: {
12822
					$align = 'R';
12823
					break;
12824
				}
12825
				default: {
12826
					$align = '';
12827
					break;
12828
				}
12829
			}
12830
		}
12831
		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12832
		$this->endTemplate();
12833
		--$this->n;
12834
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12835
		unset($this->xobjects[$tmpid]);
12836
		$popt['ap']['n'] .= 'Q EMC';
12837
		// merge options
12838
		$opt = array_merge($popt, $opt);
12839
		// remove some conflicting options
12840
		unset($opt['bs']);
12841
		// set remaining annotation data
12842
		$opt['Subtype'] = 'Widget';
12843
		$opt['ft'] = 'Tx';
12844
		$opt['t'] = $name;
12845
		// Additional annotation's parameters (check _putannotsobj() method):
12846
		//$opt['f']
12847
		//$opt['as']
12848
		//$opt['bs']
12849
		//$opt['be']
12850
		//$opt['c']
12851
		//$opt['border']
12852
		//$opt['h']
12853
		//$opt['mk'];
12854
		//$opt['mk']['r']
12855
		//$opt['mk']['bc'];
12856
		//$opt['mk']['bg'];
12857
		unset($opt['mk']['ca']);
12858
		unset($opt['mk']['rc']);
12859
		unset($opt['mk']['ac']);
12860
		unset($opt['mk']['i']);
12861
		unset($opt['mk']['ri']);
12862
		unset($opt['mk']['ix']);
12863
		unset($opt['mk']['if']);
12864
		//$opt['mk']['if']['sw'];
12865
		//$opt['mk']['if']['s'];
12866
		//$opt['mk']['if']['a'];
12867
		//$opt['mk']['if']['fb'];
12868
		unset($opt['mk']['tp']);
12869
		//$opt['tu']
12870
		//$opt['tm']
12871
		//$opt['ff']
12872
		//$opt['v']
12873
		//$opt['dv']
12874
		//$opt['a']
12875
		//$opt['aa']
12876
		//$opt['q']
12877
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12878
		if ($this->rtl) {
12879
			$this->x -= $w;
12880
		} else {
12881
			$this->x += $w;
12882
		}
12883
	}
12884
 
12885
	/**
12886
	 * Creates a RadioButton field.
12887
	 * @param string $name Field name.
12888
	 * @param int $w Width of the radio button.
12889
	 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12890
	 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12891
	 * @param string $onvalue Value to be returned if selected.
12892
	 * @param boolean $checked Define the initial state.
12893
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12894
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12895
	 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12896
	 * @public
12897
	 * @author Nicola Asuni
12898
	 * @since 4.8.000 (2009-09-07)
12899
	 */
12900
	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) {
12901
		if (TCPDF_STATIC::empty_string($x)) {
12902
			$x = $this->x;
12903
		}
12904
		if (TCPDF_STATIC::empty_string($y)) {
12905
			$y = $this->y;
12906
		}
12907
		// check page for no-write regions and adapt page margins if necessary
12908
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12909
		if ($js) {
12910
			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12911
			return;
12912
		}
12913
		if (TCPDF_STATIC::empty_string($onvalue)) {
12914
			$onvalue = 'On';
12915
		}
12916
		if ($checked) {
12917
			$defval = $onvalue;
12918
		} else {
12919
			$defval = 'Off';
12920
		}
12921
		// set font
12922
		$font = 'zapfdingbats';
12923
		if ($this->pdfa_mode) {
12924
			// all fonts must be embedded
12925
			$font = 'pdfa'.$font;
12926
		}
12927
		$this->AddFont($font);
12928
		$tmpfont = $this->getFontBuffer($font);
12929
		// set data for parent group
12930
		if (!isset($this->radiobutton_groups[$this->page])) {
12931
			$this->radiobutton_groups[$this->page] = array();
12932
		}
12933
		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12934
			$this->radiobutton_groups[$this->page][$name] = array();
12935
			++$this->n;
12936
			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12937
			$this->radio_groups[] = $this->n;
12938
		}
12939
		$kid = ($this->n + 1);
12940
		// save object ID to be added on Kids entry on parent object
12941
		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12942
		// get default style
12943
		$prop = array_merge($this->getFormDefaultProp(), $prop);
12944
		$prop['NoToggleToOff'] = 'true';
12945
		$prop['Radio'] = 'true';
12946
		$prop['borderStyle'] = 'inset';
12947
		// get annotation data
12948
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12949
		// set additional default options
12950
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12951
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12952
		$popt['da'] = $fontstyle;
12953
		// build appearance stream
12954
		$popt['ap'] = array();
12955
		$popt['ap']['n'] = array();
12956
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12957
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12958
		$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);
12959
		$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);
12960
		if (!isset($popt['mk'])) {
12961
			$popt['mk'] = array();
12962
		}
12963
		$popt['mk']['ca'] = '(l)';
12964
		// merge options
12965
		$opt = array_merge($popt, $opt);
12966
		// set remaining annotation data
12967
		$opt['Subtype'] = 'Widget';
12968
		$opt['ft'] = 'Btn';
12969
		if ($checked) {
12970
			$opt['v'] = array('/'.$onvalue);
12971
			$opt['as'] = $onvalue;
12972
		} else {
12973
			$opt['as'] = 'Off';
12974
		}
12975
		// store readonly flag
12976
		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12977
			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12978
		}
12979
		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12980
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12981
		if ($this->rtl) {
12982
			$this->x -= $w;
12983
		} else {
12984
			$this->x += $w;
12985
		}
12986
	}
12987
 
12988
	/**
12989
	 * Creates a List-box field
12990
	 * @param string $name field name
12991
	 * @param int $w width
12992
	 * @param int $h height
12993
	 * @param array $values array containing the list of values.
12994
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12995
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12996
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12997
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12998
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12999
	 * @public
13000
	 * @author Nicola Asuni
13001
	 * @since 4.8.000 (2009-09-07)
13002
	 */
13003
	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13004
		if (TCPDF_STATIC::empty_string($x)) {
13005
			$x = $this->x;
13006
		}
13007
		if (TCPDF_STATIC::empty_string($y)) {
13008
			$y = $this->y;
13009
		}
13010
		// check page for no-write regions and adapt page margins if necessary
13011
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13012
		if ($js) {
13013
			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13014
			$s = '';
13015
			foreach ($values as $value) {
13016
				if (is_array($value)) {
13017
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13018
				} else {
13019
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13020
				}
13021
			}
13022
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13023
			return;
13024
		}
13025
		// get default style
13026
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13027
		// get annotation data
13028
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13029
		// set additional default values
13030
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13031
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13032
		$popt['da'] = $fontstyle;
13033
		// build appearance stream
13034
		$popt['ap'] = array();
13035
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13036
		$text = '';
13037
		foreach($values as $item) {
13038
			if (is_array($item)) {
13039
				$text .= $item[1]."\n";
13040
			} else {
13041
				$text .= $item."\n";
13042
			}
13043
		}
13044
		$tmpid = $this->startTemplate($w, $h, false);
13045
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13046
		$this->endTemplate();
13047
		--$this->n;
13048
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13049
		unset($this->xobjects[$tmpid]);
13050
		$popt['ap']['n'] .= 'Q EMC';
13051
		// merge options
13052
		$opt = array_merge($popt, $opt);
13053
		// set remaining annotation data
13054
		$opt['Subtype'] = 'Widget';
13055
		$opt['ft'] = 'Ch';
13056
		$opt['t'] = $name;
13057
		$opt['opt'] = $values;
13058
		unset($opt['mk']['ca']);
13059
		unset($opt['mk']['rc']);
13060
		unset($opt['mk']['ac']);
13061
		unset($opt['mk']['i']);
13062
		unset($opt['mk']['ri']);
13063
		unset($opt['mk']['ix']);
13064
		unset($opt['mk']['if']);
13065
		unset($opt['mk']['tp']);
13066
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13067
		if ($this->rtl) {
13068
			$this->x -= $w;
13069
		} else {
13070
			$this->x += $w;
13071
		}
13072
	}
13073
 
13074
	/**
13075
	 * Creates a Combo-box field
13076
	 * @param string $name field name
13077
	 * @param int $w width
13078
	 * @param int $h height
13079
	 * @param array $values array containing the list of values.
13080
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13081
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13082
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13083
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13084
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13085
	 * @public
13086
	 * @author Nicola Asuni
13087
	 * @since 4.8.000 (2009-09-07)
13088
	 */
13089
	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13090
		if (TCPDF_STATIC::empty_string($x)) {
13091
			$x = $this->x;
13092
		}
13093
		if (TCPDF_STATIC::empty_string($y)) {
13094
			$y = $this->y;
13095
		}
13096
		// check page for no-write regions and adapt page margins if necessary
13097
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13098
		if ($js) {
13099
			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13100
			$s = '';
13101
			foreach ($values as $value) {
13102
				if (is_array($value)) {
13103
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13104
				} else {
13105
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13106
				}
13107
			}
13108
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13109
			return;
13110
		}
13111
		// get default style
13112
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13113
		$prop['Combo'] = true;
13114
		// get annotation data
13115
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13116
		// set additional default options
13117
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13118
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13119
		$popt['da'] = $fontstyle;
13120
		// build appearance stream
13121
		$popt['ap'] = array();
13122
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13123
		$text = '';
13124
		foreach($values as $item) {
13125
			if (is_array($item)) {
13126
				$text .= $item[1]."\n";
13127
			} else {
13128
				$text .= $item."\n";
13129
			}
13130
		}
13131
		$tmpid = $this->startTemplate($w, $h, false);
13132
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13133
		$this->endTemplate();
13134
		--$this->n;
13135
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13136
		unset($this->xobjects[$tmpid]);
13137
		$popt['ap']['n'] .= 'Q EMC';
13138
		// merge options
13139
		$opt = array_merge($popt, $opt);
13140
		// set remaining annotation data
13141
		$opt['Subtype'] = 'Widget';
13142
		$opt['ft'] = 'Ch';
13143
		$opt['t'] = $name;
13144
		$opt['opt'] = $values;
13145
		unset($opt['mk']['ca']);
13146
		unset($opt['mk']['rc']);
13147
		unset($opt['mk']['ac']);
13148
		unset($opt['mk']['i']);
13149
		unset($opt['mk']['ri']);
13150
		unset($opt['mk']['ix']);
13151
		unset($opt['mk']['if']);
13152
		unset($opt['mk']['tp']);
13153
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13154
		if ($this->rtl) {
13155
			$this->x -= $w;
13156
		} else {
13157
			$this->x += $w;
13158
		}
13159
	}
13160
 
13161
	/**
13162
	 * Creates a CheckBox field
13163
	 * @param string $name field name
13164
	 * @param int $w width
13165
	 * @param boolean $checked define the initial state.
13166
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13167
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13168
	 * @param string $onvalue value to be returned if selected.
13169
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13170
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13171
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13172
	 * @public
13173
	 * @author Nicola Asuni
13174
	 * @since 4.8.000 (2009-09-07)
13175
	 */
13176
	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) {
13177
		if (TCPDF_STATIC::empty_string($x)) {
13178
			$x = $this->x;
13179
		}
13180
		if (TCPDF_STATIC::empty_string($y)) {
13181
			$y = $this->y;
13182
		}
13183
		// check page for no-write regions and adapt page margins if necessary
13184
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13185
		if ($js) {
13186
			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13187
			return;
13188
		}
13189
		if (!isset($prop['value'])) {
13190
			$prop['value'] = array('Yes');
13191
		}
13192
		// get default style
13193
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13194
		$prop['borderStyle'] = 'inset';
13195
		// get annotation data
13196
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13197
		// set additional default options
13198
		$font = 'zapfdingbats';
13199
		if ($this->pdfa_mode) {
13200
			// all fonts must be embedded
13201
			$font = 'pdfa'.$font;
13202
		}
13203
		$this->AddFont($font);
13204
		$tmpfont = $this->getFontBuffer($font);
13205
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13206
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13207
		$popt['da'] = $fontstyle;
13208
		// build appearance stream
13209
		$popt['ap'] = array();
13210
		$popt['ap']['n'] = array();
13211
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13212
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13213
		$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);
13214
		$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);
13215
		// merge options
13216
		$opt = array_merge($popt, $opt);
13217
		// set remaining annotation data
13218
		$opt['Subtype'] = 'Widget';
13219
		$opt['ft'] = 'Btn';
13220
		$opt['t'] = $name;
13221
		if (TCPDF_STATIC::empty_string($onvalue)) {
13222
			$onvalue = 'Yes';
13223
		}
13224
		$opt['opt'] = array($onvalue);
13225
		if ($checked) {
13226
			$opt['v'] = array('/Yes');
13227
			$opt['as'] = 'Yes';
13228
		} else {
13229
			$opt['v'] = array('/Off');
13230
			$opt['as'] = 'Off';
13231
		}
13232
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13233
		if ($this->rtl) {
13234
			$this->x -= $w;
13235
		} else {
13236
			$this->x += $w;
13237
		}
13238
	}
13239
 
13240
	/**
13241
	 * Creates a button field
13242
	 * @param string $name field name
13243
	 * @param int $w width
13244
	 * @param int $h height
13245
	 * @param string $caption caption.
13246
	 * @param mixed $action action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13247
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13248
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13249
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13250
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13251
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13252
	 * @public
13253
	 * @author Nicola Asuni
13254
	 * @since 4.8.000 (2009-09-07)
13255
	 */
13256
	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13257
		if (TCPDF_STATIC::empty_string($x)) {
13258
			$x = $this->x;
13259
		}
13260
		if (TCPDF_STATIC::empty_string($y)) {
13261
			$y = $this->y;
13262
		}
13263
		// check page for no-write regions and adapt page margins if necessary
13264
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13265
		if ($js) {
13266
			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13267
			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13268
			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13269
			$this->javascript .= 'f'.$name.".highlight='push';\n";
13270
			$this->javascript .= 'f'.$name.".print=false;\n";
13271
			return;
13272
		}
13273
		// get default style
13274
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13275
		$prop['Pushbutton'] = 'true';
13276
		$prop['highlight'] = 'push';
13277
		$prop['display'] = 'display.noPrint';
13278
		// get annotation data
13279
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13280
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13281
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13282
		$popt['da'] = $fontstyle;
13283
		// build appearance stream
13284
		$popt['ap'] = array();
13285
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13286
		$tmpid = $this->startTemplate($w, $h, false);
13287
		$bw = (2 / $this->k); // border width
13288
		$border = array(
13289
			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13290
			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13291
			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13292
			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13293
		$this->setFillColor(204);
13294
		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13295
		$this->endTemplate();
13296
		--$this->n;
13297
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13298
		unset($this->xobjects[$tmpid]);
13299
		$popt['ap']['n'] .= 'Q EMC';
13300
		// set additional default options
13301
		if (!isset($popt['mk'])) {
13302
			$popt['mk'] = array();
13303
		}
13304
		$ann_obj_id = ($this->n + 1);
13305
		if (!empty($action) AND !is_array($action)) {
13306
			$ann_obj_id = ($this->n + 2);
13307
		}
13308
		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13309
		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13310
		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13311
		// merge options
13312
		$opt = array_merge($popt, $opt);
13313
		// set remaining annotation data
13314
		$opt['Subtype'] = 'Widget';
13315
		$opt['ft'] = 'Btn';
13316
		$opt['t'] = $caption;
13317
		$opt['v'] = $name;
13318
		if (!empty($action)) {
13319
			if (is_array($action)) {
13320
				// form action options as on section 12.7.5 of PDF32000_2008.
13321
				$opt['aa'] = '/D <<';
13322
				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13323
				foreach ($action AS $key => $val) {
13324
					if (($key == 'S') AND in_array($val, $bmode)) {
13325
						$opt['aa'] .= ' /S /'.$val;
13326
					} elseif (($key == 'F') AND (!empty($val))) {
13327
						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13328
					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13329
						$opt['aa'] .= ' /Fields [';
13330
						foreach ($val AS $field) {
13331
							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13332
						}
13333
						$opt['aa'] .= ']';
13334
					} elseif (($key == 'Flags')) {
13335
						$ff = 0;
13336
						if (is_array($val)) {
13337
							foreach ($val AS $flag) {
13338
								switch ($flag) {
13339
									case 'Include/Exclude': {
13340
										$ff += 1 << 0;
13341
										break;
13342
									}
13343
									case 'IncludeNoValueFields': {
13344
										$ff += 1 << 1;
13345
										break;
13346
									}
13347
									case 'ExportFormat': {
13348
										$ff += 1 << 2;
13349
										break;
13350
									}
13351
									case 'GetMethod': {
13352
										$ff += 1 << 3;
13353
										break;
13354
									}
13355
									case 'SubmitCoordinates': {
13356
										$ff += 1 << 4;
13357
										break;
13358
									}
13359
									case 'XFDF': {
13360
										$ff += 1 << 5;
13361
										break;
13362
									}
13363
									case 'IncludeAppendSaves': {
13364
										$ff += 1 << 6;
13365
										break;
13366
									}
13367
									case 'IncludeAnnotations': {
13368
										$ff += 1 << 7;
13369
										break;
13370
									}
13371
									case 'SubmitPDF': {
13372
										$ff += 1 << 8;
13373
										break;
13374
									}
13375
									case 'CanonicalFormat': {
13376
										$ff += 1 << 9;
13377
										break;
13378
									}
13379
									case 'ExclNonUserAnnots': {
13380
										$ff += 1 << 10;
13381
										break;
13382
									}
13383
									case 'ExclFKey': {
13384
										$ff += 1 << 11;
13385
										break;
13386
									}
13387
									case 'EmbedForm': {
13388
										$ff += 1 << 13;
13389
										break;
13390
									}
13391
								}
13392
							}
13393
						} else {
13394
							$ff = intval($val);
13395
						}
13396
						$opt['aa'] .= ' /Flags '.$ff;
13397
					}
13398
				}
13399
				$opt['aa'] .= ' >>';
13400
			} else {
13401
				// Javascript action or raw action command
13402
				$js_obj_id = $this->addJavascriptObject($action);
13403
				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13404
			}
13405
		}
13406
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13407
		if ($this->rtl) {
13408
			$this->x -= $w;
13409
		} else {
13410
			$this->x += $w;
13411
		}
13412
	}
13413
 
13414
	// --- END FORMS FIELDS ------------------------------------------------
13415
 
13416
	/**
13417
	 * Add certification signature (DocMDP or UR3)
13418
	 * You can set only one signature type
13419
	 * @protected
13420
	 * @author Nicola Asuni
13421
	 * @since 4.6.008 (2009-05-07)
13422
	 */
13423
	protected function _putsignature() {
13424
		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13425
			return;
13426
		}
13427
		$sigobjid = ($this->sig_obj_id + 1);
13428
		$out = $this->_getobj($sigobjid)."\n";
13429
		$out .= '<< /Type /Sig';
13430
		$out .= ' /Filter /Adobe.PPKLite';
13431
		$out .= ' /SubFilter /adbe.pkcs7.detached';
13432
		$out .= ' '.TCPDF_STATIC::$byterange_string;
13433
		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13434
		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13435
			$out .= ' /Reference ['; // array of signature reference dictionaries
13436
			$out .= ' << /Type /SigRef';
13437
			if ($this->signature_data['cert_type'] > 0) {
13438
				$out .= ' /TransformMethod /DocMDP';
13439
				$out .= ' /TransformParams <<';
13440
				$out .= ' /Type /TransformParams';
13441
				$out .= ' /P '.$this->signature_data['cert_type'];
13442
				$out .= ' /V /1.2';
13443
			} else {
13444
				$out .= ' /TransformMethod /UR3';
13445
				$out .= ' /TransformParams <<';
13446
				$out .= ' /Type /TransformParams';
13447
				$out .= ' /V /2.2';
13448
				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13449
					$out .= ' /Document['.$this->ur['document'].']';
13450
				}
13451
				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13452
					$out .= ' /Form['.$this->ur['form'].']';
13453
				}
13454
				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13455
					$out .= ' /Signature['.$this->ur['signature'].']';
13456
				}
13457
				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13458
					$out .= ' /Annots['.$this->ur['annots'].']';
13459
				}
13460
				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13461
					$out .= ' /EF['.$this->ur['ef'].']';
13462
				}
13463
				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13464
					$out .= ' /FormEX['.$this->ur['formex'].']';
13465
				}
13466
			}
13467
			$out .= ' >>'; // close TransformParams
13468
			// optional digest data (values must be calculated and replaced later)
13469
			//$out .= ' /Data ********** 0 R';
13470
			//$out .= ' /DigestMethod/MD5';
13471
			//$out .= ' /DigestLocation[********** 34]';
13472
			//$out .= ' /DigestValue<********************************>';
13473
			$out .= ' >>';
13474
			$out .= ' ]'; // end of reference
13475
		}
13476
		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13477
			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13478
		}
13479
		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13480
			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13481
		}
13482
		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13483
			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13484
		}
13485
		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13486
			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13487
		}
13488
		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13489
		$out .= ' >>';
13490
		$out .= "\n".'endobj';
13491
		$this->_out($out);
13492
	}
13493
 
13494
	/**
13495
	 * Set User's Rights for PDF Reader
13496
	 * WARNING: This is experimental and currently do not work.
13497
	 * Check the PDF Reference 8.7.1 Transform Methods,
13498
	 * Table 8.105 Entries in the UR transform parameters dictionary
13499
	 * @param boolean $enable if true enable user's rights on PDF reader
13500
	 * @param string $document Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13501
	 * @param string $annots Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13502
	 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13503
	 * @param string $signature Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13504
	 * @param string $ef Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13505
	 Names specifying additional embedded-files-related usage rights for the document.
13506
	 * @param string $formex Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13507
	 * @public
13508
	 * @author Nicola Asuni
13509
	 * @since 2.9.000 (2008-03-26)
13510
	 */
13511
	public function setUserRights(
13512
			$enable=true,
13513
			$document='/FullSave',
13514
			$annots='/Create/Delete/Modify/Copy/Import/Export',
13515
			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13516
			$signature='/Modify',
13517
			$ef='/Create/Delete/Modify/Import',
13518
			$formex='') {
13519
		$this->ur['enabled'] = $enable;
13520
		$this->ur['document'] = $document;
13521
		$this->ur['annots'] = $annots;
13522
		$this->ur['form'] = $form;
13523
		$this->ur['signature'] = $signature;
13524
		$this->ur['ef'] = $ef;
13525
		$this->ur['formex'] = $formex;
13526
		if (!$this->sign) {
13527
			$this->setSignature('', '', '', '', 0, array());
13528
		}
13529
	}
13530
 
13531
	/**
13532
	 * Enable document signature (requires the OpenSSL Library).
13533
	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13534
	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13535
	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13536
	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13537
	 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13538
	 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13539
	 * @param string $private_key_password password
13540
	 * @param string $extracerts specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13541
	 * @param int $cert_type The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13542
	 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13543
	 * @param string $approval Enable approval signature eg. for PDF incremental update
13544
	 * @public
13545
	 * @author Nicola Asuni
13546
	 * @since 4.6.005 (2009-04-24)
13547
	 */
13548
	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13549
		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13550
		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13551
		// to convert pfx certificate to pem: openssl
13552
		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13553
		$this->sign = true;
13554
		++$this->n;
13555
		$this->sig_obj_id = $this->n; // signature widget
13556
		++$this->n; // signature object ($this->sig_obj_id + 1)
13557
		$this->signature_data = array();
13558
		if (strlen($signing_cert) == 0) {
13559
			$this->Error('Please provide a certificate file and password!');
13560
		}
13561
		if (strlen($private_key) == 0) {
13562
			$private_key = $signing_cert;
13563
		}
13564
		$this->signature_data['signcert'] = $signing_cert;
13565
		$this->signature_data['privkey'] = $private_key;
13566
		$this->signature_data['password'] = $private_key_password;
13567
		$this->signature_data['extracerts'] = $extracerts;
13568
		$this->signature_data['cert_type'] = $cert_type;
13569
		$this->signature_data['info'] = $info;
13570
		$this->signature_data['approval'] = $approval;
13571
	}
13572
 
13573
	/**
13574
	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13575
	 * @param float $x Abscissa of the upper-left corner.
13576
	 * @param float $y Ordinate of the upper-left corner.
13577
	 * @param float $w Width of the signature area.
13578
	 * @param float $h Height of the signature area.
13579
	 * @param int $page option page number (if < 0 the current page is used).
13580
	 * @param string $name Name of the signature.
13581
	 * @public
13582
	 * @author Nicola Asuni
13583
	 * @since 5.3.011 (2010-06-17)
13584
	 */
13585
	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13586
		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13587
	}
13588
 
13589
	/**
13590
	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13591
	 * @param float $x Abscissa of the upper-left corner.
13592
	 * @param float $y Ordinate of the upper-left corner.
13593
	 * @param float $w Width of the signature area.
13594
	 * @param float $h Height of the signature area.
13595
	 * @param int $page option page number (if < 0 the current page is used).
13596
	 * @param string $name Name of the signature.
13597
	 * @public
13598
	 * @author Nicola Asuni
13599
	 * @since 5.9.101 (2011-07-06)
13600
	 */
13601
	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13602
		++$this->n;
13603
		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13604
	}
13605
 
13606
	/**
13607
	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13608
	 * @param float $x Abscissa of the upper-left corner.
13609
	 * @param float $y Ordinate of the upper-left corner.
13610
	 * @param float $w Width of the signature area.
13611
	 * @param float $h Height of the signature area.
13612
	 * @param int $page option page number (if < 0 the current page is used).
13613
	 * @param string $name Name of the signature.
13614
	 * @return array Array defining page and rectangle coordinates of signature appearance.
13615
	 * @protected
13616
	 * @author Nicola Asuni
13617
	 * @since 5.9.101 (2011-07-06)
13618
	 */
13619
	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13620
		$sigapp = array();
13621
		if (($page < 1) OR ($page > $this->numpages)) {
13622
			$sigapp['page'] = $this->page;
13623
		} else {
13624
			$sigapp['page'] = intval($page);
13625
		}
13626
		if (empty($name)) {
13627
			$sigapp['name'] = 'Signature';
13628
		} else {
13629
			$sigapp['name'] = $name;
13630
		}
13631
		$a = $x * $this->k;
13632
		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13633
		$c = $w * $this->k;
13634
		$d = $h * $this->k;
13635
		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13636
		return $sigapp;
13637
	}
13638
 
13639
	/**
13640
	 * Enable document timestamping (requires the OpenSSL Library).
13641
	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13642
	 * Use with digital signature only!
13643
	 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13644
	 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13645
	 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13646
	 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13647
	 * @public
13648
	 * @author Richard Stockinger
13649
	 * @since 6.0.090 (2014-06-16)
13650
	 */
13651
	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13652
		$this->tsa_data = array();
13653
		if (!function_exists('curl_init')) {
13654
			$this->Error('Please enable cURL PHP extension!');
13655
		}
13656
		if (strlen($tsa_host) == 0) {
13657
			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13658
		}
13659
		$this->tsa_data['tsa_host'] = $tsa_host;
13660
		if (is_file($tsa_username)) {
13661
			$this->tsa_data['tsa_auth'] = $tsa_username;
13662
		} else {
13663
			$this->tsa_data['tsa_username'] = $tsa_username;
13664
		}
13665
		$this->tsa_data['tsa_password'] = $tsa_password;
13666
		$this->tsa_data['tsa_cert'] = $tsa_cert;
13667
		$this->tsa_timestamp = true;
13668
	}
13669
 
13670
	/**
13671
	 * NOT YET IMPLEMENTED
13672
	 * Request TSA for a timestamp
13673
	 * @param string $signature Digital signature as binary string
13674
	 * @return string Timestamped digital signature
13675
	 * @protected
13676
	 * @author Richard Stockinger
13677
	 * @since 6.0.090 (2014-06-16)
13678
	 */
13679
	protected function applyTSA($signature) {
13680
		if (!$this->tsa_timestamp) {
13681
			return $signature;
13682
		}
13683
		//@TODO: implement this feature
13684
		return $signature;
13685
	}
13686
 
13687
	/**
13688
	 * Create a new page group.
13689
	 * NOTE: call this function before calling AddPage()
13690
	 * @param int|null $page starting group page (leave empty for next page).
13691
	 * @public
13692
	 * @since 3.0.000 (2008-03-27)
13693
	 */
13694
	public function startPageGroup($page=null) {
13695
		if (empty($page)) {
13696
			$page = $this->page + 1;
13697
		}
13698
		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13699
	}
13700
 
13701
	/**
13702
	 * Set the starting page number.
13703
	 * @param int $num Starting page number.
13704
	 * @since 5.9.093 (2011-06-16)
13705
	 * @public
13706
	 */
13707
	public function setStartingPageNumber($num=1) {
13708
		$this->starting_page_number = max(0, intval($num));
13709
	}
13710
 
13711
	/**
13712
	 * Returns the string alias used right align page numbers.
13713
	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13714
	 * @return string
13715
	 * @since 5.9.099 (2011-06-27)
13716
	 * @public
13717
	 */
13718
	public function getAliasRightShift() {
13719
		// calculate aproximatively the ratio between widths of aliases and replacements.
13720
		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13721
		$rep = str_repeat(' ', $this->GetNumChars($ref));
13722
		$wrep = $this->GetStringWidth($rep);
13723
		if ($wrep > 0) {
13724
			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13725
		} else {
13726
			$wdiff = 1;
13727
		}
13728
		$sdiff = sprintf('%F', $wdiff);
13729
		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13730
		if ($this->isUnicodeFont()) {
13731
			$alias = '{'.$alias;
13732
		}
13733
		return $alias;
13734
	}
13735
 
13736
	/**
13737
	 * Returns the string alias used for the total number of pages.
13738
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13739
	 * This alias will be replaced by the total number of pages in the document.
13740
	 * @return string
13741
	 * @since 4.0.018 (2008-08-08)
13742
	 * @public
13743
	 */
13744
	public function getAliasNbPages() {
13745
		if ($this->isUnicodeFont()) {
13746
			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13747
		}
13748
		return TCPDF_STATIC::$alias_tot_pages;
13749
	}
13750
 
13751
	/**
13752
	 * Returns the string alias used for the page number.
13753
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13754
	 * This alias will be replaced by the page number.
13755
	 * @return string
13756
	 * @since 4.5.000 (2009-01-02)
13757
	 * @public
13758
	 */
13759
	public function getAliasNumPage() {
13760
		if ($this->isUnicodeFont()) {
13761
			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13762
		}
13763
		return TCPDF_STATIC::$alias_num_page;
13764
	}
13765
 
13766
	/**
13767
	 * Return the alias for the total number of pages in the current page group.
13768
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13769
	 * This alias will be replaced by the total number of pages in this group.
13770
	 * @return string alias of the current page group
13771
	 * @public
13772
	 * @since 3.0.000 (2008-03-27)
13773
	 */
13774
	public function getPageGroupAlias() {
13775
		if ($this->isUnicodeFont()) {
13776
			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13777
		}
13778
		return TCPDF_STATIC::$alias_group_tot_pages;
13779
	}
13780
 
13781
	/**
13782
	 * Return the alias for the page number on the current page group.
13783
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13784
	 * This alias will be replaced by the page number (relative to the belonging group).
13785
	 * @return string alias of the current page group
13786
	 * @public
13787
	 * @since 4.5.000 (2009-01-02)
13788
	 */
13789
	public function getPageNumGroupAlias() {
13790
		if ($this->isUnicodeFont()) {
13791
			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13792
		}
13793
		return TCPDF_STATIC::$alias_group_num_page;
13794
	}
13795
 
13796
	/**
13797
	 * Return the current page in the group.
13798
	 * @return int current page in the group
13799
	 * @public
13800
	 * @since 3.0.000 (2008-03-27)
13801
	 */
13802
	public function getGroupPageNo() {
13803
		return $this->pagegroups[$this->currpagegroup];
13804
	}
13805
 
13806
	/**
13807
	 * Returns the current group page number formatted as a string.
13808
	 * @public
13809
	 * @since 4.3.003 (2008-11-18)
13810
	 * @see PaneNo(), formatPageNumber()
13811
	 */
13812
	public function getGroupPageNoFormatted() {
13813
		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13814
	}
13815
 
13816
	/**
13817
	 * Returns the current page number formatted as a string.
13818
	 * @public
13819
	 * @since 4.2.005 (2008-11-06)
13820
	 * @see PaneNo(), formatPageNumber()
13821
	 */
13822
	public function PageNoFormatted() {
13823
		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13824
	}
13825
 
13826
	/**
13827
	 * Put pdf layers.
13828
	 * @protected
13829
	 * @since 3.0.000 (2008-03-27)
13830
	 */
13831
	protected function _putocg() {
13832
		if (empty($this->pdflayers)) {
13833
			return;
13834
		}
13835
		foreach ($this->pdflayers as $key => $layer) {
13836
			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13837
			 $out = '<< /Type /OCG';
13838
			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13839
			 $out .= ' /Usage <<';
13840
			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13841
				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13842
			 }
13843
			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13844
			 $out .= ' >> >>';
13845
			 $out .= "\n".'endobj';
13846
			 $this->_out($out);
13847
		}
13848
	}
13849
 
13850
	/**
13851
	 * Start a new pdf layer.
13852
	 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13853
	 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13854
	 * @param boolean $view Set to true to view this layer.
13855
	 * @param boolean $lock If true lock the layer
13856
	 * @public
13857
	 * @since 5.9.102 (2011-07-13)
13858
	 */
13859
	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13860
		if ($this->state != 2) {
13861
			return;
13862
		}
13863
		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13864
		if (empty($name)) {
13865
			$name = $layer;
13866
		} else {
13867
			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13868
		}
13869
		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13870
		$this->openMarkedContent = true;
13871
		$this->_out('/OC /'.$layer.' BDC');
13872
	}
13873
 
13874
	/**
13875
	 * End the current PDF layer.
13876
	 * @public
13877
	 * @since 5.9.102 (2011-07-13)
13878
	 */
13879
	public function endLayer() {
13880
		if ($this->state != 2) {
13881
			return;
13882
		}
13883
		if ($this->openMarkedContent) {
13884
			// close existing open marked-content layer
13885
			$this->_out('EMC');
13886
			$this->openMarkedContent = false;
13887
		}
13888
	}
13889
 
13890
	/**
13891
	 * Set the visibility of the successive elements.
13892
	 * This can be useful, for instance, to put a background
13893
	 * image or color that will show on screen but won't print.
13894
	 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13895
	 * @public
13896
	 * @since 3.0.000 (2008-03-27)
13897
	 */
13898
	public function setVisibility($v) {
13899
		if ($this->state != 2) {
13900
			return;
13901
		}
13902
		$this->endLayer();
13903
		switch($v) {
13904
			case 'print': {
13905
				$this->startLayer('Print', true, false);
13906
				break;
13907
			}
13908
			case 'view':
13909
			case 'screen': {
13910
				$this->startLayer('View', false, true);
13911
				break;
13912
			}
13913
			case 'all': {
13914
				$this->_out('');
13915
				break;
13916
			}
13917
			default: {
13918
				$this->Error('Incorrect visibility: '.$v);
13919
				break;
13920
			}
13921
		}
13922
	}
13923
 
13924
	/**
13925
	 * Add transparency parameters to the current extgstate
13926
	 * @param array $parms parameters
13927
	 * @return int|void the number of extgstates
13928
	 * @protected
13929
	 * @since 3.0.000 (2008-03-27)
13930
	 */
13931
	protected function addExtGState($parms) {
13932
		if ($this->pdfa_mode || $this->pdfa_version >= 2) {
13933
			// transparencies are not allowed in PDF/A mode
13934
			return;
13935
		}
13936
		// check if this ExtGState already exist
13937
		foreach ($this->extgstates as $i => $ext) {
13938
			if ($ext['parms'] == $parms) {
13939
				if ($this->inxobj) {
13940
					// we are inside an XObject template
13941
					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13942
				}
13943
				// return reference to existing ExtGState
13944
				return $i;
13945
			}
13946
		}
13947
		$n = (count($this->extgstates) + 1);
13948
		$this->extgstates[$n] = array('parms' => $parms);
13949
		if ($this->inxobj) {
13950
			// we are inside an XObject template
13951
			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13952
		}
13953
		return $n;
13954
	}
13955
 
13956
	/**
13957
	 * Add an extgstate
13958
	 * @param int $gs extgstate
13959
	 * @protected
13960
	 * @since 3.0.000 (2008-03-27)
13961
	 */
13962
	protected function setExtGState($gs) {
13963
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
13964
			// transparency is not allowed in PDF/A-1 mode
13965
			return;
13966
		}
13967
		$this->_out(sprintf('/GS%d gs', $gs));
13968
	}
13969
 
13970
	/**
13971
	 * Put extgstates for object transparency
13972
	 * @protected
13973
	 * @since 3.0.000 (2008-03-27)
13974
	 */
13975
	protected function _putextgstates() {
13976
		foreach ($this->extgstates as $i => $ext) {
13977
			$this->extgstates[$i]['n'] = $this->_newobj();
13978
			$out = '<< /Type /ExtGState';
13979
			foreach ($ext['parms'] as $k => $v) {
13980
				if (is_float($v)) {
13981
					$v = sprintf('%F', $v);
13982
				} elseif ($v === true) {
13983
					$v = 'true';
13984
				} elseif ($v === false) {
13985
					$v = 'false';
13986
				}
13987
				$out .= ' /'.$k.' '.$v;
13988
			}
13989
			$out .= ' >>';
13990
			$out .= "\n".'endobj';
13991
			$this->_out($out);
13992
		}
13993
	}
13994
 
13995
	/**
13996
	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13997
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13998
	 * @param boolean $stroking If true apply overprint for stroking operations.
13999
	 * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking.
14000
	 * @param integer $mode Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
14001
	 * @public
14002
	 * @since 5.9.152 (2012-03-23)
14003
	 */
14004
	public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14005
		if ($this->state != 2) {
14006
			return;
14007
		}
14008
		$stroking = $stroking ? true : false;
14009
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14010
			// default value if not set
14011
			$nonstroking = $stroking;
14012
		} else {
14013
			$nonstroking = $nonstroking ? true : false;
14014
		}
14015
		if (($mode != 0) AND ($mode != 1)) {
14016
			$mode = 0;
14017
		}
14018
		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14019
		$gs = $this->addExtGState($this->overprint);
14020
		$this->setExtGState($gs);
14021
	}
14022
 
14023
	/**
14024
	 * Get the overprint mode array (OP, op, OPM).
14025
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14026
	 * @return array<string,bool|int>
14027
	 * @public
14028
	 * @since 5.9.152 (2012-03-23)
14029
	 */
14030
	public function getOverprint() {
14031
		return $this->overprint;
14032
	}
14033
 
14034
	/**
14035
	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14036
	 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14037
	 * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
14038
	 * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14039
	 * @param boolean $ais
14040
	 * @public
14041
	 * @since 3.0.000 (2008-03-27)
14042
	 */
14043
	public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) {
14044
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14045
			// transparency is not allowed in PDF/A-1 mode
14046
			return;
14047
		}
14048
		$stroking = floatval($stroking);
14049
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14050
			// default value if not set
14051
			$nonstroking = $stroking;
14052
		} else {
14053
			$nonstroking = floatval($nonstroking);
14054
		}
14055
		if ($bm[0] == '/') {
14056
			// remove trailing slash
14057
			$bm = substr($bm, 1);
14058
		}
14059
		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14060
			$bm = 'Normal';
14061
		}
14062
		$ais = $ais ? true : false;
14063
		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14064
		$gs = $this->addExtGState($this->alpha);
14065
		$this->setExtGState($gs);
14066
	}
14067
 
14068
	/**
14069
	 * Get the alpha mode array (CA, ca, BM, AIS).
14070
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14071
	 * @return array<string,bool|string>
14072
	 * @public
14073
	 * @since 5.9.152 (2012-03-23)
14074
	 */
14075
	public function getAlpha() {
14076
		return $this->alpha;
14077
	}
14078
 
14079
	/**
14080
	 * Set the default JPEG compression quality (1-100)
14081
	 * @param int $quality JPEG quality, integer between 1 and 100
14082
	 * @public
14083
	 * @since 3.0.000 (2008-03-27)
14084
	 */
14085
	public function setJPEGQuality($quality) {
14086
		if (($quality < 1) OR ($quality > 100)) {
14087
			$quality = 75;
14088
		}
14089
		$this->jpeg_quality = intval($quality);
14090
	}
14091
 
14092
	/**
14093
	 * Set the default number of columns in a row for HTML tables.
14094
	 * @param int $cols number of columns
14095
	 * @public
14096
	 * @since 3.0.014 (2008-06-04)
14097
	 */
14098
	public function setDefaultTableColumns($cols=4) {
14099
		$this->default_table_columns = intval($cols);
14100
	}
14101
 
14102
	/**
14103
	 * Set the height of the cell (line height) respect the font height.
14104
	 * @param float $h cell proportion respect font height (typical value = 1.25).
14105
	 * @public
14106
	 * @since 3.0.014 (2008-06-04)
14107
	 */
14108
	public function setCellHeightRatio($h) {
14109
		$this->cell_height_ratio = $h;
14110
	}
14111
 
14112
	/**
14113
	 * return the height of cell repect font height.
14114
	 * @public
14115
	 * @return float
14116
	 * @since 4.0.012 (2008-07-24)
14117
	 */
14118
	public function getCellHeightRatio() {
14119
		return $this->cell_height_ratio;
14120
	}
14121
 
14122
	/**
14123
	 * Set the PDF version (check PDF reference for valid values).
14124
	 * @param string $version PDF document version.
14125
	 * @public
14126
	 * @since 3.1.000 (2008-06-09)
14127
	 */
14128
	public function setPDFVersion($version='1.7') {
14129
		if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14130
			// PDF/A-1 mode
14131
			$this->PDFVersion = '1.4';
14132
		} elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14133
            // PDF/A-2 mode
14134
            $this->PDFVersion = '1.7';
14135
        } else {
14136
			$this->PDFVersion = $version;
14137
		}
14138
	}
14139
 
14140
	/**
14141
	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14142
	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14143
	 * <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>
14144
	 * @param array $preferences array of options.
14145
	 * @author Nicola Asuni
14146
	 * @public
14147
	 * @since 3.1.000 (2008-06-09)
14148
	 */
14149
	public function setViewerPreferences($preferences) {
14150
		$this->viewer_preferences = $preferences;
14151
	}
14152
 
14153
	/**
14154
	 * Paints color transition registration bars
14155
	 * @param float $x abscissa of the top left corner of the rectangle.
14156
	 * @param float $y ordinate of the top left corner of the rectangle.
14157
	 * @param float $w width of the rectangle.
14158
	 * @param float $h height of the rectangle.
14159
	 * @param boolean $transition if true prints tcolor transitions to white.
14160
	 * @param boolean $vertical if true prints bar vertically.
14161
	 * @param string $colors colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14162
	 * @author Nicola Asuni
14163
	 * @since 4.9.000 (2010-03-26)
14164
	 * @public
14165
	 */
14166
	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14167
		if (strpos($colors, 'ALLSPOT') !== false) {
14168
			// expand spot colors
14169
			$spot_colors = '';
14170
			foreach ($this->spot_colors as $spot_color_name => $v) {
14171
				$spot_colors .= ','.$spot_color_name;
14172
			}
14173
			if (!empty($spot_colors)) {
14174
				$spot_colors = substr($spot_colors, 1);
14175
				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14176
			} else {
14177
				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14178
			}
14179
		}
14180
		$bars = explode(',', $colors);
14181
		$numbars = count($bars); // number of bars to print
14182
		if ($numbars <= 0) {
14183
			return;
14184
		}
14185
		// set bar measures
14186
		if ($vertical) {
14187
			$coords = array(0, 0, 0, 1);
14188
			$wb = $w / $numbars; // bar width
14189
			$hb = $h; // bar height
14190
			$xd = $wb; // delta x
14191
			$yd = 0; // delta y
14192
		} else {
14193
			$coords = array(1, 0, 0, 0);
14194
			$wb = $w; // bar width
14195
			$hb = $h / $numbars; // bar height
14196
			$xd = 0; // delta x
14197
			$yd = $hb; // delta y
14198
		}
14199
		$xb = $x;
14200
		$yb = $y;
14201
		foreach ($bars as $col) {
14202
			switch ($col) {
14203
				// set transition colors
14204
				case 'A': { // BLACK (GRAYSCALE)
14205
					$col_a = array(255);
14206
					$col_b = array(0);
14207
					break;
14208
				}
14209
				case 'W': { // WHITE (GRAYSCALE)
14210
					$col_a = array(0);
14211
					$col_b = array(255);
14212
					break;
14213
				}
14214
				case 'R': { // RED (RGB)
14215
					$col_a = array(255,255,255);
14216
					$col_b = array(255,0,0);
14217
					break;
14218
				}
14219
				case 'G': { // GREEN (RGB)
14220
					$col_a = array(255,255,255);
14221
					$col_b = array(0,255,0);
14222
					break;
14223
				}
14224
				case 'B': { // BLUE (RGB)
14225
					$col_a = array(255,255,255);
14226
					$col_b = array(0,0,255);
14227
					break;
14228
				}
14229
				case 'C': { // CYAN (CMYK)
14230
					$col_a = array(0,0,0,0);
14231
					$col_b = array(100,0,0,0);
14232
					break;
14233
				}
14234
				case 'M': { // MAGENTA (CMYK)
14235
					$col_a = array(0,0,0,0);
14236
					$col_b = array(0,100,0,0);
14237
					break;
14238
				}
14239
				case 'Y': { // YELLOW (CMYK)
14240
					$col_a = array(0,0,0,0);
14241
					$col_b = array(0,0,100,0);
14242
					break;
14243
				}
14244
				case 'K': { // KEY - BLACK (CMYK)
14245
					$col_a = array(0,0,0,0);
14246
					$col_b = array(0,0,0,100);
14247
					break;
14248
				}
14249
				case 'RGB': { // BLACK REGISTRATION (RGB)
14250
					$col_a = array(255,255,255);
14251
					$col_b = array(0,0,0);
14252
					break;
14253
				}
14254
				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14255
					$col_a = array(0,0,0,0);
14256
					$col_b = array(100,100,100,100);
14257
					break;
14258
				}
14259
				case 'ALL': { // SPOT COLOR REGISTRATION
14260
					$col_a = array(0,0,0,0,'None');
14261
					$col_b = array(100,100,100,100,'All');
14262
					break;
14263
				}
14264
				case 'NONE': { // SKIP THIS COLOR
14265
					$col_a = array(0,0,0,0,'None');
14266
					$col_b = array(0,0,0,0,'None');
14267
					break;
14268
				}
14269
				default: { // SPECIFIC SPOT COLOR NAME
14270
					$col_a = array(0,0,0,0,'None');
14271
					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14272
					if ($col_b === false) {
14273
						// in case of error defaults to the registration color
14274
						$col_b = array(100,100,100,100,'All');
14275
					}
14276
					break;
14277
				}
14278
			}
14279
			if ($col != 'NONE') {
14280
				if ($transition) {
14281
					// color gradient
14282
					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14283
				} else {
14284
					$this->setFillColorArray($col_b);
14285
					// colored rectangle
14286
					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14287
				}
14288
				$xb += $xd;
14289
				$yb += $yd;
14290
			}
14291
		}
14292
	}
14293
 
14294
	/**
14295
	 * Paints crop marks.
14296
	 * @param float $x abscissa of the crop mark center.
14297
	 * @param float $y ordinate of the crop mark center.
14298
	 * @param float $w width of the crop mark.
14299
	 * @param float $h height of the crop mark.
14300
	 * @param string $type type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14301
	 * @param array $color crop mark color (default spot registration color).
14302
	 * @author Nicola Asuni
14303
	 * @since 4.9.000 (2010-03-26)
14304
	 * @public
14305
	 */
14306
	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14307
		$this->setLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14308
		$type = strtoupper($type);
14309
		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14310
		// split type in single components
14311
		$type = str_replace('-', ',', $type);
14312
		$type = str_replace('TL', 'T,L', $type);
14313
		$type = str_replace('TR', 'T,R', $type);
14314
		$type = str_replace('BL', 'F,L', $type);
14315
		$type = str_replace('BR', 'F,R', $type);
14316
		$type = str_replace('A', 'T,L', $type);
14317
		$type = str_replace('B', 'T,R', $type);
14318
		$type = str_replace('T,RO', 'BO', $type);
14319
		$type = str_replace('C', 'F,L', $type);
14320
		$type = str_replace('D', 'F,R', $type);
14321
		$crops = explode(',', strtoupper($type));
14322
		// remove duplicates
14323
		$crops = array_unique($crops);
14324
		$dw = ($w / 4); // horizontal space to leave before the intersection point
14325
		$dh = ($h / 4); // vertical space to leave before the intersection point
14326
		foreach ($crops as $crop) {
14327
			switch ($crop) {
14328
				case 'T':
14329
				case 'TOP': {
14330
					$x1 = $x;
14331
					$y1 = ($y - $h);
14332
					$x2 = $x;
14333
					$y2 = ($y - $dh);
14334
					break;
14335
				}
14336
				case 'F':
14337
				case 'BOTTOM': {
14338
					$x1 = $x;
14339
					$y1 = ($y + $dh);
14340
					$x2 = $x;
14341
					$y2 = ($y + $h);
14342
					break;
14343
				}
14344
				case 'L':
14345
				case 'LEFT': {
14346
					$x1 = ($x - $w);
14347
					$y1 = $y;
14348
					$x2 = ($x - $dw);
14349
					$y2 = $y;
14350
					break;
14351
				}
14352
				case 'R':
14353
				case 'RIGHT': {
14354
					$x1 = ($x + $dw);
14355
					$y1 = $y;
14356
					$x2 = ($x + $w);
14357
					$y2 = $y;
14358
					break;
14359
				}
14360
			}
14361
			$this->Line($x1, $y1, $x2, $y2);
14362
		}
14363
	}
14364
 
14365
	/**
14366
	 * Paints a registration mark
14367
	 * @param float $x abscissa of the registration mark center.
14368
	 * @param float $y ordinate of the registration mark center.
14369
	 * @param float $r radius of the crop mark.
14370
	 * @param boolean $double if true print two concentric crop marks.
14371
	 * @param array $cola crop mark color (default spot registration color 'All').
14372
	 * @param array $colb second crop mark color (default spot registration color 'None').
14373
	 * @author Nicola Asuni
14374
	 * @since 4.9.000 (2010-03-26)
14375
	 * @public
14376
	 */
14377
	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14378
		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14379
		$this->setFillColorArray($cola);
14380
		$this->PieSector($x, $y, $r, 90, 180, 'F');
14381
		$this->PieSector($x, $y, $r, 270, 360, 'F');
14382
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14383
		if ($double) {
14384
			$ri = $r * 0.5;
14385
			$this->setFillColorArray($colb);
14386
			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14387
			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14388
			$this->setFillColorArray($cola);
14389
			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14390
			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14391
			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14392
		}
14393
	}
14394
 
14395
	/**
14396
	 * Paints a CMYK registration mark
14397
	 * @param float $x abscissa of the registration mark center.
14398
	 * @param float $y ordinate of the registration mark center.
14399
	 * @param float $r radius of the crop mark.
14400
	 * @author Nicola Asuni
14401
	 * @since 6.0.038 (2013-09-30)
14402
	 * @public
14403
	 */
14404
	public function registrationMarkCMYK($x, $y, $r) {
14405
		// line width
14406
		$lw = max((0.5 / $this->k),($r / 8));
14407
		// internal radius
14408
		$ri = ($r * 0.6);
14409
		// external radius
14410
		$re = ($r * 1.3);
14411
		// Cyan
14412
		$this->setFillColorArray(array(100,0,0,0));
14413
		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14414
		// Magenta
14415
		$this->setFillColorArray(array(0,100,0,0));
14416
		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14417
		// Yellow
14418
		$this->setFillColorArray(array(0,0,100,0));
14419
		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14420
		// Key - black
14421
		$this->setFillColorArray(array(0,0,0,100));
14422
		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14423
		// registration color
14424
		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14425
		$this->setFillColorArray(array(100,100,100,100,'All'));
14426
		// external circle
14427
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14428
		// cross lines
14429
		$this->Line($x, ($y - $re), $x, ($y - $ri));
14430
		$this->Line($x, ($y + $ri), $x, ($y + $re));
14431
		$this->Line(($x - $re), $y, ($x - $ri), $y);
14432
		$this->Line(($x + $ri), $y, ($x + $re), $y);
14433
	}
14434
 
14435
	/**
14436
	 * Paints a linear colour gradient.
14437
	 * @param float $x abscissa of the top left corner of the rectangle.
14438
	 * @param float $y ordinate of the top left corner of the rectangle.
14439
	 * @param float $w width of the rectangle.
14440
	 * @param float $h height of the rectangle.
14441
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14442
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14443
	 * @param array $coords array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14444
	 * @author Andreas W\FCrmser, Nicola Asuni
14445
	 * @since 3.1.000 (2008-06-09)
14446
	 * @public
14447
	 */
14448
	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14449
		$this->Clip($x, $y, $w, $h);
14450
		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14451
	}
14452
 
14453
	/**
14454
	 * Paints a radial colour gradient.
14455
	 * @param float $x abscissa of the top left corner of the rectangle.
14456
	 * @param float $y ordinate of the top left corner of the rectangle.
14457
	 * @param float $w width of the rectangle.
14458
	 * @param float $h height of the rectangle.
14459
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14460
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14461
	 * @param array $coords array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14462
	 * @author Andreas W\FCrmser, Nicola Asuni
14463
	 * @since 3.1.000 (2008-06-09)
14464
	 * @public
14465
	 */
14466
	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14467
		$this->Clip($x, $y, $w, $h);
14468
		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14469
	}
14470
 
14471
	/**
14472
	 * Paints a coons patch mesh.
14473
	 * @param float $x abscissa of the top left corner of the rectangle.
14474
	 * @param float $y ordinate of the top left corner of the rectangle.
14475
	 * @param float $w width of the rectangle.
14476
	 * @param float $h height of the rectangle.
14477
	 * @param array $col1 first color (lower left corner) (RGB components).
14478
	 * @param array $col2 second color (lower right corner) (RGB components).
14479
	 * @param array $col3 third color (upper right corner) (RGB components).
14480
	 * @param array $col4 fourth color (upper left corner) (RGB components).
14481
	 * @param array $coords <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14482
	 * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14483
	 * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14484
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14485
	 * @author Andreas W\FCrmser, Nicola Asuni
14486
	 * @since 3.1.000 (2008-06-09)
14487
	 * @public
14488
	 */
14489
	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) {
14490
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14491
			return;
14492
		}
14493
		$this->Clip($x, $y, $w, $h);
14494
		$n = count($this->gradients) + 1;
14495
		$this->gradients[$n] = array();
14496
		$this->gradients[$n]['type'] = 6; //coons patch mesh
14497
		$this->gradients[$n]['coords'] = array();
14498
		$this->gradients[$n]['antialias'] = $antialias;
14499
		$this->gradients[$n]['colors'] = array();
14500
		$this->gradients[$n]['transparency'] = false;
14501
		//check the coords array if it is the simple array or the multi patch array
14502
		if (!isset($coords[0]['f'])) {
14503
			//simple array -> convert to multi patch array
14504
			if (!isset($col1[1])) {
14505
				$col1[1] = $col1[2] = $col1[0];
14506
			}
14507
			if (!isset($col2[1])) {
14508
				$col2[1] = $col2[2] = $col2[0];
14509
			}
14510
			if (!isset($col3[1])) {
14511
				$col3[1] = $col3[2] = $col3[0];
14512
			}
14513
			if (!isset($col4[1])) {
14514
				$col4[1] = $col4[2] = $col4[0];
14515
			}
14516
			$patch_array[0]['f'] = 0;
14517
			$patch_array[0]['points'] = $coords;
14518
			$patch_array[0]['colors'][0]['r'] = $col1[0];
14519
			$patch_array[0]['colors'][0]['g'] = $col1[1];
14520
			$patch_array[0]['colors'][0]['b'] = $col1[2];
14521
			$patch_array[0]['colors'][1]['r'] = $col2[0];
14522
			$patch_array[0]['colors'][1]['g'] = $col2[1];
14523
			$patch_array[0]['colors'][1]['b'] = $col2[2];
14524
			$patch_array[0]['colors'][2]['r'] = $col3[0];
14525
			$patch_array[0]['colors'][2]['g'] = $col3[1];
14526
			$patch_array[0]['colors'][2]['b'] = $col3[2];
14527
			$patch_array[0]['colors'][3]['r'] = $col4[0];
14528
			$patch_array[0]['colors'][3]['g'] = $col4[1];
14529
			$patch_array[0]['colors'][3]['b'] = $col4[2];
14530
		} else {
14531
			//multi patch array
14532
			$patch_array = $coords;
14533
		}
14534
		$bpcd = 65535; //16 bits per coordinate
14535
		//build the data stream
14536
		$this->gradients[$n]['stream'] = '';
14537
		$count_patch = count($patch_array);
14538
		for ($i=0; $i < $count_patch; ++$i) {
14539
			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14540
			$count_points = count($patch_array[$i]['points']);
14541
			for ($j=0; $j < $count_points; ++$j) {
14542
				//each point as 16 bit
14543
				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14544
				if ($patch_array[$i]['points'][$j] < 0) {
14545
					$patch_array[$i]['points'][$j] = 0;
14546
				}
14547
				if ($patch_array[$i]['points'][$j] > $bpcd) {
14548
					$patch_array[$i]['points'][$j] = $bpcd;
14549
				}
14550
				$this->gradients[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256));
14551
				$this->gradients[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) % 256));
14552
			}
14553
			$count_cols = count($patch_array[$i]['colors']);
14554
			for ($j=0; $j < $count_cols; ++$j) {
14555
				//each color component as 8 bit
14556
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14557
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14558
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14559
			}
14560
		}
14561
		//paint the gradient
14562
		$this->_out('/Sh'.$n.' sh');
14563
		//restore previous Graphic State
14564
		$this->_outRestoreGraphicsState();
14565
		if ($this->inxobj) {
14566
			// we are inside an XObject template
14567
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14568
		}
14569
	}
14570
 
14571
	/**
14572
	 * Set a rectangular clipping area.
14573
	 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14574
	 * @param float $y ordinate of the top left corner of the rectangle.
14575
	 * @param float $w width of the rectangle.
14576
	 * @param float $h height of the rectangle.
14577
	 * @author Andreas W\FCrmser, Nicola Asuni
14578
	 * @since 3.1.000 (2008-06-09)
14579
	 * @protected
14580
	 */
14581
	protected function Clip($x, $y, $w, $h) {
14582
		if ($this->state != 2) {
14583
			 return;
14584
		}
14585
		if ($this->rtl) {
14586
			$x = $this->w - $x - $w;
14587
		}
14588
		//save current Graphic State
14589
		$s = 'q';
14590
		//set clipping area
14591
		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14592
		//set up transformation matrix for gradient
14593
		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14594
		$this->_out($s);
14595
	}
14596
 
14597
	/**
14598
	 * Output gradient.
14599
	 * @param int $type type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14600
	 * @param array $coords array of coordinates.
14601
	 * @param array $stops array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14602
	 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14603
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14604
	 * @author Nicola Asuni
14605
	 * @since 3.1.000 (2008-06-09)
14606
	 * @public
14607
	 */
14608
	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14609
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14610
			return;
14611
		}
14612
		$n = count($this->gradients) + 1;
14613
		$this->gradients[$n] = array();
14614
		$this->gradients[$n]['type'] = $type;
14615
		$this->gradients[$n]['coords'] = $coords;
14616
		$this->gradients[$n]['antialias'] = $antialias;
14617
		$this->gradients[$n]['colors'] = array();
14618
		$this->gradients[$n]['transparency'] = false;
14619
		// color space
14620
		$numcolspace = count($stops[0]['color']);
14621
		$bcolor = array_values($background);
14622
		switch($numcolspace) {
14623
			case 5:   // SPOT
14624
			case 4: { // CMYK
14625
				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14626
				if (!empty($background)) {
14627
					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14628
				}
14629
				break;
14630
			}
14631
			case 3: { // RGB
14632
				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14633
				if (!empty($background)) {
14634
					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14635
				}
14636
				break;
14637
			}
14638
			case 1: { // GRAY SCALE
14639
				$this->gradients[$n]['colspace'] = 'DeviceGray';
14640
				if (!empty($background)) {
14641
					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14642
				}
14643
				break;
14644
			}
14645
		}
14646
		$num_stops = count($stops);
14647
		$last_stop_id = $num_stops - 1;
14648
		foreach ($stops as $key => $stop) {
14649
			$this->gradients[$n]['colors'][$key] = array();
14650
			// offset represents a location along the gradient vector
14651
			if (isset($stop['offset'])) {
14652
				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14653
			} else {
14654
				if ($key == 0) {
14655
					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14656
				} elseif ($key == $last_stop_id) {
14657
					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14658
				} else {
14659
					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14660
					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14661
				}
14662
			}
14663
			if (isset($stop['opacity'])) {
14664
				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14665
				if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) {
14666
					$this->gradients[$n]['transparency'] = true;
14667
				}
14668
			} else {
14669
				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14670
			}
14671
			// exponent for the exponential interpolation function
14672
			if (isset($stop['exponent'])) {
14673
				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14674
			} else {
14675
				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14676
			}
14677
			// set colors
14678
			$color = array_values($stop['color']);
14679
			switch($numcolspace) {
14680
				case 5:   // SPOT
14681
				case 4: { // CMYK
14682
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14683
					break;
14684
				}
14685
				case 3: { // RGB
14686
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14687
					break;
14688
				}
14689
				case 1: { // GRAY SCALE
14690
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14691
					break;
14692
				}
14693
			}
14694
		}
14695
		if ($this->gradients[$n]['transparency']) {
14696
			// paint luminosity gradient
14697
			$this->_out('/TGS'.$n.' gs');
14698
		}
14699
		//paint the gradient
14700
		$this->_out('/Sh'.$n.' sh');
14701
		//restore previous Graphic State
14702
		$this->_outRestoreGraphicsState();
14703
		if ($this->inxobj) {
14704
			// we are inside an XObject template
14705
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14706
		}
14707
	}
14708
 
14709
	/**
14710
	 * Output gradient shaders.
14711
	 * @author Nicola Asuni
14712
	 * @since 3.1.000 (2008-06-09)
14713
	 * @protected
14714
	 */
14715
	function _putshaders() {
14716
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14717
			return;
14718
		}
14719
		$idt = count($this->gradients); //index for transparency gradients
14720
		foreach ($this->gradients as $id => $grad) {
14721
			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14722
				$fc = $this->_newobj();
14723
				$out = '<<';
14724
				$out .= ' /FunctionType 3';
14725
				$out .= ' /Domain [0 1]';
14726
				$functions = '';
14727
				$bounds = '';
14728
				$encode = '';
14729
				$i = 1;
14730
				$num_cols = count($grad['colors']);
14731
				$lastcols = $num_cols - 1;
14732
				for ($i = 1; $i < $num_cols; ++$i) {
14733
					$functions .= ($fc + $i).' 0 R ';
14734
					if ($i < $lastcols) {
14735
						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14736
					}
14737
					$encode .= '0 1 ';
14738
				}
14739
				$out .= ' /Functions ['.trim($functions).']';
14740
				$out .= ' /Bounds ['.trim($bounds).']';
14741
				$out .= ' /Encode ['.trim($encode).']';
14742
				$out .= ' >>';
14743
				$out .= "\n".'endobj';
14744
				$this->_out($out);
14745
				for ($i = 1; $i < $num_cols; ++$i) {
14746
					$this->_newobj();
14747
					$out = '<<';
14748
					$out .= ' /FunctionType 2';
14749
					$out .= ' /Domain [0 1]';
14750
					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14751
					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14752
					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14753
					$out .= ' >>';
14754
					$out .= "\n".'endobj';
14755
					$this->_out($out);
14756
				}
14757
				// set transparency functions
14758
				if ($grad['transparency']) {
14759
					$ft = $this->_newobj();
14760
					$out = '<<';
14761
					$out .= ' /FunctionType 3';
14762
					$out .= ' /Domain [0 1]';
14763
					$functions = '';
14764
					$i = 1;
14765
					$num_cols = count($grad['colors']);
14766
					for ($i = 1; $i < $num_cols; ++$i) {
14767
						$functions .= ($ft + $i).' 0 R ';
14768
					}
14769
					$out .= ' /Functions ['.trim($functions).']';
14770
					$out .= ' /Bounds ['.trim($bounds).']';
14771
					$out .= ' /Encode ['.trim($encode).']';
14772
					$out .= ' >>';
14773
					$out .= "\n".'endobj';
14774
					$this->_out($out);
14775
					for ($i = 1; $i < $num_cols; ++$i) {
14776
						$this->_newobj();
14777
						$out = '<<';
14778
						$out .= ' /FunctionType 2';
14779
						$out .= ' /Domain [0 1]';
14780
						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14781
						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14782
						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14783
						$out .= ' >>';
14784
						$out .= "\n".'endobj';
14785
						$this->_out($out);
14786
					}
14787
				}
14788
			}
14789
			// set shading object
14790
			$this->_newobj();
14791
			$out = '<< /ShadingType '.$grad['type'];
14792
			if (isset($grad['colspace'])) {
14793
				$out .= ' /ColorSpace /'.$grad['colspace'];
14794
			} else {
14795
				$out .= ' /ColorSpace /DeviceRGB';
14796
			}
14797
			if (isset($grad['background']) AND !empty($grad['background'])) {
14798
				$out .= ' /Background ['.$grad['background'].']';
14799
			}
14800
			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14801
				$out .= ' /AntiAlias true';
14802
			}
14803
			if ($grad['type'] == 2) {
14804
				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14805
				$out .= ' /Domain [0 1]';
14806
				$out .= ' /Function '.$fc.' 0 R';
14807
				$out .= ' /Extend [true true]';
14808
				$out .= ' >>';
14809
			} elseif ($grad['type'] == 3) {
14810
				//x0, y0, r0, x1, y1, r1
14811
				//at this this time radius of inner circle is 0
14812
				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14813
				$out .= ' /Domain [0 1]';
14814
				$out .= ' /Function '.$fc.' 0 R';
14815
				$out .= ' /Extend [true true]';
14816
				$out .= ' >>';
14817
			} elseif ($grad['type'] == 6) {
14818
				$out .= ' /BitsPerCoordinate 16';
14819
				$out .= ' /BitsPerComponent 8';
14820
				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14821
				$out .= ' /BitsPerFlag 8';
14822
				$stream = $this->_getrawstream($grad['stream']);
14823
				$out .= ' /Length '.strlen($stream);
14824
				$out .= ' >>';
14825
				$out .= ' stream'."\n".$stream."\n".'endstream';
14826
			}
14827
			$out .= "\n".'endobj';
14828
			$this->_out($out);
14829
			if ($grad['transparency']) {
14830
				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14831
				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14832
			}
14833
			$this->gradients[$id]['id'] = $this->n;
14834
			// set pattern object
14835
			$this->_newobj();
14836
			$out = '<< /Type /Pattern /PatternType 2';
14837
			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14838
			$out .= ' >>';
14839
			$out .= "\n".'endobj';
14840
			$this->_out($out);
14841
			$this->gradients[$id]['pattern'] = $this->n;
14842
			// set shading and pattern for transparency mask
14843
			if ($grad['transparency']) {
14844
				// luminosity pattern
14845
				$idgs = $id + $idt;
14846
				$this->_newobj();
14847
				$this->_out($shading_transparency);
14848
				$this->gradients[$idgs]['id'] = $this->n;
14849
				$this->_newobj();
14850
				$out = '<< /Type /Pattern /PatternType 2';
14851
				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14852
				$out .= ' >>';
14853
				$out .= "\n".'endobj';
14854
				$this->_out($out);
14855
				$this->gradients[$idgs]['pattern'] = $this->n;
14856
				// luminosity XObject
14857
				$oid = $this->_newobj();
14858
				$this->xobjects['LX'.$oid] = array('n' => $oid);
14859
				$filter = '';
14860
				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14861
				if ($this->compress) {
14862
					$filter = ' /Filter /FlateDecode';
14863
					$stream = gzcompress($stream);
14864
				}
14865
				$stream = $this->_getrawstream($stream);
14866
				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14867
				$out .= ' /Length '.strlen($stream);
14868
				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14869
				$out .= ' /BBox [0 0 '.$rect.']';
14870
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14871
				$out .= ' /Resources <<';
14872
				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14873
				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14874
				$out .= ' >>';
14875
				$out .= ' >> ';
14876
				$out .= ' stream'."\n".$stream."\n".'endstream';
14877
				$out .= "\n".'endobj';
14878
				$this->_out($out);
14879
				// SMask
14880
				$this->_newobj();
14881
				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14882
				$this->_out($out);
14883
				// ExtGState
14884
				$this->_newobj();
14885
				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14886
				$this->_out($out);
14887
				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14888
			}
14889
		}
14890
	}
14891
 
14892
	/**
14893
	 * Draw the sector of a circle.
14894
	 * It can be used for instance to render pie charts.
14895
	 * @param float $xc abscissa of the center.
14896
	 * @param float $yc ordinate of the center.
14897
	 * @param float $r radius.
14898
	 * @param float $a start angle (in degrees).
14899
	 * @param float $b end angle (in degrees).
14900
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14901
	 * @param float $cw indicates whether to go clockwise (default: true).
14902
	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14903
	 * @author Maxime Delorme, Nicola Asuni
14904
	 * @since 3.1.000 (2008-06-09)
14905
	 * @public
14906
	 */
14907
	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14908
		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14909
	}
14910
 
14911
	/**
14912
	 * Draw the sector of an ellipse.
14913
	 * It can be used for instance to render pie charts.
14914
	 * @param float $xc abscissa of the center.
14915
	 * @param float $yc ordinate of the center.
14916
	 * @param float $rx the x-axis radius.
14917
	 * @param float $ry the y-axis radius.
14918
	 * @param float $a start angle (in degrees).
14919
	 * @param float $b end angle (in degrees).
14920
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14921
	 * @param float $cw indicates whether to go clockwise.
14922
	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14923
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14924
	 * @author Maxime Delorme, Nicola Asuni
14925
	 * @since 3.1.000 (2008-06-09)
14926
	 * @public
14927
	 */
14928
	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14929
		if ($this->state != 2) {
14930
			 return;
14931
		}
14932
		if ($this->rtl) {
14933
			$xc = ($this->w - $xc);
14934
		}
14935
		$op = TCPDF_STATIC::getPathPaintOperator($style);
14936
		if ($op == 'f') {
14937
			$line_style = array();
14938
		}
14939
		if ($cw) {
14940
			$d = $b;
14941
			$b = (360 - $a + $o);
14942
			$a = (360 - $d + $o);
14943
		} else {
14944
			$b += $o;
14945
			$a += $o;
14946
		}
14947
		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14948
		$this->_out($op);
14949
	}
14950
 
14951
	/**
14952
	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14953
	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14954
	 * Only vector drawing is supported, not text or bitmap.
14955
	 * 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).
14956
	 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14957
	 * @param float|null $x Abscissa of the upper-left corner.
14958
	 * @param float|null $y Ordinate of the upper-left corner.
14959
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14960
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14961
	 * @param mixed $link URL or identifier returned by AddLink().
14962
	 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14963
	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
14964
	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
14965
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
14966
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
14967
	 * @param boolean $fixoutvals if true remove values outside the bounding box.
14968
	 * @author Valentin Schmidt, Nicola Asuni
14969
	 * @since 3.1.000 (2008-06-09)
14970
	 * @public
14971
	 */
14972
	public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14973
		if ($this->state != 2) {
14974
			 return;
14975
		}
14976
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14977
			// convert EPS to raster image using GD or ImageMagick libraries
14978
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14979
		}
14980
		if (TCPDF_STATIC::empty_string($x)) {
14981
			$x = $this->x;
14982
		}
14983
		if (TCPDF_STATIC::empty_string($y)) {
14984
			$y = $this->y;
14985
		}
14986
		// check page for no-write regions and adapt page margins if necessary
14987
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14988
		$k = $this->k;
14989
		if ($file[0] === '@') { // image from string
14990
			$data = substr($file, 1);
14991
		} else { // EPS/AI file
14992
            $data = $this->getCachedFileContents($file);
14993
		}
14994
		if ($data === FALSE) {
14995
			$this->Error('EPS file not found: '.$file);
14996
		}
14997
		$regs = array();
14998
		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14999
		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
15000
		if (count($regs) > 1) {
15001
			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
15002
			if (strpos($version_str, 'Adobe Illustrator') !== false) {
15003
				$versexp = explode(' ', $version_str);
15004
				$version = (float)array_pop($versexp);
15005
				if ($version >= 9) {
15006
					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
15007
				}
15008
			}
15009
		}
15010
		// strip binary bytes in front of PS-header
15011
		$start = strpos($data, '%!PS-Adobe');
15012
		if ($start > 0) {
15013
			$data = substr($data, $start);
15014
		}
15015
		// find BoundingBox params
15016
		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15017
		if (count($regs) > 1) {
15018
			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15019
		} else {
15020
			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
15021
		}
15022
		$start = strpos($data, '%%EndSetup');
15023
		if ($start === false) {
15024
			$start = strpos($data, '%%EndProlog');
15025
		}
15026
		if ($start === false) {
15027
			$start = strpos($data, '%%BoundingBox');
15028
		}
15029
		$data = substr($data, $start);
15030
		$end = strpos($data, '%%PageTrailer');
15031
		if ($end===false) {
15032
			$end = strpos($data, 'showpage');
15033
		}
15034
		if ($end) {
15035
			$data = substr($data, 0, $end);
15036
		}
15037
		// calculate image width and height on document
15038
		if (($w <= 0) AND ($h <= 0)) {
15039
			$w = ($x2 - $x1) / $k;
15040
			$h = ($y2 - $y1) / $k;
15041
		} elseif ($w <= 0) {
15042
			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15043
		} elseif ($h <= 0) {
15044
			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15045
		}
15046
		// fit the image on available space
15047
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15048
		if ($this->rasterize_vector_images) {
15049
			// convert EPS to raster image using GD or ImageMagick libraries
15050
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15051
		}
15052
		// set scaling factors
15053
		$scale_x = $w / (($x2 - $x1) / $k);
15054
		$scale_y = $h / (($y2 - $y1) / $k);
15055
		// set alignment
15056
		$this->img_rb_y = $y + $h;
15057
		// set alignment
15058
		if ($this->rtl) {
15059
			if ($palign == 'L') {
15060
				$ximg = $this->lMargin;
15061
			} elseif ($palign == 'C') {
15062
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15063
			} elseif ($palign == 'R') {
15064
				$ximg = $this->w - $this->rMargin - $w;
15065
			} else {
15066
				$ximg = $x - $w;
15067
			}
15068
			$this->img_rb_x = $ximg;
15069
		} else {
15070
			if ($palign == 'L') {
15071
				$ximg = $this->lMargin;
15072
			} elseif ($palign == 'C') {
15073
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15074
			} elseif ($palign == 'R') {
15075
				$ximg = $this->w - $this->rMargin - $w;
15076
			} else {
15077
				$ximg = $x;
15078
			}
15079
			$this->img_rb_x = $ximg + $w;
15080
		}
15081
		if ($useBoundingBox) {
15082
			$dx = $ximg * $k - $x1;
15083
			$dy = $y * $k - $y1;
15084
		} else {
15085
			$dx = $ximg * $k;
15086
			$dy = $y * $k;
15087
		}
15088
		// save the current graphic state
15089
		$this->_out('q'.$this->epsmarker);
15090
		// translate
15091
		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15092
		// scale
15093
		$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15094
		// handle pc/unix/mac line endings
15095
		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15096
		$u=0;
15097
		$cnt = count($lines);
15098
		for ($i=0; $i < $cnt; ++$i) {
15099
			$line = $lines[$i];
15100
			if (($line == '') OR ($line[0] == '%')) {
15101
				continue;
15102
			}
15103
			$len = strlen($line);
15104
			// check for spot color names
15105
			$color_name = '';
15106
			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15107
				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15108
					// extract spot color name
15109
					$color_name = $matches[0];
15110
					// remove color name from string
15111
					$line = str_replace(' '.$color_name, '', $line);
15112
					// remove pharentesis from color name
15113
					$color_name = substr($color_name, 1, -1);
15114
				}
15115
			}
15116
			$chunks = explode(' ', $line);
15117
			$cmd = trim(array_pop($chunks));
15118
			// RGB
15119
			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15120
				$b = array_pop($chunks);
15121
				$g = array_pop($chunks);
15122
				$r = array_pop($chunks);
15123
				$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!
15124
				continue;
15125
			}
15126
			$skip = false;
15127
			if ($fixoutvals) {
15128
				// check for values outside the bounding box
15129
				switch ($cmd) {
15130
					case 'm':
15131
					case 'l':
15132
					case 'L': {
15133
						// skip values outside bounding box
15134
						foreach ($chunks as $key => $val) {
15135
							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15136
								$skip = true;
15137
							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15138
								$skip = true;
15139
							}
15140
						}
15141
					}
15142
				}
15143
			}
15144
			switch ($cmd) {
15145
				case 'm':
15146
				case 'l':
15147
				case 'v':
15148
				case 'y':
15149
				case 'c':
15150
				case 'k':
15151
				case 'K':
15152
				case 'g':
15153
				case 'G':
15154
				case 's':
15155
				case 'S':
15156
				case 'J':
15157
				case 'j':
15158
				case 'w':
15159
				case 'M':
15160
				case 'd':
15161
				case 'n': {
15162
					if ($skip) {
15163
						break;
15164
					}
15165
					$this->_out($line);
15166
					break;
15167
				}
15168
				case 'x': {// custom fill color
15169
					if (empty($color_name)) {
15170
						// CMYK color
15171
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15172
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15173
					} else {
15174
						// Spot Color (CMYK + tint)
15175
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15176
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15177
						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15178
						$this->_out($color_cmd);
15179
					}
15180
					break;
15181
				}
15182
				case 'X': { // custom stroke color
15183
					if (empty($color_name)) {
15184
						// CMYK color
15185
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15186
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15187
					} else {
15188
						// Spot Color (CMYK + tint)
15189
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15190
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15191
						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15192
						$this->_out($color_cmd);
15193
					}
15194
					break;
15195
				}
15196
				case 'Y':
15197
				case 'N':
15198
				case 'V':
15199
				case 'L':
15200
				case 'C': {
15201
					if ($skip) {
15202
						break;
15203
					}
15204
					$line[($len - 1)] = strtolower($cmd);
15205
					$this->_out($line);
15206
					break;
15207
				}
15208
				case 'b':
15209
				case 'B': {
15210
					$this->_out($cmd . '*');
15211
					break;
15212
				}
15213
				case 'f':
15214
				case 'F': {
15215
					if ($u > 0) {
15216
						$isU = false;
15217
						$max = min(($i + 5), $cnt);
15218
						for ($j = ($i + 1); $j < $max; ++$j) {
15219
							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15220
						}
15221
						if ($isU) {
15222
							$this->_out('f*');
15223
						}
15224
					} else {
15225
						$this->_out('f*');
15226
					}
15227
					break;
15228
				}
15229
				case '*u': {
15230
					++$u;
15231
					break;
15232
				}
15233
				case '*U': {
15234
					--$u;
15235
					break;
15236
				}
15237
			}
15238
		}
15239
		// restore previous graphic state
15240
		$this->_out($this->epsmarker.'Q');
15241
		if (!empty($border)) {
15242
			$bx = $this->x;
15243
			$by = $this->y;
15244
			$this->x = $ximg;
15245
			if ($this->rtl) {
15246
				$this->x += $w;
15247
			}
15248
			$this->y = $y;
15249
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15250
			$this->x = $bx;
15251
			$this->y = $by;
15252
		}
15253
		if ($link) {
15254
			$this->Link($ximg, $y, $w, $h, $link, 0);
15255
		}
15256
		// set pointer to align the next text/objects
15257
		switch($align) {
15258
			case 'T':{
15259
				$this->y = $y;
15260
				$this->x = $this->img_rb_x;
15261
				break;
15262
			}
15263
			case 'M':{
15264
				$this->y = $y + round($h/2);
15265
				$this->x = $this->img_rb_x;
15266
				break;
15267
			}
15268
			case 'B':{
15269
				$this->y = $this->img_rb_y;
15270
				$this->x = $this->img_rb_x;
15271
				break;
15272
			}
15273
			case 'N':{
15274
				$this->setY($this->img_rb_y);
15275
				break;
15276
			}
15277
			default:{
15278
				break;
15279
			}
15280
		}
15281
		$this->endlinex = $this->img_rb_x;
15282
	}
15283
 
15284
	/**
15285
	 * Set document barcode.
15286
	 * @param string $bc barcode
15287
	 * @public
15288
	 */
15289
	public function setBarcode($bc='') {
15290
		$this->barcode = $bc;
15291
	}
15292
 
15293
	/**
15294
	 * Get current barcode.
15295
	 * @return string
15296
	 * @public
15297
	 * @since 4.0.012 (2008-07-24)
15298
	 */
15299
	public function getBarcode() {
15300
		return $this->barcode;
15301
	}
15302
 
15303
	/**
15304
	 * Print a Linear Barcode.
15305
	 * @param string $code code to print
15306
	 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15307
	 * @param float|null $x x position in user units (null = current x position)
15308
	 * @param float|null $y y position in user units (null = current y position)
15309
	 * @param float|null $w width in user units (null = remaining page width)
15310
	 * @param float|null $h height in user units (null = remaining page height)
15311
	 * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm)
15312
	 * @param array $style array of options:<ul>
15313
	 * <li>boolean $style['border'] if true prints a border</li>
15314
	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15315
	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15316
	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15317
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15318
	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15319
	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15320
	 * <li>string $style['label'] override default label</li>
15321
	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15322
	 * <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>
15323
	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15324
	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15325
	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15326
	 * <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>
15327
	 * <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>
15328
	 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15329
	 * @author Nicola Asuni
15330
	 * @since 3.1.000 (2008-06-09)
15331
	 * @public
15332
	 */
15333
	public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') {
15334
		if (TCPDF_STATIC::empty_string(trim($code))) {
15335
			return;
15336
		}
15337
		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15338
		// save current graphic settings
15339
		$gvars = $this->getGraphicVars();
15340
		// create new barcode object
15341
		$barcodeobj = new TCPDFBarcode($code, $type);
15342
		$arrcode = $barcodeobj->getBarcodeArray();
15343
		if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15344
			$this->Error('Error in 1D barcode string');
15345
		}
15346
		if ($arrcode['maxh'] <= 0) {
15347
			$arrcode['maxh'] = 1;
15348
		}
15349
		// set default values
15350
		if (!isset($style['position'])) {
15351
			$style['position'] = '';
15352
		} elseif ($style['position'] == 'S') {
15353
			// keep this for backward compatibility
15354
			$style['position'] = '';
15355
			$style['stretch'] = true;
15356
		}
15357
		if (!isset($style['fitwidth'])) {
15358
			if (!isset($style['stretch'])) {
15359
				$style['fitwidth'] = true;
15360
			} else {
15361
				$style['fitwidth'] = false;
15362
			}
15363
		}
15364
		if ($style['fitwidth']) {
15365
			// disable stretch
15366
			$style['stretch'] = false;
15367
		}
15368
		if (!isset($style['stretch'])) {
15369
			if (($w === '') OR ($w <= 0)) {
15370
				$style['stretch'] = false;
15371
			} else {
15372
				$style['stretch'] = true;
15373
			}
15374
		}
15375
		if (!isset($style['fgcolor'])) {
15376
			$style['fgcolor'] = array(0,0,0); // default black
15377
		}
15378
		if (!isset($style['bgcolor'])) {
15379
			$style['bgcolor'] = false; // default transparent
15380
		}
15381
		if (!isset($style['border'])) {
15382
			$style['border'] = false;
15383
		}
15384
		$fontsize = 0;
15385
		if (!isset($style['text'])) {
15386
			$style['text'] = false;
15387
		}
15388
		if ($style['text'] AND isset($style['font'])) {
15389
			if (isset($style['fontsize'])) {
15390
				$fontsize = $style['fontsize'];
15391
			}
15392
			$this->setFont($style['font'], '', $fontsize);
15393
		}
15394
		if (!isset($style['stretchtext'])) {
15395
			$style['stretchtext'] = 4;
15396
		}
15397
		if (TCPDF_STATIC::empty_string($x)) {
15398
			$x = $this->x;
15399
		}
15400
		if (TCPDF_STATIC::empty_string($y)) {
15401
			$y = $this->y;
15402
		}
15403
		// check page for no-write regions and adapt page margins if necessary
15404
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15405
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
15406
			if ($this->rtl) {
15407
				$w = $x - $this->lMargin;
15408
			} else {
15409
				$w = $this->w - $this->rMargin - $x;
15410
			}
15411
		}
15412
		// padding
15413
		if (!isset($style['padding'])) {
15414
			$padding = 0;
15415
		} elseif ($style['padding'] === 'auto') {
15416
			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15417
		} else {
15418
			$padding = floatval($style['padding']);
15419
		}
15420
		// horizontal padding
15421
		if (!isset($style['hpadding'])) {
15422
			$hpadding = $padding;
15423
		} elseif ($style['hpadding'] === 'auto') {
15424
			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15425
		} else {
15426
			$hpadding = floatval($style['hpadding']);
15427
		}
15428
		// vertical padding
15429
		if (!isset($style['vpadding'])) {
15430
			$vpadding = $padding;
15431
		} elseif ($style['vpadding'] === 'auto') {
15432
			$vpadding = ($hpadding / 2);
15433
		} else {
15434
			$vpadding = floatval($style['vpadding']);
15435
		}
15436
		// calculate xres (single bar width)
15437
		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15438
		if ($style['stretch']) {
15439
			$xres = $max_xres;
15440
		} else {
15441
			if (TCPDF_STATIC::empty_string($xres)) {
15442
				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15443
			}
15444
			if ($xres > $max_xres) {
15445
				// correct xres to fit on $w
15446
				$xres = $max_xres;
15447
			}
15448
			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15449
				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15450
				$hpadding = 10 * $xres;
15451
				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15452
					$vpadding = ($hpadding / 2);
15453
				}
15454
			}
15455
		}
15456
		if ($style['fitwidth']) {
15457
			$wold = $w;
15458
			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15459
			if (isset($style['cellfitalign'])) {
15460
				switch ($style['cellfitalign']) {
15461
					case 'L': {
15462
						if ($this->rtl) {
15463
							$x -= ($wold - $w);
15464
						}
15465
						break;
15466
					}
15467
					case 'R': {
15468
						if (!$this->rtl) {
15469
							$x += ($wold - $w);
15470
						}
15471
						break;
15472
					}
15473
					case 'C': {
15474
						if ($this->rtl) {
15475
							$x -= (($wold - $w) / 2);
15476
						} else {
15477
							$x += (($wold - $w) / 2);
15478
						}
15479
						break;
15480
					}
15481
					default : {
15482
						break;
15483
					}
15484
				}
15485
			}
15486
		}
15487
		$text_height = $this->getCellHeight($fontsize / $this->k);
15488
		// height
15489
		if (TCPDF_STATIC::empty_string($h) OR ($h <= 0)) {
15490
			// set default height
15491
			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15492
		}
15493
		$barh = $h - $text_height - (2 * $vpadding);
15494
		if ($barh <=0) {
15495
			// try to reduce font or padding to fit barcode on available height
15496
			if ($text_height > $h) {
15497
				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15498
				$text_height = $this->getCellHeight($fontsize / $this->k);
15499
				$this->setFont($style['font'], '', $fontsize);
15500
			}
15501
			if ($vpadding > 0) {
15502
				$vpadding = (($h - $text_height) / 4);
15503
			}
15504
			$barh = $h - $text_height - (2 * $vpadding);
15505
		}
15506
		// fit the barcode on available space
15507
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15508
		// set alignment
15509
		$this->img_rb_y = $y + $h;
15510
		// set alignment
15511
		if ($this->rtl) {
15512
			if ($style['position'] == 'L') {
15513
				$xpos = $this->lMargin;
15514
			} elseif ($style['position'] == 'C') {
15515
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15516
			} elseif ($style['position'] == 'R') {
15517
				$xpos = $this->w - $this->rMargin - $w;
15518
			} else {
15519
				$xpos = $x - $w;
15520
			}
15521
			$this->img_rb_x = $xpos;
15522
		} else {
15523
			if ($style['position'] == 'L') {
15524
				$xpos = $this->lMargin;
15525
			} elseif ($style['position'] == 'C') {
15526
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15527
			} elseif ($style['position'] == 'R') {
15528
				$xpos = $this->w - $this->rMargin - $w;
15529
			} else {
15530
				$xpos = $x;
15531
			}
15532
			$this->img_rb_x = $xpos + $w;
15533
		}
15534
		$xpos_rect = $xpos;
15535
		if (!isset($style['align'])) {
15536
			$style['align'] = 'C';
15537
		}
15538
		switch ($style['align']) {
15539
			case 'L': {
15540
				$xpos = $xpos_rect + $hpadding;
15541
				break;
15542
			}
15543
			case 'R': {
15544
				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15545
				break;
15546
			}
15547
			case 'C':
15548
			default : {
15549
				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15550
				break;
15551
			}
15552
		}
15553
		$xpos_text = $xpos;
15554
		// barcode is always printed in LTR direction
15555
		$tempRTL = $this->rtl;
15556
		$this->rtl = false;
15557
		// print background color
15558
		if ($style['bgcolor']) {
15559
			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15560
		} elseif ($style['border']) {
15561
			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15562
		}
15563
		// set foreground color
15564
		$this->setDrawColorArray($style['fgcolor']);
15565
		$this->setTextColorArray($style['fgcolor']);
15566
		// print bars
15567
		foreach ($arrcode['bcode'] as $k => $v) {
15568
			$bw = ($v['w'] * $xres);
15569
			if ($v['t']) {
15570
				// draw a vertical bar
15571
				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15572
				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15573
			}
15574
			$xpos += $bw;
15575
		}
15576
		// print text
15577
		if ($style['text']) {
15578
			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15579
				$label = $style['label'];
15580
			} else {
15581
				$label = $code;
15582
			}
15583
			$txtwidth = ($arrcode['maxw'] * $xres);
15584
			if ($this->GetStringWidth($label) > $txtwidth) {
15585
				$style['stretchtext'] = 2;
15586
			}
15587
			// print text
15588
			$this->x = $xpos_text;
15589
			$this->y = $y + $vpadding + $barh;
15590
			$cellpadding = $this->cell_padding;
15591
			$this->setCellPadding(0);
15592
			$this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15593
			$this->cell_padding = $cellpadding;
15594
		}
15595
		// restore original direction
15596
		$this->rtl = $tempRTL;
15597
		// restore previous settings
15598
		$this->setGraphicVars($gvars);
15599
		// set pointer to align the next text/objects
15600
		switch($align) {
15601
			case 'T':{
15602
				$this->y = $y;
15603
				$this->x = $this->img_rb_x;
15604
				break;
15605
			}
15606
			case 'M':{
15607
				$this->y = $y + round($h / 2);
15608
				$this->x = $this->img_rb_x;
15609
				break;
15610
			}
15611
			case 'B':{
15612
				$this->y = $this->img_rb_y;
15613
				$this->x = $this->img_rb_x;
15614
				break;
15615
			}
15616
			case 'N':{
15617
				$this->setY($this->img_rb_y);
15618
				break;
15619
			}
15620
			default:{
15621
				break;
15622
			}
15623
		}
15624
		$this->endlinex = $this->img_rb_x;
15625
	}
15626
 
15627
	/**
15628
	 * Print 2D Barcode.
15629
	 * @param string $code code to print
15630
	 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15631
	 * @param float|null $x x position in user units
15632
	 * @param float|null $y y position in user units
15633
	 * @param float|null $w width in user units
15634
	 * @param float|null $h height in user units
15635
	 * @param array $style array of options:<ul>
15636
	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15637
	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15638
	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15639
	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15640
	 * <li>int $style['module_width'] width of a single module in points</li>
15641
	 * <li>int $style['module_height'] height of a single module in points</li>
15642
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15643
	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15644
	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15645
	 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15646
	 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15647
	 * @author Nicola Asuni
15648
	 * @since 4.5.037 (2009-04-07)
15649
	 * @public
15650
	 */
15651
	public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) {
15652
		if (TCPDF_STATIC::empty_string(trim($code))) {
15653
			return;
15654
		}
15655
		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15656
		// save current graphic settings
15657
		$gvars = $this->getGraphicVars();
15658
		// create new barcode object
15659
		$barcodeobj = new TCPDF2DBarcode($code, $type);
15660
		$arrcode = $barcodeobj->getBarcodeArray();
15661
		if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15662
			$this->Error('Error in 2D barcode string');
15663
		}
15664
		// set default values
15665
		if (!isset($style['position'])) {
15666
			$style['position'] = '';
15667
		}
15668
		if (!isset($style['fgcolor'])) {
15669
			$style['fgcolor'] = array(0,0,0); // default black
15670
		}
15671
		if (!isset($style['bgcolor'])) {
15672
			$style['bgcolor'] = false; // default transparent
15673
		}
15674
		if (!isset($style['border'])) {
15675
			$style['border'] = false;
15676
		}
15677
		// padding
15678
		if (!isset($style['padding'])) {
15679
			$style['padding'] = 0;
15680
		} elseif ($style['padding'] === 'auto') {
15681
			$style['padding'] = 4;
15682
		}
15683
		if (!isset($style['hpadding'])) {
15684
			$style['hpadding'] = $style['padding'];
15685
		} elseif ($style['hpadding'] === 'auto') {
15686
			$style['hpadding'] = 4;
15687
		}
15688
		if (!isset($style['vpadding'])) {
15689
			$style['vpadding'] = $style['padding'];
15690
		} elseif ($style['vpadding'] === 'auto') {
15691
			$style['vpadding'] = 4;
15692
		}
15693
		$hpad = (2 * $style['hpadding']);
15694
		$vpad = (2 * $style['vpadding']);
15695
		// cell (module) dimension
15696
		if (!isset($style['module_width'])) {
15697
			$style['module_width'] = 1; // width of a single module in points
15698
		}
15699
		if (!isset($style['module_height'])) {
15700
			$style['module_height'] = 1; // height of a single module in points
15701
		}
15702
		if (TCPDF_STATIC::empty_string($x)) {
15703
			$x = $this->x;
15704
		}
15705
		if (TCPDF_STATIC::empty_string($y)) {
15706
			$y = $this->y;
15707
		}
15708
		// check page for no-write regions and adapt page margins if necessary
15709
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15710
		// number of barcode columns and rows
15711
		$rows = $arrcode['num_rows'];
15712
		$cols = $arrcode['num_cols'];
15713
		if (($rows <= 0) || ($cols <= 0)){
15714
			$this->Error('Error in 2D barcode string');
15715
		}
15716
		// module width and height
15717
		$mw = $style['module_width'];
15718
		$mh = $style['module_height'];
15719
		if (($mw <= 0) OR ($mh <= 0)) {
15720
			$this->Error('Error in 2D barcode string');
15721
		}
15722
		// get max dimensions
15723
		if ($this->rtl) {
15724
			$maxw = $x - $this->lMargin;
15725
		} else {
15726
			$maxw = $this->w - $this->rMargin - $x;
15727
		}
15728
		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15729
		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15730
		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15731
		if (!$distort) {
15732
			if (($maxw * $ratioHW) > $maxh) {
15733
				$maxw = $maxh * $ratioWH;
15734
			}
15735
			if (($maxh * $ratioWH) > $maxw) {
15736
				$maxh = $maxw * $ratioHW;
15737
			}
15738
		}
15739
		// set maximum dimensions
15740
		if ($w > $maxw) {
15741
			$w = $maxw;
15742
		}
15743
		if ($h > $maxh) {
15744
			$h = $maxh;
15745
		}
15746
		// set dimensions
15747
		if ((TCPDF_STATIC::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC::empty_string($h) OR ($h <= 0))) {
15748
			$w = ($cols + $hpad) * ($mw / $this->k);
15749
			$h = ($rows + $vpad) * ($mh / $this->k);
15750
		} elseif (($w === '') OR ($w <= 0)) {
15751
			$w = $h * $ratioWH;
15752
		} elseif (($h === '') OR ($h <= 0)) {
15753
			$h = $w * $ratioHW;
15754
		}
15755
		// barcode size (excluding padding)
15756
		$bw = ($w * $cols) / ($cols + $hpad);
15757
		$bh = ($h * $rows) / ($rows + $vpad);
15758
		// dimension of single barcode cell unit
15759
		$cw = $bw / $cols;
15760
		$ch = $bh / $rows;
15761
		if (!$distort) {
15762
			if (($cw / $ch) > ($mw / $mh)) {
15763
				// correct horizontal distortion
15764
				$cw = $ch * $mw / $mh;
15765
				$bw = $cw * $cols;
15766
				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15767
			} else {
15768
				// correct vertical distortion
15769
				$ch = $cw * $mh / $mw;
15770
				$bh = $ch * $rows;
15771
				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15772
			}
15773
		}
15774
		// fit the barcode on available space
15775
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15776
		// set alignment
15777
		$this->img_rb_y = $y + $h;
15778
		// set alignment
15779
		if ($this->rtl) {
15780
			if ($style['position'] == 'L') {
15781
				$xpos = $this->lMargin;
15782
			} elseif ($style['position'] == 'C') {
15783
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15784
			} elseif ($style['position'] == 'R') {
15785
				$xpos = $this->w - $this->rMargin - $w;
15786
			} else {
15787
				$xpos = $x - $w;
15788
			}
15789
			$this->img_rb_x = $xpos;
15790
		} else {
15791
			if ($style['position'] == 'L') {
15792
				$xpos = $this->lMargin;
15793
			} elseif ($style['position'] == 'C') {
15794
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15795
			} elseif ($style['position'] == 'R') {
15796
				$xpos = $this->w - $this->rMargin - $w;
15797
			} else {
15798
				$xpos = $x;
15799
			}
15800
			$this->img_rb_x = $xpos + $w;
15801
		}
15802
		$xstart = $xpos + ($style['hpadding'] * $cw);
15803
		$ystart = $y + ($style['vpadding'] * $ch);
15804
		// barcode is always printed in LTR direction
15805
		$tempRTL = $this->rtl;
15806
		$this->rtl = false;
15807
		// print background color
15808
		if ($style['bgcolor']) {
15809
			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15810
		} elseif ($style['border']) {
15811
			$this->Rect($xpos, $y, $w, $h, 'D');
15812
		}
15813
		// set foreground color
15814
		$this->setDrawColorArray($style['fgcolor']);
15815
		// print barcode cells
15816
		// for each row
15817
		for ($r = 0; $r < $rows; ++$r) {
15818
			$xr = $xstart;
15819
			// for each column
15820
			for ($c = 0; $c < $cols; ++$c) {
15821
				if ($arrcode['bcode'][$r][$c] == 1) {
15822
					// draw a single barcode cell
15823
					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15824
				}
15825
				$xr += $cw;
15826
			}
15827
			$ystart += $ch;
15828
		}
15829
		// restore original direction
15830
		$this->rtl = $tempRTL;
15831
		// restore previous settings
15832
		$this->setGraphicVars($gvars);
15833
		// set pointer to align the next text/objects
15834
		switch($align) {
15835
			case 'T':{
15836
				$this->y = $y;
15837
				$this->x = $this->img_rb_x;
15838
				break;
15839
			}
15840
			case 'M':{
15841
				$this->y = $y + round($h/2);
15842
				$this->x = $this->img_rb_x;
15843
				break;
15844
			}
15845
			case 'B':{
15846
				$this->y = $this->img_rb_y;
15847
				$this->x = $this->img_rb_x;
15848
				break;
15849
			}
15850
			case 'N':{
15851
				$this->setY($this->img_rb_y);
15852
				break;
15853
			}
15854
			default:{
15855
				break;
15856
			}
15857
		}
15858
		$this->endlinex = $this->img_rb_x;
15859
	}
15860
 
15861
	/**
15862
	 * Returns an array containing current margins:
15863
	 * <ul>
15864
			<li>$ret['left'] = left margin</li>
15865
			<li>$ret['right'] = right margin</li>
15866
			<li>$ret['top'] = top margin</li>
15867
			<li>$ret['bottom'] = bottom margin</li>
15868
			<li>$ret['header'] = header margin</li>
15869
			<li>$ret['footer'] = footer margin</li>
15870
			<li>$ret['cell'] = cell padding array</li>
15871
			<li>$ret['padding_left'] = cell left padding</li>
15872
			<li>$ret['padding_top'] = cell top padding</li>
15873
			<li>$ret['padding_right'] = cell right padding</li>
15874
			<li>$ret['padding_bottom'] = cell bottom padding</li>
15875
	 * </ul>
15876
	 * @return array containing all margins measures
15877
	 * @public
15878
	 * @since 3.2.000 (2008-06-23)
15879
	 */
15880
	public function getMargins() {
15881
		$ret = array(
15882
			'left' => $this->lMargin,
15883
			'right' => $this->rMargin,
15884
			'top' => $this->tMargin,
15885
			'bottom' => $this->bMargin,
15886
			'header' => $this->header_margin,
15887
			'footer' => $this->footer_margin,
15888
			'cell' => $this->cell_padding,
15889
			'padding_left' => $this->cell_padding['L'],
15890
			'padding_top' => $this->cell_padding['T'],
15891
			'padding_right' => $this->cell_padding['R'],
15892
			'padding_bottom' => $this->cell_padding['B']
15893
		);
15894
		return $ret;
15895
	}
15896
 
15897
	/**
15898
	 * Returns an array containing original margins:
15899
	 * <ul>
15900
			<li>$ret['left'] = left margin</li>
15901
			<li>$ret['right'] = right margin</li>
15902
	 * </ul>
15903
	 * @return array containing all margins measures
15904
	 * @public
15905
	 * @since 4.0.012 (2008-07-24)
15906
	 */
15907
	public function getOriginalMargins() {
15908
		$ret = array(
15909
			'left' => $this->original_lMargin,
15910
			'right' => $this->original_rMargin
15911
		);
15912
		return $ret;
15913
	}
15914
 
15915
	/**
15916
	 * Returns the current font size.
15917
	 * @return float current font size
15918
	 * @public
15919
	 * @since 3.2.000 (2008-06-23)
15920
	 */
15921
	public function getFontSize() {
15922
		return $this->FontSize;
15923
	}
15924
 
15925
	/**
15926
	 * Returns the current font size in points unit.
15927
	 * @return int current font size in points unit
15928
	 * @public
15929
	 * @since 3.2.000 (2008-06-23)
15930
	 */
15931
	public function getFontSizePt() {
15932
		return $this->FontSizePt;
15933
	}
15934
 
15935
	/**
15936
	 * Returns the current font family name.
15937
	 * @return string current font family name
15938
	 * @public
15939
	 * @since 4.3.008 (2008-12-05)
15940
	 */
15941
	public function getFontFamily() {
15942
		return $this->FontFamily;
15943
	}
15944
 
15945
	/**
15946
	 * Returns the current font style.
15947
	 * @return string current font style
15948
	 * @public
15949
	 * @since 4.3.008 (2008-12-05)
15950
	 */
15951
	public function getFontStyle() {
15952
		return $this->FontStyle;
15953
	}
15954
 
15955
	/**
15956
	 * Cleanup HTML code (requires HTML Tidy library).
15957
	 * @param string $html htmlcode to fix
15958
	 * @param string $default_css CSS commands to add
15959
	 * @param array|null $tagvs parameters for setHtmlVSpace method
15960
	 * @param array|null $tidy_options options for tidy_parse_string function
15961
	 * @return string XHTML code cleaned up
15962
	 * @author Nicola Asuni
15963
	 * @public
15964
	 * @since 5.9.017 (2010-11-16)
15965
	 * @see setHtmlVSpace()
15966
	 */
15967
	public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) {
15968
		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15969
	}
15970
 
15971
	/**
15972
	 * Returns the border width from CSS property
15973
	 * @param string $width border width
15974
	 * @return int with in user units
15975
	 * @protected
15976
	 * @since 5.7.000 (2010-08-02)
15977
	 */
15978
	protected function getCSSBorderWidth($width) {
15979
		if ($width == 'thin') {
15980
			$width = (2 / $this->k);
15981
		} elseif ($width == 'medium') {
15982
			$width = (4 / $this->k);
15983
		} elseif ($width == 'thick') {
15984
			$width = (6 / $this->k);
15985
		} else {
15986
			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15987
		}
15988
		return $width;
15989
	}
15990
 
15991
	/**
15992
	 * Returns the border dash style from CSS property
15993
	 * @param string $style border style to convert
15994
	 * @return int sash style (return -1 in case of none or hidden border)
15995
	 * @protected
15996
	 * @since 5.7.000 (2010-08-02)
15997
	 */
15998
	protected function getCSSBorderDashStyle($style) {
15999
		switch (strtolower($style)) {
16000
			case 'none':
16001
			case 'hidden': {
16002
				$dash = -1;
16003
				break;
16004
			}
16005
			case 'dotted': {
16006
				$dash = 1;
16007
				break;
16008
			}
16009
			case 'dashed': {
16010
				$dash = 3;
16011
				break;
16012
			}
16013
			case 'double':
16014
			case 'groove':
16015
			case 'ridge':
16016
			case 'inset':
16017
			case 'outset':
16018
			case 'solid':
16019
			default: {
16020
				$dash = 0;
16021
				break;
16022
			}
16023
		}
16024
		return $dash;
16025
	}
16026
 
16027
	/**
16028
	 * Returns the border style array from CSS border properties
16029
	 * @param string $cssborder border properties
16030
	 * @return array containing border properties
16031
	 * @protected
16032
	 * @since 5.7.000 (2010-08-02)
16033
	 */
16034
	protected function getCSSBorderStyle($cssborder) {
16035
		$bprop = preg_split('/[\s]+/', trim($cssborder));
16036
		$count = count($bprop);
16037
		if ($count > 0 && $bprop[$count - 1] === '!important') {
16038
			unset($bprop[$count - 1]);
16039
			--$count;
16040
		}
16041
 
16042
		$border = array(); // value to be returned
16043
		switch ($count) {
16044
			case 2: {
16045
				$width = 'medium';
16046
				$style = $bprop[0];
16047
				$color = $bprop[1];
16048
				break;
16049
			}
16050
			case 1: {
16051
				$width = 'medium';
16052
				$style = $bprop[0];
16053
				$color = 'black';
16054
				break;
16055
			}
16056
			case 0: {
16057
				$width = 'medium';
16058
				$style = 'solid';
16059
				$color = 'black';
16060
				break;
16061
			}
16062
			default: {
16063
				$width = $bprop[0];
16064
				$style = $bprop[1];
16065
				$color = $bprop[2];
16066
				break;
16067
			}
16068
		}
16069
		if ($style == 'none') {
16070
			return array();
16071
		}
16072
		$border['cap'] = 'square';
16073
		$border['join'] = 'miter';
16074
		$border['dash'] = $this->getCSSBorderDashStyle($style);
16075
		if ($border['dash'] < 0) {
16076
			return array();
16077
		}
16078
		$border['width'] = $this->getCSSBorderWidth($width);
16079
		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16080
		return $border;
16081
	}
16082
 
16083
	/**
16084
	 * Get the internal Cell padding from CSS attribute.
16085
	 * @param string $csspadding padding properties
16086
	 * @param float $width width of the containing element
16087
	 * @return array of cell paddings
16088
	 * @public
16089
	 * @since 5.9.000 (2010-10-04)
16090
	 */
16091
	public function getCSSPadding($csspadding, $width=0) {
16092
		$padding = preg_split('/[\s]+/', trim($csspadding));
16093
		$cell_padding = array(); // value to be returned
16094
		switch (count($padding)) {
16095
			case 4: {
16096
				$cell_padding['T'] = $padding[0];
16097
				$cell_padding['R'] = $padding[1];
16098
				$cell_padding['B'] = $padding[2];
16099
				$cell_padding['L'] = $padding[3];
16100
				break;
16101
			}
16102
			case 3: {
16103
				$cell_padding['T'] = $padding[0];
16104
				$cell_padding['R'] = $padding[1];
16105
				$cell_padding['B'] = $padding[2];
16106
				$cell_padding['L'] = $padding[1];
16107
				break;
16108
			}
16109
			case 2: {
16110
				$cell_padding['T'] = $padding[0];
16111
				$cell_padding['R'] = $padding[1];
16112
				$cell_padding['B'] = $padding[0];
16113
				$cell_padding['L'] = $padding[1];
16114
				break;
16115
			}
16116
			case 1: {
16117
				$cell_padding['T'] = $padding[0];
16118
				$cell_padding['R'] = $padding[0];
16119
				$cell_padding['B'] = $padding[0];
16120
				$cell_padding['L'] = $padding[0];
16121
				break;
16122
			}
16123
			default: {
16124
				return $this->cell_padding;
16125
			}
16126
		}
16127
		if ($width == 0) {
16128
			$width = $this->w - $this->lMargin - $this->rMargin;
16129
		}
16130
		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16131
		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16132
		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16133
		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16134
		return $cell_padding;
16135
	}
16136
 
16137
	/**
16138
	 * Get the internal Cell margin from CSS attribute.
16139
	 * @param string $cssmargin margin properties
16140
	 * @param float $width width of the containing element
16141
	 * @return array of cell margins
16142
	 * @public
16143
	 * @since 5.9.000 (2010-10-04)
16144
	 */
16145
	public function getCSSMargin($cssmargin, $width=0) {
16146
		$margin = preg_split('/[\s]+/', trim($cssmargin));
16147
		$cell_margin = array(); // value to be returned
16148
		switch (count($margin)) {
16149
			case 4: {
16150
				$cell_margin['T'] = $margin[0];
16151
				$cell_margin['R'] = $margin[1];
16152
				$cell_margin['B'] = $margin[2];
16153
				$cell_margin['L'] = $margin[3];
16154
				break;
16155
			}
16156
			case 3: {
16157
				$cell_margin['T'] = $margin[0];
16158
				$cell_margin['R'] = $margin[1];
16159
				$cell_margin['B'] = $margin[2];
16160
				$cell_margin['L'] = $margin[1];
16161
				break;
16162
			}
16163
			case 2: {
16164
				$cell_margin['T'] = $margin[0];
16165
				$cell_margin['R'] = $margin[1];
16166
				$cell_margin['B'] = $margin[0];
16167
				$cell_margin['L'] = $margin[1];
16168
				break;
16169
			}
16170
			case 1: {
16171
				$cell_margin['T'] = $margin[0];
16172
				$cell_margin['R'] = $margin[0];
16173
				$cell_margin['B'] = $margin[0];
16174
				$cell_margin['L'] = $margin[0];
16175
				break;
16176
			}
16177
			default: {
16178
				return $this->cell_margin;
16179
			}
16180
		}
16181
		if ($width == 0) {
16182
			$width = $this->w - $this->lMargin - $this->rMargin;
16183
		}
16184
		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16185
		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16186
		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16187
		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16188
		return $cell_margin;
16189
	}
16190
 
16191
	/**
16192
	 * Get the border-spacing from CSS attribute.
16193
	 * @param string $cssbspace border-spacing CSS properties
16194
	 * @param float $width width of the containing element
16195
	 * @return array of border spacings
16196
	 * @public
16197
	 * @since 5.9.010 (2010-10-27)
16198
	 */
16199
	public function getCSSBorderMargin($cssbspace, $width=0) {
16200
		$space = preg_split('/[\s]+/', trim($cssbspace));
16201
		$border_spacing = array(); // value to be returned
16202
		switch (count($space)) {
16203
			case 2: {
16204
				$border_spacing['H'] = $space[0];
16205
				$border_spacing['V'] = $space[1];
16206
				break;
16207
			}
16208
			case 1: {
16209
				$border_spacing['H'] = $space[0];
16210
				$border_spacing['V'] = $space[0];
16211
				break;
16212
			}
16213
			default: {
16214
				return array('H' => 0, 'V' => 0);
16215
			}
16216
		}
16217
		if ($width == 0) {
16218
			$width = $this->w - $this->lMargin - $this->rMargin;
16219
		}
16220
		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16221
		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16222
		return $border_spacing;
16223
	}
16224
 
16225
	/**
16226
	 * Returns the letter-spacing value from CSS value
16227
	 * @param string $spacing letter-spacing value
16228
	 * @param float $parent font spacing (tracking) value of the parent element
16229
	 * @return float quantity to increases or decreases the space between characters in a text.
16230
	 * @protected
16231
	 * @since 5.9.000 (2010-10-02)
16232
	 */
16233
	protected function getCSSFontSpacing($spacing, $parent=0) {
16234
		$val = 0; // value to be returned
16235
		$spacing = trim($spacing);
16236
		switch ($spacing) {
16237
			case 'normal': {
16238
				$val = 0;
16239
				break;
16240
			}
16241
			case 'inherit': {
16242
				if ($parent == 'normal') {
16243
					$val = 0;
16244
				} else {
16245
					$val = $parent;
16246
				}
16247
				break;
16248
			}
16249
			default: {
16250
				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16251
			}
16252
		}
16253
		return $val;
16254
	}
16255
 
16256
	/**
16257
	 * Returns the percentage of font stretching from CSS value
16258
	 * @param string $stretch stretch mode
16259
	 * @param float $parent stretch value of the parent element
16260
	 * @return float font stretching percentage
16261
	 * @protected
16262
	 * @since 5.9.000 (2010-10-02)
16263
	 */
16264
	protected function getCSSFontStretching($stretch, $parent=100) {
16265
		$val = 100; // value to be returned
16266
		$stretch = trim($stretch);
16267
		switch ($stretch) {
16268
			case 'ultra-condensed': {
16269
				$val = 40;
16270
				break;
16271
			}
16272
			case 'extra-condensed': {
16273
				$val = 55;
16274
				break;
16275
			}
16276
			case 'condensed': {
16277
				$val = 70;
16278
				break;
16279
			}
16280
			case 'semi-condensed': {
16281
				$val = 85;
16282
				break;
16283
			}
16284
			case 'normal': {
16285
				$val = 100;
16286
				break;
16287
			}
16288
			case 'semi-expanded': {
16289
				$val = 115;
16290
				break;
16291
			}
16292
			case 'expanded': {
16293
				$val = 130;
16294
				break;
16295
			}
16296
			case 'extra-expanded': {
16297
				$val = 145;
16298
				break;
16299
			}
16300
			case 'ultra-expanded': {
16301
				$val = 160;
16302
				break;
16303
			}
16304
			case 'wider': {
16305
				$val = ($parent + 10);
16306
				break;
16307
			}
16308
			case 'narrower': {
16309
				$val = ($parent - 10);
16310
				break;
16311
			}
16312
			case 'inherit': {
16313
				if ($parent == 'normal') {
16314
					$val = 100;
16315
				} else {
16316
					$val = $parent;
16317
				}
16318
				break;
16319
			}
16320
			default: {
16321
				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16322
			}
16323
		}
16324
		return $val;
16325
	}
16326
 
16327
	/**
16328
	 * Convert HTML string containing font size value to points
16329
	 * @param string $val String containing font size value and unit.
16330
	 * @param float $refsize Reference font size in points.
16331
	 * @param float $parent_size Parent font size in points.
16332
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16333
	 * @return float value in points
16334
	 * @public
16335
	 */
16336
	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16337
		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16338
		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16339
		switch ($val) {
16340
			case 'xx-small': {
16341
				$size = ($refsize - 4);
16342
				break;
16343
			}
16344
			case 'x-small': {
16345
				$size = ($refsize - 3);
16346
				break;
16347
			}
16348
			case 'small': {
16349
				$size = ($refsize - 2);
16350
				break;
16351
			}
16352
			case 'medium': {
16353
				$size = $refsize;
16354
				break;
16355
			}
16356
			case 'large': {
16357
				$size = ($refsize + 2);
16358
				break;
16359
			}
16360
			case 'x-large': {
16361
				$size = ($refsize + 4);
16362
				break;
16363
			}
16364
			case 'xx-large': {
16365
				$size = ($refsize + 6);
16366
				break;
16367
			}
16368
			case 'smaller': {
16369
				$size = ($parent_size - 3);
16370
				break;
16371
			}
16372
			case 'larger': {
16373
				$size = ($parent_size + 3);
16374
				break;
16375
			}
16376
			default: {
16377
				$parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16378
				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16379
			}
16380
		}
16381
		return $size;
16382
	}
16383
 
16384
	/**
16385
	 * Returns the HTML DOM array.
16386
	 * @param string $html html code
16387
	 * @return array
16388
	 * @protected
16389
	 * @since 3.2.000 (2008-06-20)
16390
	 */
16391
	protected function getHtmlDomArray($html) {
16392
		// array of CSS styles ( selector => properties).
16393
		$css = array();
16394
		// get CSS array defined at previous call
16395
		$matches = array();
16396
		if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16397
			if (isset($matches[1][0])) {
16398
				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16399
			}
16400
			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16401
		}
16402
		// extract external CSS files
16403
		$matches = array();
16404
		if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16405
			foreach ($matches[1] as $key => $link) {
16406
				$type = array();
16407
				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16408
					$type = array();
16409
					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16410
					// get 'all' and 'print' media, other media types are discarded
16411
					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16412
					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16413
						$type = array();
16414
						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16415
							// read CSS data file
16416
                            $cssdata = $this->getCachedFileContents(trim($type[1]));
16417
							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16418
								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16419
							}
16420
						}
16421
					}
16422
				}
16423
			}
16424
		}
16425
		// extract style tags
16426
		$matches = array();
16427
		if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16428
			foreach ($matches[1] as $key => $media) {
16429
				$type = array();
16430
				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16431
				// get 'all' and 'print' media, other media types are discarded
16432
				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16433
				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16434
					$cssdata = $matches[2][$key];
16435
					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16436
				}
16437
			}
16438
		}
16439
		// create a special tag to contain the CSS array (used for table content)
16440
		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16441
		// remove head and style blocks
16442
		$html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16443
		$html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16444
		// define block tags
16445
		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16446
		// define self-closing tags
16447
		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16448
		// remove all unsupported tags (the line below lists all supported tags)
16449
		$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>');
16450
		//replace some blank characters
16451
		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16452
		$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);
16453
		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16454
		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16455
		$html = strtr($html, $repTable);
16456
		$offset = 0;
16457
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16458
			$html_a = substr($html, 0, $offset);
16459
			$html_b = substr($html, $offset, ($pos - $offset + 6));
16460
			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16461
				// preserve newlines on <pre> tag
16462
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16463
			}
16464
			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16465
				// preserve spaces on <pre> tag
16466
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16467
			}
16468
			$html = $html_a.$html_b.substr($html, $pos + 6);
16469
			$offset = strlen($html_a.$html_b);
16470
		}
16471
		$offset = 0;
16472
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16473
			$html_a = substr($html, 0, $offset);
16474
			$html_b = substr($html, $offset, ($pos - $offset + 11));
16475
			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16476
				// preserve newlines on <textarea> tag
16477
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16478
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16479
			}
16480
			$html = $html_a.$html_b.substr($html, $pos + 11);
16481
			$offset = strlen($html_a.$html_b);
16482
		}
16483
		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16484
		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16485
		$offset = 0;
16486
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16487
			$html_a = substr($html, 0, $offset);
16488
			$html_b = substr($html, $offset, ($pos - $offset + 9));
16489
			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16490
				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16491
				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16492
			}
16493
			$html = $html_a.$html_b.substr($html, $pos + 9);
16494
			$offset = strlen($html_a.$html_b);
16495
		}
16496
		if (preg_match("'</select'si", $html)) {
16497
			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16498
			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16499
		}
16500
		$html = str_replace("\n", ' ', $html);
16501
		// restore textarea newlines
16502
		$html = str_replace('<TBR>', "\n", $html);
16503
		// remove extra spaces from code
16504
		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16505
		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16506
		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16507
		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16508
		$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);
16509
		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16510
		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16511
		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16512
		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16513
		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16514
		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16515
		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16516
		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16517
		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16518
		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16519
		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16520
		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16521
		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16522
		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16523
		// trim string
16524
		$html = $this->stringTrim($html);
16525
		// fix br tag after li
16526
		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16527
		// fix first image tag alignment
16528
		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16529
		// pattern for generic tag
16530
		$tagpattern = '/(<[^>]+>)/';
16531
		// explodes the string
16532
		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16533
		// count elements
16534
		$maxel = count($a);
16535
		$elkey = 0;
16536
		$key = 0;
16537
		// create an array of elements
16538
		$dom = array();
16539
		$dom[$key] = array();
16540
		// set inheritable properties fot the first void element
16541
		// 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
16542
		$dom[$key]['tag'] = false;
16543
		$dom[$key]['block'] = false;
16544
		$dom[$key]['value'] = '';
16545
		$dom[$key]['parent'] = 0;
16546
		$dom[$key]['hide'] = false;
16547
		$dom[$key]['fontname'] = $this->FontFamily;
16548
		$dom[$key]['fontstyle'] = $this->FontStyle;
16549
		$dom[$key]['fontsize'] = $this->FontSizePt;
16550
		$dom[$key]['font-stretch'] = $this->font_stretching;
16551
		$dom[$key]['letter-spacing'] = $this->font_spacing;
16552
		$dom[$key]['stroke'] = $this->textstrokewidth;
16553
		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16554
		$dom[$key]['clip'] = ($this->textrendermode > 3);
16555
		$dom[$key]['line-height'] = $this->cell_height_ratio;
16556
		$dom[$key]['bgcolor'] = false;
16557
		$dom[$key]['fgcolor'] = $this->fgcolor; // color
16558
		$dom[$key]['strokecolor'] = $this->strokecolor;
16559
		$dom[$key]['align'] = '';
16560
		$dom[$key]['listtype'] = '';
16561
		$dom[$key]['text-indent'] = 0;
16562
		$dom[$key]['text-transform'] = '';
16563
		$dom[$key]['border'] = array();
16564
		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16565
		$thead = false; // true when we are inside the THEAD tag
16566
		++$key;
16567
		$level = array();
16568
		array_push($level, 0); // root
16569
		while ($elkey < $maxel) {
16570
			$dom[$key] = array();
16571
			$element = $a[$elkey];
16572
			$dom[$key]['elkey'] = $elkey;
16573
			if (preg_match($tagpattern, $element)) {
16574
				// html tag
16575
				$element = substr($element, 1, -1);
16576
				// get tag name
16577
				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16578
				$tagname = strtolower($tag[1]);
16579
				// check if we are inside a table header
16580
				if ($tagname == 'thead') {
16581
					if ($element[0] == '/') {
16582
						$thead = false;
16583
					} else {
16584
						$thead = true;
16585
					}
16586
					++$elkey;
16587
					continue;
16588
				}
16589
				$dom[$key]['tag'] = true;
16590
				$dom[$key]['value'] = $tagname;
16591
				if (in_array($dom[$key]['value'], $blocktags)) {
16592
					$dom[$key]['block'] = true;
16593
				} else {
16594
					$dom[$key]['block'] = false;
16595
				}
16596
				if ($element[0] == '/') {
16597
					// *** closing html tag
16598
					$dom[$key]['opening'] = false;
16599
					$dom[$key]['parent'] = end($level);
16600
					array_pop($level);
16601
					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16602
					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16603
					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16604
					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16605
					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16606
					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16607
					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16608
					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16609
					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16610
					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16611
					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16612
					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16613
					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16614
					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16615
					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16616
					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16617
					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16618
						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16619
					}
16620
					// set the number of columns in table tag
16621
					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16622
						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16623
					}
16624
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16625
						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16626
						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16627
							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16628
						}
16629
						$key = $i;
16630
						// mark nested tables
16631
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16632
						// remove thead sections from nested tables
16633
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16634
						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16635
					}
16636
					// store header rows on a new table
16637
					if (
16638
						($dom[$key]['value'] === 'tr')
16639
						&& !empty($dom[($dom[$key]['parent'])]['thead'])
16640
						&& ($dom[($dom[$key]['parent'])]['thead'] === true)
16641
					) {
16642
						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16643
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16644
						}
16645
						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16646
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16647
						}
16648
						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16649
							$dom[($dom[$key]['parent'])]['attribute'] = array();
16650
						}
16651
						// header elements must be always contained in a single page
16652
						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16653
					}
16654
					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16655
						// remove the nobr attributes from the table header
16656
						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16657
						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16658
					}
16659
				} else {
16660
					// *** opening or self-closing html tag
16661
					$dom[$key]['opening'] = true;
16662
					$dom[$key]['parent'] = end($level);
16663
					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16664
						// self-closing tag
16665
						$dom[$key]['self'] = true;
16666
					} else {
16667
						// opening tag
16668
						array_push($level, $key);
16669
						$dom[$key]['self'] = false;
16670
					}
16671
					// copy some values from parent
16672
					$parentkey = 0;
16673
					if ($key > 0) {
16674
						$parentkey = $dom[$key]['parent'];
16675
						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16676
						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16677
						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16678
						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16679
						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16680
						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16681
						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16682
						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16683
						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16684
						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16685
						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16686
						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16687
						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16688
						$dom[$key]['align'] = $dom[$parentkey]['align'];
16689
						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16690
						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16691
						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16692
						$dom[$key]['border'] = array();
16693
						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16694
					}
16695
					// get attributes
16696
					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16697
					$dom[$key]['attribute'] = array(); // reset attribute array
16698
                    foreach($attr_array[1] as $id => $name) {
16699
                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16700
                    }
16701
					if (!empty($css)) {
16702
						// merge CSS style to current style
16703
						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16704
						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16705
					}
16706
					// split style attributes
16707
					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16708
						// get style attributes
16709
						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16710
						$dom[$key]['style'] = array(); // reset style attribute array
16711
                        foreach($style_array[1] as $id => $name) {
16712
                            // in case of duplicate attribute the last replace the previous
16713
                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16714
                        }
16715
						// --- get some style attributes ---
16716
						// text direction
16717
						if (isset($dom[$key]['style']['direction'])) {
16718
							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16719
						}
16720
						// display
16721
						if (isset($dom[$key]['style']['display'])) {
16722
							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16723
						}
16724
						// font family
16725
						if (isset($dom[$key]['style']['font-family'])) {
16726
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16727
						}
16728
						// list-style-type
16729
						if (isset($dom[$key]['style']['list-style-type'])) {
16730
							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16731
							if ($dom[$key]['listtype'] == 'inherit') {
16732
								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16733
							}
16734
						}
16735
						// text-indent
16736
						if (isset($dom[$key]['style']['text-indent'])) {
16737
							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16738
							if ($dom[$key]['text-indent'] == 'inherit') {
16739
								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16740
							}
16741
						}
16742
						// text-transform
16743
						if (isset($dom[$key]['style']['text-transform'])) {
16744
							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16745
						}
16746
						// font size
16747
						if (isset($dom[$key]['style']['font-size'])) {
16748
							$fsize = trim($dom[$key]['style']['font-size']);
16749
							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16750
						}
16751
						// font-stretch
16752
						if (isset($dom[$key]['style']['font-stretch'])) {
16753
							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16754
						}
16755
						// letter-spacing
16756
						if (isset($dom[$key]['style']['letter-spacing'])) {
16757
							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16758
						}
16759
						// line-height (internally is the cell height ratio)
16760
						if (isset($dom[$key]['style']['line-height'])) {
16761
							$lineheight = trim($dom[$key]['style']['line-height']);
16762
							switch ($lineheight) {
16763
								// A normal line height. This is default
16764
								case 'normal': {
16765
									$dom[$key]['line-height'] = $dom[0]['line-height'];
16766
									break;
16767
								}
16768
								case 'inherit': {
16769
									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16770
								}
16771
								default: {
16772
									if (is_numeric($lineheight)) {
16773
										// convert to percentage of font height
16774
										$lineheight = ($lineheight * 100).'%';
16775
									}
16776
									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16777
									if (substr($lineheight, -1) !== '%') {
16778
										if ($dom[$key]['fontsize'] <= 0) {
16779
											$dom[$key]['line-height'] = 1;
16780
										} else {
16781
											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16782
										}
16783
									}
16784
								}
16785
							}
16786
						}
16787
						// font style
16788
						if (isset($dom[$key]['style']['font-weight'])) {
16789
							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16790
								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16791
									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16792
								}
16793
							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16794
								$dom[$key]['fontstyle'] .= 'B';
16795
							}
16796
						}
16797
						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16798
							$dom[$key]['fontstyle'] .= 'I';
16799
						}
16800
						// font color
16801
						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16802
							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16803
						} elseif ($dom[$key]['value'] == 'a') {
16804
							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16805
						}
16806
						// background color
16807
						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16808
							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16809
						}
16810
						// text-decoration
16811
						if (isset($dom[$key]['style']['text-decoration'])) {
16812
							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16813
							foreach ($decors as $dec) {
16814
								$dec = trim($dec);
16815
								if (!TCPDF_STATIC::empty_string($dec)) {
16816
									if ($dec[0] == 'u') {
16817
										// underline
16818
										$dom[$key]['fontstyle'] .= 'U';
16819
									} elseif ($dec[0] == 'l') {
16820
										// line-through
16821
										$dom[$key]['fontstyle'] .= 'D';
16822
									} elseif ($dec[0] == 'o') {
16823
										// overline
16824
										$dom[$key]['fontstyle'] .= 'O';
16825
									}
16826
								}
16827
							}
16828
						} elseif ($dom[$key]['value'] == 'a') {
16829
							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16830
						}
16831
						// check for width attribute
16832
						if (isset($dom[$key]['style']['width'])) {
16833
							$dom[$key]['width'] = $dom[$key]['style']['width'];
16834
						}
16835
						// check for height attribute
16836
						if (isset($dom[$key]['style']['height'])) {
16837
							$dom[$key]['height'] = $dom[$key]['style']['height'];
16838
						}
16839
						// check for text alignment
16840
						if (isset($dom[$key]['style']['text-align'])) {
16841
							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16842
						}
16843
						// check for CSS border properties
16844
						if (isset($dom[$key]['style']['border'])) {
16845
							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16846
							if (!empty($borderstyle)) {
16847
								$dom[$key]['border']['LTRB'] = $borderstyle;
16848
							}
16849
						}
16850
						if (isset($dom[$key]['style']['border-color'])) {
16851
							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16852
							if (isset($brd_colors[3])) {
16853
								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16854
							}
16855
							if (isset($brd_colors[1])) {
16856
								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16857
							}
16858
							if (isset($brd_colors[0])) {
16859
								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16860
							}
16861
							if (isset($brd_colors[2])) {
16862
								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16863
							}
16864
						}
16865
						if (isset($dom[$key]['style']['border-width'])) {
16866
							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16867
							if (isset($brd_widths[3])) {
16868
								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16869
							}
16870
							if (isset($brd_widths[1])) {
16871
								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16872
							}
16873
							if (isset($brd_widths[0])) {
16874
								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16875
							}
16876
							if (isset($brd_widths[2])) {
16877
								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16878
							}
16879
						}
16880
						if (isset($dom[$key]['style']['border-style'])) {
16881
							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16882
							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16883
								$dom[$key]['border']['L']['cap'] = 'square';
16884
								$dom[$key]['border']['L']['join'] = 'miter';
16885
								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16886
								if ($dom[$key]['border']['L']['dash'] < 0) {
16887
									$dom[$key]['border']['L'] = array();
16888
								}
16889
							}
16890
							if (isset($brd_styles[1])) {
16891
								$dom[$key]['border']['R']['cap'] = 'square';
16892
								$dom[$key]['border']['R']['join'] = 'miter';
16893
								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16894
								if ($dom[$key]['border']['R']['dash'] < 0) {
16895
									$dom[$key]['border']['R'] = array();
16896
								}
16897
							}
16898
							if (isset($brd_styles[0])) {
16899
								$dom[$key]['border']['T']['cap'] = 'square';
16900
								$dom[$key]['border']['T']['join'] = 'miter';
16901
								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16902
								if ($dom[$key]['border']['T']['dash'] < 0) {
16903
									$dom[$key]['border']['T'] = array();
16904
								}
16905
							}
16906
							if (isset($brd_styles[2])) {
16907
								$dom[$key]['border']['B']['cap'] = 'square';
16908
								$dom[$key]['border']['B']['join'] = 'miter';
16909
								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16910
								if ($dom[$key]['border']['B']['dash'] < 0) {
16911
									$dom[$key]['border']['B'] = array();
16912
								}
16913
							}
16914
						}
16915
						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16916
						foreach ($cellside as $bsk => $bsv) {
16917
							if (isset($dom[$key]['style']['border-'.$bsv])) {
16918
								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16919
								if (!empty($borderstyle)) {
16920
									$dom[$key]['border'][$bsk] = $borderstyle;
16921
								}
16922
							}
16923
							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16924
								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16925
							}
16926
							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16927
								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16928
							}
16929
							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16930
								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16931
								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16932
									$dom[$key]['border'][$bsk] = array();
16933
								}
16934
							}
16935
						}
16936
						// check for CSS padding properties
16937
						if (isset($dom[$key]['style']['padding'])) {
16938
							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16939
						} else {
16940
							$dom[$key]['padding'] = $this->cell_padding;
16941
						}
16942
						foreach ($cellside as $psk => $psv) {
16943
							if (isset($dom[$key]['style']['padding-'.$psv])) {
16944
								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16945
							}
16946
						}
16947
						// check for CSS margin properties
16948
						if (isset($dom[$key]['style']['margin'])) {
16949
							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16950
						} else {
16951
							$dom[$key]['margin'] = $this->cell_margin;
16952
						}
16953
						foreach ($cellside as $psk => $psv) {
16954
							if (isset($dom[$key]['style']['margin-'.$psv])) {
16955
								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16956
							}
16957
						}
16958
						// check for CSS border-spacing properties
16959
						if (isset($dom[$key]['style']['border-spacing'])) {
16960
							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16961
						}
16962
						// page-break-inside
16963
						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16964
							$dom[$key]['attribute']['nobr'] = 'true';
16965
						}
16966
						// page-break-before
16967
						if (isset($dom[$key]['style']['page-break-before'])) {
16968
							if ($dom[$key]['style']['page-break-before'] == 'always') {
16969
								$dom[$key]['attribute']['pagebreak'] = 'true';
16970
							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16971
								$dom[$key]['attribute']['pagebreak'] = 'left';
16972
							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16973
								$dom[$key]['attribute']['pagebreak'] = 'right';
16974
							}
16975
						}
16976
						// page-break-after
16977
						if (isset($dom[$key]['style']['page-break-after'])) {
16978
							if ($dom[$key]['style']['page-break-after'] == 'always') {
16979
								$dom[$key]['attribute']['pagebreakafter'] = 'true';
16980
							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16981
								$dom[$key]['attribute']['pagebreakafter'] = 'left';
16982
							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16983
								$dom[$key]['attribute']['pagebreakafter'] = 'right';
16984
							}
16985
						}
16986
					}
16987
					if (isset($dom[$key]['attribute']['display'])) {
16988
						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16989
					}
16990
					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16991
						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16992
						if (!empty($borderstyle)) {
16993
							$dom[$key]['border']['LTRB'] = $borderstyle;
16994
						}
16995
					}
16996
					// check for font tag
16997
					if ($dom[$key]['value'] == 'font') {
16998
						// font family
16999
						if (isset($dom[$key]['attribute']['face'])) {
17000
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17001
						}
17002
						// font size
17003
						if (isset($dom[$key]['attribute']['size'])) {
17004
							if ($key > 0) {
17005
								if ($dom[$key]['attribute']['size'][0] == '+') {
17006
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
17007
								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
17008
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
17009
								} else {
17010
									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17011
								}
17012
							} else {
17013
								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17014
							}
17015
						}
17016
					}
17017
					// force natural alignment for lists
17018
					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
17019
						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
17020
						if ($this->rtl) {
17021
							$dom[$key]['align'] = 'R';
17022
						} else {
17023
							$dom[$key]['align'] = 'L';
17024
						}
17025
					}
17026
					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17027
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17028
							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17029
						}
17030
					}
17031
					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17032
						$dom[$key]['fontstyle'] .= 'B';
17033
					}
17034
					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17035
						$dom[$key]['fontstyle'] .= 'I';
17036
					}
17037
					if ($dom[$key]['value'] == 'u') {
17038
						$dom[$key]['fontstyle'] .= 'U';
17039
					}
17040
					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17041
						$dom[$key]['fontstyle'] .= 'D';
17042
					}
17043
					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17044
						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17045
					}
17046
					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17047
						$dom[$key]['fontname'] = $this->default_monospaced_font;
17048
					}
17049
					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)) {
17050
						// headings h1, h2, h3, h4, h5, h6
17051
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17052
							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17053
							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17054
						}
17055
						if (!isset($dom[$key]['style']['font-weight'])) {
17056
							$dom[$key]['fontstyle'] .= 'B';
17057
						}
17058
					}
17059
					if (($dom[$key]['value'] == 'table')) {
17060
						$dom[$key]['rows'] = 0; // number of rows
17061
						$dom[$key]['trids'] = array(); // IDs of TR elements
17062
						$dom[$key]['thead'] = ''; // table header rows
17063
					}
17064
					if (($dom[$key]['value'] == 'tr')) {
17065
						$dom[$key]['cols'] = 0;
17066
						if ($thead) {
17067
							$dom[$key]['thead'] = true;
17068
							// rows on thead block are printed as a separate table
17069
						} else {
17070
							$dom[$key]['thead'] = false;
17071
							$parent = $dom[$key]['parent'];
17072
 
17073
							if (!isset($dom[$parent]['rows'])) {
17074
								$dom[$parent]['rows'] = 0;
17075
							}
17076
							// store the number of rows on table element
17077
							++$dom[$parent]['rows'];
17078
 
17079
							if (!isset($dom[$parent]['trids'])) {
17080
								$dom[$parent]['trids'] = array();
17081
							}
17082
 
17083
							// store the TR elements IDs on table element
17084
							array_push($dom[$parent]['trids'], $key);
17085
						}
17086
					}
17087
					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17088
						if (isset($dom[$key]['attribute']['colspan'])) {
17089
							$colspan = intval($dom[$key]['attribute']['colspan']);
17090
						} else {
17091
							$colspan = 1;
17092
						}
17093
						$dom[$key]['attribute']['colspan'] = $colspan;
17094
						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
17095
					}
17096
					// text direction
17097
					if (isset($dom[$key]['attribute']['dir'])) {
17098
						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17099
					}
17100
					// set foreground color attribute
17101
					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17102
						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17103
					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17104
						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17105
					}
17106
					// set background color attribute
17107
					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17108
						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17109
					}
17110
					// set stroke color attribute
17111
					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17112
						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17113
					}
17114
					// check for width attribute
17115
					if (isset($dom[$key]['attribute']['width'])) {
17116
						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
17117
					}
17118
					// check for height attribute
17119
					if (isset($dom[$key]['attribute']['height'])) {
17120
						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
17121
					}
17122
					// check for text alignment
17123
					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17124
						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17125
					}
17126
					// check for text rendering mode (the following attributes do not exist in HTML)
17127
					if (isset($dom[$key]['attribute']['stroke'])) {
17128
						// font stroke width
17129
						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17130
					}
17131
					if (isset($dom[$key]['attribute']['fill'])) {
17132
						// font fill
17133
						if ($dom[$key]['attribute']['fill'] == 'true') {
17134
							$dom[$key]['fill'] = true;
17135
						} else {
17136
							$dom[$key]['fill'] = false;
17137
						}
17138
					}
17139
					if (isset($dom[$key]['attribute']['clip'])) {
17140
						// clipping mode
17141
						if ($dom[$key]['attribute']['clip'] == 'true') {
17142
							$dom[$key]['clip'] = true;
17143
						} else {
17144
							$dom[$key]['clip'] = false;
17145
						}
17146
					}
17147
				} // end opening tag
17148
			} else {
17149
				// text
17150
				$dom[$key]['tag'] = false;
17151
				$dom[$key]['block'] = false;
17152
				$dom[$key]['parent'] = end($level);
17153
				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17154
				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17155
					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17156
					if (function_exists('mb_convert_case')) {
17157
						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17158
						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17159
							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17160
						}
17161
					} elseif (!$this->isunicode) {
17162
						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17163
							case 'capitalize': {
17164
								$element = ucwords(strtolower($element));
17165
								break;
17166
							}
17167
							case 'uppercase': {
17168
								$element = strtoupper($element);
17169
								break;
17170
							}
17171
							case 'lowercase': {
17172
								$element = strtolower($element);
17173
								break;
17174
							}
17175
						}
17176
					}
17177
					$element = preg_replace("/&NBSP;/i", "&nbsp;", $element);
17178
				}
17179
				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17180
			}
17181
			++$elkey;
17182
			++$key;
17183
		}
17184
		return $dom;
17185
	}
17186
 
17187
	/**
17188
	 * Returns the string used to find spaces
17189
	 * @return string
17190
	 * @protected
17191
	 * @author Nicola Asuni
17192
	 * @since 4.8.024 (2010-01-15)
17193
	 */
17194
	protected function getSpaceString() {
17195
		$spacestr = chr(32);
17196
		if ($this->isUnicodeFont()) {
17197
			$spacestr = chr(0).chr(32);
17198
		}
17199
		return $spacestr;
17200
	}
17201
 
17202
	/**
17203
	 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17204
	 * @param string $data serialized data
17205
	 * @return string
17206
	 * @public static
17207
	 */
17208
	protected function getHashForTCPDFtagParams($data) {
17209
		return md5(strlen($data).$this->file_id.$data);
17210
	}
17211
 
17212
	/**
17213
	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17214
	 * @param array $data parameters array
17215
	 * @return string containing serialized data
17216
	 * @public static
17217
	 */
17218
	public function serializeTCPDFtagParameters($data) {
17219
		$encoded = urlencode(json_encode($data));
17220
		return $this->getHashForTCPDFtagParams($encoded).$encoded;
17221
	}
17222
 
17223
	/**
17224
	 * Unserialize parameters to be used with TCPDF tag in HTML code.
17225
	 * @param string $data serialized data
17226
	 * @return array containing unserialized data
17227
	 * @protected static
17228
	 */
17229
	protected function unserializeTCPDFtagParameters($data) {
17230
		$hash = substr($data, 0, 32);
17231
		$encoded = substr($data, 32);
17232
		if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17233
			$this->Error('Invalid parameters');
17234
		}
17235
		return json_decode(urldecode($encoded), true);
17236
	}
17237
 
17238
	/**
17239
	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17240
	 * 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 />
17241
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17242
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17243
	 * 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
17244
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17245
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17246
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
17247
	 * @param float|null $x upper-left corner X coordinate
17248
	 * @param float|null $y upper-left corner Y coordinate
17249
	 * @param string $html html text to print. Default value: empty string.
17250
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17251
	 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
17252
Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17253
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17254
	 * @param boolean $reseth if true reset the last cell height (default true).
17255
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17256
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17257
	 * @see Multicell(), writeHTML()
17258
	 * @public
17259
	 */
17260
	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17261
		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17262
	}
17263
 
17264
	/**
17265
	 * Allows to preserve some HTML formatting (limited support).<br />
17266
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17267
	 * 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
17268
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17269
	 * @param string $html text to display
17270
	 * @param boolean $ln if true add a new line after text (default = true)
17271
	 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17272
	 * @param boolean $reseth if true reset the last cell height (default false).
17273
	 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17274
	 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17275
	 * @public
17276
	 */
17277
	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17278
		$gvars = $this->getGraphicVars();
17279
		// store current values
17280
		$prev_cell_margin = $this->cell_margin;
17281
		$prev_cell_padding = $this->cell_padding;
17282
		$prevPage = $this->page;
17283
		$prevlMargin = $this->lMargin;
17284
		$prevrMargin = $this->rMargin;
17285
		$curfontname = $this->FontFamily;
17286
		$curfontstyle = $this->FontStyle;
17287
		$curfontsize = $this->FontSizePt;
17288
		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17289
		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17290
		$curfontstretcing = $this->font_stretching;
17291
		$curfonttracking = $this->font_spacing;
17292
		$this->newline = true;
17293
		$newline = true;
17294
		$startlinepage = $this->page;
17295
		$minstartliney = $this->y;
17296
		$maxbottomliney = 0;
17297
		$startlinex = $this->x;
17298
		$startliney = $this->y;
17299
		$yshift = 0;
17300
		$loop = 0;
17301
		$curpos = 0;
17302
		$this_method_vars = array();
17303
		$undo = false;
17304
		$fontaligned = false;
17305
		$reverse_dir = false; // true when the text direction is reversed
17306
		$this->premode = false;
17307
		if ($this->inxobj) {
17308
			// we are inside an XObject template
17309
			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17310
		} elseif (isset($this->PageAnnots[$this->page])) {
17311
			$pask = count($this->PageAnnots[$this->page]);
17312
		} else {
17313
			$pask = 0;
17314
		}
17315
		if ($this->inxobj) {
17316
			// we are inside an XObject template
17317
			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17318
		} elseif (!$this->InFooter) {
17319
			if (isset($this->footerlen[$this->page])) {
17320
				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17321
			} else {
17322
				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17323
			}
17324
			$startlinepos = $this->footerpos[$this->page];
17325
		} else {
17326
			// we are inside the footer
17327
			$startlinepos = $this->pagelen[$this->page];
17328
		}
17329
		$lalign = $align;
17330
		$plalign = $align;
17331
		if ($this->rtl) {
17332
			$w = $this->x - $this->lMargin;
17333
		} else {
17334
			$w = $this->w - $this->rMargin - $this->x;
17335
		}
17336
		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17337
		if ($cell) {
17338
			if ($this->rtl) {
17339
				$this->x -= $this->cell_padding['R'];
17340
				$this->lMargin += $this->cell_padding['L'];
17341
			} else {
17342
				$this->x += $this->cell_padding['L'];
17343
				$this->rMargin += $this->cell_padding['R'];
17344
			}
17345
		}
17346
		if ($this->customlistindent >= 0) {
17347
			$this->listindent = $this->customlistindent;
17348
		} else {
17349
			$this->listindent = $this->GetStringWidth('000000');
17350
		}
17351
		$this->listindentlevel = 0;
17352
		// save previous states
17353
		$prev_cell_height_ratio = $this->cell_height_ratio;
17354
		$prev_listnum = $this->listnum;
17355
		$prev_listordered = $this->listordered;
17356
		$prev_listcount = $this->listcount;
17357
		$prev_lispacer = $this->lispacer;
17358
		$this->listnum = 0;
17359
		$this->listordered = array();
17360
		$this->listcount = array();
17361
		$this->lispacer = '';
17362
		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17363
			// reset row height
17364
			$this->resetLastH();
17365
		}
17366
		$dom = $this->getHtmlDomArray($html);
17367
		$maxel = count($dom);
17368
		$key = 0;
17369
		while ($key < $maxel) {
17370
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17371
				// store the node key
17372
				$hidden_node_key = $key;
17373
				if ($dom[$key]['self']) {
17374
					// skip just this self-closing tag
17375
					++$key;
17376
				} else {
17377
					// skip this and all children tags
17378
					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17379
						// skip hidden objects
17380
						++$key;
17381
					}
17382
					++$key;
17383
				}
17384
			}
17385
			if ($key == $maxel) break;
17386
			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17387
				// check for pagebreak
17388
				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17389
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17390
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17391
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17392
				}
17393
				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17394
					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17395
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17396
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17397
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17398
				}
17399
			}
17400
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17401
				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17402
					$dom[$key]['attribute']['nobr'] = false;
17403
				} else {
17404
					// store current object
17405
					$this->startTransaction();
17406
					// save this method vars
17407
					$this_method_vars['html'] = $html;
17408
					$this_method_vars['ln'] = $ln;
17409
					$this_method_vars['fill'] = $fill;
17410
					$this_method_vars['reseth'] = $reseth;
17411
					$this_method_vars['cell'] = $cell;
17412
					$this_method_vars['align'] = $align;
17413
					$this_method_vars['gvars'] = $gvars;
17414
					$this_method_vars['prevPage'] = $prevPage;
17415
					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17416
					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17417
					$this_method_vars['prevlMargin'] = $prevlMargin;
17418
					$this_method_vars['prevrMargin'] = $prevrMargin;
17419
					$this_method_vars['curfontname'] = $curfontname;
17420
					$this_method_vars['curfontstyle'] = $curfontstyle;
17421
					$this_method_vars['curfontsize'] = $curfontsize;
17422
					$this_method_vars['curfontascent'] = $curfontascent;
17423
					$this_method_vars['curfontdescent'] = $curfontdescent;
17424
					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17425
					$this_method_vars['curfonttracking'] = $curfonttracking;
17426
					$this_method_vars['minstartliney'] = $minstartliney;
17427
					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17428
					$this_method_vars['yshift'] = $yshift;
17429
					$this_method_vars['startlinepage'] = $startlinepage;
17430
					$this_method_vars['startlinepos'] = $startlinepos;
17431
					$this_method_vars['startlinex'] = $startlinex;
17432
					$this_method_vars['startliney'] = $startliney;
17433
					$this_method_vars['newline'] = $newline;
17434
					$this_method_vars['loop'] = $loop;
17435
					$this_method_vars['curpos'] = $curpos;
17436
					$this_method_vars['pask'] = $pask;
17437
					$this_method_vars['lalign'] = $lalign;
17438
					$this_method_vars['plalign'] = $plalign;
17439
					$this_method_vars['w'] = $w;
17440
					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17441
					$this_method_vars['prev_listnum'] = $prev_listnum;
17442
					$this_method_vars['prev_listordered'] = $prev_listordered;
17443
					$this_method_vars['prev_listcount'] = $prev_listcount;
17444
					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17445
					$this_method_vars['fontaligned'] = $fontaligned;
17446
					$this_method_vars['key'] = $key;
17447
					$this_method_vars['dom'] = $dom;
17448
				}
17449
			}
17450
			// print THEAD block
17451
			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17452
				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17453
					$this->inthead = true;
17454
					// print table header (thead)
17455
					$this->writeHTML($this->thead, false, false, false, false, '');
17456
					// check if we are on a new page or on a new column
17457
					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17458
						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17459
						// restore previous object
17460
						$this->rollbackTransaction(true);
17461
						// restore previous values
17462
						foreach ($this_method_vars as $vkey => $vval) {
17463
							$$vkey = $vval;
17464
						}
17465
						// disable table header
17466
						$tmp_thead = $this->thead;
17467
						$this->thead = '';
17468
						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17469
						$pre_y = $this->y;
17470
						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17471
							// fix for multicolumn mode
17472
							$startliney = $this->y;
17473
						}
17474
						$this->start_transaction_page = $this->page;
17475
						$this->start_transaction_y = $this->y;
17476
						// restore table header
17477
						$this->thead = $tmp_thead;
17478
						// fix table border properties
17479
						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17480
							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17481
						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17482
							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17483
						} else {
17484
							$tmp_cellspacing = 0;
17485
						}
17486
						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17487
						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17488
						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17489
						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17490
						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17491
						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17492
						// print table header (thead)
17493
						$this->writeHTML($this->thead, false, false, false, false, '');
17494
					}
17495
				}
17496
				// move $key index forward to skip THEAD block
17497
				while ( ($key < $maxel) AND (!(
17498
					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17499
					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17500
					++$key;
17501
				}
17502
			}
17503
			if ($dom[$key]['tag'] OR ($key == 0)) {
17504
				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17505
					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17506
				}
17507
				// vertically align image in line
17508
				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17509
					// get image height
17510
					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17511
					$autolinebreak = false;
17512
					if (!empty($dom[$key]['width'])) {
17513
						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17514
						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17515
							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17516
							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17517
							// add automatic line break
17518
							$autolinebreak = true;
17519
							$this->Ln('', $cell);
17520
							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17521
								// go back to evaluate this line break
17522
								--$key;
17523
							}
17524
						}
17525
					}
17526
					if (!$autolinebreak) {
17527
						if ($this->inPageBody()) {
17528
							$pre_y = $this->y;
17529
							// check for page break
17530
							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17531
								// fix for multicolumn mode
17532
								$startliney = $this->y;
17533
							}
17534
						}
17535
						if ($this->page > $startlinepage) {
17536
							// fix line splitted over two pages
17537
							if (isset($this->footerlen[$startlinepage])) {
17538
								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17539
							}
17540
							// line to be moved one page forward
17541
							$pagebuff = $this->getPageBuffer($startlinepage);
17542
							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17543
							$tstart = substr($pagebuff, 0, $startlinepos);
17544
							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17545
							// remove line from previous page
17546
							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17547
							$pagebuff = $this->getPageBuffer($this->page);
17548
							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17549
							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17550
							// add line start to current page
17551
							$yshift = ($minstartliney - $this->y);
17552
							if ($fontaligned) {
17553
								$yshift += ($curfontsize / $this->k);
17554
							}
17555
							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17556
							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17557
							// shift the annotations and links
17558
							if (isset($this->PageAnnots[$this->page])) {
17559
								$next_pask = count($this->PageAnnots[$this->page]);
17560
							} else {
17561
								$next_pask = 0;
17562
							}
17563
							if (isset($this->PageAnnots[$startlinepage])) {
17564
								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17565
									if ($pak >= $pask) {
17566
										$this->PageAnnots[$this->page][] = $pac;
17567
										unset($this->PageAnnots[$startlinepage][$pak]);
17568
										$npak = count($this->PageAnnots[$this->page]) - 1;
17569
										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17570
									}
17571
								}
17572
							}
17573
							$pask = $next_pask;
17574
							$startlinepos = $this->cntmrk[$this->page];
17575
							$startlinepage = $this->page;
17576
							$startliney = $this->y;
17577
							$this->newline = false;
17578
						}
17579
						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17580
						$minstartliney = min($this->y, $minstartliney);
17581
						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17582
					}
17583
				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17584
					// account for different font size
17585
					$pfontname = $curfontname;
17586
					$pfontstyle = $curfontstyle;
17587
					$pfontsize = $curfontsize;
17588
					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17589
					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17590
					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17591
					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17592
					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17593
					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17594
						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17595
						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17596
						if (($key < ($maxel - 1)) AND (
17597
								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17598
								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17599
								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17600
								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17601
								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17602
							)) {
17603
							if ($this->page > $startlinepage) {
17604
								// fix lines splitted over two pages
17605
								if (isset($this->footerlen[$startlinepage])) {
17606
									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17607
								}
17608
								// line to be moved one page forward
17609
								$pagebuff = $this->getPageBuffer($startlinepage);
17610
								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17611
								$tstart = substr($pagebuff, 0, $startlinepos);
17612
								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17613
								// remove line start from previous page
17614
								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17615
								$pagebuff = $this->getPageBuffer($this->page);
17616
								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17617
								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17618
								// add line start to current page
17619
								$yshift = ($minstartliney - $this->y);
17620
								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17621
								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17622
								// shift the annotations and links
17623
								if (isset($this->PageAnnots[$this->page])) {
17624
									$next_pask = count($this->PageAnnots[$this->page]);
17625
								} else {
17626
									$next_pask = 0;
17627
								}
17628
								if (isset($this->PageAnnots[$startlinepage])) {
17629
									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17630
										if ($pak >= $pask) {
17631
											$this->PageAnnots[$this->page][] = $pac;
17632
											unset($this->PageAnnots[$startlinepage][$pak]);
17633
											$npak = count($this->PageAnnots[$this->page]) - 1;
17634
											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17635
										}
17636
									}
17637
								}
17638
								$pask = $next_pask;
17639
								$startlinepos = $this->cntmrk[$this->page];
17640
								$startlinepage = $this->page;
17641
								$startliney = $this->y;
17642
							}
17643
							if (!isset($dom[$key]['line-height'])) {
17644
								$dom[$key]['line-height'] = $this->cell_height_ratio;
17645
							}
17646
							if (!$dom[$key]['block']) {
17647
								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']))) {
17648
									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17649
								}
17650
								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17651
									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17652
									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)))) {
17653
										$minstartliney = min($this->y, $line_align_data[1]);
17654
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17655
									} else {
17656
										$minstartliney = min($this->y, $minstartliney);
17657
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17658
									}
17659
									$line_align_data = $current_line_align_data;
17660
								}
17661
							}
17662
							$this->cell_height_ratio = $dom[$key]['line-height'];
17663
							$fontaligned = true;
17664
						}
17665
						$this->setFont($fontname, $fontstyle, $fontsize);
17666
						// reset row height
17667
						$this->resetLastH();
17668
						$curfontname = $fontname;
17669
						$curfontstyle = $fontstyle;
17670
						$curfontsize = $fontsize;
17671
						$curfontascent = $fontascent;
17672
						$curfontdescent = $fontdescent;
17673
					}
17674
				}
17675
				// set text rendering mode
17676
				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17677
				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17678
				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17679
				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17680
				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17681
					$this->setFontStretching($dom[$key]['font-stretch']);
17682
				}
17683
				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17684
					$this->setFontSpacing($dom[$key]['letter-spacing']);
17685
				}
17686
				if (($plalign == 'J') AND $dom[$key]['block']) {
17687
					$plalign = '';
17688
				}
17689
				// get current position on page buffer
17690
				$curpos = $this->pagelen[$startlinepage];
17691
				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17692
					$this->setFillColorArray($dom[$key]['bgcolor']);
17693
					$wfill = true;
17694
				} else {
17695
					$wfill = $fill | false;
17696
				}
17697
				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17698
					$this->setTextColorArray($dom[$key]['fgcolor']);
17699
				}
17700
				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17701
					$this->setDrawColorArray($dom[$key]['strokecolor']);
17702
				}
17703
				if (isset($dom[$key]['align'])) {
17704
					$lalign = $dom[$key]['align'];
17705
				}
17706
				if (TCPDF_STATIC::empty_string($lalign)) {
17707
					$lalign = $align;
17708
				}
17709
			}
17710
			// align lines
17711
			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17712
				$newline = true;
17713
				$fontaligned = false;
17714
				// we are at the beginning of a new line
17715
				if (isset($startlinex)) {
17716
					$yshift = ($minstartliney - $startliney);
17717
					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17718
						$yshift = 0;
17719
					}
17720
					$t_x = 0;
17721
					// the last line must be shifted to be aligned as requested
17722
					$linew = abs($this->endlinex - $startlinex);
17723
					if ($this->inxobj) {
17724
						// we are inside an XObject template
17725
						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17726
						if (isset($opentagpos)) {
17727
							$midpos = $opentagpos;
17728
						} else {
17729
							$midpos = 0;
17730
						}
17731
						if ($midpos > 0) {
17732
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17733
							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17734
						} else {
17735
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17736
							$pend = '';
17737
						}
17738
					} else {
17739
						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17740
						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17741
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17742
							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17743
						} elseif (isset($opentagpos)) {
17744
							$midpos = $opentagpos;
17745
						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17746
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17747
							$midpos = $this->footerpos[$startlinepage];
17748
						} else {
17749
							$midpos = 0;
17750
						}
17751
						if ($midpos > 0) {
17752
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17753
							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17754
						} else {
17755
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17756
							$pend = '';
17757
						}
17758
					}
17759
					if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17760
						// calculate shifting amount
17761
						$tw = $w;
17762
						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17763
							$tw += $this->cell_padding['R'];
17764
						}
17765
						if ($this->lMargin != $prevlMargin) {
17766
							$tw += ($prevlMargin - $this->lMargin);
17767
						}
17768
						if ($this->rMargin != $prevrMargin) {
17769
							$tw += ($prevrMargin - $this->rMargin);
17770
						}
17771
						$one_space_width = $this->GetStringWidth(chr(32));
17772
						$no = 0; // number of spaces on a line contained on a single block
17773
						if ($this->isRTLTextDir()) { // RTL
17774
							// remove left space if exist
17775
							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17776
							if ($pos1 > 0) {
17777
								$pos1 = intval($pos1);
17778
								if ($this->isUnicodeFont()) {
17779
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17780
									$spacelen = 2;
17781
								} else {
17782
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17783
									$spacelen = 1;
17784
								}
17785
								if ($pos1 == $pos2) {
17786
									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17787
									if (substr($pmid, $pos1, 4) == '[()]') {
17788
										$linew -= $one_space_width;
17789
									} elseif ($pos1 == strpos($pmid, '[(')) {
17790
										$no = 1;
17791
									}
17792
								}
17793
							}
17794
						} else { // LTR
17795
							// remove right space if exist
17796
							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17797
							if ($pos1 > 0) {
17798
								$pos1 = intval($pos1);
17799
								if ($this->isUnicodeFont()) {
17800
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17801
									$spacelen = 2;
17802
								} else {
17803
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17804
									$spacelen = 1;
17805
								}
17806
								if ($pos1 == $pos2) {
17807
									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17808
									$linew -= $one_space_width;
17809
								}
17810
							}
17811
						}
17812
						$mdiff = ($tw - $linew);
17813
						if ($plalign == 'C') {
17814
							if ($this->rtl) {
17815
								$t_x = -($mdiff / 2);
17816
							} else {
17817
								$t_x = ($mdiff / 2);
17818
							}
17819
						} elseif ($plalign == 'R') {
17820
							// right alignment on LTR document
17821
							$t_x = $mdiff;
17822
						} elseif ($plalign == 'L') {
17823
							// left alignment on RTL document
17824
							$t_x = -$mdiff;
17825
						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17826
							// Justification
17827
							if ($this->isRTLTextDir()) {
17828
								// align text on the left
17829
								$t_x = -$mdiff;
17830
							}
17831
							$ns = 0; // number of spaces
17832
							$pmidtemp = $pmid;
17833
							// escape special characters
17834
							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17835
							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17836
							// search spaces
17837
							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17838
								$spacestr = $this->getSpaceString();
17839
								$maxkk = count($lnstring[1]) - 1;
17840
								for ($kk=0; $kk <= $maxkk; ++$kk) {
17841
									// restore special characters
17842
									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17843
									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17844
									// store number of spaces on the strings
17845
									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17846
									// count total spaces on line
17847
									$ns += $lnstring[2][$kk];
17848
									$lnstring[3][$kk] = $ns;
17849
								}
17850
								if ($ns == 0) {
17851
									$ns = 1;
17852
								}
17853
								// calculate additional space to add to each existing space
17854
								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17855
								if ($this->FontSize <= 0) {
17856
									$this->FontSize = 1;
17857
								}
17858
								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17859
								if ($this->font_spacing != 0) {
17860
									// fixed spacing mode
17861
									$osw = -1000 * $this->font_spacing / $this->FontSize;
17862
									$spacewidthu += $osw;
17863
								}
17864
								$nsmax = $ns;
17865
								$ns = 0;
17866
								reset($lnstring);
17867
								$offset = 0;
17868
								$strcount = 0;
17869
								$prev_epsposbeg = 0;
17870
								$textpos = 0;
17871
								if ($this->isRTLTextDir()) {
17872
									$textpos = $this->wPt;
17873
								}
17874
								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17875
									// check if we are inside a string section '[( ... )]'
17876
									$stroffset = strpos($pmid, '[(', $offset);
17877
									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17878
										// set offset to the end of string section
17879
										$offset = strpos($pmid, ')]', $stroffset);
17880
										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17881
											$offset = strpos($pmid, ')]', ($offset + 1));
17882
										}
17883
										if ($offset === false) {
17884
											$this->Error('HTML Justification: malformed PDF code.');
17885
										}
17886
										continue;
17887
									}
17888
									if ($this->isRTLTextDir()) {
17889
										$spacew = ($spacewidth * ($nsmax - $ns));
17890
									} else {
17891
										$spacew = ($spacewidth * $ns);
17892
									}
17893
									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17894
									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17895
									if ($epsposend !== null) {
17896
										$epsposend += strlen($this->epsmarker.'Q');
17897
										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17898
										if ($epsposbeg === null) {
17899
											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17900
											$prev_epsposbeg = $epsposbeg;
17901
										}
17902
										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17903
											// shift EPS images
17904
											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17905
											$pmid_b = substr($pmid, 0, $epsposbeg);
17906
											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17907
											$pmid_e = substr($pmid, $epsposend);
17908
											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17909
											$offset = $epsposend;
17910
											continue;
17911
										}
17912
									}
17913
									$currentxpos = 0;
17914
									// shift blocks of code
17915
									switch ($strpiece[2][0]) {
17916
										case 'Td':
17917
										case 'cm':
17918
										case 'm':
17919
										case 'l': {
17920
											// get current X position
17921
											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17922
											if (!isset($xmatches[1])) {
17923
												break;
17924
											}
17925
											$currentxpos = $xmatches[1];
17926
											$textpos = $currentxpos;
17927
											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17928
												$ns = $lnstring[3][$strcount];
17929
												if ($this->isRTLTextDir()) {
17930
													$spacew = ($spacewidth * ($nsmax - $ns));
17931
												}
17932
												++$strcount;
17933
											}
17934
											// justify block
17935
											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17936
												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17937
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17938
												unset($pmatch, $newpmid);
17939
											}
17940
											break;
17941
										}
17942
										case 're': {
17943
											// justify block
17944
											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17945
												$this->lispacer = '';
17946
												break;
17947
											}
17948
											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17949
											if (!isset($xmatches[1])) {
17950
												break;
17951
											}
17952
											$currentxpos = $xmatches[1];
17953
											$x_diff = 0;
17954
											$w_diff = 0;
17955
											if ($this->isRTLTextDir()) { // RTL
17956
												if ($currentxpos < $textpos) {
17957
													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17958
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17959
												} else {
17960
													if ($strcount > 0) {
17961
														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17962
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17963
													}
17964
												}
17965
											} else { // LTR
17966
												if ($currentxpos > $textpos) {
17967
													if ($strcount > 0) {
17968
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17969
													}
17970
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
17971
												} else {
17972
													if ($strcount > 1) {
17973
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17974
													}
17975
													if ($strcount > 0) {
17976
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17977
													}
17978
												}
17979
											}
17980
											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17981
												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17982
												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17983
												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17984
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17985
												unset($pmatch, $newpmid, $newx, $neww);
17986
											}
17987
											break;
17988
										}
17989
										case 'c': {
17990
											// get current X position
17991
											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);
17992
											if (!isset($xmatches[1])) {
17993
												break;
17994
											}
17995
											$currentxpos = $xmatches[1];
17996
											// justify block
17997
											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) {
17998
												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17999
												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
18000
												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
18001
												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
18002
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18003
												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
18004
											}
18005
											break;
18006
										}
18007
									}
18008
									// shift the annotations and links
18009
									$cxpos = ($currentxpos / $this->k);
18010
									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
18011
									if ($this->inxobj) {
18012
										// we are inside an XObject template
18013
										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18014
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18015
												if ($cxpos > $lmpos) {
18016
													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
18017
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18018
												} else {
18019
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18020
												}
18021
												break;
18022
											}
18023
										}
18024
									} elseif (isset($this->PageAnnots[$this->page])) {
18025
										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18026
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18027
												if ($cxpos > $lmpos) {
18028
													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
18029
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18030
												} else {
18031
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18032
												}
18033
												break;
18034
											}
18035
										}
18036
									}
18037
								} // end of while
18038
								// remove markers
18039
								$pmid = str_replace('x*#!#*x', '', $pmid);
18040
								if ($this->isUnicodeFont()) {
18041
									// multibyte characters
18042
									$spacew = $spacewidthu;
18043
									if ($this->font_stretching != 100) {
18044
										// word spacing is affected by stretching
18045
										$spacew /= ($this->font_stretching / 100);
18046
									}
18047
									// escape special characters
18048
									$pos = 0;
18049
									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18050
									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18051
									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18052
										foreach($pamatch[0] as $pk => $pmatch) {
18053
											$replace = $pamatch[1][$pk];
18054
											$replace = str_replace('#!#OP#!#', '(', $replace);
18055
											$replace = str_replace('#!#CP#!#', ')', $replace);
18056
											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18057
											$pos = strpos($pmid, $pmatch, $pos);
18058
											if ($pos !== FALSE) {
18059
												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18060
											}
18061
											++$pos;
18062
										}
18063
										unset($pamatch);
18064
									}
18065
									if ($this->inxobj) {
18066
										// we are inside an XObject template
18067
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18068
									} else {
18069
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18070
									}
18071
									$endlinepos = strlen($pstart."\n".$pmid."\n");
18072
								} else {
18073
									// non-unicode (single-byte characters)
18074
									if ($this->font_stretching != 100) {
18075
										// word spacing (Tw) is affected by stretching
18076
										$spacewidth /= ($this->font_stretching / 100);
18077
									}
18078
									$rs = sprintf('%F Tw', $spacewidth);
18079
									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18080
									if ($this->inxobj) {
18081
										// we are inside an XObject template
18082
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18083
									} else {
18084
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18085
									}
18086
									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18087
								}
18088
							}
18089
						} // end of J
18090
					} // end if $startlinex
18091
					if (($t_x != 0) OR ($yshift < 0)) {
18092
						// shift the line
18093
						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18094
						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18095
						$endlinepos = strlen($pstart);
18096
						if ($this->inxobj) {
18097
							// we are inside an XObject template
18098
							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18099
							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18100
								if ($pak >= $pask) {
18101
									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18102
									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18103
								}
18104
							}
18105
						} else {
18106
							$this->setPageBuffer($startlinepage, $pstart.$pend);
18107
							// shift the annotations and links
18108
							if (isset($this->PageAnnots[$this->page])) {
18109
								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18110
									if ($pak >= $pask) {
18111
										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18112
										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18113
									}
18114
								}
18115
							}
18116
						}
18117
						$this->y -= $yshift;
18118
					}
18119
				}
18120
				$pbrk = $this->checkPageBreak($this->lasth);
18121
				$this->newline = false;
18122
				$startlinex = $this->x;
18123
				$startliney = $this->y;
18124
				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18125
					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18126
				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18127
					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
18128
				} else {
18129
					$minstartliney = $startliney;
18130
					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18131
				}
18132
				$startlinepage = $this->page;
18133
				if (isset($endlinepos) AND (!$pbrk)) {
18134
					$startlinepos = $endlinepos;
18135
				} else {
18136
					if ($this->inxobj) {
18137
						// we are inside an XObject template
18138
						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18139
					} elseif (!$this->InFooter) {
18140
						if (isset($this->footerlen[$this->page])) {
18141
							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18142
						} else {
18143
							$this->footerpos[$this->page] = $this->pagelen[$this->page];
18144
						}
18145
						$startlinepos = $this->footerpos[$this->page];
18146
					} else {
18147
						$startlinepos = $this->pagelen[$this->page];
18148
					}
18149
				}
18150
				unset($endlinepos);
18151
				$plalign = $lalign;
18152
				if (isset($this->PageAnnots[$this->page])) {
18153
					$pask = count($this->PageAnnots[$this->page]);
18154
				} else {
18155
					$pask = 0;
18156
				}
18157
				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18158
					AND (isset($this->emptypagemrk[$this->page]))
18159
					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18160
					$this->setFont($fontname, $fontstyle, $fontsize);
18161
					if ($wfill) {
18162
						$this->setFillColorArray($this->bgcolor);
18163
					}
18164
				}
18165
			} // end newline
18166
			if (isset($opentagpos)) {
18167
				unset($opentagpos);
18168
			}
18169
			if ($dom[$key]['tag']) {
18170
				if ($dom[$key]['opening']) {
18171
					// get text indentation (if any)
18172
					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18173
						$this->textindent = $dom[$key]['text-indent'];
18174
						$this->newline = true;
18175
					}
18176
					// table
18177
					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18178
						// available page width
18179
						if ($this->rtl) {
18180
							$wtmp = $this->x - $this->lMargin;
18181
						} else {
18182
							$wtmp = $this->w - $this->rMargin - $this->x;
18183
						}
18184
						// get cell spacing
18185
						if (isset($dom[$key]['attribute']['cellspacing'])) {
18186
							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18187
							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18188
						} elseif (isset($dom[$key]['border-spacing'])) {
18189
							$cellspacing = $dom[$key]['border-spacing'];
18190
						} else {
18191
							$cellspacing = array('H' => 0, 'V' => 0);
18192
						}
18193
						// table width
18194
						if (isset($dom[$key]['width'])) {
18195
							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18196
						} else {
18197
							$table_width = $wtmp;
18198
						}
18199
						$table_width -= (2 * $cellspacing['H']);
18200
						if (!$this->inthead) {
18201
							$this->y += $cellspacing['V'];
18202
						}
18203
						if ($this->rtl) {
18204
							$cellspacingx = -$cellspacing['H'];
18205
						} else {
18206
							$cellspacingx = $cellspacing['H'];
18207
						}
18208
						// total table width without cellspaces
18209
						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18210
						// minimum column width
18211
						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18212
						// array of custom column widths
18213
						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18214
					}
18215
					// table row
18216
					if ($dom[$key]['value'] == 'tr') {
18217
						// reset column counter
18218
						$colid = 0;
18219
					}
18220
					// table cell
18221
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18222
						$trid = $dom[$key]['parent'];
18223
						$table_el = $dom[$trid]['parent'];
18224
						if (!isset($dom[$table_el]['cols'])) {
18225
							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18226
						}
18227
						// store border info
18228
						$tdborder = 0;
18229
						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18230
							$tdborder = $dom[$key]['border'];
18231
						}
18232
						$colspan = intval($dom[$key]['attribute']['colspan']);
18233
						if ($colspan <= 0) {
18234
							$colspan = 1;
18235
						}
18236
						$old_cell_padding = $this->cell_padding;
18237
						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18238
							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18239
							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18240
						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18241
							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18242
						} else {
18243
							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18244
						}
18245
						$this->cell_padding = $current_cell_padding;
18246
						if (isset($dom[$key]['height'])) {
18247
							// minimum cell height
18248
							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18249
						} else {
18250
							$cellh = 0;
18251
						}
18252
						if (isset($dom[$key]['content'])) {
18253
							$cell_content = $dom[$key]['content'];
18254
						} else {
18255
							$cell_content = '&nbsp;';
18256
						}
18257
						$tagtype = $dom[$key]['value'];
18258
						$parentid = $key;
18259
						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18260
							// move $key index forward
18261
							++$key;
18262
						}
18263
						if (!isset($dom[$trid]['startpage'])) {
18264
							$dom[$trid]['startpage'] = $this->page;
18265
						} else {
18266
							$this->setPage($dom[$trid]['startpage']);
18267
						}
18268
						if (!isset($dom[$trid]['startcolumn'])) {
18269
							$dom[$trid]['startcolumn'] = $this->current_column;
18270
						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18271
							$tmpx = $this->x;
18272
							$this->selectColumn($dom[$trid]['startcolumn']);
18273
							$this->x = $tmpx;
18274
						}
18275
						if (!isset($dom[$trid]['starty'])) {
18276
							$dom[$trid]['starty'] = $this->y;
18277
						} else {
18278
							$this->y = $dom[$trid]['starty'];
18279
						}
18280
						if (!isset($dom[$trid]['startx'])) {
18281
							$dom[$trid]['startx'] = $this->x;
18282
							$this->x += $cellspacingx;
18283
						} else {
18284
							$this->x += ($cellspacingx / 2);
18285
						}
18286
						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18287
							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18288
						} else {
18289
							$rowspan = 1;
18290
						}
18291
						// skip row-spanned cells started on the previous rows
18292
						if (isset($dom[$table_el]['rowspans'])) {
18293
							$rsk = 0;
18294
							$rskmax = count($dom[$table_el]['rowspans']);
18295
							while ($rsk < $rskmax) {
18296
								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18297
								$rsstartx = $trwsp['startx'];
18298
								$rsendx = $trwsp['endx'];
18299
								// account for margin changes
18300
								if ($trwsp['startpage'] < $this->page) {
18301
									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18302
										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18303
										$rsstartx -= $dl;
18304
										$rsendx -= $dl;
18305
									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18306
										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18307
										$rsstartx += $dl;
18308
										$rsendx += $dl;
18309
									}
18310
								}
18311
								if (($trwsp['rowspan'] > 0)
18312
									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18313
									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18314
									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18315
									// set the starting X position of the current cell
18316
									$this->x = $rsendx + $cellspacingx;
18317
									// increment column indicator
18318
									$colid += $trwsp['colspan'];
18319
									if (($trwsp['rowspan'] == 1)
18320
										AND (isset($dom[$trid]['endy']))
18321
										AND (isset($dom[$trid]['endpage']))
18322
										AND (isset($dom[$trid]['endcolumn']))
18323
										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18324
										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18325
										// set ending Y position for row
18326
										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18327
										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18328
									}
18329
									$rsk = 0;
18330
								} else {
18331
									++$rsk;
18332
								}
18333
							}
18334
						}
18335
						if (isset($dom[$parentid]['width'])) {
18336
							// user specified width
18337
							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18338
							$tmpcw = ($cellw / $colspan);
18339
							for ($i = 0; $i < $colspan; ++$i) {
18340
								$table_colwidths[($colid + $i)] = $tmpcw;
18341
							}
18342
						} else {
18343
							// inherit column width
18344
							$cellw = 0;
18345
							for ($i = 0; $i < $colspan; ++$i) {
18346
								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18347
							}
18348
						}
18349
						$cellw += (($colspan - 1) * $cellspacing['H']);
18350
						// increment column indicator
18351
						$colid += $colspan;
18352
						// add rowspan information to table element
18353
						if ($rowspan > 1) {
18354
							$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));
18355
						}
18356
						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18357
						if ($rowspan > 1) {
18358
							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18359
						}
18360
						// push background colors
18361
						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18362
							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18363
						}
18364
						// store border info
18365
						if (!empty($tdborder)) {
18366
							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18367
						}
18368
						$prevLastH = $this->lasth;
18369
						// store some info for multicolumn mode
18370
						if ($this->rtl) {
18371
							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18372
						} else {
18373
							$this->colxshift['x'] = $this->x - $this->lMargin;
18374
						}
18375
						$this->colxshift['s'] = $cellspacing;
18376
						$this->colxshift['p'] = $current_cell_padding;
18377
						// ****** write the cell content ******
18378
						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18379
						// restore some values
18380
						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18381
						$this->lasth = $prevLastH;
18382
						$this->cell_padding = $old_cell_padding;
18383
						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18384
						// update the end of row position
18385
						if ($rowspan <= 1) {
18386
							if (isset($dom[$trid]['endy'])) {
18387
								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18388
									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18389
								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18390
									$dom[$trid]['endy'] = $this->y;
18391
								}
18392
							} else {
18393
								$dom[$trid]['endy'] = $this->y;
18394
							}
18395
							if (isset($dom[$trid]['endpage'])) {
18396
								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18397
							} else {
18398
								$dom[$trid]['endpage'] = $this->page;
18399
							}
18400
							if (isset($dom[$trid]['endcolumn'])) {
18401
								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18402
							} else {
18403
								$dom[$trid]['endcolumn'] = $this->current_column;
18404
							}
18405
						} else {
18406
							// account for row-spanned cells
18407
							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18408
							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18409
							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18410
							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18411
						}
18412
						if (isset($dom[$table_el]['rowspans'])) {
18413
							// update endy and endpage on rowspanned cells
18414
							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18415
								if ($trwsp['rowspan'] > 0) {
18416
									if (isset($dom[$trid]['endpage'])) {
18417
										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18418
											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18419
										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18420
											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18421
											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18422
											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18423
										} else {
18424
											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18425
										}
18426
									}
18427
								}
18428
							}
18429
						}
18430
						$this->x += ($cellspacingx / 2);
18431
					} else {
18432
						// opening tag (or self-closing tag)
18433
						if (!isset($opentagpos)) {
18434
							if ($this->inxobj) {
18435
								// we are inside an XObject template
18436
								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18437
							} elseif (!$this->InFooter) {
18438
								if (isset($this->footerlen[$this->page])) {
18439
									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18440
								} else {
18441
									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18442
								}
18443
								$opentagpos = $this->footerpos[$this->page];
18444
							}
18445
						}
18446
						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18447
					}
18448
				} else { // closing tag
18449
					$prev_numpages = $this->numpages;
18450
					$old_bordermrk = $this->bordermrk[$this->page];
18451
					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18452
					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18453
						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18454
					}
18455
					if ($prev_numpages > $this->numpages) {
18456
						$startlinepage = $this->page;
18457
					}
18458
				}
18459
			} elseif (strlen($dom[$key]['value']) > 0) {
18460
				// print list-item
18461
				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18462
					$this->setFont($pfontname, $pfontstyle, $pfontsize);
18463
					$this->resetLastH();
18464
					$minstartliney = $this->y;
18465
					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18466
					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18467
						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18468
					}
18469
					$this->setFont($curfontname, $curfontstyle, $curfontsize);
18470
					$this->resetLastH();
18471
					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18472
						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18473
						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18474
						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18475
						$minstartliney = min($this->y, $minstartliney);
18476
						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18477
					}
18478
				}
18479
				// text
18480
				$this->htmlvspace = 0;
18481
				$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']);
18482
				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18483
					// reverse spaces order
18484
					$lsp = ''; // left spaces
18485
					$rsp = ''; // right spaces
18486
					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18487
						$lsp = $matches[1];
18488
					}
18489
					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18490
						$rsp = $matches[1];
18491
					}
18492
					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18493
				}
18494
				if ($newline) {
18495
					if (!$this->premode) {
18496
						$prelen = strlen($dom[$key]['value']);
18497
						if ($this->isRTLTextDir() AND !$isRTLString) {
18498
							// right trim except non-breaking space
18499
							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18500
						} else {
18501
							// left trim except non-breaking space
18502
							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18503
						}
18504
						$postlen = strlen($dom[$key]['value']);
18505
						if (($postlen == 0) AND ($prelen > 0)) {
18506
							$dom[$key]['trimmed_space'] = true;
18507
						}
18508
					}
18509
					$newline = false;
18510
					$firstblock = true;
18511
				} else {
18512
					$firstblock = false;
18513
					// replace empty multiple spaces string with a single space
18514
					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18515
				}
18516
				$strrest = '';
18517
				if ($this->rtl) {
18518
					$this->x -= $this->textindent;
18519
				} else {
18520
					$this->x += $this->textindent;
18521
				}
18522
				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18523
					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18524
					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18525
						// HTML <a> Link
18526
						$hrefcolor = '';
18527
						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18528
							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18529
						}
18530
						$hrefstyle = -1;
18531
						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18532
							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18533
						}
18534
						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18535
					} else {
18536
						$wadj = 0; // space to leave for block continuity
18537
						if ($this->rtl) {
18538
							$cwa = ($this->x - $this->lMargin);
18539
						} else {
18540
							$cwa = ($this->w - $this->rMargin - $this->x);
18541
						}
18542
						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18543
							// check the next text blocks for continuity
18544
							$nkey = ($key + 1);
18545
							$write_block = true;
18546
							$same_textdir = true;
18547
							$tmp_fontname = $this->FontFamily;
18548
							$tmp_fontstyle = $this->FontStyle;
18549
							$tmp_fontsize = $this->FontSizePt;
18550
							while ($write_block AND isset($dom[$nkey])) {
18551
								if ($dom[$nkey]['tag']) {
18552
									if ($dom[$nkey]['block']) {
18553
										// end of block
18554
										$write_block = false;
18555
									}
18556
									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18557
									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18558
									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18559
									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18560
								} else {
18561
									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18562
									if (isset($nextstr[0]) AND $same_textdir) {
18563
										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18564
										if (isset($nextstr[1])) {
18565
											$write_block = false;
18566
										}
18567
									}
18568
								}
18569
								++$nkey;
18570
							}
18571
						}
18572
						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18573
							$wadj = 0;
18574
							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18575
							$numblks = count($nextstr);
18576
							if ($numblks > 1) {
18577
								// try to split on blank spaces
18578
								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18579
							} else {
18580
								// set the entire block on new line
18581
								$wadj = $this->GetStringWidth($nextstr[0]);
18582
							}
18583
						}
18584
						// check for reversed text direction
18585
						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18586
							// LTR text on RTL direction or RTL text on LTR direction
18587
							$reverse_dir = true;
18588
							$this->rtl = !$this->rtl;
18589
							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18590
							if ($this->rtl) {
18591
								$this->x += $revshift;
18592
							} else {
18593
								$this->x -= $revshift;
18594
							}
18595
							$xws = $this->x;
18596
						}
18597
						// ****** write only until the end of the line and get the rest ******
18598
						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18599
						// restore default direction
18600
						if ($reverse_dir AND ($wadj == 0)) {
18601
							$this->x = $xws; // @phpstan-ignore-line
18602
							$this->rtl = !$this->rtl;
18603
							$reverse_dir = false;
18604
						}
18605
					}
18606
				}
18607
				$this->textindent = 0;
18608
				if (strlen($strrest) > 0) {
18609
					// store the remaining string on the previous $key position
18610
					$this->newline = true;
18611
					if ($strrest == $dom[$key]['value']) {
18612
						// used to avoid infinite loop
18613
						++$loop;
18614
					} else {
18615
						$loop = 0;
18616
					}
18617
					$dom[$key]['value'] = $strrest;
18618
					if ($cell) {
18619
						if ($this->rtl) {
18620
							$this->x -= $this->cell_padding['R'];
18621
						} else {
18622
							$this->x += $this->cell_padding['L'];
18623
						}
18624
					}
18625
					if ($loop < 3) {
18626
						--$key;
18627
					}
18628
				} else {
18629
					$loop = 0;
18630
					// add the positive font spacing of the last character (if any)
18631
					 if ($this->font_spacing > 0) {
18632
					 	if ($this->rtl) {
18633
							$this->x -= $this->font_spacing;
18634
						} else {
18635
							$this->x += $this->font_spacing;
18636
						}
18637
					}
18638
				}
18639
			}
18640
			++$key;
18641
			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')) {
18642
				// check if we are on a new page or on a new column
18643
				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18644
					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18645
					// restore previous object
18646
					$this->rollbackTransaction(true);
18647
					// restore previous values
18648
					foreach ($this_method_vars as $vkey => $vval) {
18649
						$$vkey = $vval;
18650
					}
18651
					if (!empty($dom[$key]['thead'])) {
18652
						$this->inthead = true;
18653
					}
18654
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18655
					$pre_y = $this->y;
18656
					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18657
						$startliney = $this->y;
18658
					}
18659
					$undo = true; // avoid infinite loop
18660
				} else {
18661
					$undo = false;
18662
				}
18663
			}
18664
		} // end for each $key
18665
		// align the last line
18666
		if (isset($startlinex)) {
18667
			$yshift = ($minstartliney - $startliney);
18668
			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18669
				$yshift = 0;
18670
			}
18671
			$t_x = 0;
18672
			// the last line must be shifted to be aligned as requested
18673
			$linew = abs($this->endlinex - $startlinex);
18674
			if ($this->inxobj) {
18675
				// we are inside an XObject template
18676
				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18677
				if (isset($opentagpos)) {
18678
					$midpos = $opentagpos;
18679
				} else {
18680
					$midpos = 0;
18681
				}
18682
				if ($midpos > 0) {
18683
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18684
					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18685
				} else {
18686
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18687
					$pend = '';
18688
				}
18689
			} else {
18690
				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18691
				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18692
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18693
					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18694
				} elseif (isset($opentagpos)) {
18695
					$midpos = $opentagpos;
18696
				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18697
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18698
					$midpos = $this->footerpos[$startlinepage];
18699
				} else {
18700
					$midpos = 0;
18701
				}
18702
				if ($midpos > 0) {
18703
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18704
					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18705
				} else {
18706
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18707
					$pend = '';
18708
				}
18709
			}
18710
			if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18711
				// calculate shifting amount
18712
				$tw = $w;
18713
				if ($this->lMargin != $prevlMargin) {
18714
					$tw += ($prevlMargin - $this->lMargin);
18715
				}
18716
				if ($this->rMargin != $prevrMargin) {
18717
					$tw += ($prevrMargin - $this->rMargin);
18718
				}
18719
				$one_space_width = $this->GetStringWidth(chr(32));
18720
				$no = 0; // number of spaces on a line contained on a single block
18721
				if ($this->isRTLTextDir()) { // RTL
18722
					// remove left space if exist
18723
					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18724
					if ($pos1 > 0) {
18725
						$pos1 = intval($pos1);
18726
						if ($this->isUnicodeFont()) {
18727
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18728
							$spacelen = 2;
18729
						} else {
18730
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18731
							$spacelen = 1;
18732
						}
18733
						if ($pos1 == $pos2) {
18734
							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18735
							if (substr($pmid, $pos1, 4) == '[()]') {
18736
								$linew -= $one_space_width;
18737
							} elseif ($pos1 == strpos($pmid, '[(')) {
18738
								$no = 1;
18739
							}
18740
						}
18741
					}
18742
				} else { // LTR
18743
					// remove right space if exist
18744
					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18745
					if ($pos1 > 0) {
18746
						$pos1 = intval($pos1);
18747
						if ($this->isUnicodeFont()) {
18748
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18749
							$spacelen = 2;
18750
						} else {
18751
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18752
							$spacelen = 1;
18753
						}
18754
						if ($pos1 == $pos2) {
18755
							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18756
							$linew -= $one_space_width;
18757
						}
18758
					}
18759
				}
18760
				$mdiff = ($tw - $linew);
18761
				if ($plalign == 'C') {
18762
					if ($this->rtl) {
18763
						$t_x = -($mdiff / 2);
18764
					} else {
18765
						$t_x = ($mdiff / 2);
18766
					}
18767
				} elseif ($plalign == 'R') {
18768
					// right alignment on LTR document
18769
					$t_x = $mdiff;
18770
				} elseif ($plalign == 'L') {
18771
					// left alignment on RTL document
18772
					$t_x = -$mdiff;
18773
				}
18774
			} // end if startlinex
18775
			if (($t_x != 0) OR ($yshift < 0)) {
18776
				// shift the line
18777
				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18778
				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18779
				$endlinepos = strlen($pstart);
18780
				if ($this->inxobj) {
18781
					// we are inside an XObject template
18782
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18783
					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18784
						if ($pak >= $pask) {
18785
							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18786
							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18787
						}
18788
					}
18789
				} else {
18790
					$this->setPageBuffer($startlinepage, $pstart.$pend);
18791
					// shift the annotations and links
18792
					if (isset($this->PageAnnots[$this->page])) {
18793
						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18794
							if ($pak >= $pask) {
18795
								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18796
								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18797
							}
18798
						}
18799
					}
18800
				}
18801
				$this->y -= $yshift;
18802
				$yshift = 0;
18803
			}
18804
		}
18805
		// restore previous values
18806
		$this->setGraphicVars($gvars);
18807
		if ($this->num_columns > 1) {
18808
			$this->selectColumn();
18809
		} elseif ($this->page > $prevPage) {
18810
			$this->lMargin = $this->pagedim[$this->page]['olm'];
18811
			$this->rMargin = $this->pagedim[$this->page]['orm'];
18812
		}
18813
		// restore previous list state
18814
		$this->cell_height_ratio = $prev_cell_height_ratio;
18815
		$this->listnum = $prev_listnum;
18816
		$this->listordered = $prev_listordered;
18817
		$this->listcount = $prev_listcount;
18818
		$this->lispacer = $prev_lispacer;
18819
		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18820
			$this->Ln($this->lasth);
18821
			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18822
				$this->y = $maxbottomliney;
18823
			}
18824
		}
18825
		unset($dom);
18826
	}
18827
 
18828
	/**
18829
	 * Process opening tags.
18830
	 * @param array $dom html dom array
18831
	 * @param int $key current element id
18832
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18833
	 * @return array $dom
18834
	 * @protected
18835
	 */
18836
	protected function openHTMLTagHandler($dom, $key, $cell) {
18837
		$tag = $dom[$key];
18838
		$parent = $dom[($dom[$key]['parent'])];
18839
		$firsttag = ($key == 1);
18840
		// check for text direction attribute
18841
		if (isset($tag['dir'])) {
18842
			$this->setTempRTL($tag['dir']);
18843
		} else {
18844
			$this->tmprtl = false;
18845
		}
18846
		if ($tag['block']) {
18847
			$hbz = 0; // distance from y to line bottom
18848
			$hb = 0; // vertical space between block tags
18849
			// calculate vertical space for block tags
18850
			if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18851
				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18852
			} elseif (isset($tag['fontsize'])) {
18853
				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18854
			} else {
18855
				$cur_h = $this->getCellHeight($this->FontSize);
18856
			}
18857
			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18858
				$on = $this->tagvspaces[$tag['value']][0]['n'];
18859
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18860
				$on = 0.6;
18861
			} else {
18862
				$on = 1;
18863
			}
18864
			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18865
				$hb = 0;
18866
			} else {
18867
				$hb = ($on * $cur_h);
18868
			}
18869
			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18870
				if (isset($parent['fontsize'])) {
18871
					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18872
				} else {
18873
					$hbz = $this->getCellHeight($this->FontSize);
18874
				}
18875
			}
18876
			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18877
				// fix vertical space after table
18878
				$hbz = 0;
18879
			}
18880
			// closing vertical space
18881
			$hbc = 0;
18882
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18883
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18884
			} elseif (isset($parent['fontsize'])) {
18885
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18886
			} else {
18887
				$pre_h = $this->getCellHeight($this->FontSize);
18888
			}
18889
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18890
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18891
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18892
				$cn = 0.6;
18893
			} else {
18894
				$cn = 1;
18895
			}
18896
			if (isset($this->tagvspaces[$tag['value']][1])) {
18897
				$hbc = ($cn * $pre_h);
18898
			}
18899
		}
18900
		// Opening tag
18901
		switch($tag['value']) {
18902
			case 'table': {
18903
				$cp = 0;
18904
				$cs = 0;
18905
				$dom[$key]['rowspans'] = array();
18906
				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18907
					$this->htmlvspace = 0;
18908
					// set table header
18909
					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18910
						// set table header
18911
						$this->thead = $dom[$key]['thead'];
18912
						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18913
							$this->theadMargins = array();
18914
							$this->theadMargins['cell_padding'] = $this->cell_padding;
18915
							$this->theadMargins['lmargin'] = $this->lMargin;
18916
							$this->theadMargins['rmargin'] = $this->rMargin;
18917
							$this->theadMargins['page'] = $this->page;
18918
							$this->theadMargins['cell'] = $cell;
18919
							$this->theadMargins['gvars'] = $this->getGraphicVars();
18920
						}
18921
					}
18922
				}
18923
				// store current margins and page
18924
				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18925
				if (isset($tag['attribute']['cellpadding'])) {
18926
					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18927
					$this->setCellPadding($pad);
18928
				} elseif (isset($tag['padding'])) {
18929
					$this->cell_padding = $tag['padding'];
18930
				}
18931
				if (isset($tag['attribute']['cellspacing'])) {
18932
					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18933
				} elseif (isset($tag['border-spacing'])) {
18934
					$cs = $tag['border-spacing']['V'];
18935
				}
18936
				$prev_y = $this->y;
18937
				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18938
					$this->inthead = true;
18939
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18940
					$this->checkPageBreak($this->PageBreakTrigger + 1);
18941
				}
18942
				break;
18943
			}
18944
			case 'tr': {
18945
				// array of columns positions
18946
				$dom[$key]['cellpos'] = array();
18947
				break;
18948
			}
18949
			case 'hr': {
18950
				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18951
					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18952
				} else {
18953
					$hrHeight = $this->GetLineWidth();
18954
				}
18955
				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18956
				$x = $this->GetX();
18957
				$y = $this->GetY();
18958
				$wtmp = $this->w - $this->lMargin - $this->rMargin;
18959
				if ($cell) {
18960
					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18961
				}
18962
				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18963
					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18964
				} else {
18965
					$hrWidth = $wtmp;
18966
				}
18967
				$prevlinewidth = $this->GetLineWidth();
18968
				$this->setLineWidth($hrHeight);
18969
 
18970
				$lineStyle = array();
18971
                    		if (isset($tag['fgcolor'])) {
18972
		                        $lineStyle['color'] = $tag['fgcolor'];
18973
                    		}
18974
 
18975
                    		if (isset($tag['fgcolor'])) {
18976
                        		$lineStyle['color'] = $tag['fgcolor'];
18977
                    		}
18978
 
18979
                    		if (isset($tag['style']['cap'])) {
18980
                        		$lineStyle['cap'] = $tag['style']['cap'];
18981
                    		}
18982
 
18983
                    		if (isset($tag['style']['join'])) {
18984
                        		$lineStyle['join'] = $tag['style']['join'];
18985
                    		}
18986
 
18987
                    		if (isset($tag['style']['dash'])) {
18988
                        		$lineStyle['dash'] = $tag['style']['dash'];
18989
                    		}
18990
 
18991
                    		if (isset($tag['style']['phase'])) {
18992
                        		$lineStyle['phase'] = $tag['style']['phase'];
18993
                    		}
18994
 
18995
				$lineStyle = array_filter($lineStyle);
18996
 
18997
				$this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
18998
				$this->setLineWidth($prevlinewidth);
18999
				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
19000
				break;
19001
			}
19002
			case 'a': {
19003
				if (array_key_exists('href', $tag['attribute'])) {
19004
					$this->HREF['url'] = $tag['attribute']['href'];
19005
				}
19006
				break;
19007
			}
19008
			case 'img': {
19009
				if (empty($tag['attribute']['src'])) {
19010
					break;
19011
				}
19012
				$imgsrc = $tag['attribute']['src'];
19013
				if ($imgsrc[0] === '@') {
19014
					// data stream
19015
					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
19016
					$type = '';
19017
				} else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19018
					$imgsrc = '@'.base64_decode($reg[2]);
19019
					$type = $reg[1];
19020
				} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
19021
                    // get image type from a local file path
19022
                    $imgsrc = substr($imgsrc, 7);
19023
                    $type = TCPDF_IMAGES::getImageFileType($imgsrc);
19024
                } else {
19025
					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19026
						// fix image path
19027
						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
19028
						if (($findroot === false) OR ($findroot > 1)) {
19029
							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
19030
								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
19031
							} else {
19032
								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
19033
							}
19034
						}
19035
						$imgsrc = urldecode($imgsrc);
19036
						$testscrtype = @parse_url($imgsrc);
19037
						if (empty($testscrtype['query'])) {
19038
							// convert URL to server path
19039
							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
19040
						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
19041
							// convert URL to server path
19042
							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
19043
						}
19044
					}
19045
					// get image type
19046
					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
19047
				}
19048
				if (!isset($tag['width'])) {
19049
					$tag['width'] = 0;
19050
				}
19051
				if (!isset($tag['height'])) {
19052
					$tag['height'] = 0;
19053
				}
19054
				//if (!isset($tag['attribute']['align'])) {
19055
					// the only alignment supported is "bottom"
19056
					// further development is required for other modes.
19057
					$tag['attribute']['align'] = 'bottom';
19058
				//}
19059
				switch($tag['attribute']['align']) {
19060
					case 'top': {
19061
						$align = 'T';
19062
						break;
19063
					}
19064
					case 'middle': {
19065
						$align = 'M';
19066
						break;
19067
					}
19068
					case 'bottom': {
19069
						$align = 'B';
19070
						break;
19071
					}
19072
					default: {
19073
						$align = 'B';
19074
						break;
19075
					}
19076
				}
19077
				$prevy = $this->y;
19078
				$xpos = $this->x;
19079
				$imglink = '';
19080
				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19081
					$imglink = $this->HREF['url'];
19082
					if ($imglink[0] == '#') {
19083
						// convert url to internal link
19084
						$lnkdata = explode(',', $imglink);
19085
						if (isset($lnkdata[0])) {
19086
							$page = intval(substr($lnkdata[0], 1));
19087
							if (empty($page) OR ($page <= 0)) {
19088
								$page = $this->page;
19089
							}
19090
							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19091
								$lnky = floatval($lnkdata[1]);
19092
							} else {
19093
								$lnky = 0;
19094
							}
19095
							$imglink = $this->AddLink();
19096
							$this->setLink($imglink, $lnky, $page);
19097
						}
19098
					}
19099
				}
19100
				$border = 0;
19101
				if (isset($tag['border']) AND !empty($tag['border'])) {
19102
					// currently only support 1 (frame) or a combination of 'LTRB'
19103
					$border = $tag['border'];
19104
				}
19105
				$iw = '';
19106
				if (isset($tag['width'])) {
19107
					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19108
				}
19109
				$ih = '';
19110
				if (isset($tag['height'])) {
19111
					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19112
				}
19113
				if (($type == 'eps') OR ($type == 'ai')) {
19114
					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19115
				} elseif ($type == 'svg') {
19116
					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19117
				} else {
19118
					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19119
				}
19120
				switch($align) {
19121
					case 'T': {
19122
						$this->y = $prevy;
19123
						break;
19124
					}
19125
					case 'M': {
19126
						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19127
						break;
19128
					}
19129
					case 'B': {
19130
						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19131
						break;
19132
					}
19133
				}
19134
				break;
19135
			}
19136
			case 'dl': {
19137
				++$this->listnum;
19138
				if ($this->listnum == 1) {
19139
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19140
				} else {
19141
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19142
				}
19143
				break;
19144
			}
19145
			case 'dt': {
19146
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19147
				break;
19148
			}
19149
			case 'dd': {
19150
				if ($this->rtl) {
19151
					$this->rMargin += $this->listindent;
19152
				} else {
19153
					$this->lMargin += $this->listindent;
19154
				}
19155
				++$this->listindentlevel;
19156
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19157
				break;
19158
			}
19159
			case 'ul':
19160
			case 'ol': {
19161
				++$this->listnum;
19162
				if ($tag['value'] == 'ol') {
19163
					$this->listordered[$this->listnum] = true;
19164
				} else {
19165
					$this->listordered[$this->listnum] = false;
19166
				}
19167
				if (isset($tag['attribute']['start'])) {
19168
					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19169
				} else {
19170
					$this->listcount[$this->listnum] = 0;
19171
				}
19172
				if ($this->rtl) {
19173
					$this->rMargin += $this->listindent;
19174
					$this->x -= $this->listindent;
19175
				} else {
19176
					$this->lMargin += $this->listindent;
19177
					$this->x += $this->listindent;
19178
				}
19179
				++$this->listindentlevel;
19180
				if ($this->listnum == 1) {
19181
					if ($key > 1) {
19182
						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19183
					}
19184
				} else {
19185
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19186
				}
19187
				break;
19188
			}
19189
			case 'li': {
19190
				if ($key > 2) {
19191
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19192
				}
19193
				if ($this->listordered[$this->listnum]) {
19194
					// ordered item
19195
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19196
						$this->lispacer = $parent['attribute']['type'];
19197
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19198
						$this->lispacer = $parent['listtype'];
19199
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19200
						$this->lispacer = $this->lisymbol;
19201
					} else {
19202
						$this->lispacer = '#';
19203
					}
19204
					++$this->listcount[$this->listnum];
19205
					if (isset($tag['attribute']['value'])) {
19206
						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19207
					}
19208
				} else {
19209
					// unordered item
19210
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19211
						$this->lispacer = $parent['attribute']['type'];
19212
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19213
						$this->lispacer = $parent['listtype'];
19214
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19215
						$this->lispacer = $this->lisymbol;
19216
					} else {
19217
						$this->lispacer = '!';
19218
					}
19219
				}
19220
				break;
19221
			}
19222
			case 'blockquote': {
19223
				if ($this->rtl) {
19224
					$this->rMargin += $this->listindent;
19225
				} else {
19226
					$this->lMargin += $this->listindent;
19227
				}
19228
				++$this->listindentlevel;
19229
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19230
				break;
19231
			}
19232
			case 'br': {
19233
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19234
				break;
19235
			}
19236
			case 'div': {
19237
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19238
				break;
19239
			}
19240
			case 'p': {
19241
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19242
				break;
19243
			}
19244
			case 'pre': {
19245
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19246
				$this->premode = true;
19247
				break;
19248
			}
19249
			case 'sup': {
19250
				$this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19251
				break;
19252
			}
19253
			case 'sub': {
19254
				$this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19255
				break;
19256
			}
19257
			case 'h1':
19258
			case 'h2':
19259
			case 'h3':
19260
			case 'h4':
19261
			case 'h5':
19262
			case 'h6': {
19263
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19264
				break;
19265
			}
19266
			// Form fields (since 4.8.000 - 2009-09-07)
19267
			case 'form': {
19268
				if (isset($tag['attribute']['action'])) {
19269
					$this->form_action = $tag['attribute']['action'];
19270
				} else {
19271
					$this->Error('Please explicitly set action attribute path!');
19272
				}
19273
				if (isset($tag['attribute']['enctype'])) {
19274
					$this->form_enctype = $tag['attribute']['enctype'];
19275
				} else {
19276
					$this->form_enctype = 'application/x-www-form-urlencoded';
19277
				}
19278
				if (isset($tag['attribute']['method'])) {
19279
					$this->form_mode = $tag['attribute']['method'];
19280
				} else {
19281
					$this->form_mode = 'post';
19282
				}
19283
				break;
19284
			}
19285
			case 'input': {
19286
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19287
					$name = $tag['attribute']['name'];
19288
				} else {
19289
					break;
19290
				}
19291
				$prop = array();
19292
				$opt = array();
19293
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19294
					$prop['readonly'] = true;
19295
				}
19296
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19297
					$value = $tag['attribute']['value'];
19298
				}
19299
				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19300
					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19301
				}
19302
				$h = $this->getCellHeight($this->FontSize);
19303
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19304
					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19305
				} else {
19306
					$w = $h;
19307
				}
19308
				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19309
					$checked = true;
19310
				} else {
19311
					$checked = false;
19312
				}
19313
				if (isset($tag['align'])) {
19314
					switch ($tag['align']) {
19315
						case 'C': {
19316
							$opt['q'] = 1;
19317
							break;
19318
						}
19319
						case 'R': {
19320
							$opt['q'] = 2;
19321
							break;
19322
						}
19323
						case 'L':
19324
						default: {
19325
							break;
19326
						}
19327
					}
19328
				}
19329
				switch ($tag['attribute']['type']) {
19330
					case 'text': {
19331
						if (isset($value)) {
19332
							$opt['v'] = $value;
19333
						}
19334
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19335
						break;
19336
					}
19337
					case 'password': {
19338
						if (isset($value)) {
19339
							$opt['v'] = $value;
19340
						}
19341
						$prop['password'] = 'true';
19342
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19343
						break;
19344
					}
19345
					case 'checkbox': {
19346
						if (!isset($value)) {
19347
							break;
19348
						}
19349
						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19350
						break;
19351
					}
19352
					case 'radio': {
19353
						if (!isset($value)) {
19354
							break;
19355
						}
19356
						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19357
						break;
19358
					}
19359
					case 'submit': {
19360
						if (!isset($value)) {
19361
							$value = 'submit';
19362
						}
19363
						$w = $this->GetStringWidth($value) * 1.5;
19364
						$h *= 1.6;
19365
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19366
						$action = array();
19367
						$action['S'] = 'SubmitForm';
19368
						$action['F'] = $this->form_action;
19369
						if ($this->form_enctype != 'FDF') {
19370
							$action['Flags'] = array('ExportFormat');
19371
						}
19372
						if ($this->form_mode == 'get') {
19373
							$action['Flags'] = array('GetMethod');
19374
						}
19375
						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19376
						break;
19377
					}
19378
					case 'reset': {
19379
						if (!isset($value)) {
19380
							$value = 'reset';
19381
						}
19382
						$w = $this->GetStringWidth($value) * 1.5;
19383
						$h *= 1.6;
19384
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19385
						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19386
						break;
19387
					}
19388
					case 'file': {
19389
						$prop['fileSelect'] = 'true';
19390
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19391
						if (!isset($value)) {
19392
							$value = '*';
19393
						}
19394
						$w = $this->GetStringWidth($value) * 2;
19395
						$h *= 1.2;
19396
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19397
						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19398
						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19399
						break;
19400
					}
19401
					case 'hidden': {
19402
						if (isset($value)) {
19403
							$opt['v'] = $value;
19404
						}
19405
						$opt['f'] = array('invisible', 'hidden');
19406
						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19407
						break;
19408
					}
19409
					case 'image': {
19410
						// THIS TYPE MUST BE FIXED
19411
						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19412
							$img = $tag['attribute']['src'];
19413
						} else {
19414
							break;
19415
						}
19416
						$value = 'img';
19417
						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19418
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19419
							$jsaction = $tag['attribute']['onclick'];
19420
						} else {
19421
							$jsaction = '';
19422
						}
19423
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19424
						break;
19425
					}
19426
					case 'button': {
19427
						if (!isset($value)) {
19428
							$value = ' ';
19429
						}
19430
						$w = $this->GetStringWidth($value) * 1.5;
19431
						$h *= 1.6;
19432
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19433
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19434
							$jsaction = $tag['attribute']['onclick'];
19435
						} else {
19436
							$jsaction = '';
19437
						}
19438
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19439
						break;
19440
					}
19441
				}
19442
				break;
19443
			}
19444
			case 'textarea': {
19445
				$prop = array();
19446
				$opt = array();
19447
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19448
					$prop['readonly'] = true;
19449
				}
19450
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19451
					$name = $tag['attribute']['name'];
19452
				} else {
19453
					break;
19454
				}
19455
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19456
					$opt['v'] = $tag['attribute']['value'];
19457
				}
19458
				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19459
					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19460
				} else {
19461
					$w = 40;
19462
				}
19463
				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19464
					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19465
				} else {
19466
					$h = 10;
19467
				}
19468
				$prop['multiline'] = 'true';
19469
				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19470
				break;
19471
			}
19472
			case 'select': {
19473
				$h = $this->getCellHeight($this->FontSize);
19474
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19475
					$h *= ($tag['attribute']['size'] + 1);
19476
				}
19477
				$prop = array();
19478
				$opt = array();
19479
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19480
					$name = $tag['attribute']['name'];
19481
				} else {
19482
					break;
19483
				}
19484
				$w = 0;
19485
				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19486
					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19487
					$values = array();
19488
					foreach ($options as $val) {
19489
						if (strpos($val, '#!TaB!#') !== false) {
19490
							$opts = explode('#!TaB!#', $val);
19491
							$values[] = $opts;
19492
							$w = max($w, $this->GetStringWidth($opts[1]));
19493
						} else {
19494
							$values[] = $val;
19495
							$w = max($w, $this->GetStringWidth($val));
19496
						}
19497
					}
19498
				} else {
19499
					break;
19500
				}
19501
				$w *= 2;
19502
				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19503
					$prop['multipleSelection'] = 'true';
19504
					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19505
				} else {
19506
					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19507
				}
19508
				break;
19509
			}
19510
			case 'tcpdf': {
19511
				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19512
					// Special tag used to call TCPDF methods
19513
					if (isset($tag['attribute']['method'])) {
19514
						$tcpdf_method = $tag['attribute']['method'];
19515
						if (method_exists($this, $tcpdf_method)) {
19516
							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19517
								$params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19518
								call_user_func_array(array($this, $tcpdf_method), $params);
19519
							} else {
19520
								$this->$tcpdf_method();
19521
							}
19522
							$this->newline = true;
19523
						}
19524
					}
19525
				}
19526
				break;
19527
			}
19528
			default: {
19529
				break;
19530
			}
19531
		}
19532
		// define tags that support borders and background colors
19533
		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19534
		if (in_array($tag['value'], $bordertags)) {
19535
			// set border
19536
			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19537
		}
19538
		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19539
			$pba = $dom[$key]['attribute']['pagebreakafter'];
19540
			// check for pagebreak
19541
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19542
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19543
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19544
			}
19545
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19546
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19547
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19548
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19549
			}
19550
		}
19551
		return $dom;
19552
	}
19553
 
19554
	/**
19555
	 * Process closing tags.
19556
	 * @param array $dom html dom array
19557
	 * @param int $key current element id
19558
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19559
	 * @param int $maxbottomliney maximum y value of current line
19560
	 * @return array $dom
19561
	 * @protected
19562
	 */
19563
	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19564
		$tag = $dom[$key];
19565
		$parent = $dom[($dom[$key]['parent'])];
19566
		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19567
		$in_table_head = false;
19568
		// maximum x position (used to draw borders)
19569
		if ($this->rtl) {
19570
			$xmax = $this->w;
19571
		} else {
19572
			$xmax = 0;
19573
		}
19574
		if ($tag['block']) {
19575
			$hbz = 0; // distance from y to line bottom
19576
			$hb = 0; // vertical space between block tags
19577
			// calculate vertical space for block tags
19578
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19579
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19580
			} elseif (isset($parent['fontsize'])) {
19581
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19582
			} else {
19583
				$pre_h = $this->getCellHeight($this->FontSize);
19584
			}
19585
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19586
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19587
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19588
				$cn = 0.6;
19589
			} else {
19590
				$cn = 1;
19591
			}
19592
			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19593
				$hb = 0;
19594
			} else {
19595
				$hb = ($cn * $pre_h);
19596
			}
19597
			if ($maxbottomliney > $this->PageBreakTrigger) {
19598
				$hbz = $this->getCellHeight($this->FontSize);
19599
			} elseif ($this->y < $maxbottomliney) {
19600
				$hbz = ($maxbottomliney - $this->y);
19601
			}
19602
		}
19603
		// Closing tag
19604
		switch($tag['value']) {
19605
			case 'tr': {
19606
				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19607
				if (!isset($parent['endy'])) {
19608
					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19609
					$parent['endy'] = $this->y;
19610
				}
19611
				if (!isset($parent['endpage'])) {
19612
					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19613
					$parent['endpage'] = $this->page;
19614
				}
19615
				if (!isset($parent['endcolumn'])) {
19616
					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19617
					$parent['endcolumn'] = $this->current_column;
19618
				}
19619
				// update row-spanned cells
19620
				if (isset($dom[$table_el]['rowspans'])) {
19621
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19622
						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19623
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19624
							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19625
								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19626
							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19627
								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19628
								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19629
								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19630
							}
19631
						}
19632
					}
19633
					// report new endy and endpage to the rowspanned cells
19634
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19635
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19636
							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19637
							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19638
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19639
							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19640
							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19641
							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19642
						}
19643
					}
19644
					// update remaining rowspanned cells
19645
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19646
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19647
							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19648
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19649
							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19650
						}
19651
					}
19652
				}
19653
				$prev_page = $this->page;
19654
				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19655
				if ($this->num_columns > 1) {
19656
					if (($prev_page < $this->page)
19657
						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19658
							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19659
						// page jump
19660
						$this->selectColumn(0);
19661
						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19662
						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19663
					} else {
19664
						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19665
						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19666
					}
19667
				} else {
19668
					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19669
				}
19670
				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19671
					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19672
				} elseif (isset($dom[$table_el]['border-spacing'])) {
19673
					$this->y += $dom[$table_el]['border-spacing']['V'];
19674
				}
19675
				$this->Ln(0, $cell);
19676
				if ($this->current_column == $parent['startcolumn']) {
19677
					$this->x = $parent['startx'];
19678
				}
19679
				// account for booklet mode
19680
				if ($this->page > $parent['startpage']) {
19681
					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19682
						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19683
					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19684
						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19685
					}
19686
				}
19687
				break;
19688
			}
19689
			case 'tablehead':
19690
				// closing tag used for the thead part
19691
				$in_table_head = true;
19692
				$this->inthead = false;
19693
			case 'table': {
19694
				$table_el = $parent;
19695
				// set default border
19696
				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19697
					// set default border
19698
					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19699
				} else {
19700
					$border = 0;
19701
				}
19702
				$default_border = $border;
19703
				// fix bottom line alignment of last line before page break
19704
				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19705
					// update row-spanned cells
19706
					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19707
						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19708
							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19709
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19710
							}
19711
							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19712
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19713
							}
19714
						}
19715
					}
19716
					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19717
						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19718
						$dom[$prevtrkey]['endy'] = $pgendy;
19719
						// update row-spanned cells
19720
						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19721
							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19722
								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19723
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19724
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19725
								}
19726
							}
19727
						}
19728
					}
19729
					$prevtrkey = $trkey;
19730
					$table_el = $dom[($dom[$key]['parent'])];
19731
				}
19732
				// for each row
19733
				if (!empty($table_el['trids'])) {
19734
					unset($xmax);
19735
				}
19736
				foreach ($table_el['trids'] as $j => $trkey) {
19737
					$parent = $dom[$trkey];
19738
					if (!isset($xmax)) {
19739
						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19740
					}
19741
					// for each cell on the row
19742
					foreach ($parent['cellpos'] as $k => $cellpos) {
19743
						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19744
							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19745
							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19746
							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19747
							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19748
							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19749
							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19750
							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19751
						} else {
19752
							$endy = $parent['endy'];
19753
							$startpage = $parent['startpage'];
19754
							$endpage = $parent['endpage'];
19755
							$startcolumn = $parent['startcolumn'];
19756
							$endcolumn = $parent['endcolumn'];
19757
						}
19758
						if ($this->num_columns == 0) {
19759
							$this->num_columns = 1;
19760
						}
19761
						if (isset($cellpos['border'])) {
19762
							$border = $cellpos['border'];
19763
						}
19764
						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19765
							$this->setFillColorArray($cellpos['bgcolor']);
19766
							$fill = true;
19767
						} else {
19768
							$fill = false;
19769
						}
19770
						$x = $cellpos['startx'];
19771
						$y = $parent['starty'];
19772
						$starty = $y;
19773
						$w = abs($cellpos['endx'] - $cellpos['startx']);
19774
						// get border modes
19775
						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19776
						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19777
						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19778
						// design borders around HTML cells.
19779
						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19780
							$ccode = '';
19781
							$this->setPage($page);
19782
							if ($this->num_columns < 2) {
19783
								// single-column mode
19784
								$this->x = $x;
19785
								$this->y = $this->tMargin;
19786
							}
19787
							// account for margin changes
19788
							if ($page > $startpage) {
19789
								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19790
									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19791
								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19792
									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19793
								}
19794
							}
19795
							if ($startpage == $endpage) { // single page
19796
								$deltacol = 0;
19797
								$deltath = 0;
19798
								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19799
									$this->selectColumn($column);
19800
									if ($startcolumn == $endcolumn) { // single column
19801
										$cborder = $border;
19802
										$h = $endy - $parent['starty'];
19803
										$this->y = $y;
19804
										$this->x = $x;
19805
									} elseif ($column == $startcolumn) { // first column
19806
										$cborder = $border_start;
19807
										$this->y = $starty;
19808
										$this->x = $x;
19809
										$h = $this->h - $this->y - $this->bMargin;
19810
										if ($this->rtl) {
19811
											$deltacol = $this->x + $this->rMargin - $this->w;
19812
										} else {
19813
											$deltacol = $this->x - $this->lMargin;
19814
										}
19815
									} elseif ($column == $endcolumn) { // end column
19816
										$cborder = $border_end;
19817
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19818
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19819
										}
19820
										$this->x += $deltacol;
19821
										$h = $endy - $this->y;
19822
									} else { // middle column
19823
										$cborder = $border_middle;
19824
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19825
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19826
										}
19827
										$this->x += $deltacol;
19828
										$h = $this->h - $this->y - $this->bMargin;
19829
									}
19830
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19831
								} // end for each column
19832
							} elseif ($page == $startpage) { // first page
19833
								$deltacol = 0;
19834
								$deltath = 0;
19835
								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19836
									$this->selectColumn($column);
19837
									if ($column == $startcolumn) { // first column
19838
										$cborder = $border_start;
19839
										$this->y = $starty;
19840
										$this->x = $x;
19841
										$h = $this->h - $this->y - $this->bMargin;
19842
										if ($this->rtl) {
19843
											$deltacol = $this->x + $this->rMargin - $this->w;
19844
										} else {
19845
											$deltacol = $this->x - $this->lMargin;
19846
										}
19847
									} else { // middle column
19848
										$cborder = $border_middle;
19849
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19850
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19851
										}
19852
										$this->x += $deltacol;
19853
										$h = $this->h - $this->y - $this->bMargin;
19854
									}
19855
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19856
								} // end for each column
19857
							} elseif ($page == $endpage) { // last page
19858
								$deltacol = 0;
19859
								$deltath = 0;
19860
								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19861
									$this->selectColumn($column);
19862
									if ($column == $endcolumn) { // end column
19863
										$cborder = $border_end;
19864
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19865
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19866
										}
19867
										$this->x += $deltacol;
19868
										$h = $endy - $this->y;
19869
									} else { // middle column
19870
										$cborder = $border_middle;
19871
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19872
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19873
										}
19874
										$this->x += $deltacol;
19875
										$h = $this->h - $this->y - $this->bMargin;
19876
									}
19877
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19878
								} // end for each column
19879
							} else { // middle page
19880
								$deltacol = 0;
19881
								$deltath = 0;
19882
								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19883
									$this->selectColumn($column);
19884
									$cborder = $border_middle;
19885
									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19886
										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19887
									}
19888
									$this->x += $deltacol;
19889
									$h = $this->h - $this->y - $this->bMargin;
19890
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19891
								} // end for each column
19892
							}
19893
							if (!empty($cborder) OR !empty($fill)) {
19894
								$offsetlen = strlen($ccode);
19895
								// draw border and fill
19896
								if ($this->inxobj) {
19897
									// we are inside an XObject template
19898
									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19899
										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19900
										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19901
										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19902
									} else {
19903
										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19904
										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19905
									}
19906
									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19907
									$pstart = substr($pagebuff, 0, $pagemark);
19908
									$pend = substr($pagebuff, $pagemark);
19909
									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19910
								} else {
19911
									// draw border and fill
19912
									if (end($this->transfmrk[$this->page]) !== false) {
19913
										$pagemarkkey = key($this->transfmrk[$this->page]);
19914
										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19915
									} elseif ($this->InFooter) {
19916
										$pagemark = $this->footerpos[$this->page];
19917
									} else {
19918
										$pagemark = $this->intmrk[$this->page];
19919
									}
19920
									$pagebuff = $this->getPageBuffer($this->page);
19921
									$pstart = substr($pagebuff, 0, $pagemark);
19922
									$pend = substr($pagebuff, $pagemark);
19923
									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19924
								}
19925
							}
19926
						} // end for each page
19927
						// restore default border
19928
						$border = $default_border;
19929
					} // end for each cell on the row
19930
					if (isset($table_el['attribute']['cellspacing'])) {
19931
						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19932
					} elseif (isset($table_el['border-spacing'])) {
19933
						$this->y += $table_el['border-spacing']['V'];
19934
					}
19935
					$this->Ln(0, $cell);
19936
					$this->x = $parent['startx'];
19937
					if ($endpage > $startpage) {
19938
						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19939
							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19940
						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19941
							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19942
						}
19943
					}
19944
				}
19945
				if (!$in_table_head) { // we are not inside a thead section
19946
					$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null;
19947
					// reset row height
19948
					$this->resetLastH();
19949
					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19950
						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19951
						if (($plendiff > 0) AND ($plendiff < 60)) {
19952
							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19953
							if (substr($pagediff, 0, 5) == 'BT /F') {
19954
								// the difference is only a font setting
19955
								$plendiff = 0;
19956
							}
19957
						}
19958
						if ($plendiff == 0) {
19959
							// remove last blank page
19960
							$this->deletePage($this->numpages);
19961
						}
19962
					}
19963
					if (isset($this->theadMargins['top'])) {
19964
						// restore top margin
19965
						$this->tMargin = $this->theadMargins['top'];
19966
					}
19967
					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19968
						// reset main table header
19969
						$this->thead = '';
19970
						$this->theadMargins = array();
19971
						$this->pagedim[$this->page]['tm'] = $this->tMargin;
19972
					}
19973
				}
19974
				$parent = $table_el;
19975
				break;
19976
			}
19977
			case 'a': {
19978
				$this->HREF = array();
19979
				break;
19980
			}
19981
			case 'sup': {
19982
				$this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19983
				break;
19984
			}
19985
			case 'sub': {
19986
				$this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19987
				break;
19988
			}
19989
			case 'div': {
19990
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19991
				break;
19992
			}
19993
			case 'blockquote': {
19994
				if ($this->rtl) {
19995
					$this->rMargin -= $this->listindent;
19996
				} else {
19997
					$this->lMargin -= $this->listindent;
19998
				}
19999
				--$this->listindentlevel;
20000
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20001
				break;
20002
			}
20003
			case 'p': {
20004
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20005
				break;
20006
			}
20007
			case 'pre': {
20008
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20009
				$this->premode = false;
20010
				break;
20011
			}
20012
			case 'dl': {
20013
				--$this->listnum;
20014
				if ($this->listnum <= 0) {
20015
					$this->listnum = 0;
20016
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20017
				} else {
20018
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20019
				}
20020
				$this->resetLastH();
20021
				break;
20022
			}
20023
			case 'dt': {
20024
				$this->lispacer = '';
20025
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20026
				break;
20027
			}
20028
			case 'dd': {
20029
				$this->lispacer = '';
20030
				if ($this->rtl) {
20031
					$this->rMargin -= $this->listindent;
20032
				} else {
20033
					$this->lMargin -= $this->listindent;
20034
				}
20035
				--$this->listindentlevel;
20036
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20037
				break;
20038
			}
20039
			case 'ul':
20040
			case 'ol': {
20041
				--$this->listnum;
20042
				$this->lispacer = '';
20043
				if ($this->rtl) {
20044
					$this->rMargin -= $this->listindent;
20045
				} else {
20046
					$this->lMargin -= $this->listindent;
20047
				}
20048
				--$this->listindentlevel;
20049
				if ($this->listnum <= 0) {
20050
					$this->listnum = 0;
20051
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20052
				} else {
20053
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20054
				}
20055
				$this->resetLastH();
20056
				break;
20057
			}
20058
			case 'li': {
20059
				$this->lispacer = '';
20060
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20061
				break;
20062
			}
20063
			case 'h1':
20064
			case 'h2':
20065
			case 'h3':
20066
			case 'h4':
20067
			case 'h5':
20068
			case 'h6': {
20069
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20070
				break;
20071
			}
20072
			// Form fields (since 4.8.000 - 2009-09-07)
20073
			case 'form': {
20074
				$this->form_action = '';
20075
				$this->form_enctype = 'application/x-www-form-urlencoded';
20076
				break;
20077
			}
20078
			default : {
20079
				break;
20080
			}
20081
		}
20082
		// draw border and background (if any)
20083
		$this->drawHTMLTagBorder($parent, $xmax);
20084
		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20085
			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20086
			// check for pagebreak
20087
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20088
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20089
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20090
			}
20091
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20092
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20093
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20094
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20095
			}
20096
		}
20097
		$this->tmprtl = false;
20098
		return $dom;
20099
	}
20100
 
20101
	/**
20102
	 * Add vertical spaces if needed.
20103
	 * @param string $hbz Distance between current y and line bottom.
20104
	 * @param string $hb The height of the break.
20105
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20106
	 * @param boolean $firsttag set to true when the tag is the first.
20107
	 * @param boolean $lasttag set to true when the tag is the last.
20108
	 * @protected
20109
	 */
20110
	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20111
		if ($firsttag) {
20112
			$this->Ln(0, $cell);
20113
			$this->htmlvspace = 0;
20114
			return;
20115
		}
20116
		if ($lasttag) {
20117
			$this->Ln($hbz, $cell);
20118
			$this->htmlvspace = 0;
20119
			return;
20120
		}
20121
		if ($hb < $this->htmlvspace) {
20122
			$hd = 0;
20123
		} else {
20124
			$hd = $hb - $this->htmlvspace;
20125
			$this->htmlvspace = $hb;
20126
		}
20127
		$this->Ln(($hbz + $hd), $cell);
20128
	}
20129
 
20130
	/**
20131
	 * Return the starting coordinates to draw an html border
20132
	 * @return array containing top-left border coordinates
20133
	 * @protected
20134
	 * @since 5.7.000 (2010-08-03)
20135
	 */
20136
	protected function getBorderStartPosition() {
20137
		if ($this->rtl) {
20138
			$xmax = $this->lMargin;
20139
		} else {
20140
			$xmax = $this->w - $this->rMargin;
20141
		}
20142
		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20143
	}
20144
 
20145
	/**
20146
	 * Draw an HTML block border and fill
20147
	 * @param array $tag array of tag properties.
20148
	 * @param int $xmax end X coordinate for border.
20149
	 * @protected
20150
	 * @since 5.7.000 (2010-08-03)
20151
	 */
20152
	protected function drawHTMLTagBorder($tag, $xmax) {
20153
		if (!isset($tag['borderposition'])) {
20154
			// nothing to draw
20155
			return;
20156
		}
20157
		$prev_x = $this->x;
20158
		$prev_y = $this->y;
20159
		$prev_lasth = $this->lasth;
20160
		$border = 0;
20161
		$fill = false;
20162
		$this->lasth = 0;
20163
		if (isset($tag['border']) AND !empty($tag['border'])) {
20164
			// get border style
20165
			$border = $tag['border'];
20166
			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20167
				// border for table header
20168
				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20169
			}
20170
		}
20171
		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20172
			// get background color
20173
			$old_bgcolor = $this->bgcolor;
20174
			$this->setFillColorArray($tag['bgcolor']);
20175
			$fill = true;
20176
		}
20177
		if (!$border AND !$fill) {
20178
			// nothing to draw
20179
			return;
20180
		}
20181
		if (isset($tag['attribute']['cellspacing'])) {
20182
			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20183
			$cellspacing = array('H' => $clsp, 'V' => $clsp);
20184
		} elseif (isset($tag['border-spacing'])) {
20185
			$cellspacing = $tag['border-spacing'];
20186
		} else {
20187
			$cellspacing = array('H' => 0, 'V' => 0);
20188
		}
20189
		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20190
			// draw the border externally respect the sqare edge.
20191
			$border['mode'] = 'ext';
20192
		}
20193
		if ($this->rtl) {
20194
			if ($xmax >= $tag['borderposition']['x']) {
20195
				$xmax = $tag['borderposition']['xmax'];
20196
			}
20197
			$w = ($tag['borderposition']['x'] - $xmax);
20198
		} else {
20199
			if ($xmax <= $tag['borderposition']['x']) {
20200
				$xmax = $tag['borderposition']['xmax'];
20201
			}
20202
			$w = ($xmax - $tag['borderposition']['x']);
20203
		}
20204
		if ($w <= 0) {
20205
			return;
20206
		}
20207
		$w += $cellspacing['H'];
20208
		$startpage = $tag['borderposition']['page'];
20209
		$startcolumn = $tag['borderposition']['column'];
20210
		$x = $tag['borderposition']['x'];
20211
		$y = $tag['borderposition']['y'];
20212
		$endpage = $this->page;
20213
		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20214
		$currentY = $this->y;
20215
		$this->x = $x;
20216
		// get latest column
20217
		$endcolumn = $this->current_column;
20218
		if ($this->num_columns == 0) {
20219
			$this->num_columns = 1;
20220
		}
20221
		// get border modes
20222
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20223
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20224
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20225
		// temporary disable page regions
20226
		$temp_page_regions = $this->page_regions;
20227
		$this->page_regions = array();
20228
		// design borders around HTML cells.
20229
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20230
			$ccode = '';
20231
			$this->setPage($page);
20232
			if ($this->num_columns < 2) {
20233
				// single-column mode
20234
				$this->x = $x;
20235
				$this->y = $this->tMargin;
20236
			}
20237
			// account for margin changes
20238
			if ($page > $startpage) {
20239
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20240
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20241
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20242
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20243
				}
20244
			}
20245
			if ($startpage == $endpage) {
20246
				// single page
20247
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20248
					$this->selectColumn($column);
20249
					if ($startcolumn == $endcolumn) { // single column
20250
						$cborder = $border;
20251
						$h = ($currentY - $y) + $cellspacing['V'];
20252
						$this->y = $starty;
20253
					} elseif ($column == $startcolumn) { // first column
20254
						$cborder = $border_start;
20255
						$this->y = $starty;
20256
						$h = $this->h - $this->y - $this->bMargin;
20257
					} elseif ($column == $endcolumn) { // end column
20258
						$cborder = $border_end;
20259
						$h = $currentY - $this->y;
20260
					} else { // middle column
20261
						$cborder = $border_middle;
20262
						$h = $this->h - $this->y - $this->bMargin;
20263
					}
20264
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20265
				} // end for each column
20266
			} elseif ($page == $startpage) { // first page
20267
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20268
					$this->selectColumn($column);
20269
					if ($column == $startcolumn) { // first column
20270
						$cborder = $border_start;
20271
						$this->y = $starty;
20272
						$h = $this->h - $this->y - $this->bMargin;
20273
					} else { // middle column
20274
						$cborder = $border_middle;
20275
						$h = $this->h - $this->y - $this->bMargin;
20276
					}
20277
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20278
				} // end for each column
20279
			} elseif ($page == $endpage) { // last page
20280
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20281
					$this->selectColumn($column);
20282
					if ($column == $endcolumn) {
20283
						// end column
20284
						$cborder = $border_end;
20285
						$h = $currentY - $this->y;
20286
					} else {
20287
						// middle column
20288
						$cborder = $border_middle;
20289
						$h = $this->h - $this->y - $this->bMargin;
20290
					}
20291
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20292
				} // end for each column
20293
			} else { // middle page
20294
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20295
					$this->selectColumn($column);
20296
					$cborder = $border_middle;
20297
					$h = $this->h - $this->y - $this->bMargin;
20298
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20299
				} // end for each column
20300
			}
20301
			if ($cborder OR $fill) {
20302
				$offsetlen = strlen($ccode);
20303
				// draw border and fill
20304
				if ($this->inxobj) {
20305
					// we are inside an XObject template
20306
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20307
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20308
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20309
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20310
					} else {
20311
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20312
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20313
					}
20314
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20315
					$pstart = substr($pagebuff, 0, $pagemark);
20316
					$pend = substr($pagebuff, $pagemark);
20317
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20318
				} else {
20319
					if (end($this->transfmrk[$this->page]) !== false) {
20320
						$pagemarkkey = key($this->transfmrk[$this->page]);
20321
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20322
					} elseif ($this->InFooter) {
20323
						$pagemark = $this->footerpos[$this->page];
20324
					} else {
20325
						$pagemark = $this->intmrk[$this->page];
20326
					}
20327
					$pagebuff = $this->getPageBuffer($this->page);
20328
					$pstart = substr($pagebuff, 0, $pagemark);
20329
					$pend = substr($pagebuff, $pagemark);
20330
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20331
					$this->bordermrk[$this->page] += $offsetlen;
20332
					$this->cntmrk[$this->page] += $offsetlen;
20333
				}
20334
			}
20335
		} // end for each page
20336
		// restore page regions
20337
		$this->page_regions = $temp_page_regions;
20338
		if (isset($old_bgcolor)) {
20339
			// restore background color
20340
			$this->setFillColorArray($old_bgcolor);
20341
		}
20342
		// restore pointer position
20343
		$this->x = $prev_x;
20344
		$this->y = $prev_y;
20345
		$this->lasth = $prev_lasth;
20346
	}
20347
 
20348
	/**
20349
	 * Set the default bullet to be used as LI bullet symbol
20350
	 * @param string $symbol character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20351
	 * @public
20352
	 * @since 4.0.028 (2008-09-26)
20353
	 */
20354
	public function setLIsymbol($symbol='!') {
20355
		// check for custom image symbol
20356
		if (substr($symbol, 0, 4) == 'img|') {
20357
			$this->lisymbol = $symbol;
20358
			return;
20359
		}
20360
		$symbol = strtolower($symbol);
20361
		$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');
20362
		if (in_array($symbol, $valid_symbols)) {
20363
			$this->lisymbol = $symbol;
20364
		} else {
20365
			$this->lisymbol = '';
20366
		}
20367
	}
20368
 
20369
	/**
20370
	 * Set the booklet mode for double-sided pages.
20371
	 * @param boolean $booklet true set the booklet mode on, false otherwise.
20372
	 * @param float $inner Inner page margin.
20373
	 * @param float $outer Outer page margin.
20374
	 * @public
20375
	 * @since 4.2.000 (2008-10-29)
20376
	 */
20377
	public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20378
		$this->booklet = $booklet;
20379
		if ($inner >= 0) {
20380
			$this->lMargin = $inner;
20381
		}
20382
		if ($outer >= 0) {
20383
			$this->rMargin = $outer;
20384
		}
20385
	}
20386
 
20387
	/**
20388
	 * Swap the left and right margins.
20389
	 * @param boolean $reverse if true swap left and right margins.
20390
	 * @protected
20391
	 * @since 4.2.000 (2008-10-29)
20392
	 */
20393
	protected function swapMargins($reverse=true) {
20394
		if ($reverse) {
20395
			// swap left and right margins
20396
			$mtemp = $this->original_lMargin;
20397
			$this->original_lMargin = $this->original_rMargin;
20398
			$this->original_rMargin = $mtemp;
20399
			$deltam = $this->original_lMargin - $this->original_rMargin;
20400
			$this->lMargin += $deltam;
20401
			$this->rMargin -= $deltam;
20402
		}
20403
	}
20404
 
20405
	/**
20406
	 * Set the vertical spaces for HTML tags.
20407
	 * The array must have the following structure (example):
20408
	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20409
	 * The first array level contains the tag names,
20410
	 * the second level contains 0 for opening tags or 1 for closing tags,
20411
	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20412
	 * If the h parameter is not specified, default values are used.
20413
	 * @param array $tagvs array of tags and relative vertical spaces.
20414
	 * @public
20415
	 * @since 4.2.001 (2008-10-30)
20416
	 */
20417
	public function setHtmlVSpace($tagvs) {
20418
		$this->tagvspaces = $tagvs;
20419
	}
20420
 
20421
	/**
20422
	 * Set custom width for list indentation.
20423
	 * @param float $width width of the indentation. Use negative value to disable it.
20424
	 * @public
20425
	 * @since 4.2.007 (2008-11-12)
20426
	 */
20427
	public function setListIndentWidth($width) {
20428
		return $this->customlistindent = floatval($width);
20429
	}
20430
 
20431
	/**
20432
	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20433
	 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20434
	 * @public
20435
	 * @since 4.2.010 (2008-11-14)
20436
	 */
20437
	public function setOpenCell($isopen) {
20438
		$this->opencell = $isopen;
20439
	}
20440
 
20441
	/**
20442
	 * Set the color and font style for HTML links.
20443
	 * @param array $color RGB array of colors
20444
	 * @param string $fontstyle additional font styles to add
20445
	 * @public
20446
	 * @since 4.4.003 (2008-12-09)
20447
	 */
20448
	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20449
		$this->htmlLinkColorArray = $color;
20450
		$this->htmlLinkFontStyle = $fontstyle;
20451
	}
20452
 
20453
	/**
20454
	 * Convert HTML string containing value and unit of measure to user's units or points.
20455
	 * @param string $htmlval String containing values and unit.
20456
	 * @param string $refsize Reference value in points.
20457
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20458
	 * @param boolean $points If true returns points, otherwise returns value in user's units.
20459
	 * @return float value in user's unit or point if $points=true
20460
	 * @public
20461
	 * @since 4.4.004 (2008-12-10)
20462
	 */
20463
	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20464
		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20465
		$retval = 0;
20466
		$value = 0;
20467
		$unit = 'px';
20468
		if ($points) {
20469
			$k = 1;
20470
		} else {
20471
			$k = $this->k;
20472
		}
20473
		if (in_array($defaultunit, $supportedunits)) {
20474
			$unit = $defaultunit;
20475
		}
20476
		if (is_numeric($htmlval)) {
20477
			$value = floatval($htmlval);
20478
		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20479
			$value = floatval($mnum[1]);
20480
			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20481
				if (in_array($munit[1], $supportedunits)) {
20482
					$unit = $munit[1];
20483
				}
20484
			}
20485
		}
20486
		switch ($unit) {
20487
			// percentage
20488
			case '%': {
20489
				$retval = (($value * $refsize) / 100);
20490
				break;
20491
			}
20492
			// relative-size
20493
			case 'em': {
20494
				$retval = ($value * $refsize);
20495
				break;
20496
			}
20497
			// height of lower case 'x' (about half the font-size)
20498
			case 'ex': {
20499
				$retval = ($value * ($refsize / 2));
20500
				break;
20501
			}
20502
			// absolute-size
20503
			case 'in': {
20504
				$retval = (($value * $this->dpi) / $k);
20505
				break;
20506
			}
20507
			// centimeters
20508
			case 'cm': {
20509
				$retval = (($value / 2.54 * $this->dpi) / $k);
20510
				break;
20511
			}
20512
			// millimeters
20513
			case 'mm': {
20514
				$retval = (($value / 25.4 * $this->dpi) / $k);
20515
				break;
20516
			}
20517
			// one pica is 12 points
20518
			case 'pc': {
20519
				$retval = (($value * 12) / $k);
20520
				break;
20521
			}
20522
			// points
20523
			case 'pt': {
20524
				$retval = ($value / $k);
20525
				break;
20526
			}
20527
			// pixels
20528
			case 'px': {
20529
				$retval = $this->pixelsToUnits($value);
20530
				if ($points) {
20531
					$retval *= $this->k;
20532
				}
20533
				break;
20534
			}
20535
		}
20536
		return $retval;
20537
	}
20538
 
20539
	/**
20540
	 * Output an HTML list bullet or ordered item symbol
20541
	 * @param int $listdepth list nesting level
20542
	 * @param string $listtype type of list
20543
	 * @param float $size current font size
20544
	 * @protected
20545
	 * @since 4.4.004 (2008-12-10)
20546
	 */
20547
	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20548
		if ($this->state != 2) {
20549
			return;
20550
		}
20551
		$size /= $this->k;
20552
		$fill = '';
20553
		$bgcolor = $this->bgcolor;
20554
		$color = $this->fgcolor;
20555
		$strokecolor = $this->strokecolor;
20556
		$width = 0;
20557
		$textitem = '';
20558
		$tmpx = $this->x;
20559
		$lspace = $this->GetStringWidth('  ');
20560
		if ($listtype == '^') {
20561
			// special symbol used for avoid justification of rect bullet
20562
			$this->lispacer = '';
20563
			return;
20564
		} elseif ($listtype == '!') {
20565
			// set default list type for unordered list
20566
			$deftypes = array('disc', 'circle', 'square');
20567
			$listtype = $deftypes[($listdepth - 1) % 3];
20568
		} elseif ($listtype == '#') {
20569
			// set default list type for ordered list
20570
			$listtype = 'decimal';
20571
		} elseif (substr($listtype, 0, 4) == 'img|') {
20572
			// custom image type ('img|type|width|height|image.ext')
20573
			$img = explode('|', $listtype);
20574
			$listtype = 'img';
20575
		}
20576
		switch ($listtype) {
20577
			// unordered types
20578
			case 'none': {
20579
				break;
20580
			}
20581
			case 'disc': {
20582
				$r = $size / 6;
20583
				$lspace += (2 * $r);
20584
				if ($this->rtl) {
20585
					$this->x += $lspace;
20586
				} else {
20587
					$this->x -= $lspace;
20588
				}
20589
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20590
				break;
20591
			}
20592
			case 'circle': {
20593
				$r = $size / 6;
20594
				$lspace += (2 * $r);
20595
				if ($this->rtl) {
20596
					$this->x += $lspace;
20597
				} else {
20598
					$this->x -= $lspace;
20599
				}
20600
				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20601
				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20602
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20603
				$this->_out($prev_line_style); // restore line settings
20604
				break;
20605
			}
20606
			case 'square': {
20607
				$l = $size / 3;
20608
				$lspace += $l;
20609
				if ($this->rtl) {;
20610
					$this->x += $lspace;
20611
				} else {
20612
					$this->x -= $lspace;
20613
				}
20614
				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20615
				break;
20616
			}
20617
			case 'img': {
20618
				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20619
				$lspace += $img[2];
20620
				if ($this->rtl) {;
20621
					$this->x += $lspace;
20622
				} else {
20623
					$this->x -= $lspace;
20624
				}
20625
				$imgtype = strtolower($img[1]);
20626
				$prev_y = $this->y;
20627
				switch ($imgtype) {
20628
					case 'svg': {
20629
						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20630
						break;
20631
					}
20632
					case 'ai':
20633
					case 'eps': {
20634
						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20635
						break;
20636
					}
20637
					default: {
20638
						$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);
20639
						break;
20640
					}
20641
				}
20642
				$this->y = $prev_y;
20643
				break;
20644
			}
20645
			// ordered types
20646
			// $this->listcount[$this->listnum];
20647
			// $textitem
20648
			case '1':
20649
			case 'decimal': {
20650
				$textitem = $this->listcount[$this->listnum];
20651
				break;
20652
			}
20653
			case 'decimal-leading-zero': {
20654
				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20655
				break;
20656
			}
20657
			case 'i':
20658
			case 'lower-roman': {
20659
				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20660
				break;
20661
			}
20662
			case 'I':
20663
			case 'upper-roman': {
20664
				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20665
				break;
20666
			}
20667
			case 'a':
20668
			case 'lower-alpha':
20669
			case 'lower-latin': {
20670
				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20671
				break;
20672
			}
20673
			case 'A':
20674
			case 'upper-alpha':
20675
			case 'upper-latin': {
20676
				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20677
				break;
20678
			}
20679
			case 'lower-greek': {
20680
				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20681
				break;
20682
			}
20683
			/*
20684
			// Types to be implemented (special handling)
20685
			case 'hebrew': {
20686
				break;
20687
			}
20688
			case 'armenian': {
20689
				break;
20690
			}
20691
			case 'georgian': {
20692
				break;
20693
			}
20694
			case 'cjk-ideographic': {
20695
				break;
20696
			}
20697
			case 'hiragana': {
20698
				break;
20699
			}
20700
			case 'katakana': {
20701
				break;
20702
			}
20703
			case 'hiragana-iroha': {
20704
				break;
20705
			}
20706
			case 'katakana-iroha': {
20707
				break;
20708
			}
20709
			*/
20710
			default: {
20711
				$textitem = $this->listcount[$this->listnum];
20712
			}
20713
		}
20714
		if (!TCPDF_STATIC::empty_string($textitem)) {
20715
			// Check whether we need a new page or new column
20716
			$prev_y = $this->y;
20717
			$h = $this->getCellHeight($this->FontSize);
20718
			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20719
				$tmpx = $this->x;
20720
			}
20721
			// print ordered item
20722
			if ($this->rtl) {
20723
				$textitem = '.'.$textitem;
20724
			} else {
20725
				$textitem = $textitem.'.';
20726
			}
20727
			$lspace += $this->GetStringWidth($textitem);
20728
			if ($this->rtl) {
20729
				$this->x += $lspace;
20730
			} else {
20731
				$this->x -= $lspace;
20732
			}
20733
			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20734
		}
20735
		$this->x = $tmpx;
20736
		$this->lispacer = '^';
20737
		// restore colors
20738
		$this->setFillColorArray($bgcolor);
20739
		$this->setDrawColorArray($strokecolor);
20740
		$this->settextColorArray($color);
20741
	}
20742
 
20743
	/**
20744
	 * Returns current graphic variables as array.
20745
	 * @return array of graphic variables
20746
	 * @protected
20747
	 * @since 4.2.010 (2008-11-14)
20748
	 */
20749
	protected function getGraphicVars() {
20750
		$grapvars = array(
20751
			'FontFamily' => $this->FontFamily,
20752
			'FontStyle' => $this->FontStyle,
20753
			'FontSizePt' => $this->FontSizePt,
20754
			'rMargin' => $this->rMargin,
20755
			'lMargin' => $this->lMargin,
20756
			'cell_padding' => $this->cell_padding,
20757
			'cell_margin' => $this->cell_margin,
20758
			'LineWidth' => $this->LineWidth,
20759
			'linestyleWidth' => $this->linestyleWidth,
20760
			'linestyleCap' => $this->linestyleCap,
20761
			'linestyleJoin' => $this->linestyleJoin,
20762
			'linestyleDash' => $this->linestyleDash,
20763
			'textrendermode' => $this->textrendermode,
20764
			'textstrokewidth' => $this->textstrokewidth,
20765
			'DrawColor' => $this->DrawColor,
20766
			'FillColor' => $this->FillColor,
20767
			'TextColor' => $this->TextColor,
20768
			'ColorFlag' => $this->ColorFlag,
20769
			'bgcolor' => $this->bgcolor,
20770
			'fgcolor' => $this->fgcolor,
20771
			'htmlvspace' => $this->htmlvspace,
20772
			'listindent' => $this->listindent,
20773
			'listindentlevel' => $this->listindentlevel,
20774
			'listnum' => $this->listnum,
20775
			'listordered' => $this->listordered,
20776
			'listcount' => $this->listcount,
20777
			'lispacer' => $this->lispacer,
20778
			'cell_height_ratio' => $this->cell_height_ratio,
20779
			'font_stretching' => $this->font_stretching,
20780
			'font_spacing' => $this->font_spacing,
20781
			'alpha' => $this->alpha,
20782
			// extended
20783
			'lasth' => $this->lasth,
20784
			'tMargin' => $this->tMargin,
20785
			'bMargin' => $this->bMargin,
20786
			'AutoPageBreak' => $this->AutoPageBreak,
20787
			'PageBreakTrigger' => $this->PageBreakTrigger,
20788
			'x' => $this->x,
20789
			'y' => $this->y,
20790
			'w' => $this->w,
20791
			'h' => $this->h,
20792
			'wPt' => $this->wPt,
20793
			'hPt' => $this->hPt,
20794
			'fwPt' => $this->fwPt,
20795
			'fhPt' => $this->fhPt,
20796
			'page' => $this->page,
20797
			'current_column' => $this->current_column,
20798
			'num_columns' => $this->num_columns
20799
			);
20800
		return $grapvars;
20801
	}
20802
 
20803
	/**
20804
	 * Set graphic variables.
20805
	 * @param array $gvars array of graphic variablesto restore
20806
	 * @param boolean $extended if true restore extended graphic variables
20807
	 * @protected
20808
	 * @since 4.2.010 (2008-11-14)
20809
	 */
20810
	protected function setGraphicVars($gvars, $extended=false) {
20811
		if ($this->state != 2) {
20812
			 return;
20813
		}
20814
		$this->FontFamily = $gvars['FontFamily'];
20815
		$this->FontStyle = $gvars['FontStyle'];
20816
		$this->FontSizePt = $gvars['FontSizePt'];
20817
		$this->rMargin = $gvars['rMargin'];
20818
		$this->lMargin = $gvars['lMargin'];
20819
		$this->cell_padding = $gvars['cell_padding'];
20820
		$this->cell_margin = $gvars['cell_margin'];
20821
		$this->LineWidth = $gvars['LineWidth'];
20822
		$this->linestyleWidth = $gvars['linestyleWidth'];
20823
		$this->linestyleCap = $gvars['linestyleCap'];
20824
		$this->linestyleJoin = $gvars['linestyleJoin'];
20825
		$this->linestyleDash = $gvars['linestyleDash'];
20826
		$this->textrendermode = $gvars['textrendermode'];
20827
		$this->textstrokewidth = $gvars['textstrokewidth'];
20828
		$this->DrawColor = $gvars['DrawColor'];
20829
		$this->FillColor = $gvars['FillColor'];
20830
		$this->TextColor = $gvars['TextColor'];
20831
		$this->ColorFlag = $gvars['ColorFlag'];
20832
		$this->bgcolor = $gvars['bgcolor'];
20833
		$this->fgcolor = $gvars['fgcolor'];
20834
		$this->htmlvspace = $gvars['htmlvspace'];
20835
		$this->listindent = $gvars['listindent'];
20836
		$this->listindentlevel = $gvars['listindentlevel'];
20837
		$this->listnum = $gvars['listnum'];
20838
		$this->listordered = $gvars['listordered'];
20839
		$this->listcount = $gvars['listcount'];
20840
		$this->lispacer = $gvars['lispacer'];
20841
		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20842
		$this->font_stretching = $gvars['font_stretching'];
20843
		$this->font_spacing = $gvars['font_spacing'];
20844
		$this->alpha = $gvars['alpha'];
20845
		if ($extended) {
20846
			// restore extended values
20847
			$this->lasth = $gvars['lasth'];
20848
			$this->tMargin = $gvars['tMargin'];
20849
			$this->bMargin = $gvars['bMargin'];
20850
			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20851
			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20852
			$this->x = $gvars['x'];
20853
			$this->y = $gvars['y'];
20854
			$this->w = $gvars['w'];
20855
			$this->h = $gvars['h'];
20856
			$this->wPt = $gvars['wPt'];
20857
			$this->hPt = $gvars['hPt'];
20858
			$this->fwPt = $gvars['fwPt'];
20859
			$this->fhPt = $gvars['fhPt'];
20860
			$this->page = $gvars['page'];
20861
			$this->current_column = $gvars['current_column'];
20862
			$this->num_columns = $gvars['num_columns'];
20863
		}
20864
		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20865
		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20866
			$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20867
		}
20868
	}
20869
 
20870
	/**
20871
	 * Outputs the "save graphics state" operator 'q'
20872
	 * @protected
20873
	 */
20874
	protected function _outSaveGraphicsState() {
20875
		$this->_out('q');
20876
	}
20877
 
20878
	/**
20879
	 * Outputs the "restore graphics state" operator 'Q'
20880
	 * @protected
20881
	 */
20882
	protected function _outRestoreGraphicsState() {
20883
		$this->_out('Q');
20884
	}
20885
 
20886
	/**
20887
	 * Set buffer content (always append data).
20888
	 * @param string $data data
20889
	 * @protected
20890
	 * @since 4.5.000 (2009-01-02)
20891
	 */
20892
	protected function setBuffer($data) {
20893
		$this->bufferlen += strlen($data);
20894
		$this->buffer .= $data;
20895
	}
20896
 
20897
	/**
20898
	 * Replace the buffer content
20899
	 * @param string $data data
20900
	 * @protected
20901
	 * @since 5.5.000 (2010-06-22)
20902
	 */
20903
	protected function replaceBuffer($data) {
20904
		$this->bufferlen = strlen($data);
20905
		$this->buffer = $data;
20906
	}
20907
 
20908
	/**
20909
	 * Get buffer content.
20910
	 * @return string buffer content
20911
	 * @protected
20912
	 * @since 4.5.000 (2009-01-02)
20913
	 */
20914
	protected function getBuffer() {
20915
		return $this->buffer;
20916
	}
20917
 
20918
	/**
20919
	 * Set page buffer content.
20920
	 * @param int $page page number
20921
	 * @param string $data page data
20922
	 * @param boolean $append if true append data, false replace.
20923
	 * @protected
20924
	 * @since 4.5.000 (2008-12-31)
20925
	 */
20926
	protected function setPageBuffer($page, $data, $append=false) {
20927
		if ($append) {
20928
			$this->pages[$page] .= $data;
20929
		} else {
20930
			$this->pages[$page] = $data;
20931
		}
20932
		if ($append AND isset($this->pagelen[$page])) {
20933
			$this->pagelen[$page] += strlen($data);
20934
		} else {
20935
			$this->pagelen[$page] = strlen($data);
20936
		}
20937
	}
20938
 
20939
	/**
20940
	 * Get page buffer content.
20941
	 * @param int $page page number
20942
	 * @return string page buffer content or false in case of error
20943
	 * @protected
20944
	 * @since 4.5.000 (2008-12-31)
20945
	 */
20946
	protected function getPageBuffer($page) {
20947
		if (isset($this->pages[$page])) {
20948
			return $this->pages[$page];
20949
		}
20950
		return false;
20951
	}
20952
 
20953
	/**
20954
	 * Set image buffer content.
20955
	 * @param string $image image key
20956
	 * @param array $data image data
20957
	 * @return int image index number
20958
	 * @protected
20959
	 * @since 4.5.000 (2008-12-31)
20960
	 */
20961
	protected function setImageBuffer($image, $data) {
20962
		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20963
			$this->imagekeys[$this->numimages] = $image;
20964
			$data['i'] = $this->numimages;
20965
			++$this->numimages;
20966
		}
20967
		$this->images[$image] = $data;
20968
		return $data['i'];
20969
	}
20970
 
20971
	/**
20972
	 * Set image buffer content for a specified sub-key.
20973
	 * @param string $image image key
20974
	 * @param string $key image sub-key
20975
	 * @param array $data image data
20976
	 * @protected
20977
	 * @since 4.5.000 (2008-12-31)
20978
	 */
20979
	protected function setImageSubBuffer($image, $key, $data) {
20980
		if (!isset($this->images[$image])) {
20981
			$this->setImageBuffer($image, array());
20982
		}
20983
		$this->images[$image][$key] = $data;
20984
	}
20985
 
20986
	/**
20987
	 * Get image buffer content.
20988
	 * @param string $image image key
20989
	 * @return string|false image buffer content or false in case of error
20990
	 * @protected
20991
	 * @since 4.5.000 (2008-12-31)
20992
	 */
20993
	protected function getImageBuffer($image) {
20994
		if (isset($this->images[$image])) {
20995
			return $this->images[$image];
20996
		}
20997
		return false;
20998
	}
20999
 
21000
	/**
21001
	 * Set font buffer content.
21002
	 * @param string $font font key
21003
	 * @param array $data font data
21004
	 * @protected
21005
	 * @since 4.5.000 (2009-01-02)
21006
	 */
21007
	protected function setFontBuffer($font, $data) {
21008
		$this->fonts[$font] = $data;
21009
		if (!in_array($font, $this->fontkeys)) {
21010
			$this->fontkeys[] = $font;
21011
			// store object ID for current font
21012
			++$this->n;
21013
			$this->font_obj_ids[$font] = $this->n;
21014
			$this->setFontSubBuffer($font, 'n', $this->n);
21015
		}
21016
	}
21017
 
21018
	/**
21019
	 * Set font buffer content.
21020
	 * @param string $font font key
21021
	 * @param string $key font sub-key
21022
	 * @param mixed $data font data
21023
	 * @protected
21024
	 * @since 4.5.000 (2009-01-02)
21025
	 */
21026
	protected function setFontSubBuffer($font, $key, $data) {
21027
		if (!isset($this->fonts[$font])) {
21028
			$this->setFontBuffer($font, array());
21029
		}
21030
		$this->fonts[$font][$key] = $data;
21031
	}
21032
 
21033
	/**
21034
	 * Get font buffer content.
21035
	 * @param string $font font key
21036
	 * @return string|false font buffer content or false in case of error
21037
	 * @protected
21038
	 * @since 4.5.000 (2009-01-02)
21039
	 */
21040
	protected function getFontBuffer($font) {
21041
		if (isset($this->fonts[$font])) {
21042
			return $this->fonts[$font];
21043
		}
21044
		return false;
21045
	}
21046
 
21047
	/**
21048
	 * Move a page to a previous position.
21049
	 * @param int $frompage number of the source page
21050
	 * @param int $topage number of the destination page (must be less than $frompage)
21051
	 * @return bool true in case of success, false in case of error.
21052
	 * @public
21053
	 * @since 4.5.000 (2009-01-02)
21054
	 */
21055
	public function movePage($frompage, $topage) {
21056
		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21057
			return false;
21058
		}
21059
		if ($frompage == $this->page) {
21060
			// close the page before moving it
21061
			$this->endPage();
21062
		}
21063
		// move all page-related states
21064
		$tmppage = $this->getPageBuffer($frompage);
21065
		$tmppagedim = $this->pagedim[$frompage];
21066
		$tmppagelen = $this->pagelen[$frompage];
21067
		$tmpintmrk = $this->intmrk[$frompage];
21068
		$tmpbordermrk = $this->bordermrk[$frompage];
21069
		$tmpcntmrk = $this->cntmrk[$frompage];
21070
		$tmppageobjects = $this->pageobjects[$frompage];
21071
		if (isset($this->footerpos[$frompage])) {
21072
			$tmpfooterpos = $this->footerpos[$frompage];
21073
		}
21074
		if (isset($this->footerlen[$frompage])) {
21075
			$tmpfooterlen = $this->footerlen[$frompage];
21076
		}
21077
		if (isset($this->transfmrk[$frompage])) {
21078
			$tmptransfmrk = $this->transfmrk[$frompage];
21079
		}
21080
		if (isset($this->PageAnnots[$frompage])) {
21081
			$tmpannots = $this->PageAnnots[$frompage];
21082
		}
21083
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21084
			for ($i = $frompage; $i > $topage; --$i) {
21085
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21086
					--$this->pagegroups[$this->newpagegroup[$i]];
21087
					break;
21088
				}
21089
			}
21090
			for ($i = $topage; $i > 0; --$i) {
21091
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21092
					++$this->pagegroups[$this->newpagegroup[$i]];
21093
					break;
21094
				}
21095
			}
21096
		}
21097
		for ($i = $frompage; $i > $topage; --$i) {
21098
			$j = $i - 1;
21099
			// shift pages down
21100
			$this->setPageBuffer($i, $this->getPageBuffer($j));
21101
			$this->pagedim[$i] = $this->pagedim[$j];
21102
			$this->pagelen[$i] = $this->pagelen[$j];
21103
			$this->intmrk[$i] = $this->intmrk[$j];
21104
			$this->bordermrk[$i] = $this->bordermrk[$j];
21105
			$this->cntmrk[$i] = $this->cntmrk[$j];
21106
			$this->pageobjects[$i] = $this->pageobjects[$j];
21107
			if (isset($this->footerpos[$j])) {
21108
				$this->footerpos[$i] = $this->footerpos[$j];
21109
			} elseif (isset($this->footerpos[$i])) {
21110
				unset($this->footerpos[$i]);
21111
			}
21112
			if (isset($this->footerlen[$j])) {
21113
				$this->footerlen[$i] = $this->footerlen[$j];
21114
			} elseif (isset($this->footerlen[$i])) {
21115
				unset($this->footerlen[$i]);
21116
			}
21117
			if (isset($this->transfmrk[$j])) {
21118
				$this->transfmrk[$i] = $this->transfmrk[$j];
21119
			} elseif (isset($this->transfmrk[$i])) {
21120
				unset($this->transfmrk[$i]);
21121
			}
21122
			if (isset($this->PageAnnots[$j])) {
21123
				$this->PageAnnots[$i] = $this->PageAnnots[$j];
21124
			} elseif (isset($this->PageAnnots[$i])) {
21125
				unset($this->PageAnnots[$i]);
21126
			}
21127
			if (isset($this->newpagegroup[$j])) {
21128
				$this->newpagegroup[$i] = $this->newpagegroup[$j];
21129
				unset($this->newpagegroup[$j]);
21130
			}
21131
			if ($this->currpagegroup == $j) {
21132
				$this->currpagegroup = $i;
21133
			}
21134
		}
21135
		$this->setPageBuffer($topage, $tmppage);
21136
		$this->pagedim[$topage] = $tmppagedim;
21137
		$this->pagelen[$topage] = $tmppagelen;
21138
		$this->intmrk[$topage] = $tmpintmrk;
21139
		$this->bordermrk[$topage] = $tmpbordermrk;
21140
		$this->cntmrk[$topage] = $tmpcntmrk;
21141
		$this->pageobjects[$topage] = $tmppageobjects;
21142
		if (isset($tmpfooterpos)) {
21143
			$this->footerpos[$topage] = $tmpfooterpos;
21144
		} elseif (isset($this->footerpos[$topage])) {
21145
			unset($this->footerpos[$topage]);
21146
		}
21147
		if (isset($tmpfooterlen)) {
21148
			$this->footerlen[$topage] = $tmpfooterlen;
21149
		} elseif (isset($this->footerlen[$topage])) {
21150
			unset($this->footerlen[$topage]);
21151
		}
21152
		if (isset($tmptransfmrk)) {
21153
			$this->transfmrk[$topage] = $tmptransfmrk;
21154
		} elseif (isset($this->transfmrk[$topage])) {
21155
			unset($this->transfmrk[$topage]);
21156
		}
21157
		if (isset($tmpannots)) {
21158
			$this->PageAnnots[$topage] = $tmpannots;
21159
		} elseif (isset($this->PageAnnots[$topage])) {
21160
			unset($this->PageAnnots[$topage]);
21161
		}
21162
		// adjust outlines
21163
		$tmpoutlines = $this->outlines;
21164
		foreach ($tmpoutlines as $key => $outline) {
21165
			if (!$outline['f']) {
21166
				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21167
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
21168
				} elseif ($outline['p'] == $frompage) {
21169
					$this->outlines[$key]['p'] = $topage;
21170
				}
21171
			}
21172
		}
21173
		// adjust dests
21174
		$tmpdests = $this->dests;
21175
		foreach ($tmpdests as $key => $dest) {
21176
			if (!$dest['f']) {
21177
				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21178
					$this->dests[$key]['p'] = ($dest['p'] + 1);
21179
				} elseif ($dest['p'] == $frompage) {
21180
					$this->dests[$key]['p'] = $topage;
21181
				}
21182
			}
21183
		}
21184
		// adjust links
21185
		$tmplinks = $this->links;
21186
		foreach ($tmplinks as $key => $link) {
21187
			if (!$link['f']) {
21188
				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21189
					$this->links[$key]['p'] = ($link['p'] + 1);
21190
				} elseif ($link['p'] == $frompage) {
21191
					$this->links[$key]['p'] = $topage;
21192
				}
21193
			}
21194
		}
21195
		// adjust javascript
21196
		$jfrompage = $frompage;
21197
		$jtopage = $topage;
21198
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21199
			foreach($pamatch[0] as $pk => $pmatch) {
21200
				$pagenum = intval($pamatch[3][$pk]) + 1;
21201
				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21202
					$newpage = ($pagenum + 1);
21203
				} elseif ($pagenum == $jfrompage) {
21204
					$newpage = $jtopage;
21205
				} else {
21206
					$newpage = $pagenum;
21207
				}
21208
				--$newpage;
21209
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21210
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21211
			}
21212
			unset($pamatch);
21213
		}
21214
		// return to last page
21215
		$this->lastPage(true);
21216
		return true;
21217
	}
21218
 
21219
	/**
21220
	 * Remove the specified page.
21221
	 * @param int $page page to remove
21222
	 * @return bool true in case of success, false in case of error.
21223
	 * @public
21224
	 * @since 4.6.004 (2009-04-23)
21225
	 */
21226
	public function deletePage($page) {
21227
		if (($page < 1) OR ($page > $this->numpages)) {
21228
			return false;
21229
		}
21230
		// delete current page
21231
		unset($this->pages[$page]);
21232
		unset($this->pagedim[$page]);
21233
		unset($this->pagelen[$page]);
21234
		unset($this->intmrk[$page]);
21235
		unset($this->bordermrk[$page]);
21236
		unset($this->cntmrk[$page]);
21237
		foreach ($this->pageobjects[$page] as $oid) {
21238
			if (isset($this->offsets[$oid])){
21239
				unset($this->offsets[$oid]);
21240
			}
21241
		}
21242
		unset($this->pageobjects[$page]);
21243
		if (isset($this->footerpos[$page])) {
21244
			unset($this->footerpos[$page]);
21245
		}
21246
		if (isset($this->footerlen[$page])) {
21247
			unset($this->footerlen[$page]);
21248
		}
21249
		if (isset($this->transfmrk[$page])) {
21250
			unset($this->transfmrk[$page]);
21251
		}
21252
		if (isset($this->PageAnnots[$page])) {
21253
			unset($this->PageAnnots[$page]);
21254
		}
21255
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21256
			for ($i = $page; $i > 0; --$i) {
21257
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21258
					--$this->pagegroups[$this->newpagegroup[$i]];
21259
					break;
21260
				}
21261
			}
21262
		}
21263
		if (isset($this->pageopen[$page])) {
21264
			unset($this->pageopen[$page]);
21265
		}
21266
		if ($page < $this->numpages) {
21267
			// update remaining pages
21268
			for ($i = $page; $i < $this->numpages; ++$i) {
21269
				$j = $i + 1;
21270
				// shift pages
21271
				$this->setPageBuffer($i, $this->getPageBuffer($j));
21272
				$this->pagedim[$i] = $this->pagedim[$j];
21273
				$this->pagelen[$i] = $this->pagelen[$j];
21274
				$this->intmrk[$i] = $this->intmrk[$j];
21275
				$this->bordermrk[$i] = $this->bordermrk[$j];
21276
				$this->cntmrk[$i] = $this->cntmrk[$j];
21277
				$this->pageobjects[$i] = $this->pageobjects[$j];
21278
				if (isset($this->footerpos[$j])) {
21279
					$this->footerpos[$i] = $this->footerpos[$j];
21280
				} elseif (isset($this->footerpos[$i])) {
21281
					unset($this->footerpos[$i]);
21282
				}
21283
				if (isset($this->footerlen[$j])) {
21284
					$this->footerlen[$i] = $this->footerlen[$j];
21285
				} elseif (isset($this->footerlen[$i])) {
21286
					unset($this->footerlen[$i]);
21287
				}
21288
				if (isset($this->transfmrk[$j])) {
21289
					$this->transfmrk[$i] = $this->transfmrk[$j];
21290
				} elseif (isset($this->transfmrk[$i])) {
21291
					unset($this->transfmrk[$i]);
21292
				}
21293
				if (isset($this->PageAnnots[$j])) {
21294
					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21295
				} elseif (isset($this->PageAnnots[$i])) {
21296
					unset($this->PageAnnots[$i]);
21297
				}
21298
				if (isset($this->newpagegroup[$j])) {
21299
					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21300
					unset($this->newpagegroup[$j]);
21301
				}
21302
				if ($this->currpagegroup == $j) {
21303
					$this->currpagegroup = $i;
21304
				}
21305
				if (isset($this->pageopen[$j])) {
21306
					$this->pageopen[$i] = $this->pageopen[$j];
21307
				} elseif (isset($this->pageopen[$i])) {
21308
					unset($this->pageopen[$i]);
21309
				}
21310
			}
21311
			// remove last page
21312
			unset($this->pages[$this->numpages]);
21313
			unset($this->pagedim[$this->numpages]);
21314
			unset($this->pagelen[$this->numpages]);
21315
			unset($this->intmrk[$this->numpages]);
21316
			unset($this->bordermrk[$this->numpages]);
21317
			unset($this->cntmrk[$this->numpages]);
21318
			foreach ($this->pageobjects[$this->numpages] as $oid) {
21319
				if (isset($this->offsets[$oid])){
21320
					unset($this->offsets[$oid]);
21321
				}
21322
			}
21323
			unset($this->pageobjects[$this->numpages]);
21324
			if (isset($this->footerpos[$this->numpages])) {
21325
				unset($this->footerpos[$this->numpages]);
21326
			}
21327
			if (isset($this->footerlen[$this->numpages])) {
21328
				unset($this->footerlen[$this->numpages]);
21329
			}
21330
			if (isset($this->transfmrk[$this->numpages])) {
21331
				unset($this->transfmrk[$this->numpages]);
21332
			}
21333
			if (isset($this->PageAnnots[$this->numpages])) {
21334
				unset($this->PageAnnots[$this->numpages]);
21335
			}
21336
			if (isset($this->newpagegroup[$this->numpages])) {
21337
				unset($this->newpagegroup[$this->numpages]);
21338
			}
21339
			if ($this->currpagegroup == $this->numpages) {
21340
				$this->currpagegroup = ($this->numpages - 1);
21341
			}
21342
			if (isset($this->pagegroups[$this->numpages])) {
21343
				unset($this->pagegroups[$this->numpages]);
21344
			}
21345
			if (isset($this->pageopen[$this->numpages])) {
21346
				unset($this->pageopen[$this->numpages]);
21347
			}
21348
		}
21349
		--$this->numpages;
21350
		$this->page = $this->numpages;
21351
		// adjust outlines
21352
		$tmpoutlines = $this->outlines;
21353
		foreach ($tmpoutlines as $key => $outline) {
21354
			if (!$outline['f']) {
21355
				if ($outline['p'] > $page) {
21356
					$this->outlines[$key]['p'] = $outline['p'] - 1;
21357
				} elseif ($outline['p'] == $page) {
21358
					unset($this->outlines[$key]);
21359
				}
21360
			}
21361
		}
21362
		// adjust dests
21363
		$tmpdests = $this->dests;
21364
		foreach ($tmpdests as $key => $dest) {
21365
			if (!$dest['f']) {
21366
				if ($dest['p'] > $page) {
21367
					$this->dests[$key]['p'] = $dest['p'] - 1;
21368
				} elseif ($dest['p'] == $page) {
21369
					unset($this->dests[$key]);
21370
				}
21371
			}
21372
		}
21373
		// adjust links
21374
		$tmplinks = $this->links;
21375
		foreach ($tmplinks as $key => $link) {
21376
			if (!$link['f']) {
21377
				if ($link['p'] > $page) {
21378
					$this->links[$key]['p'] = $link['p'] - 1;
21379
				} elseif ($link['p'] == $page) {
21380
					unset($this->links[$key]);
21381
				}
21382
			}
21383
		}
21384
		// adjust javascript
21385
		$jpage = $page;
21386
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21387
			foreach($pamatch[0] as $pk => $pmatch) {
21388
				$pagenum = intval($pamatch[3][$pk]) + 1;
21389
				if ($pagenum >= $jpage) {
21390
					$newpage = ($pagenum - 1);
21391
				} elseif ($pagenum == $jpage) {
21392
					$newpage = 1;
21393
				} else {
21394
					$newpage = $pagenum;
21395
				}
21396
				--$newpage;
21397
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21398
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21399
			}
21400
			unset($pamatch);
21401
		}
21402
		// return to last page
21403
		if ($this->numpages > 0) {
21404
			$this->lastPage(true);
21405
		}
21406
		return true;
21407
	}
21408
 
21409
	/**
21410
	 * Clone the specified page to a new page.
21411
	 * @param int $page number of page to copy (0 = current page)
21412
	 * @return bool true in case of success, false in case of error.
21413
	 * @public
21414
	 * @since 4.9.015 (2010-04-20)
21415
	 */
21416
	public function copyPage($page=0) {
21417
		if ($page == 0) {
21418
			// default value
21419
			$page = $this->page;
21420
		}
21421
		if (($page < 1) OR ($page > $this->numpages)) {
21422
			return false;
21423
		}
21424
		// close the last page
21425
		$this->endPage();
21426
		// copy all page-related states
21427
		++$this->numpages;
21428
		$this->page = $this->numpages;
21429
		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21430
		$this->pagedim[$this->page] = $this->pagedim[$page];
21431
		$this->pagelen[$this->page] = $this->pagelen[$page];
21432
		$this->intmrk[$this->page] = $this->intmrk[$page];
21433
		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21434
		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21435
		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21436
		$this->pageopen[$this->page] = false;
21437
		if (isset($this->footerpos[$page])) {
21438
			$this->footerpos[$this->page] = $this->footerpos[$page];
21439
		}
21440
		if (isset($this->footerlen[$page])) {
21441
			$this->footerlen[$this->page] = $this->footerlen[$page];
21442
		}
21443
		if (isset($this->transfmrk[$page])) {
21444
			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21445
		}
21446
		if (isset($this->PageAnnots[$page])) {
21447
			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21448
		}
21449
		if (isset($this->newpagegroup[$page])) {
21450
			// start a new group
21451
			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21452
			$this->currpagegroup = $this->newpagegroup[$this->page];
21453
			$this->pagegroups[$this->currpagegroup] = 1;
21454
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21455
			++$this->pagegroups[$this->currpagegroup];
21456
		}
21457
		// copy outlines
21458
		$tmpoutlines = $this->outlines;
21459
		foreach ($tmpoutlines as $key => $outline) {
21460
			if ($outline['p'] == $page) {
21461
				$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']);
21462
			}
21463
		}
21464
		// copy links
21465
		$tmplinks = $this->links;
21466
		foreach ($tmplinks as $key => $link) {
21467
			if ($link['p'] == $page) {
21468
				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21469
			}
21470
		}
21471
		// return to last page
21472
		$this->lastPage(true);
21473
		return true;
21474
	}
21475
 
21476
	/**
21477
	 * Output a Table of Content Index (TOC).
21478
	 * This method must be called after all Bookmarks were set.
21479
	 * Before calling this method you have to open the page using the addTOCPage() method.
21480
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21481
	 * You can override this method to achieve different styles.
21482
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21483
	 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21484
	 * @param string $filler string used to fill the space between text and page number.
21485
	 * @param string $toc_name name to use for TOC bookmark.
21486
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21487
	 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21488
	 * @public
21489
	 * @author Nicola Asuni
21490
	 * @since 4.5.000 (2009-01-02)
21491
	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21492
	 */
21493
	public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21494
		$fontsize = $this->FontSizePt;
21495
		$fontfamily = $this->FontFamily;
21496
		$fontstyle = $this->FontStyle;
21497
		$w = $this->w - $this->lMargin - $this->rMargin;
21498
		$spacer = $this->GetStringWidth(chr(32)) * 4;
21499
		$lmargin = $this->lMargin;
21500
		$rmargin = $this->rMargin;
21501
		$x_start = $this->GetX();
21502
		$page_first = $this->page;
21503
		$current_page = $this->page;
21504
		$page_fill_start = false;
21505
		$page_fill_end = false;
21506
		$current_column = $this->current_column;
21507
		if (TCPDF_STATIC::empty_string($numbersfont)) {
21508
			$numbersfont = $this->default_monospaced_font;
21509
		}
21510
		if (TCPDF_STATIC::empty_string($filler)) {
21511
			$filler = ' ';
21512
		}
21513
		if (TCPDF_STATIC::empty_string($page)) {
21514
			$gap = ' ';
21515
		} else {
21516
			$gap = '';
21517
			if ($page < 1) {
21518
				$page = 1;
21519
			}
21520
		}
21521
		$this->setFont($numbersfont, $fontstyle, $fontsize);
21522
		$numwidth = $this->GetStringWidth('00000');
21523
		$maxpage = 0; //used for pages on attached documents
21524
		foreach ($this->outlines as $key => $outline) {
21525
			// check for extra pages (used for attachments)
21526
			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21527
				$outline['p'] += ($this->page - $page_first);
21528
			}
21529
			if ($this->rtl) {
21530
				$aligntext = 'R';
21531
				$alignnum = 'L';
21532
			} else {
21533
				$aligntext = 'L';
21534
				$alignnum = 'R';
21535
			}
21536
			if ($outline['l'] == 0) {
21537
				$this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21538
			} else {
21539
				$this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21540
			}
21541
			$this->setTextColorArray($outline['c']);
21542
			// check for page break
21543
			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21544
			// set margins and X position
21545
			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21546
				$this->lMargin = $lmargin;
21547
				$this->rMargin = $rmargin;
21548
			} else {
21549
				if ($this->current_column != $current_column) {
21550
					if ($this->rtl) {
21551
						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21552
					} else {
21553
						$x_start = $this->columns[$this->current_column]['x'];
21554
					}
21555
				}
21556
				$lmargin = $this->lMargin;
21557
				$rmargin = $this->rMargin;
21558
				$current_page = $this->page;
21559
				$current_column = $this->current_column;
21560
			}
21561
			$this->setX($x_start);
21562
			$indent = ($spacer * $outline['l']);
21563
			if ($this->rtl) {
21564
				$this->x -= $indent;
21565
				$this->rMargin = $this->w - $this->x;
21566
			} else {
21567
				$this->x += $indent;
21568
				$this->lMargin = $this->x;
21569
			}
21570
			$link = $this->AddLink();
21571
			$this->setLink($link, $outline['y'], $outline['p']);
21572
			// write the text
21573
			if ($this->rtl) {
21574
				$txt = ' '.$outline['t'];
21575
			} else {
21576
				$txt = $outline['t'].' ';
21577
			}
21578
			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21579
			if ($this->rtl) {
21580
				$tw = $this->x - $this->lMargin;
21581
			} else {
21582
				$tw = $this->w - $this->rMargin - $this->x;
21583
			}
21584
			$this->setFont($numbersfont, $fontstyle, $fontsize);
21585
			if (TCPDF_STATIC::empty_string($page)) {
21586
				$pagenum = $outline['p'];
21587
			} else {
21588
				// placemark to be replaced with the correct number
21589
				$pagenum = '{#'.($outline['p']).'}';
21590
				if ($this->isUnicodeFont()) {
21591
					$pagenum = '{'.$pagenum.'}';
21592
				}
21593
				$maxpage = max($maxpage, $outline['p']);
21594
			}
21595
			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21596
			$wfiller = $this->GetStringWidth($filler);
21597
			if ($wfiller > 0) {
21598
				$numfills = floor($fw / $wfiller);
21599
			} else {
21600
				$numfills = 0;
21601
			}
21602
			if ($numfills > 0) {
21603
				$rowfill = str_repeat($filler, $numfills);
21604
			} else {
21605
				$rowfill = '';
21606
			}
21607
			if ($this->rtl) {
21608
				$pagenum = $pagenum.$gap.$rowfill;
21609
			} else {
21610
				$pagenum = $rowfill.$gap.$pagenum;
21611
			}
21612
			// write the number
21613
			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21614
		}
21615
		$page_last = $this->getPage();
21616
		$numpages = ($page_last - $page_first + 1);
21617
		// account for booklet mode
21618
		if ($this->booklet) {
21619
			// check if a blank page is required before TOC
21620
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21621
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21622
			if ($page_fill_start) {
21623
				// add a page at the end (to be moved before TOC)
21624
				$this->addPage();
21625
				++$page_last;
21626
				++$numpages;
21627
			}
21628
			if ($page_fill_end) {
21629
				// add a page at the end
21630
				$this->addPage();
21631
				++$page_last;
21632
				++$numpages;
21633
			}
21634
		}
21635
		$maxpage = max($maxpage, $page_last);
21636
		if (!TCPDF_STATIC::empty_string($page)) {
21637
			for ($p = $page_first; $p <= $page_last; ++$p) {
21638
				// get page data
21639
				$temppage = $this->getPageBuffer($p);
21640
				for ($n = 1; $n <= $maxpage; ++$n) {
21641
					// update page numbers
21642
					$a = '{#'.$n.'}';
21643
					// get page number aliases
21644
					$pnalias = $this->getInternalPageNumberAliases($a);
21645
					// calculate replacement number
21646
					if (($n >= $page) AND ($n <= $this->numpages)) {
21647
						$np = $n + $numpages;
21648
					} else {
21649
						$np = $n;
21650
					}
21651
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21652
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21653
					// replace aliases with numbers
21654
					foreach ($pnalias['u'] as $u) {
21655
						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21656
						if ($this->rtl) {
21657
							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21658
						} else {
21659
							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21660
						}
21661
						$temppage = str_replace($u, $nr, $temppage);
21662
					}
21663
					foreach ($pnalias['a'] as $a) {
21664
						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21665
						if ($this->rtl) {
21666
							$nr = $na.' '.$sfill;
21667
						} else {
21668
							$nr = $sfill.' '.$na;
21669
						}
21670
						$temppage = str_replace($a, $nr, $temppage);
21671
					}
21672
				}
21673
				// save changes
21674
				$this->setPageBuffer($p, $temppage);
21675
			}
21676
			// move pages
21677
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21678
			if ($page_fill_start) {
21679
				$this->movePage($page_last, $page_first);
21680
			}
21681
			for ($i = 0; $i < $numpages; ++$i) {
21682
				$this->movePage($page_last, $page);
21683
			}
21684
		}
21685
	}
21686
 
21687
	/**
21688
	 * Output a Table Of Content Index (TOC) using HTML templates.
21689
	 * This method must be called after all Bookmarks were set.
21690
	 * Before calling this method you have to open the page using the addTOCPage() method.
21691
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21692
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21693
	 * @param string $toc_name name to use for TOC bookmark.
21694
	 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21695
	 * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21696
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21697
	 * @param array $color RGB color array for title (values from 0 to 255).
21698
	 * @public
21699
	 * @author Nicola Asuni
21700
	 * @since 5.0.001 (2010-05-06)
21701
	 * @see addTOCPage(), endTOCPage(), addTOC()
21702
	 */
21703
	public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21704
		$filler = ' ';
21705
		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21706
		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21707
		// set new style for link
21708
		$this->htmlLinkColorArray = array();
21709
		$this->htmlLinkFontStyle = '';
21710
		$page_first = $this->getPage();
21711
		$page_fill_start = false;
21712
		$page_fill_end = false;
21713
		// get the font type used for numbers in each template
21714
		$current_font = $this->FontFamily;
21715
		foreach ($templates as $level => $html) {
21716
			$dom = $this->getHtmlDomArray($html);
21717
			foreach ($dom as $key => $value) {
21718
				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21719
					$this->setFont($dom[($key - 1)]['fontname']);
21720
					$templates['F'.$level] = $this->isUnicodeFont();
21721
				}
21722
			}
21723
		}
21724
		$this->setFont($current_font);
21725
		$maxpage = 0; //used for pages on attached documents
21726
		foreach ($this->outlines as $key => $outline) {
21727
			// get HTML template
21728
			$row = $templates[$outline['l']];
21729
			if (TCPDF_STATIC::empty_string($page)) {
21730
				$pagenum = $outline['p'];
21731
			} else {
21732
				// placemark to be replaced with the correct number
21733
				$pagenum = '{#'.($outline['p']).'}';
21734
				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21735
					$pagenum = '{'.$pagenum.'}';
21736
				}
21737
				$maxpage = max($maxpage, $outline['p']);
21738
			}
21739
			// replace templates with current values
21740
			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21741
			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21742
			// add link to page
21743
			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21744
			// write bookmark entry
21745
			$this->writeHTML($row, false, false, true, false, '');
21746
		}
21747
		// restore link styles
21748
		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21749
		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21750
		// move TOC page and replace numbers
21751
		$page_last = $this->getPage();
21752
		$numpages = ($page_last - $page_first + 1);
21753
		// account for booklet mode
21754
		if ($this->booklet) {
21755
			// check if a blank page is required before TOC
21756
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21757
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21758
			if ($page_fill_start) {
21759
				// add a page at the end (to be moved before TOC)
21760
				$this->addPage();
21761
				++$page_last;
21762
				++$numpages;
21763
			}
21764
			if ($page_fill_end) {
21765
				// add a page at the end
21766
				$this->addPage();
21767
				++$page_last;
21768
				++$numpages;
21769
			}
21770
		}
21771
		$maxpage = max($maxpage, $page_last);
21772
		if (!TCPDF_STATIC::empty_string($page)) {
21773
			for ($p = $page_first; $p <= $page_last; ++$p) {
21774
				// get page data
21775
				$temppage = $this->getPageBuffer($p);
21776
				for ($n = 1; $n <= $maxpage; ++$n) {
21777
					// update page numbers
21778
					$a = '{#'.$n.'}';
21779
					// get page number aliases
21780
					$pnalias = $this->getInternalPageNumberAliases($a);
21781
					// calculate replacement number
21782
					if ($n >= $page) {
21783
						$np = $n + $numpages;
21784
					} else {
21785
						$np = $n;
21786
					}
21787
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21788
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21789
					// replace aliases with numbers
21790
					foreach ($pnalias['u'] as $u) {
21791
						if ($correct_align) {
21792
							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21793
							if ($this->rtl) {
21794
								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21795
							} else {
21796
								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21797
							}
21798
						} else {
21799
							$nr = $nu;
21800
						}
21801
						$temppage = str_replace($u, $nr, $temppage);
21802
					}
21803
					foreach ($pnalias['a'] as $a) {
21804
						if ($correct_align) {
21805
							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21806
							if ($this->rtl) {
21807
								$nr = $na.' '.$sfill;
21808
							} else {
21809
								$nr = $sfill.' '.$na;
21810
							}
21811
						} else {
21812
							$nr = $na;
21813
						}
21814
						$temppage = str_replace($a, $nr, $temppage);
21815
					}
21816
				}
21817
				// save changes
21818
				$this->setPageBuffer($p, $temppage);
21819
			}
21820
			// move pages
21821
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21822
			if ($page_fill_start) {
21823
				$this->movePage($page_last, $page_first);
21824
			}
21825
			for ($i = 0; $i < $numpages; ++$i) {
21826
				$this->movePage($page_last, $page);
21827
			}
21828
		}
21829
	}
21830
 
21831
	/**
21832
	 * Stores a copy of the current TCPDF object used for undo operation.
21833
	 * @public
21834
	 * @since 4.5.029 (2009-03-19)
21835
	 */
21836
	public function startTransaction() {
21837
		if (isset($this->objcopy)) {
21838
			// remove previous copy
21839
			$this->commitTransaction();
21840
		}
21841
		// record current page number and Y position
21842
		$this->start_transaction_page = $this->page;
21843
		$this->start_transaction_y = $this->y;
21844
		// clone current object
21845
		$this->objcopy = TCPDF_STATIC::objclone($this);
21846
	}
21847
 
21848
	/**
21849
	 * Delete the copy of the current TCPDF object used for undo operation.
21850
	 * @public
21851
	 * @since 4.5.029 (2009-03-19)
21852
	 */
21853
	public function commitTransaction() {
21854
		if (isset($this->objcopy)) {
21855
			$this->objcopy->_destroy(true, true);
21856
			/* The unique file_id should not be used during cleanup again */
21857
			$this->objcopy->file_id = NULL;
21858
			unset($this->objcopy);
21859
		}
21860
	}
21861
 
21862
	/**
21863
	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21864
	 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21865
	 * @return TCPDF object.
21866
	 * @public
21867
	 * @since 4.5.029 (2009-03-19)
21868
	 */
21869
	public function rollbackTransaction($self=false) {
21870
		if (isset($this->objcopy)) {
21871
			$objcopy = $this->objcopy;
21872
			$this->_destroy(true, true);
21873
			if ($self) {
21874
				$objvars = get_object_vars($objcopy);
21875
				foreach ($objvars as $key => $value) {
21876
					$this->$key = $value;
21877
				}
21878
				$objcopy->_destroy(true, true);
21879
				/* The unique file_id should not be used during cleanup again */
21880
				$objcopy->file_id = NULL;
21881
				unset($objcopy);
21882
				return $this;
21883
			}
21884
			/* The unique file_id should not be used during cleanup again */
21885
			$this->file_id = NULL;
21886
			return $objcopy;
21887
		}
21888
		return $this;
21889
	}
21890
 
21891
	// --- MULTI COLUMNS METHODS -----------------------
21892
 
21893
	/**
21894
	 * Set multiple columns of the same size
21895
	 * @param int $numcols number of columns (set to zero to disable columns mode)
21896
	 * @param int $width column width
21897
	 * @param int|null $y column starting Y position (leave empty for current Y position)
21898
	 * @public
21899
	 * @since 4.9.001 (2010-03-28)
21900
	 */
21901
	public function setEqualColumns($numcols=0, $width=0, $y=null) {
21902
		$this->columns = array();
21903
		if ($numcols < 2) {
21904
			$numcols = 0;
21905
			$this->columns = array();
21906
		} else {
21907
			// maximum column width
21908
			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21909
			if (($width == 0) OR ($width > $maxwidth)) {
21910
				$width = $maxwidth;
21911
			}
21912
			if (TCPDF_STATIC::empty_string($y)) {
21913
				$y = $this->y;
21914
			}
21915
			// space between columns
21916
			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21917
			// fill the columns array (with, space, starting Y position)
21918
			for ($i = 0; $i < $numcols; ++$i) {
21919
				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21920
			}
21921
		}
21922
		$this->num_columns = $numcols;
21923
		$this->current_column = 0;
21924
		$this->column_start_page = $this->page;
21925
		$this->selectColumn(0);
21926
	}
21927
 
21928
	/**
21929
	 * Remove columns and reset page margins.
21930
	 * @public
21931
	 * @since 5.9.072 (2011-04-26)
21932
	 */
21933
	public function resetColumns() {
21934
		$this->lMargin = $this->original_lMargin;
21935
		$this->rMargin = $this->original_rMargin;
21936
		$this->setEqualColumns();
21937
	}
21938
 
21939
	/**
21940
	 * Set columns array.
21941
	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21942
	 * @param array $columns
21943
	 * @public
21944
	 * @since 4.9.001 (2010-03-28)
21945
	 */
21946
	public function setColumnsArray($columns) {
21947
		$this->columns = $columns;
21948
		$this->num_columns = count($columns);
21949
		$this->current_column = 0;
21950
		$this->column_start_page = $this->page;
21951
		$this->selectColumn(0);
21952
	}
21953
 
21954
	/**
21955
	 * Set position at a given column
21956
	 * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21957
	 * @public
21958
	 * @since 4.9.001 (2010-03-28)
21959
	 */
21960
	public function selectColumn($col=null) {
21961
		if (TCPDF_STATIC::empty_string($col)) {
21962
			$col = $this->current_column;
21963
		} elseif ($col >= $this->num_columns) {
21964
			$col = 0;
21965
		}
21966
		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21967
		$enable_thead = false;
21968
		if ($this->num_columns > 1) {
21969
			if ($col != $this->current_column) {
21970
				// move Y pointer at the top of the column
21971
				if ($this->column_start_page == $this->page) {
21972
					$this->y = $this->columns[$col]['y'];
21973
				} else {
21974
					$this->y = $this->tMargin;
21975
				}
21976
				// Avoid to write table headers more than once
21977
				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21978
					$enable_thead = true;
21979
					$this->maxselcol['page'] = $this->page;
21980
					$this->maxselcol['column'] = $col;
21981
				}
21982
			}
21983
			$xshift = $this->colxshift;
21984
			// set X position of the current column by case
21985
			$listindent = ($this->listindentlevel * $this->listindent);
21986
			// calculate column X position
21987
			$colpos = 0;
21988
			for ($i = 0; $i < $col; ++$i) {
21989
				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21990
			}
21991
			if ($this->rtl) {
21992
				$x = $this->w - $this->original_rMargin - $colpos;
21993
				$this->rMargin = ($this->w - $x + $listindent);
21994
				$this->lMargin = ($x - $this->columns[$col]['w']);
21995
				$this->x = $x - $listindent;
21996
			} else {
21997
				$x = $this->original_lMargin + $colpos;
21998
				$this->lMargin = ($x + $listindent);
21999
				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
22000
				$this->x = $x + $listindent;
22001
			}
22002
			$this->columns[$col]['x'] = $x;
22003
		}
22004
		$this->current_column = $col;
22005
		// fix for HTML mode
22006
		$this->newline = true;
22007
		// print HTML table header (if any)
22008
		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
22009
			if ($enable_thead) {
22010
				// print table header
22011
				$this->writeHTML($this->thead, false, false, false, false, '');
22012
				$this->y += $xshift['s']['V'];
22013
				// store end of header position
22014
				if (!isset($this->columns[$col]['th'])) {
22015
					$this->columns[$col]['th'] = array();
22016
				}
22017
				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22018
				$this->lasth = 0;
22019
			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22020
				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22021
			}
22022
		}
22023
		// account for an html table cell over multiple columns
22024
		if ($this->rtl) {
22025
			$this->rMargin += $xshift['x'];
22026
			$this->x -= ($xshift['x'] + $xshift['p']['R']);
22027
		} else {
22028
			$this->lMargin += $xshift['x'];
22029
			$this->x += $xshift['x'] + $xshift['p']['L'];
22030
		}
22031
	}
22032
 
22033
	/**
22034
	 * Return the current column number
22035
	 * @return int current column number
22036
	 * @public
22037
	 * @since 5.5.011 (2010-07-08)
22038
	 */
22039
	public function getColumn() {
22040
		return $this->current_column;
22041
	}
22042
 
22043
	/**
22044
	 * Return the current number of columns.
22045
	 * @return int number of columns
22046
	 * @public
22047
	 * @since 5.8.018 (2010-08-25)
22048
	 */
22049
	public function getNumberOfColumns() {
22050
		return $this->num_columns;
22051
	}
22052
 
22053
	/**
22054
	 * Set Text rendering mode.
22055
	 * @param int $stroke outline size in user units (0 = disable).
22056
	 * @param boolean $fill if true fills the text (default).
22057
	 * @param boolean $clip if true activate clipping mode
22058
	 * @public
22059
	 * @since 4.9.008 (2009-04-02)
22060
	 */
22061
	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22062
		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22063
		// convert text rendering parameters
22064
		if ($stroke < 0 || !is_numeric($stroke)) {
22065
			$stroke = 0;
22066
		}
22067
		if ($fill === true) {
22068
			if ($stroke > 0) {
22069
				if ($clip === true) {
22070
					// Fill, then stroke text and add to path for clipping
22071
					$textrendermode = 6;
22072
				} else {
22073
					// Fill, then stroke text
22074
					$textrendermode = 2;
22075
				}
22076
				$textstrokewidth = $stroke;
22077
			} else {
22078
				if ($clip === true) {
22079
					// Fill text and add to path for clipping
22080
					$textrendermode = 4;
22081
				} else {
22082
					// Fill text
22083
					$textrendermode = 0;
22084
				}
22085
			}
22086
		} else {
22087
			if ($stroke > 0) {
22088
				if ($clip === true) {
22089
					// Stroke text and add to path for clipping
22090
					$textrendermode = 5;
22091
				} else {
22092
					// Stroke text
22093
					$textrendermode = 1;
22094
				}
22095
				$textstrokewidth = $stroke;
22096
			} else {
22097
				if ($clip === true) {
22098
					// Add text to path for clipping
22099
					$textrendermode = 7;
22100
				} else {
22101
					// Neither fill nor stroke text (invisible)
22102
					$textrendermode = 3;
22103
				}
22104
			}
22105
		}
22106
		$this->textrendermode = $textrendermode;
22107
		$this->textstrokewidth = $stroke;
22108
	}
22109
 
22110
	/**
22111
	 * Set parameters for drop shadow effect for text.
22112
	 * @param array $params Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
22113
	 * @since 5.9.174 (2012-07-25)
22114
	 * @public
22115
	*/
22116
	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22117
		if (isset($params['enabled'])) {
22118
			$this->txtshadow['enabled'] = $params['enabled']?true:false;
22119
		} else {
22120
			$this->txtshadow['enabled'] = false;
22121
		}
22122
		if (isset($params['depth_w'])) {
22123
			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
22124
		} else {
22125
			$this->txtshadow['depth_w'] = 0;
22126
		}
22127
		if (isset($params['depth_h'])) {
22128
			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
22129
		} else {
22130
			$this->txtshadow['depth_h'] = 0;
22131
		}
22132
		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22133
			$this->txtshadow['color'] = $params['color'];
22134
		} else {
22135
			$this->txtshadow['color'] = $this->strokecolor;
22136
		}
22137
		if (isset($params['opacity'])) {
22138
			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22139
		} else {
22140
			$this->txtshadow['opacity'] = 1;
22141
		}
22142
		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'))) {
22143
			$this->txtshadow['blend_mode'] = $params['blend_mode'];
22144
		} else {
22145
			$this->txtshadow['blend_mode'] = 'Normal';
22146
		}
22147
		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22148
			$this->txtshadow['enabled'] = false;
22149
		}
22150
	}
22151
 
22152
	/**
22153
	 * Return the text shadow parameters array.
22154
	 * @return array array of parameters.
22155
	 * @since 5.9.174 (2012-07-25)
22156
	 * @public
22157
	 */
22158
	public function getTextShadow() {
22159
		return $this->txtshadow;
22160
	}
22161
 
22162
	/**
22163
	 * Returns an array of chars containing soft hyphens.
22164
	 * @param array $word array of chars
22165
	 * @param array $patterns Array of hypenation patterns.
22166
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22167
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22168
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22169
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22170
	 * @param int $charmax Maximum length of broken piece of word.
22171
	 * @return array text with soft hyphens
22172
	 * @author Nicola Asuni
22173
	 * @since 4.9.012 (2010-04-12)
22174
	 * @protected
22175
	 */
22176
	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22177
		$hyphenword = array(); // hyphens positions
22178
		$numchars = count($word);
22179
		if ($numchars <= $charmin) {
22180
			return $word;
22181
		}
22182
		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22183
		// some words will be returned as-is
22184
		$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})(\]?)$/';
22185
		if (preg_match($pattern, $word_string) > 0) {
22186
			// email
22187
			return $word;
22188
		}
22189
		$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})(\]?)$/';
22190
		if (preg_match($pattern, $word_string) > 0) {
22191
			// URL
22192
			return $word;
22193
		}
22194
		if (isset($dictionary[$word_string])) {
22195
			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22196
		}
22197
		// surround word with '_' characters
22198
		$tmpword = array_merge(array(46), $word, array(46));
22199
		$tmpnumchars = $numchars + 2;
22200
		$maxpos = $tmpnumchars - 1;
22201
		for ($pos = 0; $pos < $maxpos; ++$pos) {
22202
			$imax = min(($tmpnumchars - $pos), $charmax);
22203
			for ($i = 1; $i <= $imax; ++$i) {
22204
				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22205
				if (isset($patterns[$subword])) {
22206
					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22207
					$pattern_length = count($pattern);
22208
					$digits = 1;
22209
					for ($j = 0; $j < $pattern_length; ++$j) {
22210
						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22211
						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22212
							if ($j == 0) {
22213
								$zero = $pos - 1;
22214
							} else {
22215
								$zero = $pos + $j - $digits;
22216
							}
22217
							// get hyphenation level
22218
							$level = ($pattern[$j] - 48);
22219
							// if two levels from two different patterns match at the same point, the higher one is selected.
22220
							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22221
								$hyphenword[$zero] = $level;
22222
							}
22223
							++$digits;
22224
						}
22225
					}
22226
				}
22227
			}
22228
		}
22229
		$inserted = 0;
22230
		$maxpos = $numchars - $rightmin;
22231
		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22232
			// only odd levels indicate allowed hyphenation points
22233
			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22234
				// 173 = soft hyphen character
22235
				array_splice($word, $i + $inserted, 0, 173);
22236
				++$inserted;
22237
			}
22238
		}
22239
		return $word;
22240
	}
22241
 
22242
	/**
22243
	 * Returns text with soft hyphens.
22244
	 * @param string $text text to process
22245
	 * @param mixed $patterns Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22246
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22247
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22248
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22249
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22250
	 * @param int $charmax Maximum length of broken piece of word.
22251
	 * @return string text with soft hyphens
22252
	 * @author Nicola Asuni
22253
	 * @since 4.9.012 (2010-04-12)
22254
	 * @public
22255
	 */
22256
	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22257
		$text = $this->unhtmlentities($text);
22258
		$word = array(); // last word
22259
		$txtarr = array(); // text to be returned
22260
		$intag = false; // true if we are inside an HTML tag
22261
		$skip = false; // true to skip hyphenation
22262
		if (!is_array($patterns)) {
22263
			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22264
		}
22265
		// get array of characters
22266
		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22267
		// for each char
22268
		foreach ($unichars as $char) {
22269
			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22270
				// letter character
22271
				$word[] = $char;
22272
			} else {
22273
				// other type of character
22274
				if (!TCPDF_STATIC::empty_string($word)) {
22275
					// hypenate the word
22276
					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22277
					$word = array();
22278
				}
22279
				$txtarr[] = $char;
22280
				if (chr($char) == '<') {
22281
					// we are inside an HTML tag
22282
					$intag = true;
22283
				} elseif ($intag AND (chr($char) == '>')) {
22284
					// end of HTML tag
22285
					$intag = false;
22286
					// check for style tag
22287
					$expected = array(115, 116, 121, 108, 101); // = 'style'
22288
					$current = array_slice($txtarr, -6, 5); // last 5 chars
22289
					$compare = array_diff($expected, $current);
22290
					if (empty($compare)) {
22291
						// check if it is a closing tag
22292
						$expected = array(47); // = '/'
22293
						$current = array_slice($txtarr, -7, 1);
22294
						$compare = array_diff($expected, $current);
22295
						if (empty($compare)) {
22296
							// closing style tag
22297
							$skip = false;
22298
						} else {
22299
							// opening style tag
22300
							$skip = true;
22301
						}
22302
					}
22303
				}
22304
			}
22305
		}
22306
		if (!TCPDF_STATIC::empty_string($word)) {
22307
			// hypenate the word
22308
			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22309
		}
22310
		// convert char array to string and return
22311
		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22312
	}
22313
 
22314
	/**
22315
	 * Enable/disable rasterization of vector images using ImageMagick library.
22316
	 * @param boolean $mode if true enable rasterization, false otherwise.
22317
	 * @public
22318
	 * @since 5.0.000 (2010-04-27)
22319
	 */
22320
	public function setRasterizeVectorImages($mode) {
22321
		$this->rasterize_vector_images = $mode;
22322
	}
22323
 
22324
	/**
22325
	 * Enable or disable default option for font subsetting.
22326
	 * @param boolean $enable if true enable font subsetting by default.
22327
	 * @author Nicola Asuni
22328
	 * @public
22329
	 * @since 5.3.002 (2010-06-07)
22330
	 */
22331
	public function setFontSubsetting($enable=true) {
22332
		if ($this->pdfa_mode) {
22333
			$this->font_subsetting = false;
22334
		} else {
22335
			$this->font_subsetting = $enable ? true : false;
22336
		}
22337
	}
22338
 
22339
	/**
22340
	 * Return the default option for font subsetting.
22341
	 * @return bool default font subsetting state.
22342
	 * @author Nicola Asuni
22343
	 * @public
22344
	 * @since 5.3.002 (2010-06-07)
22345
	 */
22346
	public function getFontSubsetting() {
22347
		return $this->font_subsetting;
22348
	}
22349
 
22350
	/**
22351
	 * Left trim the input string
22352
	 * @param string $str string to trim
22353
	 * @param string $replace string that replace spaces.
22354
	 * @return string left trimmed string
22355
	 * @author Nicola Asuni
22356
	 * @public
22357
	 * @since 5.8.000 (2010-08-11)
22358
	 */
22359
	public function stringLeftTrim($str, $replace='') {
22360
		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22361
	}
22362
 
22363
	/**
22364
	 * Right trim the input string
22365
	 * @param string $str string to trim
22366
	 * @param string $replace string that replace spaces.
22367
	 * @return string right trimmed string
22368
	 * @author Nicola Asuni
22369
	 * @public
22370
	 * @since 5.8.000 (2010-08-11)
22371
	 */
22372
	public function stringRightTrim($str, $replace='') {
22373
		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22374
	}
22375
 
22376
	/**
22377
	 * Trim the input string
22378
	 * @param string $str string to trim
22379
	 * @param string $replace string that replace spaces.
22380
	 * @return string trimmed string
22381
	 * @author Nicola Asuni
22382
	 * @public
22383
	 * @since 5.8.000 (2010-08-11)
22384
	 */
22385
	public function stringTrim($str, $replace='') {
22386
		$str = $this->stringLeftTrim($str, $replace);
22387
		$str = $this->stringRightTrim($str, $replace);
22388
		return $str;
22389
	}
22390
 
22391
	/**
22392
	 * Return true if the current font is unicode type.
22393
	 * @return bool true for unicode font, false otherwise.
22394
	 * @author Nicola Asuni
22395
	 * @public
22396
	 * @since 5.8.002 (2010-08-14)
22397
	 */
22398
	public function isUnicodeFont() {
22399
		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22400
	}
22401
 
22402
	/**
22403
	 * Return normalized font name
22404
	 * @param string $fontfamily property string containing font family names
22405
	 * @return string normalized font name
22406
	 * @author Nicola Asuni
22407
	 * @public
22408
	 * @since 5.8.004 (2010-08-17)
22409
	 */
22410
	public function getFontFamilyName($fontfamily) {
22411
		// remove spaces and symbols
22412
		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22413
		// extract all font names
22414
		$fontslist = preg_split('/[,]/', $fontfamily);
22415
		// find first valid font name
22416
		foreach ($fontslist as $font) {
22417
			// replace font variations
22418
			$font = preg_replace('/regular$/', '', $font);
22419
			$font = preg_replace('/italic$/', 'I', $font);
22420
			$font = preg_replace('/oblique$/', 'I', $font);
22421
			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22422
			// replace common family names and core fonts
22423
			$pattern = array();
22424
			$replacement = array();
22425
			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22426
			$replacement[] = 'times';
22427
			$pattern[] = '/^sansserif/';
22428
			$replacement[] = 'helvetica';
22429
			$pattern[] = '/^monospace/';
22430
			$replacement[] = 'courier';
22431
			$font = preg_replace($pattern, $replacement, $font);
22432
			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22433
				return $font;
22434
			}
22435
		}
22436
		// return current font as default
22437
		return $this->CurrentFont['fontkey'];
22438
	}
22439
 
22440
	/**
22441
	 * Start a new XObject Template.
22442
	 * 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).
22443
	 * 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.
22444
	 * Note: X,Y coordinates will be reset to 0,0.
22445
	 * @param int $w Template width in user units (empty string or zero = page width less margins).
22446
	 * @param int $h Template height in user units (empty string or zero = page height less margins).
22447
	 * @param mixed $group Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22448
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22449
	 * @author Nicola Asuni
22450
	 * @public
22451
	 * @since 5.8.017 (2010-08-24)
22452
	 * @see endTemplate(), printTemplate()
22453
	 */
22454
	public function startTemplate($w=0, $h=0, $group=false) {
22455
		if ($this->inxobj) {
22456
			// we are already inside an XObject template
22457
			return false;
22458
		}
22459
		$this->inxobj = true;
22460
		++$this->n;
22461
		// XObject ID
22462
		$this->xobjid = 'XT'.$this->n;
22463
		// object ID
22464
		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22465
		// store current graphic state
22466
		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22467
		// initialize data
22468
		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22469
		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22470
		$this->xobjects[$this->xobjid]['outdata'] = '';
22471
		$this->xobjects[$this->xobjid]['xobjects'] = array();
22472
		$this->xobjects[$this->xobjid]['images'] = array();
22473
		$this->xobjects[$this->xobjid]['fonts'] = array();
22474
		$this->xobjects[$this->xobjid]['annotations'] = array();
22475
		$this->xobjects[$this->xobjid]['extgstates'] = array();
22476
		$this->xobjects[$this->xobjid]['gradients'] = array();
22477
		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22478
		// set new environment
22479
		$this->num_columns = 1;
22480
		$this->current_column = 0;
22481
		$this->setAutoPageBreak(false);
22482
		if (($w === '') OR ($w <= 0)) {
22483
			$w = $this->w - $this->lMargin - $this->rMargin;
22484
		}
22485
		if (($h === '') OR ($h <= 0)) {
22486
			$h = $this->h - $this->tMargin - $this->bMargin;
22487
		}
22488
		$this->xobjects[$this->xobjid]['x'] = 0;
22489
		$this->xobjects[$this->xobjid]['y'] = 0;
22490
		$this->xobjects[$this->xobjid]['w'] = $w;
22491
		$this->xobjects[$this->xobjid]['h'] = $h;
22492
		$this->w = $w;
22493
		$this->h = $h;
22494
		$this->wPt = $this->w * $this->k;
22495
		$this->hPt = $this->h * $this->k;
22496
		$this->fwPt = $this->wPt;
22497
		$this->fhPt = $this->hPt;
22498
		$this->x = 0;
22499
		$this->y = 0;
22500
		$this->lMargin = 0;
22501
		$this->rMargin = 0;
22502
		$this->tMargin = 0;
22503
		$this->bMargin = 0;
22504
		// set group mode
22505
		$this->xobjects[$this->xobjid]['group'] = $group;
22506
		return $this->xobjid;
22507
	}
22508
 
22509
	/**
22510
	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22511
	 * 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).
22512
	 * 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.
22513
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22514
	 * @author Nicola Asuni
22515
	 * @public
22516
	 * @since 5.8.017 (2010-08-24)
22517
	 * @see startTemplate(), printTemplate()
22518
	 */
22519
	public function endTemplate() {
22520
		if (!$this->inxobj) {
22521
			// we are not inside a template
22522
			return false;
22523
		}
22524
		$this->inxobj = false;
22525
		// restore previous graphic state
22526
		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22527
		return $this->xobjid;
22528
	}
22529
 
22530
	/**
22531
	 * Print an XObject Template.
22532
	 * You can print an XObject Template inside the currently opened Template.
22533
	 * 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).
22534
	 * 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.
22535
	 * @param string $id The ID of XObject Template to print.
22536
	 * @param float|null $x X position in user units (empty string = current x position)
22537
	 * @param float|null $y Y position in user units (empty string = current y position)
22538
	 * @param float $w Width in user units (zero = remaining page width)
22539
	 * @param float $h Height in user units (zero = remaining page height)
22540
	 * @param string $align Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22541
	 * @param string $palign Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22542
	 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22543
	 * @author Nicola Asuni
22544
	 * @public
22545
	 * @since 5.8.017 (2010-08-24)
22546
	 * @see startTemplate(), endTemplate()
22547
	 */
22548
	public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22549
		if ($this->state != 2) {
22550
			 return;
22551
		}
22552
		if (!isset($this->xobjects[$id])) {
22553
			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22554
		}
22555
		if ($this->inxobj) {
22556
			if ($id == $this->xobjid) {
22557
				// close current template
22558
				$this->endTemplate();
22559
			} else {
22560
				// use the template as resource for the template currently opened
22561
				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22562
			}
22563
		}
22564
		// set default values
22565
		if (TCPDF_STATIC::empty_string($x)) {
22566
			$x = $this->x;
22567
		}
22568
		if (TCPDF_STATIC::empty_string($y)) {
22569
			$y = $this->y;
22570
		}
22571
		// check page for no-write regions and adapt page margins if necessary
22572
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22573
		$ow = $this->xobjects[$id]['w'];
22574
		if ($ow <= 0) {
22575
			$ow = 1;
22576
		}
22577
		$oh = $this->xobjects[$id]['h'];
22578
		if ($oh <= 0) {
22579
			$oh = 1;
22580
		}
22581
		// calculate template width and height on document
22582
		if (($w <= 0) AND ($h <= 0)) {
22583
			$w = $ow;
22584
			$h = $oh;
22585
		} elseif ($w <= 0) {
22586
			$w = $h * $ow / $oh;
22587
		} elseif ($h <= 0) {
22588
			$h = $w * $oh / $ow;
22589
		}
22590
		// fit the template on available space
22591
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22592
		// set page alignment
22593
		$rb_y = $y + $h;
22594
		// set alignment
22595
		if ($this->rtl) {
22596
			if ($palign == 'L') {
22597
				$xt = $this->lMargin;
22598
			} elseif ($palign == 'C') {
22599
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22600
			} elseif ($palign == 'R') {
22601
				$xt = $this->w - $this->rMargin - $w;
22602
			} else {
22603
				$xt = $x - $w;
22604
			}
22605
			$rb_x = $xt;
22606
		} else {
22607
			if ($palign == 'L') {
22608
				$xt = $this->lMargin;
22609
			} elseif ($palign == 'C') {
22610
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22611
			} elseif ($palign == 'R') {
22612
				$xt = $this->w - $this->rMargin - $w;
22613
			} else {
22614
				$xt = $x;
22615
			}
22616
			$rb_x = $xt + $w;
22617
		}
22618
		// print XObject Template + Transformation matrix
22619
		$this->StartTransform();
22620
		// translate and scale
22621
		$sx = ($w / $ow);
22622
		$sy = ($h / $oh);
22623
		$tm = array();
22624
		$tm[0] = $sx;
22625
		$tm[1] = 0;
22626
		$tm[2] = 0;
22627
		$tm[3] = $sy;
22628
		$tm[4] = $xt * $this->k;
22629
		$tm[5] = ($this->h - $h - $y) * $this->k;
22630
		$this->Transform($tm);
22631
		// set object
22632
		$this->_out('/'.$id.' Do');
22633
		$this->StopTransform();
22634
		// add annotations
22635
		if (!empty($this->xobjects[$id]['annotations'])) {
22636
			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22637
				// transform original coordinates
22638
				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22639
				$ax = ($coordlt[4] / $this->k);
22640
				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22641
				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22642
				$aw = ($coordrb[4] / $this->k) - $ax;
22643
				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22644
				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22645
			}
22646
		}
22647
		// set pointer to align the next text/objects
22648
		switch($align) {
22649
			case 'T': {
22650
				$this->y = $y;
22651
				$this->x = $rb_x;
22652
				break;
22653
			}
22654
			case 'M': {
22655
				$this->y = $y + round($h/2);
22656
				$this->x = $rb_x;
22657
				break;
22658
			}
22659
			case 'B': {
22660
				$this->y = $rb_y;
22661
				$this->x = $rb_x;
22662
				break;
22663
			}
22664
			case 'N': {
22665
				$this->setY($rb_y);
22666
				break;
22667
			}
22668
			default:{
22669
				break;
22670
			}
22671
		}
22672
	}
22673
 
22674
	/**
22675
	 * Set the percentage of character stretching.
22676
	 * @param int $perc percentage of stretching (100 = no stretching)
22677
	 * @author Nicola Asuni
22678
	 * @public
22679
	 * @since 5.9.000 (2010-09-29)
22680
	 */
22681
	public function setFontStretching($perc=100) {
22682
		$this->font_stretching = $perc;
22683
	}
22684
 
22685
	/**
22686
	 * Get the percentage of character stretching.
22687
	 * @return float stretching value
22688
	 * @author Nicola Asuni
22689
	 * @public
22690
	 * @since 5.9.000 (2010-09-29)
22691
	 */
22692
	public function getFontStretching() {
22693
		return $this->font_stretching;
22694
	}
22695
 
22696
	/**
22697
	 * Set the amount to increase or decrease the space between characters in a text.
22698
	 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22699
	 * @author Nicola Asuni
22700
	 * @public
22701
	 * @since 5.9.000 (2010-09-29)
22702
	 */
22703
	public function setFontSpacing($spacing=0) {
22704
		$this->font_spacing = $spacing;
22705
	}
22706
 
22707
	/**
22708
	 * Get the amount to increase or decrease the space between characters in a text.
22709
	 * @return int font spacing (tracking) value
22710
	 * @author Nicola Asuni
22711
	 * @public
22712
	 * @since 5.9.000 (2010-09-29)
22713
	 */
22714
	public function getFontSpacing() {
22715
		return $this->font_spacing;
22716
	}
22717
 
22718
	/**
22719
	 * Return an array of no-write page regions
22720
	 * @return array of no-write page regions
22721
	 * @author Nicola Asuni
22722
	 * @public
22723
	 * @since 5.9.003 (2010-10-13)
22724
	 * @see setPageRegions(), addPageRegion()
22725
	 */
22726
	public function getPageRegions() {
22727
		return $this->page_regions;
22728
	}
22729
 
22730
	/**
22731
	 * Set no-write regions on page.
22732
	 * 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.
22733
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22734
	 * You can set multiple regions for the same page.
22735
	 * @param array $regions array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22736
	 * @author Nicola Asuni
22737
	 * @public
22738
	 * @since 5.9.003 (2010-10-13)
22739
	 * @see addPageRegion(), getPageRegions()
22740
	 */
22741
	public function setPageRegions($regions=array()) {
22742
		// empty current regions array
22743
		$this->page_regions = array();
22744
		// add regions
22745
		foreach ($regions as $data) {
22746
			$this->addPageRegion($data);
22747
		}
22748
	}
22749
 
22750
	/**
22751
	 * Add a single no-write region on selected page.
22752
	 * 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.
22753
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22754
	 * You can set multiple regions for the same page.
22755
	 * @param array $region array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22756
	 * @author Nicola Asuni
22757
	 * @public
22758
	 * @since 5.9.003 (2010-10-13)
22759
	 * @see setPageRegions(), getPageRegions()
22760
	 */
22761
	public function addPageRegion($region) {
22762
		if (!isset($region['page']) OR empty($region['page'])) {
22763
			$region['page'] = $this->page;
22764
		}
22765
		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22766
			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22767
			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22768
			$this->page_regions[] = $region;
22769
		}
22770
	}
22771
 
22772
	/**
22773
	 * Remove a single no-write region.
22774
	 * @param int $key region key
22775
	 * @author Nicola Asuni
22776
	 * @public
22777
	 * @since 5.9.003 (2010-10-13)
22778
	 * @see setPageRegions(), getPageRegions()
22779
	 */
22780
	public function removePageRegion($key) {
22781
		if (isset($this->page_regions[$key])) {
22782
			unset($this->page_regions[$key]);
22783
		}
22784
	}
22785
 
22786
	/**
22787
	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22788
	 * 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.
22789
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22790
	 * @param float $h height of the text/image/object to print in user units
22791
	 * @param float $x current X coordinate in user units
22792
	 * @param float $y current Y coordinate in user units
22793
	 * @return float[] array($x, $y)
22794
	 * @author Nicola Asuni
22795
	 * @protected
22796
	 * @since 5.9.003 (2010-10-13)
22797
	 */
22798
	protected function checkPageRegions($h, $x, $y) {
22799
		// set default values
22800
		if ($x === '') {
22801
			$x = $this->x;
22802
		}
22803
		if ($y === '') {
22804
			$y = $this->y;
22805
		}
22806
		if (!$this->check_page_regions OR empty($this->page_regions)) {
22807
			// no page regions defined
22808
			return array($x, $y);
22809
		}
22810
		if (empty($h)) {
22811
			$h = $this->getCellHeight($this->FontSize);
22812
		}
22813
		// check for page break
22814
		if ($this->checkPageBreak($h, $y)) {
22815
			// the content will be printed on a new page
22816
			$x = $this->x;
22817
			$y = $this->y;
22818
		}
22819
		if ($this->num_columns > 1) {
22820
			if ($this->rtl) {
22821
				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22822
			} else {
22823
				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22824
			}
22825
		} else {
22826
			if ($this->rtl) {
22827
				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22828
			} else {
22829
				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22830
			}
22831
		}
22832
		// adjust coordinates and page margins
22833
		foreach ($this->page_regions as $regid => $regdata) {
22834
			if ($regdata['page'] == $this->page) {
22835
				// check region boundaries
22836
				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22837
					// Y is inside the region
22838
					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22839
					$yt = max($y, $regdata['yt']);
22840
					$yb = min(($yt + $h), $regdata['yb']);
22841
					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22842
					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22843
					if ($regdata['side'] == 'L') { // left side
22844
						$new_margin = max($xt, $xb);
22845
						if ($this->lMargin < $new_margin) {
22846
							if ($this->rtl) {
22847
								// adjust left page margin
22848
								$this->lMargin = max(0, $new_margin);
22849
							}
22850
							if ($x < $new_margin) {
22851
								// adjust x position
22852
								$x = $new_margin;
22853
								if ($new_margin > ($this->w - $this->rMargin)) {
22854
									// adjust y position
22855
									$y = $regdata['yb'] - $h;
22856
								}
22857
							}
22858
						}
22859
					} elseif ($regdata['side'] == 'R') { // right side
22860
						$new_margin = min($xt, $xb);
22861
						if (($this->w - $this->rMargin) > $new_margin) {
22862
							if (!$this->rtl) {
22863
								// adjust right page margin
22864
								$this->rMargin = max(0, ($this->w - $new_margin));
22865
							}
22866
							if ($x > $new_margin) {
22867
								// adjust x position
22868
								$x = $new_margin;
22869
								if ($new_margin > $this->lMargin) {
22870
									// adjust y position
22871
									$y = $regdata['yb'] - $h;
22872
								}
22873
							}
22874
						}
22875
					}
22876
				}
22877
			}
22878
		}
22879
		return array($x, $y);
22880
	}
22881
 
22882
	// --- SVG METHODS ---------------------------------------------------------
22883
 
22884
	/**
22885
	 * Embedd a Scalable Vector Graphics (SVG) image.
22886
	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22887
	 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
22888
	 * @param float|null $x Abscissa of the upper-left corner.
22889
	 * @param float|null $y Ordinate of the upper-left corner.
22890
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22891
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22892
	 * @param mixed $link URL or identifier returned by AddLink().
22893
	 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
22894
	 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22895
	 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22896
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
22897
	 * @author Nicola Asuni
22898
	 * @since 5.0.000 (2010-05-02)
22899
	 * @public
22900
	 */
22901
	public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22902
		if ($this->state != 2) {
22903
			 return;
22904
		}
22905
		// reset SVG vars
22906
		$this->svggradients = array();
22907
		$this->svggradientid = 0;
22908
		$this->svgdefsmode = false;
22909
		$this->svgdefs = array();
22910
		$this->svgclipmode = false;
22911
		$this->svgclippaths = array();
22912
		$this->svgcliptm = array();
22913
		$this->svgclipid = 0;
22914
		$this->svgtext = '';
22915
		$this->svgtextmode = array();
22916
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22917
			// convert SVG to raster image using GD or ImageMagick libraries
22918
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22919
		}
22920
		if ($file[0] === '@') { // image from string
22921
			$this->svgdir = '';
22922
			$svgdata = substr($file, 1);
22923
		} else { // SVG file
22924
			$this->svgdir = dirname($file);
22925
            $svgdata = $this->getCachedFileContents($file);
22926
		}
22927
		if ($svgdata === FALSE) {
22928
			$this->Error('SVG file not found: '.$file);
22929
		}
22930
		if (TCPDF_STATIC::empty_string($x)) {
22931
			$x = $this->x;
22932
		}
22933
		if (TCPDF_STATIC::empty_string($y)) {
22934
			$y = $this->y;
22935
		}
22936
		// check page for no-write regions and adapt page margins if necessary
22937
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22938
		$k = $this->k;
22939
		$ox = 0;
22940
		$oy = 0;
22941
		$ow = $w;
22942
		$oh = $h;
22943
		$aspect_ratio_align = 'xMidYMid';
22944
		$aspect_ratio_ms = 'meet';
22945
		$regs = array();
22946
		// get original image width and height
22947
		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22948
		if (isset($regs[1]) AND !empty($regs[1])) {
22949
			$tmp = array();
22950
			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22951
				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22952
			}
22953
			$tmp = array();
22954
			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22955
				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22956
			}
22957
			$tmp = array();
22958
			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22959
				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22960
			}
22961
			$tmp = array();
22962
			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22963
				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22964
			}
22965
			$tmp = array();
22966
			$view_box = array();
22967
			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22968
				if (count($tmp) == 5) {
22969
					array_shift($tmp);
22970
					foreach ($tmp as $key => $val) {
22971
						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22972
					}
22973
					$ox = $view_box[0];
22974
					$oy = $view_box[1];
22975
				}
22976
				// get aspect ratio
22977
				$tmp = array();
22978
				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22979
					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22980
					switch (count($aspect_ratio)) {
22981
						case 3: {
22982
							$aspect_ratio_align = $aspect_ratio[1];
22983
							$aspect_ratio_ms = $aspect_ratio[2];
22984
							break;
22985
						}
22986
						case 2: {
22987
							$aspect_ratio_align = $aspect_ratio[0];
22988
							$aspect_ratio_ms = $aspect_ratio[1];
22989
							break;
22990
						}
22991
						case 1: {
22992
							$aspect_ratio_align = $aspect_ratio[0];
22993
							$aspect_ratio_ms = 'meet';
22994
							break;
22995
						}
22996
					}
22997
				}
22998
			}
22999
		}
23000
		if ($ow <= 0) {
23001
			$ow = 1;
23002
		}
23003
		if ($oh <= 0) {
23004
			$oh = 1;
23005
		}
23006
		// calculate image width and height on document
23007
		if (($w <= 0) AND ($h <= 0)) {
23008
			// convert image size to document unit
23009
			$w = $ow;
23010
			$h = $oh;
23011
		} elseif ($w <= 0) {
23012
			$w = $h * $ow / $oh;
23013
		} elseif ($h <= 0) {
23014
			$h = $w * $oh / $ow;
23015
		}
23016
		// fit the image on available space
23017
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23018
		if ($this->rasterize_vector_images) {
23019
			// convert SVG to raster image using GD or ImageMagick libraries
23020
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23021
		}
23022
		// set alignment
23023
		$this->img_rb_y = $y + $h;
23024
		// set alignment
23025
		if ($this->rtl) {
23026
			if ($palign == 'L') {
23027
				$ximg = $this->lMargin;
23028
			} elseif ($palign == 'C') {
23029
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23030
			} elseif ($palign == 'R') {
23031
				$ximg = $this->w - $this->rMargin - $w;
23032
			} else {
23033
				$ximg = $x - $w;
23034
			}
23035
			$this->img_rb_x = $ximg;
23036
		} else {
23037
			if ($palign == 'L') {
23038
				$ximg = $this->lMargin;
23039
			} elseif ($palign == 'C') {
23040
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23041
			} elseif ($palign == 'R') {
23042
				$ximg = $this->w - $this->rMargin - $w;
23043
			} else {
23044
				$ximg = $x;
23045
			}
23046
			$this->img_rb_x = $ximg + $w;
23047
		}
23048
		// store current graphic vars
23049
		$gvars = $this->getGraphicVars();
23050
		// store SVG position and scale factors
23051
		$svgoffset_x = ($ximg - $ox) * $this->k;
23052
		$svgoffset_y = -($y - $oy) * $this->k;
23053
		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23054
			$ow = $view_box[2];
23055
			$oh = $view_box[3];
23056
		} else {
23057
			if ($ow <= 0) {
23058
				$ow = $w;
23059
			}
23060
			if ($oh <= 0) {
23061
				$oh = $h;
23062
			}
23063
		}
23064
		$svgscale_x = $w / $ow;
23065
		$svgscale_y = $h / $oh;
23066
		// scaling and alignment
23067
		if ($aspect_ratio_align != 'none') {
23068
			// store current scaling values
23069
			$svgscale_old_x = $svgscale_x;
23070
			$svgscale_old_y = $svgscale_y;
23071
			// force uniform scaling
23072
			if ($aspect_ratio_ms == 'slice') {
23073
				// the entire viewport is covered by the viewBox
23074
				if ($svgscale_x > $svgscale_y) {
23075
					$svgscale_y = $svgscale_x;
23076
				} elseif ($svgscale_x < $svgscale_y) {
23077
					$svgscale_x = $svgscale_y;
23078
				}
23079
			} else { // meet
23080
				// the entire viewBox is visible within the viewport
23081
				if ($svgscale_x < $svgscale_y) {
23082
					$svgscale_y = $svgscale_x;
23083
				} elseif ($svgscale_x > $svgscale_y) {
23084
					$svgscale_x = $svgscale_y;
23085
				}
23086
			}
23087
			// correct X alignment
23088
			switch (substr($aspect_ratio_align, 1, 3)) {
23089
				case 'Min': {
23090
					// do nothing
23091
					break;
23092
				}
23093
				case 'Max': {
23094
					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23095
					break;
23096
				}
23097
				default:
23098
				case 'Mid': {
23099
					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23100
					break;
23101
				}
23102
			}
23103
			// correct Y alignment
23104
			switch (substr($aspect_ratio_align, 5)) {
23105
				case 'Min': {
23106
					// do nothing
23107
					break;
23108
				}
23109
				case 'Max': {
23110
					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23111
					break;
23112
				}
23113
				default:
23114
				case 'Mid': {
23115
					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23116
					break;
23117
				}
23118
			}
23119
		}
23120
		// store current page break mode
23121
		$page_break_mode = $this->AutoPageBreak;
23122
		$page_break_margin = $this->getBreakMargin();
23123
		$cell_padding = $this->cell_padding;
23124
		$this->setCellPadding(0);
23125
		$this->setAutoPageBreak(false);
23126
		// save the current graphic state
23127
		$this->_out('q'.$this->epsmarker);
23128
		// set initial clipping mask
23129
		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23130
		// scale and translate
23131
		$e = $ox * $this->k * (1 - $svgscale_x);
23132
		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23133
		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23134
		// creates a new XML parser to be used by the other XML functions
23135
		$parser = xml_parser_create('UTF-8');
23136
		// the following function allows to use parser inside object
23137
		xml_set_object($parser, $this);
23138
		// disable case-folding for this XML parser
23139
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23140
		// sets the element handler functions for the XML parser
23141
		xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler');
23142
		// sets the character data handler function for the XML parser
23143
		xml_set_character_data_handler($parser, 'segSVGContentHandler');
23144
		// start parsing an XML document
23145
		if (!xml_parse($parser, $svgdata)) {
23146
			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23147
			$this->Error($error_message);
23148
		}
23149
		// free this XML parser
23150
		xml_parser_free($parser);
23151
 
23152
		// >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23153
		unset($parser);
23154
 
23155
		// restore previous graphic state
23156
		$this->_out($this->epsmarker.'Q');
23157
		// restore graphic vars
23158
		$this->setGraphicVars($gvars);
23159
		$this->lasth = $gvars['lasth'];
23160
		if (!empty($border)) {
23161
			$bx = $this->x;
23162
			$by = $this->y;
23163
			$this->x = $ximg;
23164
			if ($this->rtl) {
23165
				$this->x += $w;
23166
			}
23167
			$this->y = $y;
23168
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23169
			$this->x = $bx;
23170
			$this->y = $by;
23171
		}
23172
		if ($link) {
23173
			$this->Link($ximg, $y, $w, $h, $link, 0);
23174
		}
23175
		// set pointer to align the next text/objects
23176
		switch($align) {
23177
			case 'T':{
23178
				$this->y = $y;
23179
				$this->x = $this->img_rb_x;
23180
				break;
23181
			}
23182
			case 'M':{
23183
				$this->y = $y + round($h/2);
23184
				$this->x = $this->img_rb_x;
23185
				break;
23186
			}
23187
			case 'B':{
23188
				$this->y = $this->img_rb_y;
23189
				$this->x = $this->img_rb_x;
23190
				break;
23191
			}
23192
			case 'N':{
23193
				$this->setY($this->img_rb_y);
23194
				break;
23195
			}
23196
			default:{
23197
				// restore pointer to starting position
23198
				$this->x = $gvars['x'];
23199
				$this->y = $gvars['y'];
23200
				$this->page = $gvars['page'];
23201
				$this->current_column = $gvars['current_column'];
23202
				$this->tMargin = $gvars['tMargin'];
23203
				$this->bMargin = $gvars['bMargin'];
23204
				$this->w = $gvars['w'];
23205
				$this->h = $gvars['h'];
23206
				$this->wPt = $gvars['wPt'];
23207
				$this->hPt = $gvars['hPt'];
23208
				$this->fwPt = $gvars['fwPt'];
23209
				$this->fhPt = $gvars['fhPt'];
23210
				break;
23211
			}
23212
		}
23213
		$this->endlinex = $this->img_rb_x;
23214
		// restore page break
23215
		$this->setAutoPageBreak($page_break_mode, $page_break_margin);
23216
		$this->cell_padding = $cell_padding;
23217
	}
23218
 
23219
	/**
23220
	 * Convert SVG transformation matrix to PDF.
23221
	 * @param array $tm original SVG transformation matrix
23222
	 * @return array transformation matrix
23223
	 * @protected
23224
	 * @since 5.0.000 (2010-05-02)
23225
	 */
23226
	protected function convertSVGtMatrix($tm) {
23227
		$a = $tm[0];
23228
		$b = -$tm[1];
23229
		$c = -$tm[2];
23230
		$d = $tm[3];
23231
		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23232
		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23233
		$x = 0;
23234
		$y = $this->h * $this->k;
23235
		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23236
		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23237
		return array($a, $b, $c, $d, $e, $f);
23238
	}
23239
 
23240
	/**
23241
	 * Apply SVG graphic transformation matrix.
23242
	 * @param array $tm original SVG transformation matrix
23243
	 * @protected
23244
	 * @since 5.0.000 (2010-05-02)
23245
	 */
23246
	protected function SVGTransform($tm) {
23247
		$this->Transform($this->convertSVGtMatrix($tm));
23248
	}
23249
 
23250
	/**
23251
	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23252
	 * @param array $svgstyle array of SVG styles to apply
23253
	 * @param array $prevsvgstyle array of previous SVG style
23254
	 * @param int $x X origin of the bounding box
23255
	 * @param int $y Y origin of the bounding box
23256
	 * @param int $w width of the bounding box
23257
	 * @param int $h height of the bounding box
23258
	 * @param string $clip_function clip function
23259
	 * @param array $clip_params array of parameters for clipping function
23260
	 * @return string style
23261
	 * @author Nicola Asuni
23262
	 * @since 5.0.000 (2010-05-02)
23263
	 * @protected
23264
	 */
23265
	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23266
		if ($this->state != 2) {
23267
			 return;
23268
		}
23269
		$objstyle = '';
23270
		$minlen = (0.01 / $this->k); // minimum acceptable length
23271
		if (!isset($svgstyle['opacity'])) {
23272
			return $objstyle;
23273
		}
23274
		// clip-path
23275
		$regs = array();
23276
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23277
			$clip_path = $this->svgclippaths[$regs[1]];
23278
			foreach ($clip_path as $cp) {
23279
				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23280
			}
23281
		}
23282
		// opacity
23283
		if ($svgstyle['opacity'] != 1) {
23284
			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23285
		}
23286
		// color
23287
		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23288
		$this->setFillColorArray($fill_color);
23289
		// text color
23290
		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23291
		$this->setTextColorArray($text_color);
23292
		// clip
23293
		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23294
			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23295
			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23296
			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23297
			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23298
			$cx = $x + $left;
23299
			$cy = $y + $top;
23300
			$cw = $w - $left - $right;
23301
			$ch = $h - $top - $bottom;
23302
			if ($svgstyle['clip-rule'] == 'evenodd') {
23303
				$clip_rule = 'CNZ';
23304
			} else {
23305
				$clip_rule = 'CEO';
23306
			}
23307
			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23308
		}
23309
		// fill
23310
		$regs = array();
23311
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23312
			// gradient
23313
			$gradient = $this->svggradients[$regs[1]];
23314
			if (isset($gradient['xref'])) {
23315
				// reference to another gradient definition
23316
				$newgradient = $this->svggradients[$gradient['xref']];
23317
				$newgradient['coords'] = $gradient['coords'];
23318
				$newgradient['mode'] = $gradient['mode'];
23319
				$newgradient['type'] = $gradient['type'];
23320
				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23321
				if (isset($gradient['gradientTransform'])) {
23322
					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23323
				}
23324
				$gradient = $newgradient;
23325
			}
23326
			//save current Graphic State
23327
			$this->_outSaveGraphicsState();
23328
			//set clipping area
23329
			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23330
				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23331
				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23332
					list($x, $y, $w, $h) = $bbox;
23333
				}
23334
			}
23335
			if ($gradient['mode'] == 'measure') {
23336
				if (!isset($gradient['coords'][4])) {
23337
					$gradient['coords'][4] = 0.5;
23338
				}
23339
				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23340
					$gtm = $gradient['gradientTransform'];
23341
					// apply transformation matrix
23342
					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23343
					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23344
					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23345
					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23346
					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23347
					$gradient['coords'][0] = $xa;
23348
					$gradient['coords'][1] = $ya;
23349
					$gradient['coords'][2] = $xb;
23350
					$gradient['coords'][3] = $yb;
23351
					$gradient['coords'][4] = $r;
23352
				}
23353
				// convert SVG coordinates to user units
23354
				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23355
				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23356
				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23357
				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23358
				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23359
				if ($w <= $minlen) {
23360
					$w = $minlen;
23361
				}
23362
				if ($h <= $minlen) {
23363
					$h = $minlen;
23364
				}
23365
				// shift units
23366
				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23367
					// convert to SVG coordinate system
23368
					$gradient['coords'][0] += $x;
23369
					$gradient['coords'][1] += $y;
23370
					$gradient['coords'][2] += $x;
23371
					$gradient['coords'][3] += $y;
23372
				}
23373
				// calculate percentages
23374
				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23375
				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23376
				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23377
				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23378
				$gradient['coords'][4] /= $w;
23379
			} elseif ($gradient['mode'] == 'percentage') {
23380
				foreach($gradient['coords'] as $key => $val) {
23381
					$gradient['coords'][$key] = (intval($val) / 100);
23382
					if ($val < 0) {
23383
						$gradient['coords'][$key] = 0;
23384
					} elseif ($val > 1) {
23385
						$gradient['coords'][$key] = 1;
23386
					}
23387
				}
23388
			}
23389
			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23390
				// single color (no shading)
23391
				$gradient['coords'][0] = 1;
23392
				$gradient['coords'][1] = 0;
23393
				$gradient['coords'][2] = 0.999;
23394
				$gradient['coords'][3] = 0;
23395
			}
23396
			// swap Y coordinates
23397
			$tmp = $gradient['coords'][1];
23398
			$gradient['coords'][1] = $gradient['coords'][3];
23399
			$gradient['coords'][3] = $tmp;
23400
			// set transformation map for gradient
23401
			$cy = ($this->h - $y);
23402
			if ($gradient['type'] == 3) {
23403
				// circular gradient
23404
				$cy -= ($gradient['coords'][1] * ($w + $h));
23405
				$h = $w = max($w, $h);
23406
			} else {
23407
				$cy -= $h;
23408
			}
23409
			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23410
			if (count($gradient['stops']) > 1) {
23411
				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23412
			}
23413
		} elseif ($svgstyle['fill'] != 'none') {
23414
			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23415
			if ($svgstyle['fill-opacity'] != 1) {
23416
				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23417
			}
23418
			$this->setFillColorArray($fill_color);
23419
			if ($svgstyle['fill-rule'] == 'evenodd') {
23420
				$objstyle .= 'F*';
23421
			} else {
23422
				$objstyle .= 'F';
23423
			}
23424
		}
23425
		// stroke
23426
		if ($svgstyle['stroke'] != 'none') {
23427
			if ($svgstyle['stroke-opacity'] != 1) {
23428
				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23429
			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23430
				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23431
			}
23432
			$stroke_style = array(
23433
				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23434
				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23435
				'cap' => $svgstyle['stroke-linecap'],
23436
				'join' => $svgstyle['stroke-linejoin']
23437
				);
23438
			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23439
				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23440
			}
23441
			$this->setLineStyle($stroke_style);
23442
			$objstyle .= 'D';
23443
		}
23444
		// font
23445
		$regs = array();
23446
		if (!empty($svgstyle['font'])) {
23447
			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23448
				$font_family = $this->getFontFamilyName($regs[1]);
23449
			} else {
23450
				$font_family = $svgstyle['font-family'];
23451
			}
23452
			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23453
				$font_size = trim($regs[1]);
23454
			} else {
23455
				$font_size = $svgstyle['font-size'];
23456
			}
23457
			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23458
				$font_style = trim($regs[1]);
23459
			} else {
23460
				$font_style = $svgstyle['font-style'];
23461
			}
23462
			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23463
				$font_weight = trim($regs[1]);
23464
			} else {
23465
				$font_weight = $svgstyle['font-weight'];
23466
			}
23467
			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23468
				$font_stretch = trim($regs[1]);
23469
			} else {
23470
				$font_stretch = $svgstyle['font-stretch'];
23471
			}
23472
			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23473
				$font_spacing = trim($regs[1]);
23474
			} else {
23475
				$font_spacing = $svgstyle['letter-spacing'];
23476
			}
23477
		} else {
23478
			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23479
			$font_size = $svgstyle['font-size'];
23480
			$font_style = $svgstyle['font-style'];
23481
			$font_weight = $svgstyle['font-weight'];
23482
			$font_stretch = $svgstyle['font-stretch'];
23483
			$font_spacing = $svgstyle['letter-spacing'];
23484
		}
23485
		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23486
		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23487
		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23488
		switch ($font_style) {
23489
			case 'italic': {
23490
				$font_style = 'I';
23491
				break;
23492
			}
23493
			case 'oblique': {
23494
				$font_style = 'I';
23495
				break;
23496
			}
23497
			default:
23498
			case 'normal': {
23499
				$font_style = '';
23500
				break;
23501
			}
23502
		}
23503
		switch ($font_weight) {
23504
			case 'bold':
23505
			case 'bolder': {
23506
				$font_style .= 'B';
23507
				break;
23508
			}
23509
			case 'normal': {
23510
				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23511
					$font_family = substr($font_family, 0, -2).'I';
23512
				} elseif (substr($font_family, -1) == 'B') {
23513
					$font_family = substr($font_family, 0, -1);
23514
				}
23515
				break;
23516
			}
23517
		}
23518
		switch ($svgstyle['text-decoration']) {
23519
			case 'underline': {
23520
				$font_style .= 'U';
23521
				break;
23522
			}
23523
			case 'overline': {
23524
				$font_style .= 'O';
23525
				break;
23526
			}
23527
			case 'line-through': {
23528
				$font_style .= 'D';
23529
				break;
23530
			}
23531
			default:
23532
			case 'none': {
23533
				break;
23534
			}
23535
		}
23536
		$this->setFont($font_family, $font_style, $font_size);
23537
		$this->setFontStretching($font_stretch);
23538
		$this->setFontSpacing($font_spacing);
23539
		return $objstyle;
23540
	}
23541
 
23542
	/**
23543
	 * Draws an SVG path
23544
	 * @param string $d attribute d of the path SVG element
23545
	 * @param string $style Style of rendering. Possible values are:
23546
	 * <ul>
23547
	 *	 <li>D or empty string: Draw (default).</li>
23548
	 *	 <li>F: Fill.</li>
23549
	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23550
	 *	 <li>DF or FD: Draw and fill.</li>
23551
	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23552
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23553
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23554
	 * </ul>
23555
	 * @return array of container box measures (x, y, w, h)
23556
	 * @author Nicola Asuni
23557
	 * @since 5.0.000 (2010-05-02)
23558
	 * @protected
23559
	 */
23560
	protected function SVGPath($d, $style='') {
23561
		if ($this->state != 2) {
23562
			return;
23563
		}
23564
		// set fill/stroke style
23565
		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23566
		if (empty($op)) {
23567
			return;
23568
		}
23569
		$paths = array();
23570
		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23571
		$d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23572
		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23573
		$x = 0;
23574
		$y = 0;
23575
		$x1 = 0;
23576
		$y1 = 0;
23577
		$x2 = 0;
23578
		$y2 = 0;
23579
		$xmin = 2147483647;
23580
		$xmax = 0;
23581
		$ymin = 2147483647;
23582
		$ymax = 0;
23583
		$xinitial = 0;
23584
		$yinitial = 0;
23585
		$relcoord = false;
23586
		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23587
		$firstcmd = true; // used to print first point
23588
		// draw curve pieces
23589
		foreach ($paths as $key => $val) {
23590
			// get curve type
23591
			$cmd = trim($val[1]);
23592
			if (strtolower($cmd) == $cmd) {
23593
				// use relative coordinated instead of absolute
23594
				$relcoord = true;
23595
				$xoffset = $x;
23596
				$yoffset = $y;
23597
			} else {
23598
				$relcoord = false;
23599
				$xoffset = 0;
23600
				$yoffset = 0;
23601
			}
23602
			$params = array();
23603
			if (isset($val[2])) {
23604
				// get curve parameters
23605
				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23606
				$params = array();
23607
				foreach ($rawparams as $ck => $cp) {
23608
					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23609
					if (abs($params[$ck]) < $minlen) {
23610
						// approximate little values to zero
23611
						$params[$ck] = 0;
23612
					}
23613
				}
23614
			}
23615
			// store current origin point
23616
			$x0 = $x;
23617
			$y0 = $y;
23618
			switch (strtoupper($cmd)) {
23619
				case 'M': { // moveto
23620
					foreach ($params as $ck => $cp) {
23621
						if (($ck % 2) == 0) {
23622
							$x = $cp + $xoffset;
23623
						} else {
23624
							$y = $cp + $yoffset;
23625
							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23626
								if ($ck == 1) {
23627
									$this->_outPoint($x, $y);
23628
									$firstcmd = false;
23629
									$xinitial = $x;
23630
									$yinitial = $y;
23631
								} else {
23632
									$this->_outLine($x, $y);
23633
								}
23634
								$x0 = $x;
23635
								$y0 = $y;
23636
							}
23637
							$xmin = min($xmin, $x);
23638
							$ymin = min($ymin, $y);
23639
							$xmax = max($xmax, $x);
23640
							$ymax = max($ymax, $y);
23641
							if ($relcoord) {
23642
								$xoffset = $x;
23643
								$yoffset = $y;
23644
							}
23645
						}
23646
					}
23647
					break;
23648
				}
23649
				case 'L': { // lineto
23650
					foreach ($params as $ck => $cp) {
23651
						if (($ck % 2) == 0) {
23652
							$x = $cp + $xoffset;
23653
						} else {
23654
							$y = $cp + $yoffset;
23655
							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23656
								$this->_outLine($x, $y);
23657
								$x0 = $x;
23658
								$y0 = $y;
23659
							}
23660
							$xmin = min($xmin, $x);
23661
							$ymin = min($ymin, $y);
23662
							$xmax = max($xmax, $x);
23663
							$ymax = max($ymax, $y);
23664
							if ($relcoord) {
23665
								$xoffset = $x;
23666
								$yoffset = $y;
23667
							}
23668
						}
23669
					}
23670
					break;
23671
				}
23672
				case 'H': { // horizontal lineto
23673
					foreach ($params as $ck => $cp) {
23674
						$x = $cp + $xoffset;
23675
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23676
							$this->_outLine($x, $y);
23677
							$x0 = $x;
23678
							$y0 = $y;
23679
						}
23680
						$xmin = min($xmin, $x);
23681
						$xmax = max($xmax, $x);
23682
						if ($relcoord) {
23683
							$xoffset = $x;
23684
						}
23685
					}
23686
					break;
23687
				}
23688
				case 'V': { // vertical lineto
23689
					foreach ($params as $ck => $cp) {
23690
						$y = $cp + $yoffset;
23691
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23692
							$this->_outLine($x, $y);
23693
							$x0 = $x;
23694
							$y0 = $y;
23695
						}
23696
						$ymin = min($ymin, $y);
23697
						$ymax = max($ymax, $y);
23698
						if ($relcoord) {
23699
							$yoffset = $y;
23700
						}
23701
					}
23702
					break;
23703
				}
23704
				case 'C': { // curveto
23705
					foreach ($params as $ck => $cp) {
23706
						$params[$ck] = $cp;
23707
						if ((($ck + 1) % 6) == 0) {
23708
							$x1 = $params[($ck - 5)] + $xoffset;
23709
							$y1 = $params[($ck - 4)] + $yoffset;
23710
							$x2 = $params[($ck - 3)] + $xoffset;
23711
							$y2 = $params[($ck - 2)] + $yoffset;
23712
							$x = $params[($ck - 1)] + $xoffset;
23713
							$y = $params[($ck)] + $yoffset;
23714
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23715
							$xmin = min($xmin, $x, $x1, $x2);
23716
							$ymin = min($ymin, $y, $y1, $y2);
23717
							$xmax = max($xmax, $x, $x1, $x2);
23718
							$ymax = max($ymax, $y, $y1, $y2);
23719
							if ($relcoord) {
23720
								$xoffset = $x;
23721
								$yoffset = $y;
23722
							}
23723
						}
23724
					}
23725
					break;
23726
				}
23727
				case 'S': { // shorthand/smooth curveto
23728
					foreach ($params as $ck => $cp) {
23729
						$params[$ck] = $cp;
23730
						if ((($ck + 1) % 4) == 0) {
23731
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23732
								$x1 = (2 * $x) - $x2;
23733
								$y1 = (2 * $y) - $y2;
23734
							} else {
23735
								$x1 = $x;
23736
								$y1 = $y;
23737
							}
23738
							$x2 = $params[($ck - 3)] + $xoffset;
23739
							$y2 = $params[($ck - 2)] + $yoffset;
23740
							$x = $params[($ck - 1)] + $xoffset;
23741
							$y = $params[($ck)] + $yoffset;
23742
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23743
							$xmin = min($xmin, $x, $x1, $x2);
23744
							$ymin = min($ymin, $y, $y1, $y2);
23745
							$xmax = max($xmax, $x, $x1, $x2);
23746
							$ymax = max($ymax, $y, $y1, $y2);
23747
							if ($relcoord) {
23748
								$xoffset = $x;
23749
								$yoffset = $y;
23750
							}
23751
						}
23752
					}
23753
					break;
23754
				}
23755
				case 'Q': { // quadratic Bezier curveto
23756
					foreach ($params as $ck => $cp) {
23757
						$params[$ck] = $cp;
23758
						if ((($ck + 1) % 4) == 0) {
23759
							// convert quadratic points to cubic points
23760
							$x1 = $params[($ck - 3)] + $xoffset;
23761
							$y1 = $params[($ck - 2)] + $yoffset;
23762
							$xa = ($x + (2 * $x1)) / 3;
23763
							$ya = ($y + (2 * $y1)) / 3;
23764
							$x = $params[($ck - 1)] + $xoffset;
23765
							$y = $params[($ck)] + $yoffset;
23766
							$xb = ($x + (2 * $x1)) / 3;
23767
							$yb = ($y + (2 * $y1)) / 3;
23768
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23769
							$xmin = min($xmin, $x, $xa, $xb);
23770
							$ymin = min($ymin, $y, $ya, $yb);
23771
							$xmax = max($xmax, $x, $xa, $xb);
23772
							$ymax = max($ymax, $y, $ya, $yb);
23773
							if ($relcoord) {
23774
								$xoffset = $x;
23775
								$yoffset = $y;
23776
							}
23777
						}
23778
					}
23779
					break;
23780
				}
23781
				case 'T': { // shorthand/smooth quadratic Bezier curveto
23782
					foreach ($params as $ck => $cp) {
23783
						$params[$ck] = $cp;
23784
						if (($ck % 2) != 0) {
23785
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23786
								$x1 = (2 * $x) - $x1;
23787
								$y1 = (2 * $y) - $y1;
23788
							} else {
23789
								$x1 = $x;
23790
								$y1 = $y;
23791
							}
23792
							// convert quadratic points to cubic points
23793
							$xa = ($x + (2 * $x1)) / 3;
23794
							$ya = ($y + (2 * $y1)) / 3;
23795
							$x = $params[($ck - 1)] + $xoffset;
23796
							$y = $params[($ck)] + $yoffset;
23797
							$xb = ($x + (2 * $x1)) / 3;
23798
							$yb = ($y + (2 * $y1)) / 3;
23799
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23800
							$xmin = min($xmin, $x, $xa, $xb);
23801
							$ymin = min($ymin, $y, $ya, $yb);
23802
							$xmax = max($xmax, $x, $xa, $xb);
23803
							$ymax = max($ymax, $y, $ya, $yb);
23804
							if ($relcoord) {
23805
								$xoffset = $x;
23806
								$yoffset = $y;
23807
							}
23808
						}
23809
					}
23810
					break;
23811
				}
23812
				case 'A': { // elliptical arc
23813
					foreach ($params as $ck => $cp) {
23814
						$params[$ck] = $cp;
23815
						if ((($ck + 1) % 7) == 0) {
23816
							$x0 = $x;
23817
							$y0 = $y;
23818
							$rx = max(abs($params[($ck - 6)]), .000000001);
23819
							$ry = max(abs($params[($ck - 5)]), .000000001);
23820
							$ang = -$rawparams[($ck - 4)];
23821
							$angle = deg2rad($ang);
23822
							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23823
							$fs = $rawparams[($ck - 2)]; // sweep-flag
23824
							$x = $params[($ck - 1)] + $xoffset;
23825
							$y = $params[$ck] + $yoffset;
23826
							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23827
								// endpoints are almost identical
23828
								$xmin = min($xmin, $x);
23829
								$ymin = min($ymin, $y);
23830
								$xmax = max($xmax, $x);
23831
								$ymax = max($ymax, $y);
23832
							} else {
23833
								$cos_ang = cos($angle);
23834
								$sin_ang = sin($angle);
23835
								$a = (($x0 - $x) / 2);
23836
								$b = (($y0 - $y) / 2);
23837
								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23838
								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23839
								$rx2 = $rx * $rx;
23840
								$ry2 = $ry * $ry;
23841
								$xa2 = $xa * $xa;
23842
								$ya2 = $ya * $ya;
23843
								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23844
								if ($delta > 1) {
23845
									$rx *= sqrt($delta);
23846
									$ry *= sqrt($delta);
23847
									$rx2 = $rx * $rx;
23848
									$ry2 = $ry * $ry;
23849
								}
23850
								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23851
								if ($numerator < 0) {
23852
									$root = 0;
23853
								} else {
23854
									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23855
								}
23856
								if ($fa == $fs){
23857
									$root *= -1;
23858
								}
23859
								$cax = $root * (($rx * $ya) / $ry);
23860
								$cay = -$root * (($ry * $xa) / $rx);
23861
								// coordinates of ellipse center
23862
								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23863
								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23864
								// get angles
23865
								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23866
								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23867
								if (($fs == 0) AND ($dang > 0)) {
23868
									$dang -= (2 * M_PI);
23869
								} elseif (($fs == 1) AND ($dang < 0)) {
23870
									$dang += (2 * M_PI);
23871
								}
23872
								$angf = $angs - $dang;
23873
								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23874
									// reverse angles
23875
									$tmp = $angs;
23876
									$angs = $angf;
23877
									$angf = $tmp;
23878
								}
23879
								$angs = round(rad2deg($angs), 6);
23880
								$angf = round(rad2deg($angf), 6);
23881
								// covent angles to positive values
23882
								if (($angs < 0) AND ($angf < 0)) {
23883
									$angs += 360;
23884
									$angf += 360;
23885
								}
23886
								$pie = false;
23887
								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23888
									$pie = true;
23889
								}
23890
								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23891
								$xmin = min($xmin, $x, $axmin);
23892
								$ymin = min($ymin, $y, $aymin);
23893
								$xmax = max($xmax, $x, $axmax);
23894
								$ymax = max($ymax, $y, $aymax);
23895
							}
23896
							if ($relcoord) {
23897
								$xoffset = $x;
23898
								$yoffset = $y;
23899
							}
23900
						}
23901
					}
23902
					break;
23903
				}
23904
				case 'Z': {
23905
					$this->_out('h');
23906
					$x = $x0 = $xinitial;
23907
					$y = $y0 = $yinitial;
23908
					break;
23909
				}
23910
			}
23911
			$firstcmd = false;
23912
		} // end foreach
23913
		$this->_out($op);
23914
		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23915
	}
23916
 
23917
	/**
23918
	 * Return the tag name without the namespace
23919
	 * @param string $name Tag name
23920
	 * @protected
23921
	 */
23922
	protected function removeTagNamespace($name) {
23923
		if(strpos($name, ':') !== false) {
23924
			$parts = explode(':', $name);
23925
			return $parts[(sizeof($parts) - 1)];
23926
		}
23927
		return $name;
23928
	}
23929
 
23930
	/**
23931
	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23932
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
23933
	 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
23934
	 * @param array $attribs The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
23935
	 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
23936
	 * @author Nicola Asuni
23937
	 * @since 5.0.000 (2010-05-02)
23938
	 * @protected
23939
	 */
23940
	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23941
		$name = $this->removeTagNamespace($name);
23942
		// check if we are in clip mode
23943
		if ($this->svgclipmode) {
23944
			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23945
			return;
23946
		}
23947
		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23948
			if (isset($attribs['id'])) {
23949
				$attribs['child_elements'] = array();
23950
				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23951
				return;
23952
			}
23953
			if (end($this->svgdefs) !== FALSE) {
23954
				$last_svgdefs_id = key($this->svgdefs);
23955
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23956
					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23957
					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23958
					return;
23959
				}
23960
			}
23961
			return;
23962
		}
23963
		$clipping = false;
23964
		if ($parser == 'clip-path') {
23965
			// set clipping mode
23966
			$clipping = true;
23967
		}
23968
		// get styling properties
23969
		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23970
		$svgstyle = $this->svgstyles[0]; // set default style
23971
		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23972
			// default fill attribute for clipping
23973
			$attribs['fill'] = 'none';
23974
		}
23975
		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23976
			// fix style for regular expression
23977
			$attribs['style'] = ';'.$attribs['style'];
23978
		}
23979
		foreach ($prev_svgstyle as $key => $val) {
23980
			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23981
				// inherit previous value
23982
				$svgstyle[$key] = $val;
23983
			}
23984
			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23985
				// specific attribute settings
23986
				if ($attribs[$key] == 'inherit') {
23987
					$svgstyle[$key] = $val;
23988
				} else {
23989
					$svgstyle[$key] = $attribs[$key];
23990
				}
23991
			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23992
				// CSS style syntax
23993
				$attrval = array();
23994
				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23995
					if ($attrval[1] == 'inherit') {
23996
						$svgstyle[$key] = $val;
23997
					} else {
23998
						$svgstyle[$key] = $attrval[1];
23999
					}
24000
				}
24001
			}
24002
		}
24003
		// transformation matrix
24004
		if (!empty($ctm)) {
24005
			$tm = $ctm;
24006
		} else {
24007
			$tm = array(1,0,0,1,0,0);
24008
		}
24009
		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
24010
			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
24011
		}
24012
		$svgstyle['transfmatrix'] = $tm;
24013
		$invisible = false;
24014
		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
24015
			// the current graphics element is invisible (nothing is painted)
24016
			$invisible = true;
24017
		}
24018
		// process tag
24019
		switch($name) {
24020
			case 'defs': {
24021
				$this->svgdefsmode = true;
24022
				break;
24023
			}
24024
			// clipPath
24025
			case 'clipPath': {
24026
				if ($invisible) {
24027
					break;
24028
				}
24029
				$this->svgclipmode = true;
24030
				if (!isset($attribs['id'])) {
24031
					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
24032
				}
24033
				$this->svgclipid = $attribs['id'];
24034
				$this->svgclippaths[$this->svgclipid] = array();
24035
				$this->svgcliptm[$this->svgclipid] = $tm;
24036
				break;
24037
			}
24038
			case 'svg': {
24039
				// start of SVG object
24040
				if(++$this->svg_tag_depth <= 1) {
24041
					break;
24042
				}
24043
				// inner SVG
24044
				array_push($this->svgstyles, $svgstyle);
24045
				$this->StartTransform();
24046
				$svgX = (isset($attribs['x'])?$attribs['x']:0);
24047
				$svgY = (isset($attribs['y'])?$attribs['y']:0);
24048
				$svgW = (isset($attribs['width'])?$attribs['width']:0);
24049
				$svgH = (isset($attribs['height'])?$attribs['height']:0);
24050
				// set x, y position using transform matrix
24051
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24052
				$this->SVGTransform($tm);
24053
				// set clipping for width and height
24054
				$x = 0;
24055
				$y = 0;
24056
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
24057
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
24058
				// draw clipping rect
24059
				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24060
				// parse viewbox, calculate extra transformation matrix
24061
				if (isset($attribs['viewBox'])) {
24062
					$tmp = array();
24063
					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24064
					$tmp = $tmp[0];
24065
					if (sizeof($tmp) == 4) {
24066
						$vx = $tmp[0];
24067
						$vy = $tmp[1];
24068
						$vw = $tmp[2];
24069
						$vh = $tmp[3];
24070
						// get aspect ratio
24071
						$tmp = array();
24072
						$aspectX = 'xMid';
24073
						$aspectY = 'YMid';
24074
						$fit = 'meet';
24075
						if (isset($attribs['preserveAspectRatio'])) {
24076
							if($attribs['preserveAspectRatio'] == 'none') {
24077
								$fit = 'none';
24078
							} else {
24079
								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24080
								$tmp = $tmp[0];
24081
								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24082
									$aspectX = substr($tmp[0], 0, 4);
24083
									$aspectY = substr($tmp[0], 4, 4);
24084
									$fit = $tmp[1];
24085
								}
24086
							}
24087
						}
24088
						$wr = ($svgW / $vw);
24089
						$hr = ($svgH / $vh);
24090
						$ax = $ay = 0;
24091
						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24092
							if ($aspectX == 'xMax') {
24093
								$ax = (($vw * ($wr / $hr)) - $vw);
24094
							}
24095
							if ($aspectX == 'xMid') {
24096
								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24097
							}
24098
							$wr = $hr;
24099
						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24100
							if ($aspectY == 'YMax') {
24101
								$ay = (($vh * ($hr / $wr)) - $vh);
24102
							}
24103
							if ($aspectY == 'YMid') {
24104
								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24105
							}
24106
							$hr = $wr;
24107
						}
24108
						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24109
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24110
						$this->SVGTransform($tm);
24111
					}
24112
				}
24113
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24114
				break;
24115
			}
24116
			case 'g': {
24117
				// group together related graphics elements
24118
				array_push($this->svgstyles, $svgstyle);
24119
				$this->StartTransform();
24120
				$x = (isset($attribs['x'])?$attribs['x']:0);
24121
				$y = (isset($attribs['y'])?$attribs['y']:0);
24122
				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24123
				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24124
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24125
				$this->SVGTransform($tm);
24126
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24127
				break;
24128
			}
24129
			case 'linearGradient': {
24130
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24131
					break;
24132
				}
24133
				if (!isset($attribs['id'])) {
24134
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24135
				}
24136
				$this->svggradientid = $attribs['id'];
24137
				$this->svggradients[$this->svggradientid] = array();
24138
				$this->svggradients[$this->svggradientid]['type'] = 2;
24139
				$this->svggradients[$this->svggradientid]['stops'] = array();
24140
				if (isset($attribs['gradientUnits'])) {
24141
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24142
				} else {
24143
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24144
				}
24145
				//$attribs['spreadMethod']
24146
				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24147
					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24148
						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24149
						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24150
						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24151
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24152
				} else {
24153
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24154
				}
24155
				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24156
				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24157
				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24158
				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24159
				if (isset($attribs['gradientTransform'])) {
24160
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24161
				}
24162
				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24163
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24164
					// gradient is defined on another place
24165
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24166
				}
24167
				break;
24168
			}
24169
			case 'radialGradient': {
24170
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24171
					break;
24172
				}
24173
				if (!isset($attribs['id'])) {
24174
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24175
				}
24176
				$this->svggradientid = $attribs['id'];
24177
				$this->svggradients[$this->svggradientid] = array();
24178
				$this->svggradients[$this->svggradientid]['type'] = 3;
24179
				$this->svggradients[$this->svggradientid]['stops'] = array();
24180
				if (isset($attribs['gradientUnits'])) {
24181
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24182
				} else {
24183
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24184
				}
24185
				//$attribs['spreadMethod']
24186
				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24187
					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24188
					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24189
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24190
				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24191
					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24192
				} else {
24193
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24194
				}
24195
				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24196
				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24197
				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24198
				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24199
				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24200
				if (isset($attribs['gradientTransform'])) {
24201
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24202
				}
24203
				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24204
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24205
					// gradient is defined on another place
24206
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24207
				}
24208
				break;
24209
			}
24210
			case 'stop': {
24211
				// gradient stops
24212
				if (substr($attribs['offset'], -1) == '%') {
24213
					$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24214
				} else {
24215
					$offset = floatval($attribs['offset']);
24216
					if ($offset > 1) {
24217
						$offset /= 100;
24218
					}
24219
				}
24220
				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24221
				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24222
				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24223
				break;
24224
			}
24225
			// paths
24226
			case 'path': {
24227
				if ($invisible) {
24228
					break;
24229
				}
24230
				if (isset($attribs['d'])) {
24231
					$d = trim($attribs['d']);
24232
					if (!empty($d)) {
24233
						$x = (isset($attribs['x'])?$attribs['x']:0);
24234
						$y = (isset($attribs['y'])?$attribs['y']:0);
24235
						$w = (isset($attribs['width'])?$attribs['width']:1);
24236
						$h = (isset($attribs['height'])?$attribs['height']:1);
24237
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24238
						if ($clipping) {
24239
							$this->SVGTransform($tm);
24240
							$this->SVGPath($d, 'CNZ');
24241
						} else {
24242
							$this->StartTransform();
24243
							$this->SVGTransform($tm);
24244
							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24245
							if (!empty($obstyle)) {
24246
								$this->SVGPath($d, $obstyle);
24247
							}
24248
							$this->StopTransform();
24249
						}
24250
					}
24251
				}
24252
				break;
24253
			}
24254
			// shapes
24255
			case 'rect': {
24256
				if ($invisible) {
24257
					break;
24258
				}
24259
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24260
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24261
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24262
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24263
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24264
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24265
				if ($clipping) {
24266
					$this->SVGTransform($tm);
24267
					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24268
				} else {
24269
					$this->StartTransform();
24270
					$this->SVGTransform($tm);
24271
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24272
					if (!empty($obstyle)) {
24273
						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24274
					}
24275
					$this->StopTransform();
24276
				}
24277
				break;
24278
			}
24279
			case 'circle': {
24280
				if ($invisible) {
24281
					break;
24282
				}
24283
				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24284
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24285
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24286
				$x = ($cx - $r);
24287
				$y = ($cy - $r);
24288
				$w = (2 * $r);
24289
				$h = $w;
24290
				if ($clipping) {
24291
					$this->SVGTransform($tm);
24292
					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24293
				} else {
24294
					$this->StartTransform();
24295
					$this->SVGTransform($tm);
24296
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24297
					if (!empty($obstyle)) {
24298
						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24299
					}
24300
					$this->StopTransform();
24301
				}
24302
				break;
24303
			}
24304
			case 'ellipse': {
24305
				if ($invisible) {
24306
					break;
24307
				}
24308
				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24309
				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24310
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24311
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24312
				$x = ($cx - $rx);
24313
				$y = ($cy - $ry);
24314
				$w = (2 * $rx);
24315
				$h = (2 * $ry);
24316
				if ($clipping) {
24317
					$this->SVGTransform($tm);
24318
					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24319
				} else {
24320
					$this->StartTransform();
24321
					$this->SVGTransform($tm);
24322
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24323
					if (!empty($obstyle)) {
24324
						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24325
					}
24326
					$this->StopTransform();
24327
				}
24328
				break;
24329
			}
24330
			case 'line': {
24331
				if ($invisible) {
24332
					break;
24333
				}
24334
				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24335
				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24336
				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24337
				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24338
				$x = $x1;
24339
				$y = $y1;
24340
				$w = abs($x2 - $x1);
24341
				$h = abs($y2 - $y1);
24342
				if (!$clipping) {
24343
					$this->StartTransform();
24344
					$this->SVGTransform($tm);
24345
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24346
					$this->Line($x1, $y1, $x2, $y2);
24347
					$this->StopTransform();
24348
				}
24349
				break;
24350
			}
24351
			case 'polyline':
24352
			case 'polygon': {
24353
				if ($invisible) {
24354
					break;
24355
				}
24356
				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24357
				$points = trim($points);
24358
				// note that point may use a complex syntax not covered here
24359
				$points = preg_split('/[\,\s]+/si', $points);
24360
				if (count($points) < 4) {
24361
					break;
24362
				}
24363
				$p = array();
24364
				$xmin = 2147483647;
24365
				$xmax = 0;
24366
				$ymin = 2147483647;
24367
				$ymax = 0;
24368
				foreach ($points as $key => $val) {
24369
					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24370
					if (($key % 2) == 0) {
24371
						// X coordinate
24372
						$xmin = min($xmin, $p[$key]);
24373
						$xmax = max($xmax, $p[$key]);
24374
					} else {
24375
						// Y coordinate
24376
						$ymin = min($ymin, $p[$key]);
24377
						$ymax = max($ymax, $p[$key]);
24378
					}
24379
				}
24380
				$x = $xmin;
24381
				$y = $ymin;
24382
				$w = ($xmax - $xmin);
24383
				$h = ($ymax - $ymin);
24384
				if ($name == 'polyline') {
24385
					$this->StartTransform();
24386
					$this->SVGTransform($tm);
24387
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24388
					if (!empty($obstyle)) {
24389
						$this->PolyLine($p, $obstyle, array(), array());
24390
					}
24391
					$this->StopTransform();
24392
				} else { // polygon
24393
					if ($clipping) {
24394
						$this->SVGTransform($tm);
24395
						$this->Polygon($p, 'CNZ', array(), array(), true);
24396
					} else {
24397
						$this->StartTransform();
24398
						$this->SVGTransform($tm);
24399
						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24400
						if (!empty($obstyle)) {
24401
							$this->Polygon($p, $obstyle, array(), array(), true);
24402
						}
24403
						$this->StopTransform();
24404
					}
24405
				}
24406
				break;
24407
			}
24408
			// image
24409
			case 'image': {
24410
				if ($invisible) {
24411
					break;
24412
				}
24413
				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24414
					break;
24415
				}
24416
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24417
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24418
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24419
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24420
				$img = $attribs['xlink:href'];
24421
				if (!$clipping) {
24422
					$this->StartTransform();
24423
					$this->SVGTransform($tm);
24424
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24425
					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24426
						// embedded image encoded as base64
24427
						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24428
					} else {
24429
						// fix image path
24430
						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24431
							// replace relative path with full server path
24432
							$img = $this->svgdir.'/'.$img;
24433
						}
24434
						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24435
							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24436
							if (($findroot === false) OR ($findroot > 1)) {
24437
								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24438
									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24439
								} else {
24440
									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24441
								}
24442
							}
24443
						}
24444
						$img = urldecode($img);
24445
						$testscrtype = @parse_url($img);
24446
						if (empty($testscrtype['query'])) {
24447
							// convert URL to server path
24448
							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24449
						} elseif (preg_match('|^https?://|', $img) !== 1) {
24450
							// convert server path to URL
24451
							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24452
						}
24453
					}
24454
					// get image type
24455
					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24456
					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24457
						$this->ImageEps($img, $x, $y, $w, $h);
24458
					} elseif ($imgtype == 'svg') {
24459
						// store SVG vars
24460
						$svggradients = $this->svggradients;
24461
						$svggradientid = $this->svggradientid;
24462
						$svgdefsmode = $this->svgdefsmode;
24463
						$svgdefs = $this->svgdefs;
24464
						$svgclipmode = $this->svgclipmode;
24465
						$svgclippaths = $this->svgclippaths;
24466
						$svgcliptm = $this->svgcliptm;
24467
						$svgclipid = $this->svgclipid;
24468
						$svgtext = $this->svgtext;
24469
						$svgtextmode = $this->svgtextmode;
24470
						$this->ImageSVG($img, $x, $y, $w, $h);
24471
						// restore SVG vars
24472
						$this->svggradients = $svggradients;
24473
						$this->svggradientid = $svggradientid;
24474
						$this->svgdefsmode = $svgdefsmode;
24475
						$this->svgdefs = $svgdefs;
24476
						$this->svgclipmode = $svgclipmode;
24477
						$this->svgclippaths = $svgclippaths;
24478
						$this->svgcliptm = $svgcliptm;
24479
						$this->svgclipid = $svgclipid;
24480
						$this->svgtext = $svgtext;
24481
						$this->svgtextmode = $svgtextmode;
24482
					} else {
24483
						$this->Image($img, $x, $y, $w, $h);
24484
					}
24485
					$this->StopTransform();
24486
				}
24487
				break;
24488
			}
24489
			// text
24490
			case 'text':
24491
			case 'tspan': {
24492
				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24493
					// @TODO: unsupported feature
24494
				}
24495
				// only basic support - advanced features must be implemented
24496
				$this->svgtextmode['invisible'] = $invisible;
24497
				if ($invisible) {
24498
					break;
24499
				}
24500
				array_push($this->svgstyles, $svgstyle);
24501
				if (isset($attribs['x'])) {
24502
					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24503
				} elseif ($name == 'tspan') {
24504
					$x = $this->x;
24505
				} else {
24506
					$x = 0;
24507
				}
24508
				if (isset($attribs['dx'])) {
24509
					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24510
				}
24511
				if (isset($attribs['y'])) {
24512
					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24513
				} elseif ($name == 'tspan') {
24514
					$y = $this->y;
24515
				} else {
24516
					$y = 0;
24517
				}
24518
				if (isset($attribs['dy'])) {
24519
					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24520
				}
24521
				$svgstyle['text-color'] = $svgstyle['fill'];
24522
				$this->svgtext = '';
24523
				if (isset($svgstyle['text-anchor'])) {
24524
					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24525
				} else {
24526
					$this->svgtextmode['text-anchor'] = 'start';
24527
				}
24528
				if (isset($svgstyle['direction'])) {
24529
					if ($svgstyle['direction'] == 'rtl') {
24530
						$this->svgtextmode['rtl'] = true;
24531
					} else {
24532
						$this->svgtextmode['rtl'] = false;
24533
					}
24534
				} else {
24535
					$this->svgtextmode['rtl'] = false;
24536
				}
24537
				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24538
					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24539
				} else {
24540
					$this->svgtextmode['stroke'] = false;
24541
				}
24542
				$this->StartTransform();
24543
				$this->SVGTransform($tm);
24544
				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24545
				$this->x = $x;
24546
				$this->y = $y;
24547
				break;
24548
			}
24549
			// use
24550
			case 'use': {
24551
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24552
					$svgdefid = substr($attribs['xlink:href'], 1);
24553
					if (isset($this->svgdefs[$svgdefid])) {
24554
						$use = $this->svgdefs[$svgdefid];
24555
						if (isset($attribs['xlink:href'])) {
24556
							unset($attribs['xlink:href']);
24557
						}
24558
						if (isset($attribs['id'])) {
24559
							unset($attribs['id']);
24560
						}
24561
						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24562
							$attribs['x'] += $use['attribs']['x'];
24563
						}
24564
						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24565
							$attribs['y'] += $use['attribs']['y'];
24566
						}
24567
						if (empty($attribs['style'])) {
24568
							$attribs['style'] = '';
24569
						}
24570
						if (!empty($use['attribs']['style'])) {
24571
							// merge styles
24572
							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24573
						}
24574
						$attribs = array_merge($use['attribs'], $attribs);
24575
						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24576
						return;
24577
					}
24578
				}
24579
				break;
24580
			}
24581
			default: {
24582
				break;
24583
			}
24584
		} // end of switch
24585
		// process child elements
24586
		if (!empty($attribs['child_elements'])) {
24587
			$child_elements = $attribs['child_elements'];
24588
			unset($attribs['child_elements']);
24589
			foreach($child_elements as $child_element) {
24590
				if (empty($child_element['attribs']['closing_tag'])) {
24591
					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24592
				} else {
24593
					if (isset($child_element['attribs']['content'])) {
24594
						$this->svgtext = $child_element['attribs']['content'];
24595
					}
24596
					$this->endSVGElementHandler('child-tag', $child_element['name']);
24597
				}
24598
			}
24599
		}
24600
	}
24601
 
24602
	/**
24603
	 * Sets the closing SVG element handler function for the XML parser.
24604
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24605
	 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24606
	 * @author Nicola Asuni
24607
	 * @since 5.0.000 (2010-05-02)
24608
	 * @protected
24609
	 */
24610
	protected function endSVGElementHandler($parser, $name) {
24611
		$name = $this->removeTagNamespace($name);
24612
		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24613
			if (end($this->svgdefs) !== FALSE) {
24614
				$last_svgdefs_id = key($this->svgdefs);
24615
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24616
					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24617
						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24618
							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24619
							return;
24620
						}
24621
					}
24622
					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24623
						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24624
						return;
24625
					}
24626
				}
24627
			}
24628
			return;
24629
		}
24630
		switch($name) {
24631
			case 'defs': {
24632
				$this->svgdefsmode = false;
24633
				break;
24634
			}
24635
			// clipPath
24636
			case 'clipPath': {
24637
				$this->svgclipmode = false;
24638
				break;
24639
			}
24640
			case 'svg': {
24641
				if (--$this->svg_tag_depth <= 0) {
24642
					break;
24643
				}
24644
			}
24645
			case 'g': {
24646
				// ungroup: remove last style from array
24647
				array_pop($this->svgstyles);
24648
				$this->StopTransform();
24649
				break;
24650
			}
24651
			case 'text':
24652
			case 'tspan': {
24653
				if ($this->svgtextmode['invisible']) {
24654
					// This implementation must be fixed to following the rule:
24655
					// 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.
24656
					break;
24657
				}
24658
				// print text
24659
				$text = $this->svgtext;
24660
				//$text = $this->stringTrim($text);
24661
				$textlen = $this->GetStringWidth($text);
24662
				if ($this->svgtextmode['text-anchor'] != 'start') {
24663
					// check if string is RTL text
24664
					if ($this->svgtextmode['text-anchor'] == 'end') {
24665
						if ($this->svgtextmode['rtl']) {
24666
							$this->x += $textlen;
24667
						} else {
24668
							$this->x -= $textlen;
24669
						}
24670
					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24671
						if ($this->svgtextmode['rtl']) {
24672
							$this->x += ($textlen / 2);
24673
						} else {
24674
							$this->x -= ($textlen / 2);
24675
						}
24676
					}
24677
				}
24678
				$textrendermode = $this->textrendermode;
24679
				$textstrokewidth = $this->textstrokewidth;
24680
				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24681
				if ($name == 'text') {
24682
					// store current coordinates
24683
					$tmpx = $this->x;
24684
					$tmpy = $this->y;
24685
				}
24686
				// print the text
24687
				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24688
				if ($name == 'text') {
24689
					// restore coordinates
24690
					$this->x = $tmpx;
24691
					$this->y = $tmpy;
24692
				}
24693
				// restore previous rendering mode
24694
				$this->textrendermode = $textrendermode;
24695
				$this->textstrokewidth = $textstrokewidth;
24696
				$this->svgtext = '';
24697
				$this->StopTransform();
24698
				if (!$this->svgdefsmode) {
24699
					array_pop($this->svgstyles);
24700
				}
24701
				break;
24702
			}
24703
			default: {
24704
				break;
24705
			}
24706
		}
24707
	}
24708
 
24709
	/**
24710
	 * Sets the character data handler function for the XML parser.
24711
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24712
	 * @param string $data The second parameter, data, contains the character data as a string.
24713
	 * @author Nicola Asuni
24714
	 * @since 5.0.000 (2010-05-02)
24715
	 * @protected
24716
	 */
24717
	protected function segSVGContentHandler($parser, $data) {
24718
		$this->svgtext .= $data;
24719
	}
24720
 
24721
	// --- END SVG METHODS -----------------------------------------------------
24722
 
24723
    /**
24724
     * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24725
     * @param string $file
24726
     * @return string
24727
     */
24728
    protected function getCachedFileContents($file)
24729
    {
24730
        if (!isset($this->fileContentCache[$file])) {
24731
            $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24732
        }
24733
        return $this->fileContentCache[$file];
24734
    }
24735
 
24736
    /**
24737
     * Avoid multiple calls to an external server to see if a file exists
24738
     * @param string $file
24739
     * @return bool
24740
     */
24741
    protected function fileExists($file)
24742
    {
24743
        if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24744
            return true;
24745
        }
24746
 
24747
        return TCPDF_STATIC::file_exists($file);
24748
    }
24749
 
24750
} // END OF TCPDF CLASS
24751
 
24752
//============================================================+
24753
// END OF FILE
24754
//============================================================+