Passed
Push — master ( a46cb4...8ff499 )
by Michael
27:12 queued 15:45
created

TCPDF::IncludeJS()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nop 1
dl 0
loc 2
rs 10
1
<?php
2
//============================================================+
3
// File name   : tcpdf.php
4
// Version     : 6.10.0
5
// Begin       : 2002-08-03
6
// Last Update : 2025-05-27
7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - [email protected]
8
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9
// -------------------------------------------------------------------
10
// Copyright (C) 2002-2025 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.10.0
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.10.0
132
 * @author Nicola Asuni - [email protected]
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
	/**
842
	 * Internal secret used to encrypt data.
843
	 * @protected
844
	 * @since 6.7.5 (2024-03-21)
845
	 */
846
	protected $hash_key;
847
848
	// --- bookmark ---
849
850
	/**
851
	 * Outlines for bookmark.
852
	 * @protected
853
	 * @since 2.1.002 (2008-02-12)
854
	 */
855
	protected $outlines = array();
856
857
	/**
858
	 * Outline root for bookmark.
859
	 * @protected
860
	 * @since 2.1.002 (2008-02-12)
861
	 */
862
	protected $OutlineRoot;
863
864
	// --- javascript and form ---
865
866
	/**
867
	 * Javascript code.
868
	 * @protected
869
	 * @since 2.1.002 (2008-02-12)
870
	 */
871
	protected $javascript = '';
872
873
	/**
874
	 * Javascript counter.
875
	 * @protected
876
	 * @since 2.1.002 (2008-02-12)
877
	 */
878
	protected $n_js;
879
880
	/**
881
	 * line through state
882
	 * @protected
883
	 * @since 2.8.000 (2008-03-19)
884
	 */
885
	protected $linethrough;
886
887
	/**
888
	 * Array with additional document-wide usage rights for the document.
889
	 * @protected
890
	 * @since 5.8.014 (2010-08-23)
891
	 */
892
	protected $ur = array();
893
894
	/**
895
	 * DPI (Dot Per Inch) Document Resolution (do not change).
896
	 * @protected
897
	 * @since 3.0.000 (2008-03-27)
898
	 */
899
	protected $dpi = 72;
900
901
	/**
902
	 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
903
	 * @protected
904
	 * @since 3.0.000 (2008-03-27)
905
	 */
906
	protected $newpagegroup = array();
907
908
	/**
909
	 * Array that contains the number of pages in each page group.
910
	 * @protected
911
	 * @since 3.0.000 (2008-03-27)
912
	 */
913
	protected $pagegroups = array();
914
915
	/**
916
	 * Current page group number.
917
	 * @protected
918
	 * @since 3.0.000 (2008-03-27)
919
	 */
920
	protected $currpagegroup = 0;
921
922
	/**
923
	 * Array of transparency objects and parameters.
924
	 * @protected
925
	 * @since 3.0.000 (2008-03-27)
926
	 */
927
	protected $extgstates;
928
929
	/**
930
	 * Set the default JPEG compression quality (1-100).
931
	 * @protected
932
	 * @since 3.0.000 (2008-03-27)
933
	 */
934
	protected $jpeg_quality;
935
936
	/**
937
	 * Default cell height ratio.
938
	 * @protected
939
	 * @since 3.0.014 (2008-05-23)
940
	 * @var float
941
	 */
942
	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
943
944
	/**
945
	 * PDF viewer preferences.
946
	 * @protected
947
	 * @since 3.1.000 (2008-06-09)
948
	 */
949
	protected $viewer_preferences;
950
951
	/**
952
	 * A name object specifying how the document should be displayed when opened.
953
	 * @protected
954
	 * @since 3.1.000 (2008-06-09)
955
	 */
956
	protected $PageMode;
957
958
	/**
959
	 * Array for storing gradient information.
960
	 * @protected
961
	 * @since 3.1.000 (2008-06-09)
962
	 */
963
	protected $gradients = array();
964
965
	/**
966
	 * Array used to store positions inside the pages buffer (keys are the page numbers).
967
	 * @protected
968
	 * @since 3.2.000 (2008-06-26)
969
	 */
970
	protected $intmrk = array();
971
972
	/**
973
	 * Array used to store positions inside the pages buffer (keys are the page numbers).
974
	 * @protected
975
	 * @since 5.7.000 (2010-08-03)
976
	 */
977
	protected $bordermrk = array();
978
979
	/**
980
	 * Array used to store page positions to track empty pages (keys are the page numbers).
981
	 * @protected
982
	 * @since 5.8.007 (2010-08-18)
983
	 */
984
	protected $emptypagemrk = array();
985
986
	/**
987
	 * Array used to store content positions inside the pages buffer (keys are the page numbers).
988
	 * @protected
989
	 * @since 4.6.021 (2009-07-20)
990
	 */
991
	protected $cntmrk = array();
992
993
	/**
994
	 * Array used to store footer positions of each page.
995
	 * @protected
996
	 * @since 3.2.000 (2008-07-01)
997
	 */
998
	protected $footerpos = array();
999
1000
	/**
1001
	 * Array used to store footer length of each page.
1002
	 * @protected
1003
	 * @since 4.0.014 (2008-07-29)
1004
	 */
1005
	protected $footerlen = array();
1006
1007
	/**
1008
	 * Boolean flag to indicate if a new line is created.
1009
	 * @protected
1010
	 * @since 3.2.000 (2008-07-01)
1011
	 */
1012
	protected $newline = true;
1013
1014
	/**
1015
	 * End position of the latest inserted line.
1016
	 * @protected
1017
	 * @since 3.2.000 (2008-07-01)
1018
	 */
1019
	protected $endlinex = 0;
1020
1021
	/**
1022
	 * PDF string for width value of the last line.
1023
	 * @protected
1024
	 * @since 4.0.006 (2008-07-16)
1025
	 */
1026
	protected $linestyleWidth = '';
1027
1028
	/**
1029
	 * PDF string for CAP value of the last line.
1030
	 * @protected
1031
	 * @since 4.0.006 (2008-07-16)
1032
	 */
1033
	protected $linestyleCap = '0 J';
1034
1035
	/**
1036
	 * PDF string for join value of the last line.
1037
	 * @protected
1038
	 * @since 4.0.006 (2008-07-16)
1039
	 */
1040
	protected $linestyleJoin = '0 j';
1041
1042
	/**
1043
	 * PDF string for dash value of the last line.
1044
	 * @protected
1045
	 * @since 4.0.006 (2008-07-16)
1046
	 */
1047
	protected $linestyleDash = '[] 0 d';
1048
1049
	/**
1050
	 * Boolean flag to indicate if marked-content sequence is open.
1051
	 * @protected
1052
	 * @since 4.0.013 (2008-07-28)
1053
	 */
1054
	protected $openMarkedContent = false;
1055
1056
	/**
1057
	 * Count the latest inserted vertical spaces on HTML.
1058
	 * @protected
1059
	 * @since 4.0.021 (2008-08-24)
1060
	 */
1061
	protected $htmlvspace = 0;
1062
1063
	/**
1064
	 * Array of Spot colors.
1065
	 * @protected
1066
	 * @since 4.0.024 (2008-09-12)
1067
	 */
1068
	protected $spot_colors = array();
1069
1070
	/**
1071
	 * Symbol used for HTML unordered list items.
1072
	 * @protected
1073
	 * @since 4.0.028 (2008-09-26)
1074
	 */
1075
	protected $lisymbol = '';
1076
1077
	/**
1078
	 * String used to mark the beginning and end of EPS image blocks.
1079
	 * @protected
1080
	 * @since 4.1.000 (2008-10-18)
1081
	 */
1082
	protected $epsmarker = 'x#!#EPS#!#x';
1083
1084
	/**
1085
	 * Array of transformation matrix.
1086
	 * @protected
1087
	 * @since 4.2.000 (2008-10-29)
1088
	 */
1089
	protected $transfmatrix = array();
1090
1091
	/**
1092
	 * Current key for transformation matrix.
1093
	 * @protected
1094
	 * @since 4.8.005 (2009-09-17)
1095
	 */
1096
	protected $transfmatrix_key = 0;
1097
1098
	/**
1099
	 * Booklet mode for double-sided pages.
1100
	 * @protected
1101
	 * @since 4.2.000 (2008-10-29)
1102
	 */
1103
	protected $booklet = false;
1104
1105
	/**
1106
	 * Epsilon value used for float calculations.
1107
	 * @protected
1108
	 * @since 4.2.000 (2008-10-29)
1109
	 */
1110
	protected $feps = 0.005;
1111
1112
	/**
1113
	 * Array used for custom vertical spaces for HTML tags.
1114
	 * @protected
1115
	 * @since 4.2.001 (2008-10-30)
1116
	 */
1117
	protected $tagvspaces = array();
1118
1119
	/**
1120
	 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1121
	 * @protected
1122
	 * @since 4.2.007 (2008-11-12)
1123
	 */
1124
	protected $customlistindent = -1;
1125
1126
	/**
1127
	 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1128
	 * @protected
1129
	 * @since 4.2.010 (2008-11-14)
1130
	 */
1131
	protected $opencell = true;
1132
1133
	/**
1134
	 * Array of files to embedd.
1135
	 * @protected
1136
	 * @since 4.4.000 (2008-12-07)
1137
	 */
1138
	protected $embeddedfiles = array();
1139
1140
	/**
1141
	 * Boolean flag to indicate if we are inside a PRE tag.
1142
	 * @protected
1143
	 * @since 4.4.001 (2008-12-08)
1144
	 */
1145
	protected $premode = false;
1146
1147
	/**
1148
	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1149
	 * keys are the page numbers
1150
	 * @protected
1151
	 * @since 4.4.002 (2008-12-09)
1152
	 */
1153
	protected $transfmrk = array();
1154
1155
	/**
1156
	 * Default color for html links.
1157
	 * @protected
1158
	 * @since 4.4.003 (2008-12-09)
1159
	 */
1160
	protected $htmlLinkColorArray = array(0, 0, 255);
1161
1162
	/**
1163
	 * Default font style to add to html links.
1164
	 * @protected
1165
	 * @since 4.4.003 (2008-12-09)
1166
	 */
1167
	protected $htmlLinkFontStyle = 'U';
1168
1169
	/**
1170
	 * Counts the number of pages.
1171
	 * @protected
1172
	 * @since 4.5.000 (2008-12-31)
1173
	 */
1174
	protected $numpages = 0;
1175
1176
	/**
1177
	 * Array containing page lengths in bytes.
1178
	 * @protected
1179
	 * @since 4.5.000 (2008-12-31)
1180
	 */
1181
	protected $pagelen = array();
1182
1183
	/**
1184
	 * Counts the number of pages.
1185
	 * @protected
1186
	 * @since 4.5.000 (2008-12-31)
1187
	 */
1188
	protected $numimages = 0;
1189
1190
	/**
1191
	 * Store the image keys.
1192
	 * @protected
1193
	 * @since 4.5.000 (2008-12-31)
1194
	 */
1195
	protected $imagekeys = array();
1196
1197
	/**
1198
	 * Length of the buffer in bytes.
1199
	 * @protected
1200
	 * @since 4.5.000 (2008-12-31)
1201
	 */
1202
	protected $bufferlen = 0;
1203
1204
	/**
1205
	 * Counts the number of fonts.
1206
	 * @protected
1207
	 * @since 4.5.000 (2009-01-02)
1208
	 */
1209
	protected $numfonts = 0;
1210
1211
	/**
1212
	 * Store the font keys.
1213
	 * @protected
1214
	 * @since 4.5.000 (2009-01-02)
1215
	 */
1216
	protected $fontkeys = array();
1217
1218
	/**
1219
	 * Store the font object IDs.
1220
	 * @protected
1221
	 * @since 4.8.001 (2009-09-09)
1222
	 */
1223
	protected $font_obj_ids = array();
1224
1225
	/**
1226
	 * Store the fage status (true when opened, false when closed).
1227
	 * @protected
1228
	 * @since 4.5.000 (2009-01-02)
1229
	 */
1230
	protected $pageopen = array();
1231
1232
	/**
1233
	 * Default monospace font.
1234
	 * @protected
1235
	 * @since 4.5.025 (2009-03-10)
1236
	 */
1237
	protected $default_monospaced_font = 'courier';
1238
1239
	/**
1240
	 * Cloned copy of the current class object.
1241
	 * @protected
1242
	 * @since 4.5.029 (2009-03-19)
1243
	 */
1244
	protected $objcopy;
1245
1246
	/**
1247
	 * Array used to store the lengths of cache files.
1248
	 * @protected
1249
	 * @since 4.5.029 (2009-03-19)
1250
	 */
1251
	protected $cache_file_length = array();
1252
1253
	/**
1254
	 * Table header content to be repeated on each new page.
1255
	 * @protected
1256
	 * @since 4.5.030 (2009-03-20)
1257
	 */
1258
	protected $thead = '';
1259
1260
	/**
1261
	 * Margins used for table header.
1262
	 * @protected
1263
	 * @since 4.5.030 (2009-03-20)
1264
	 */
1265
	protected $theadMargins = array();
1266
1267
	/**
1268
	 * Boolean flag to enable document digital signature.
1269
	 * @protected
1270
	 * @since 4.6.005 (2009-04-24)
1271
	 */
1272
	protected $sign = false;
1273
1274
	/**
1275
	 * Digital signature data.
1276
	 * @protected
1277
	 * @since 4.6.005 (2009-04-24)
1278
	 */
1279
	protected $signature_data = array();
1280
1281
	/**
1282
	 * Digital signature max length.
1283
	 * @protected
1284
	 * @since 4.6.005 (2009-04-24)
1285
	 */
1286
	protected $signature_max_length = 11742;
1287
1288
	/**
1289
	 * Data for digital signature appearance.
1290
	 * @protected
1291
	 * @since 5.3.011 (2010-06-16)
1292
	 */
1293
	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1294
1295
	/**
1296
	 * Array of empty digital signature appearances.
1297
	 * @protected
1298
	 * @since 5.9.101 (2011-07-06)
1299
	 */
1300
	protected $empty_signature_appearance = array();
1301
1302
	/**
1303
	 * Boolean flag to enable document timestamping with TSA.
1304
	 * @protected
1305
	 * @since 6.0.085 (2014-06-19)
1306
	 */
1307
	protected $tsa_timestamp = false;
1308
1309
	/**
1310
	 * Timestamping data.
1311
	 * @protected
1312
	 * @since 6.0.085 (2014-06-19)
1313
	 */
1314
	protected $tsa_data = array();
1315
1316
	/**
1317
	 * Regular expression used to find blank characters (required for word-wrapping).
1318
	 * @protected
1319
	 * @since 4.6.006 (2009-04-28)
1320
	 */
1321
	protected $re_spaces = '/[^\S\xa0]/';
1322
1323
	/**
1324
	 * Array of $re_spaces parts.
1325
	 * @protected
1326
	 * @since 5.5.011 (2010-07-09)
1327
	 */
1328
	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1329
1330
	/**
1331
	 * Digital signature object ID.
1332
	 * @protected
1333
	 * @since 4.6.022 (2009-06-23)
1334
	 */
1335
	protected $sig_obj_id = 0;
1336
1337
	/**
1338
	 * ID of page objects.
1339
	 * @protected
1340
	 * @since 4.7.000 (2009-08-29)
1341
	 */
1342
	protected $page_obj_id = array();
1343
1344
	/**
1345
	 * List of form annotations IDs.
1346
	 * @protected
1347
	 * @since 4.8.000 (2009-09-07)
1348
	 */
1349
	protected $form_obj_id = array();
1350
1351
	/**
1352
	 * 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.
1353
	 * @protected
1354
	 * @since 4.8.000 (2009-09-07)
1355
	 */
1356
	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1357
1358
	/**
1359
	 * Javascript objects array.
1360
	 * @protected
1361
	 * @since 4.8.000 (2009-09-07)
1362
	 */
1363
	protected $js_objects = array();
1364
1365
	/**
1366
	 * Current form action (used during XHTML rendering).
1367
	 * @protected
1368
	 * @since 4.8.000 (2009-09-07)
1369
	 */
1370
	protected $form_action = '';
1371
1372
	/**
1373
	 * Current form encryption type (used during XHTML rendering).
1374
	 * @protected
1375
	 * @since 4.8.000 (2009-09-07)
1376
	 */
1377
	protected $form_enctype = 'application/x-www-form-urlencoded';
1378
1379
	/**
1380
	 * Current method to submit forms.
1381
	 * @protected
1382
	 * @since 4.8.000 (2009-09-07)
1383
	 */
1384
	protected $form_mode = 'post';
1385
1386
	/**
1387
	 * List of fonts used on form fields (fontname => fontkey).
1388
	 * @protected
1389
	 * @since 4.8.001 (2009-09-09)
1390
	 */
1391
	protected $annotation_fonts = array();
1392
1393
	/**
1394
	 * List of radio buttons parent objects.
1395
	 * @protected
1396
	 * @since 4.8.001 (2009-09-09)
1397
	 */
1398
	protected $radiobutton_groups = array();
1399
1400
	/**
1401
	 * List of radio group objects IDs.
1402
	 * @protected
1403
	 * @since 4.8.001 (2009-09-09)
1404
	 */
1405
	protected $radio_groups = array();
1406
1407
	/**
1408
	 * Text indentation value (used for text-indent CSS attribute).
1409
	 * @protected
1410
	 * @since 4.8.006 (2009-09-23)
1411
	 */
1412
	protected $textindent = 0;
1413
1414
	/**
1415
	 * Store page number when startTransaction() is called.
1416
	 * @protected
1417
	 * @since 4.8.006 (2009-09-23)
1418
	 */
1419
	protected $start_transaction_page = 0;
1420
1421
	/**
1422
	 * Store Y position when startTransaction() is called.
1423
	 * @protected
1424
	 * @since 4.9.001 (2010-03-28)
1425
	 */
1426
	protected $start_transaction_y = 0;
1427
1428
	/**
1429
	 * True when we are printing the thead section on a new page.
1430
	 * @protected
1431
	 * @since 4.8.027 (2010-01-25)
1432
	 */
1433
	protected $inthead = false;
1434
1435
	/**
1436
	 * Array of column measures (width, space, starting Y position).
1437
	 * @protected
1438
	 * @since 4.9.001 (2010-03-28)
1439
	 */
1440
	protected $columns = array();
1441
1442
	/**
1443
	 * Number of colums.
1444
	 * @protected
1445
	 * @since 4.9.001 (2010-03-28)
1446
	 */
1447
	protected $num_columns = 1;
1448
1449
	/**
1450
	 * Current column number.
1451
	 * @protected
1452
	 * @since 4.9.001 (2010-03-28)
1453
	 */
1454
	protected $current_column = 0;
1455
1456
	/**
1457
	 * Starting page for columns.
1458
	 * @protected
1459
	 * @since 4.9.001 (2010-03-28)
1460
	 */
1461
	protected $column_start_page = 0;
1462
1463
	/**
1464
	 * Maximum page and column selected.
1465
	 * @protected
1466
	 * @since 5.8.000 (2010-08-11)
1467
	 */
1468
	protected $maxselcol = array('page' => 0, 'column' => 0);
1469
1470
	/**
1471
	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1472
	 * @protected
1473
	 * @since 5.8.000 (2010-08-11)
1474
	 */
1475
	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1476
1477
	/**
1478
	 * 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.
1479
	 * @protected
1480
	 * @since 4.9.008 (2010-04-03)
1481
	 */
1482
	protected $textrendermode = 0;
1483
1484
	/**
1485
	 * Text stroke width in doc units.
1486
	 * @protected
1487
	 * @since 4.9.008 (2010-04-03)
1488
	 */
1489
	protected $textstrokewidth = 0;
1490
1491
	/**
1492
	 * Current stroke color.
1493
	 * @protected
1494
	 * @since 4.9.008 (2010-04-03)
1495
	 */
1496
	protected $strokecolor;
1497
1498
	/**
1499
	 * Default unit of measure for document.
1500
	 * @protected
1501
	 * @since 5.0.000 (2010-04-22)
1502
	 */
1503
	protected $pdfunit = 'mm';
1504
1505
	/**
1506
	 * Boolean flag true when we are on TOC (Table Of Content) page.
1507
	 * @protected
1508
	 */
1509
	protected $tocpage = false;
1510
1511
	/**
1512
	 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1513
	 * @protected
1514
	 * @since 5.0.000 (2010-04-26)
1515
	 */
1516
	protected $rasterize_vector_images = false;
1517
1518
	/**
1519
	 * Boolean flag: if true enables font subsetting by default.
1520
	 * @protected
1521
	 * @since 5.3.002 (2010-06-07)
1522
	 */
1523
	protected $font_subsetting = true;
1524
1525
	/**
1526
	 * Array of default graphic settings.
1527
	 * @protected
1528
	 * @since 5.5.008 (2010-07-02)
1529
	 */
1530
	protected $default_graphic_vars = array();
1531
1532
	/**
1533
	 * Array of XObjects.
1534
	 * @protected
1535
	 * @since 5.8.014 (2010-08-23)
1536
	 */
1537
	protected $xobjects = array();
1538
1539
	/**
1540
	 * Boolean value true when we are inside an XObject.
1541
	 * @protected
1542
	 * @since 5.8.017 (2010-08-24)
1543
	 */
1544
	protected $inxobj = false;
1545
1546
	/**
1547
	 * Current XObject ID.
1548
	 * @protected
1549
	 * @since 5.8.017 (2010-08-24)
1550
	 */
1551
	protected $xobjid = '';
1552
1553
	/**
1554
	 * Percentage of character stretching.
1555
	 * @protected
1556
	 * @since 5.9.000 (2010-09-29)
1557
	 */
1558
	protected $font_stretching = 100;
1559
1560
	/**
1561
	 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1562
	 * @protected
1563
	 * @since 5.9.000 (2010-09-29)
1564
	 */
1565
	protected $font_spacing = 0;
1566
1567
	/**
1568
	 * Array of no-write regions.
1569
	 * ('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)
1570
	 * @protected
1571
	 * @since 5.9.003 (2010-10-14)
1572
	 */
1573
	protected $page_regions = array();
1574
1575
	/**
1576
	 * Boolean value true when page region check is active.
1577
	 * @protected
1578
	 */
1579
	protected $check_page_regions = true;
1580
1581
	/**
1582
	 * Array of PDF layers data.
1583
	 * @protected
1584
	 * @since 5.9.102 (2011-07-13)
1585
	 */
1586
	protected $pdflayers = array();
1587
1588
	/**
1589
	 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1590
	 * @protected
1591
	 * @since 5.9.097 (2011-06-23)
1592
	 */
1593
	protected $dests = array();
1594
1595
	/**
1596
	 * Object ID for Named Destinations
1597
	 * @protected
1598
	 * @since 5.9.097 (2011-06-23)
1599
	 */
1600
	protected $n_dests;
1601
1602
	/**
1603
	 * Embedded Files Names
1604
	 * @protected
1605
	 * @since 5.9.204 (2013-01-23)
1606
	 */
1607
	protected $efnames = array();
1608
1609
	/**
1610
	 * Directory used for the last SVG image.
1611
	 * @protected
1612
	 * @since 5.0.000 (2010-05-05)
1613
	 */
1614
	protected $svgdir = '';
1615
1616
	/**
1617
	 *  Deafult unit of measure for SVG.
1618
	 * @protected
1619
	 * @since 5.0.000 (2010-05-02)
1620
	 */
1621
	protected $svgunit = 'px';
1622
1623
	/**
1624
	 * Array of SVG gradients.
1625
	 * @protected
1626
	 * @since 5.0.000 (2010-05-02)
1627
	 */
1628
	protected $svggradients = array();
1629
1630
	/**
1631
	 * ID of last SVG gradient.
1632
	 * @protected
1633
	 * @since 5.0.000 (2010-05-02)
1634
	 */
1635
	protected $svggradientid = 0;
1636
1637
	/**
1638
	 * Boolean value true when in SVG defs group.
1639
	 * @protected
1640
	 * @since 5.0.000 (2010-05-02)
1641
	 */
1642
	protected $svgdefsmode = false;
1643
1644
	/**
1645
	 * Array of SVG defs.
1646
	 * @protected
1647
	 * @since 5.0.000 (2010-05-02)
1648
	 */
1649
	protected $svgdefs = array();
1650
1651
	/**
1652
	 * Boolean value true when in SVG clipPath tag.
1653
	 * @protected
1654
	 * @since 5.0.000 (2010-04-26)
1655
	 */
1656
	protected $svgclipmode = false;
1657
1658
	/**
1659
	 * Array of SVG clipPath commands.
1660
	 * @protected
1661
	 * @since 5.0.000 (2010-05-02)
1662
	 */
1663
	protected $svgclippaths = array();
1664
1665
	/**
1666
	 * Array of SVG clipPath tranformation matrix.
1667
	 * @protected
1668
	 * @since 5.8.022 (2010-08-31)
1669
	 */
1670
	protected $svgcliptm = array();
1671
1672
	/**
1673
	 * ID of last SVG clipPath.
1674
	 * @protected
1675
	 * @since 5.0.000 (2010-05-02)
1676
	 */
1677
	protected $svgclipid = 0;
1678
1679
	/**
1680
	 * SVG text.
1681
	 * @protected
1682
	 * @since 5.0.000 (2010-05-02)
1683
	 */
1684
	protected $svgtext = '';
1685
1686
	/**
1687
	 * SVG text properties.
1688
	 * @protected
1689
	 * @since 5.8.013 (2010-08-23)
1690
	 */
1691
	protected $svgtextmode = array();
1692
1693
	/**
1694
	 * Array of SVG properties.
1695
	 * @protected
1696
	 * @since 5.0.000 (2010-05-02)
1697
	 */
1698
	protected $svgstyles = array(array(
1699
		'alignment-baseline' => 'auto',
1700
		'baseline-shift' => 'baseline',
1701
		'clip' => 'auto',
1702
		'clip-path' => 'none',
1703
		'clip-rule' => 'nonzero',
1704
		'color' => 'black',
1705
		'color-interpolation' => 'sRGB',
1706
		'color-interpolation-filters' => 'linearRGB',
1707
		'color-profile' => 'auto',
1708
		'color-rendering' => 'auto',
1709
		'cursor' => 'auto',
1710
		'direction' => 'ltr',
1711
		'display' => 'inline',
1712
		'dominant-baseline' => 'auto',
1713
		'enable-background' => 'accumulate',
1714
		'fill' => 'black',
1715
		'fill-opacity' => 1,
1716
		'fill-rule' => 'nonzero',
1717
		'filter' => 'none',
1718
		'flood-color' => 'black',
1719
		'flood-opacity' => 1,
1720
		'font' => '',
1721
		'font-family' => 'helvetica',
1722
		'font-size' => 'medium',
1723
		'font-size-adjust' => 'none',
1724
		'font-stretch' => 'normal',
1725
		'font-style' => 'normal',
1726
		'font-variant' => 'normal',
1727
		'font-weight' => 'normal',
1728
		'glyph-orientation-horizontal' => '0deg',
1729
		'glyph-orientation-vertical' => 'auto',
1730
		'image-rendering' => 'auto',
1731
		'kerning' => 'auto',
1732
		'letter-spacing' => 'normal',
1733
		'lighting-color' => 'white',
1734
		'marker' => '',
1735
		'marker-end' => 'none',
1736
		'marker-mid' => 'none',
1737
		'marker-start' => 'none',
1738
		'mask' => 'none',
1739
		'opacity' => 1,
1740
		'overflow' => 'auto',
1741
		'pointer-events' => 'visiblePainted',
1742
		'shape-rendering' => 'auto',
1743
		'stop-color' => 'black',
1744
		'stop-opacity' => 1,
1745
		'stroke' => 'none',
1746
		'stroke-dasharray' => 'none',
1747
		'stroke-dashoffset' => 0,
1748
		'stroke-linecap' => 'butt',
1749
		'stroke-linejoin' => 'miter',
1750
		'stroke-miterlimit' => 4,
1751
		'stroke-opacity' => 1,
1752
		'stroke-width' => 1,
1753
		'text-anchor' => 'start',
1754
		'text-decoration' => 'none',
1755
		'text-rendering' => 'auto',
1756
		'unicode-bidi' => 'normal',
1757
		'visibility' => 'visible',
1758
		'word-spacing' => 'normal',
1759
		'writing-mode' => 'lr-tb',
1760
		'text-color' => 'black',
1761
		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1762
		));
1763
1764
	/**
1765
	 * If true force sRGB color profile for all document.
1766
	 * @protected
1767
	 * @since 5.9.121 (2011-09-28)
1768
	 */
1769
	protected $force_srgb = false;
1770
1771
	/**
1772
	 * If true set the document to PDF/A mode.
1773
	 * @protected
1774
	 * @since 5.9.121 (2011-09-27)
1775
	 */
1776
	protected $pdfa_mode = false;
1777
1778
	/**
1779
	 * version of PDF/A mode (1 - 3).
1780
	 * @protected
1781
	 * @since 6.2.26 (2019-03-12)
1782
	 */
1783
	protected $pdfa_version = 1;
1784
1785
	/**
1786
	 * Document creation date-time
1787
	 * @protected
1788
	 * @since 5.9.152 (2012-03-22)
1789
	 */
1790
	protected $doc_creation_timestamp;
1791
1792
	/**
1793
	 * Document modification date-time
1794
	 * @protected
1795
	 * @since 5.9.152 (2012-03-22)
1796
	 */
1797
	protected $doc_modification_timestamp;
1798
1799
	/**
1800
	 * Custom XMP data.
1801
	 * @protected
1802
	 * @since 5.9.128 (2011-10-06)
1803
	 */
1804
	protected $custom_xmp = '';
1805
1806
	/**
1807
	 * Custom XMP RDF data.
1808
	 * @protected
1809
	 * @since 6.3.0 (2019-09-19)
1810
	 */
1811
	protected $custom_xmp_rdf = '';
1812
1813
	/**
1814
	 * Custom XMP RDF pdfaextension data.
1815
	 * @protected
1816
	 * @since 6.9.0 (2025-02-11)
1817
	 */
1818
	protected $custom_xmp_rdf_pdfaExtension = '';
1819
1820
	/**
1821
	 * Overprint mode array.
1822
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1823
	 * @protected
1824
	 * @since 5.9.152 (2012-03-23)
1825
	 * @var array<string,bool|int>
1826
	 */
1827
	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1828
1829
	/**
1830
	 * Alpha mode array.
1831
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1832
	 * @protected
1833
	 * @since 5.9.152 (2012-03-23)
1834
	 */
1835
	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1836
1837
	/**
1838
	 * Define the page boundaries boxes to be set on document.
1839
	 * @protected
1840
	 * @since 5.9.152 (2012-03-23)
1841
	 */
1842
	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1843
1844
	/**
1845
	 * If true print TCPDF meta link.
1846
	 * @protected
1847
	 * @since 5.9.152 (2012-03-23)
1848
	 */
1849
	protected $tcpdflink = true;
1850
1851
	/**
1852
	 * Cache array for computed GD gamma values.
1853
	 * @protected
1854
	 * @since 5.9.1632 (2012-06-05)
1855
	 */
1856
	protected $gdgammacache = array();
1857
1858
    /**
1859
     * Cache array for file content
1860
     * @protected
1861
     * @var array
1862
     * @since 6.3.5 (2020-09-28)
1863
     */
1864
	protected $fileContentCache = array();
1865
1866
	/**
1867
	 * Whether to allow local file path in image html tags, when prefixed with file://
1868
	 *
1869
	 * @var bool
1870
	 * @protected
1871
	 * @since 6.4 (2020-07-23)
1872
	 */
1873
	protected $allowLocalFiles = false;
1874
1875
	//------------------------------------------------------------
1876
	// METHODS
1877
	//------------------------------------------------------------
1878
1879
	/**
1880
	 * This is the class constructor.
1881
	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1882
	 *
1883
	 * @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>
1884
	 * @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.
1885
	 * @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().
1886
	 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1887
	 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1888
	 * @param boolean $diskcache DEPRECATED FEATURE
1889
	 * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1890
	 * @public
1891
	 * @see getPageSizeFromFormat(), setPageFormat()
1892
	 */
1893
	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1894
		// set file ID for trailer
1895
		$serformat = (is_array($format) ? json_encode($format) : $format);
1896
		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1897
		$this->hash_key = hash_hmac('sha256', TCPDF_STATIC::getRandomSeed($this->file_id), TCPDF_STATIC::getRandomSeed('TCPDF'), false);
1898
		$this->font_obj_ids = array();
1899
		$this->page_obj_id = array();
1900
		$this->form_obj_id = array();
1901
		// set pdf/a mode
1902
		if ($pdfa != false) {
1903
			$this->pdfa_mode = true;
1904
			$this->pdfa_version = $pdfa;  // 1 or 3
1905
		} else
1906
			$this->pdfa_mode = false;
1907
1908
		$this->force_srgb = false;
1909
		// set language direction
1910
		$this->rtl = false;
1911
		$this->tmprtl = false;
1912
		// some checks
1913
		$this->_dochecks();
1914
		// initialization of properties
1915
		$this->isunicode = $unicode;
1916
		$this->page = 0;
1917
		$this->transfmrk[0] = array();
1918
		$this->pagedim = array();
1919
		$this->n = 2;
1920
		$this->buffer = '';
1921
		$this->pages = array();
1922
		$this->state = 0;
1923
		$this->fonts = array();
1924
		$this->FontFiles = array();
1925
		$this->diffs = array();
1926
		$this->images = array();
1927
		$this->links = array();
1928
		$this->gradients = array();
1929
		$this->InFooter = false;
1930
		$this->lasth = 0;
1931
		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1932
		$this->FontStyle = '';
1933
		$this->FontSizePt = 12;
1934
		$this->underline = false;
1935
		$this->overline = false;
1936
		$this->linethrough = false;
1937
		$this->DrawColor = '0 G';
1938
		$this->FillColor = '0 g';
1939
		$this->TextColor = '0 g';
1940
		$this->ColorFlag = false;
1941
		$this->pdflayers = array();
1942
		// encryption values
1943
		$this->encrypted = false;
1944
		$this->last_enc_key = '';
1945
		// standard Unicode fonts
1946
		$this->CoreFonts = array(
1947
			'courier'=>'Courier',
1948
			'courierB'=>'Courier-Bold',
1949
			'courierI'=>'Courier-Oblique',
1950
			'courierBI'=>'Courier-BoldOblique',
1951
			'helvetica'=>'Helvetica',
1952
			'helveticaB'=>'Helvetica-Bold',
1953
			'helveticaI'=>'Helvetica-Oblique',
1954
			'helveticaBI'=>'Helvetica-BoldOblique',
1955
			'times'=>'Times-Roman',
1956
			'timesB'=>'Times-Bold',
1957
			'timesI'=>'Times-Italic',
1958
			'timesBI'=>'Times-BoldItalic',
1959
			'symbol'=>'Symbol',
1960
			'zapfdingbats'=>'ZapfDingbats'
1961
		);
1962
		// set scale factor
1963
		$this->setPageUnit($unit);
1964
		// set page format and orientation
1965
		$this->setPageFormat($format, $orientation);
1966
		// page margins (1 cm)
1967
		$margin = 28.35 / $this->k;
1968
		$this->setMargins($margin, $margin);
1969
		$this->clMargin = $this->lMargin;
1970
		$this->crMargin = $this->rMargin;
1971
		// internal cell padding
1972
		$cpadding = $margin / 10;
1973
		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1974
		// cell margins
1975
		$this->setCellMargins(0, 0, 0, 0);
1976
		// line width (0.2 mm)
1977
		$this->LineWidth = 0.57 / $this->k;
1978
		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1979
		$this->linestyleCap = '0 J';
1980
		$this->linestyleJoin = '0 j';
1981
		$this->linestyleDash = '[] 0 d';
1982
		// automatic page break
1983
		$this->setAutoPageBreak(true, (2 * $margin));
1984
		// full width display mode
1985
		$this->setDisplayMode('fullwidth');
1986
		// compression
1987
		$this->setCompression();
1988
		// set default PDF version number
1989
		$this->setPDFVersion();
1990
		$this->tcpdflink = true;
1991
		$this->encoding = $encoding;
1992
		$this->HREF = array();
1993
		$this->getFontsList();
1994
		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1995
		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1996
		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1997
		$this->extgstates = array();
1998
		$this->setTextShadow();
1999
		// signature
2000
		$this->sign = false;
2001
		$this->tsa_timestamp = false;
2002
		$this->tsa_data = array();
2003
		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
2004
		$this->empty_signature_appearance = array();
2005
		// user's rights
2006
		$this->ur['enabled'] = false;
2007
		$this->ur['document'] = '/FullSave';
2008
		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
2009
		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
2010
		$this->ur['signature'] = '/Modify';
2011
		$this->ur['ef'] = '/Create/Delete/Modify/Import';
2012
		$this->ur['formex'] = '';
2013
		// set default JPEG quality
2014
		$this->jpeg_quality = 75;
2015
		// initialize some settings
2016
		TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
2017
		// set default font
2018
		$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2019
		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2020
		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2021
		// check if PCRE Unicode support is enabled
2022
		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2023
			// PCRE unicode support is turned ON
2024
			// \s     : any whitespace character
2025
			// \p{Z}  : any separator
2026
			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2027
			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2028
			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2029
			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2030
		} else {
2031
			// PCRE unicode support is turned OFF
2032
			$this->setSpacesRE('/[^\S\xa0]/');
2033
		}
2034
		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2035
		// set document creation and modification timestamp
2036
		$this->doc_creation_timestamp = time();
2037
		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
2038
		// get default graphic vars
2039
		$this->default_graphic_vars = $this->getGraphicVars();
2040
		$this->header_xobj_autoreset = false;
2041
		$this->custom_xmp = '';
2042
		$this->custom_xmp_rdf = '';
2043
	}
2044
2045
	/**
2046
	 * Default destructor.
2047
	 * @public
2048
	 * @since 1.53.0.TC016
2049
	 */
2050
	public function __destruct() {
2051
		// cleanup
2052
		$this->_destroy(true);
2053
	}
2054
2055
	/**
2056
	 * Set the units of measure for the document.
2057
	 * @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.
2058
	 * @public
2059
	 * @since 3.0.015 (2008-06-06)
2060
	 */
2061
	public function setPageUnit($unit) {
2062
		$unit = strtolower($unit);
2063
		//Set scale factor
2064
		switch ($unit) {
2065
			// points
2066
			case 'px':
2067
			case 'pt': {
2068
				$this->k = 1;
2069
				break;
2070
			}
2071
			// millimeters
2072
			case 'mm': {
2073
				$this->k = $this->dpi / 25.4;
2074
				break;
2075
			}
2076
			// centimeters
2077
			case 'cm': {
2078
				$this->k = $this->dpi / 2.54;
2079
				break;
2080
			}
2081
			// inches
2082
			case 'in': {
2083
				$this->k = $this->dpi;
2084
				break;
2085
			}
2086
			// unsupported unit
2087
			default : {
2088
				$this->Error('Incorrect unit: '.$unit);
2089
				break;
2090
			}
2091
		}
2092
		$this->pdfunit = $unit;
2093
		if (isset($this->CurOrientation)) {
2094
			$this->setPageOrientation($this->CurOrientation);
2095
		}
2096
	}
2097
2098
	/**
2099
	 * Change the format of the current page
2100
	 * @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>
2101
	 * <li>['format'] = page format name (one of the above);</li>
2102
	 * <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>
2103
	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2104
	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2105
	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2106
	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2107
	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2108
	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2109
	 * <li>['CropBox'] : the visible region of default user space:</li>
2110
	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2111
	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2112
	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2113
	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2114
	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2115
	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2116
	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2117
	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2118
	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2119
	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2120
	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2121
	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2122
	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2123
	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2124
	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2125
	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2126
	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2127
	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2128
	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2129
	 * <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>
2130
	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2131
	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2132
	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2133
	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2134
	 * <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>
2135
	 * <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>
2136
	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2137
	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2138
	 * <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>
2139
	 * <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>
2140
	 * <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>
2141
	 * <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>
2142
	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2143
	 * </ul>
2144
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2145
	 * <li>P or Portrait (default)</li>
2146
	 * <li>L or Landscape</li>
2147
	 * <li>'' (empty string) for automatic orientation</li>
2148
	 * </ul>
2149
	 * @protected
2150
	 * @since 3.0.015 (2008-06-06)
2151
	 * @see getPageSizeFromFormat()
2152
	 */
2153
	protected function setPageFormat($format, $orientation='P') {
2154
		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2155
			// remove inherited values
2156
			unset($this->pagedim[$this->page]);
2157
		}
2158
		if (is_string($format)) {
2159
			// get page measures from format name
2160
			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2161
			$this->fwPt = $pf[0];
2162
			$this->fhPt = $pf[1];
2163
		} else {
2164
			// the boundaries of the physical medium on which the page shall be displayed or printed
2165
			if (isset($format['MediaBox'])) {
2166
				$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);
2167
				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2168
				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2169
			} else {
2170
				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2171
					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2172
				} else {
2173
					if (!isset($format['format'])) {
2174
						// default value
2175
						$format['format'] = 'A4';
2176
					}
2177
					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2178
				}
2179
				$this->fwPt = $pf[0];
2180
				$this->fhPt = $pf[1];
2181
				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2182
			}
2183
			// the visible region of default user space
2184
			if (isset($format['CropBox'])) {
2185
				$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);
2186
			}
2187
			// the region to which the contents of the page shall be clipped when output in a production environment
2188
			if (isset($format['BleedBox'])) {
2189
				$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);
2190
			}
2191
			// the intended dimensions of the finished page after trimming
2192
			if (isset($format['TrimBox'])) {
2193
				$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);
2194
			}
2195
			// the page's meaningful content (including potential white space)
2196
			if (isset($format['ArtBox'])) {
2197
				$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);
2198
			}
2199
			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2200
			if (isset($format['BoxColorInfo'])) {
2201
				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2202
			}
2203
			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2204
				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2205
				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2206
			}
2207
			if (isset($format['PZ'])) {
2208
				// The page's preferred zoom (magnification) factor
2209
				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2210
			}
2211
			if (isset($format['trans'])) {
2212
				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2213
				if (isset($format['trans']['Dur'])) {
2214
					// The page's display duration
2215
					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2216
				}
2217
				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2218
				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2219
					// The transition style that shall be used when moving to this page from another during a presentation
2220
					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2221
					$valid_effect = array('Split', 'Blinds');
2222
					$valid_vals = array('H', 'V');
2223
					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2224
						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2225
					}
2226
					$valid_effect = array('Split', 'Box', 'Fly');
2227
					$valid_vals = array('I', 'O');
2228
					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2229
						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2230
					}
2231
					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2232
					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2233
						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2234
							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2235
							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2236
							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2237
						}
2238
					}
2239
					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2240
						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2241
					}
2242
					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2243
						$this->pagedim[$this->page]['trans']['B'] = 'true';
2244
					}
2245
				} else {
2246
					$this->pagedim[$this->page]['trans']['S'] = 'R';
2247
				}
2248
				if (isset($format['trans']['D'])) {
2249
					// The duration of the transition effect, in seconds
2250
					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2251
				} else {
2252
					$this->pagedim[$this->page]['trans']['D'] = 1;
2253
				}
2254
			}
2255
		}
2256
		$this->setPageOrientation($orientation);
2257
	}
2258
2259
	/**
2260
	 * Set page orientation.
2261
	 * @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>
2262
	 * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2263
	 * @param float|null $bottommargin bottom margin of the page.
2264
	 * @public
2265
	 * @since 3.0.015 (2008-06-06)
2266
	 */
2267
	public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) {
2268
		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2269
			// the boundaries of the physical medium on which the page shall be displayed or printed
2270
			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2271
		}
2272
		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2273
			// the visible region of default user space
2274
			$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);
2275
		}
2276
		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2277
			// the region to which the contents of the page shall be clipped when output in a production environment
2278
			$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);
2279
		}
2280
		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2281
			// the intended dimensions of the finished page after trimming
2282
			$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);
2283
		}
2284
		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2285
			// the page's meaningful content (including potential white space)
2286
			$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);
2287
		}
2288
		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2289
			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2290
			$this->pagedim[$this->page]['Rotate'] = 0;
2291
		}
2292
		if (!isset($this->pagedim[$this->page]['PZ'])) {
2293
			// The page's preferred zoom (magnification) factor
2294
			$this->pagedim[$this->page]['PZ'] = 1;
2295
		}
2296
		if ($this->fwPt > $this->fhPt) {
2297
			// landscape
2298
			$default_orientation = 'L';
2299
		} else {
2300
			// portrait
2301
			$default_orientation = 'P';
2302
		}
2303
		$valid_orientations = array('P', 'L');
2304
		if (empty($orientation)) {
2305
			$orientation = $default_orientation;
2306
		} else {
2307
			$orientation = strtoupper($orientation[0]);
2308
		}
2309
		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2310
			$this->CurOrientation = $orientation;
2311
			$this->wPt = $this->fhPt;
2312
			$this->hPt = $this->fwPt;
2313
		} else {
2314
			$this->CurOrientation = $default_orientation;
2315
			$this->wPt = $this->fwPt;
2316
			$this->hPt = $this->fhPt;
2317
		}
2318
		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2319
			// swap X and Y coordinates (change page orientation)
2320
			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2321
		}
2322
		$this->w = ($this->wPt / $this->k);
2323
		$this->h = ($this->hPt / $this->k);
2324
		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2325
			if (isset($this->AutoPageBreak)) {
2326
				$autopagebreak = $this->AutoPageBreak;
2327
			} else {
2328
				$autopagebreak = true;
2329
			}
2330
		}
2331
		if (TCPDF_STATIC::empty_string($bottommargin)) {
2332
			if (isset($this->bMargin)) {
2333
				$bottommargin = $this->bMargin;
2334
			} else {
2335
				// default value = 2 cm
2336
				$bottommargin = 2 * 28.35 / $this->k;
2337
			}
2338
		}
2339
		$this->setAutoPageBreak($autopagebreak, $bottommargin);
2340
		// store page dimensions
2341
		$this->pagedim[$this->page]['w'] = $this->wPt;
2342
		$this->pagedim[$this->page]['h'] = $this->hPt;
2343
		$this->pagedim[$this->page]['wk'] = $this->w;
2344
		$this->pagedim[$this->page]['hk'] = $this->h;
2345
		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2346
		$this->pagedim[$this->page]['bm'] = $bottommargin;
2347
		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2348
		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2349
		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2350
		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2351
		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2352
		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2353
	}
2354
2355
	/**
2356
	 * Set regular expression to detect withespaces or word separators.
2357
	 * The pattern delimiter must be the forward-slash character "/".
2358
	 * Some example patterns are:
2359
	 * <pre>
2360
	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2361
	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2362
	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2363
	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2364
	 *      \s     : any whitespace character
2365
	 *      \p{Z}  : any separator
2366
	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2367
	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2368
	 * </pre>
2369
	 * @param string $re regular expression (leave empty for default).
2370
	 * @public
2371
	 * @since 4.6.016 (2009-06-15)
2372
	 */
2373
	public function setSpacesRE($re='/[^\S\xa0]/') {
2374
		$this->re_spaces = $re;
2375
		$re_parts = explode('/', $re);
2376
		// get pattern parts
2377
		$this->re_space = array();
2378
		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2379
			$this->re_space['p'] = $re_parts[1];
2380
		} else {
2381
			$this->re_space['p'] = '[\s]';
2382
		}
2383
		// set pattern modifiers
2384
		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2385
			$this->re_space['m'] = $re_parts[2];
2386
		} else {
2387
			$this->re_space['m'] = '';
2388
		}
2389
	}
2390
2391
	/**
2392
	 * Enable or disable Right-To-Left language mode
2393
	 * @param boolean $enable if true enable Right-To-Left language mode.
2394
	 * @param boolean $resetx if true reset the X position on direction change.
2395
	 * @public
2396
	 * @since 2.0.000 (2008-01-03)
2397
	 */
2398
	public function setRTL($enable, $resetx=true) {
2399
		$enable = $enable ? true : false;
2400
		$resetx = ($resetx AND ($enable != $this->rtl));
2401
		$this->rtl = $enable;
2402
		$this->tmprtl = false;
2403
		if ($resetx) {
2404
			$this->Ln(0);
2405
		}
2406
	}
2407
2408
	/**
2409
	 * Return the RTL status
2410
	 * @return bool
2411
	 * @public
2412
	 * @since 4.0.012 (2008-07-24)
2413
	 */
2414
	public function getRTL() {
2415
		return $this->rtl;
2416
	}
2417
2418
	/**
2419
	 * Force temporary RTL language direction
2420
	 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2421
	 * @public
2422
	 * @since 2.1.000 (2008-01-09)
2423
	 */
2424
	public function setTempRTL($mode) {
2425
		$newmode = false;
2426
		switch (strtoupper($mode)) {
2427
			case 'LTR':
2428
			case 'L': {
2429
				if ($this->rtl) {
2430
					$newmode = 'L';
2431
				}
2432
				break;
2433
			}
2434
			case 'RTL':
2435
			case 'R': {
2436
				if (!$this->rtl) {
2437
					$newmode = 'R';
2438
				}
2439
				break;
2440
			}
2441
			case false:
2442
			default: {
2443
				$newmode = false;
2444
				break;
2445
			}
2446
		}
2447
		$this->tmprtl = $newmode;
2448
	}
2449
2450
	/**
2451
	 * Return the current temporary RTL status
2452
	 * @return bool
2453
	 * @public
2454
	 * @since 4.8.014 (2009-11-04)
2455
	 */
2456
	public function isRTLTextDir() {
2457
		return ($this->rtl OR ($this->tmprtl == 'R'));
2458
	}
2459
2460
	/**
2461
	 * Set the last cell height.
2462
	 * @param float $h cell height.
2463
	 * @author Nicola Asuni
2464
	 * @public
2465
	 * @since 1.53.0.TC034
2466
	 */
2467
	public function setLastH($h) {
2468
		$this->lasth = $h;
2469
	}
2470
2471
	/**
2472
	 * Return the cell height
2473
	 * @param int $fontsize Font size in internal units
2474
	 * @param boolean $padding If true add cell padding
2475
	 * @public
2476
	 * @return float
2477
	 */
2478
	public function getCellHeight($fontsize, $padding=TRUE) {
2479
		$height = ($fontsize * $this->cell_height_ratio);
2480
		if ($padding && !empty($this->cell_padding)) {
2481
			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2482
		}
2483
		return round($height, 6);
2484
	}
2485
2486
	/**
2487
	 * Reset the last cell height.
2488
	 * @public
2489
	 * @since 5.9.000 (2010-10-03)
2490
	 */
2491
	public function resetLastH() {
2492
		$this->lasth = $this->getCellHeight($this->FontSize);
2493
	}
2494
2495
	/**
2496
	 * Get the last cell height.
2497
	 * @return float last cell height
2498
	 * @public
2499
	 * @since 4.0.017 (2008-08-05)
2500
	 */
2501
	public function getLastH() {
2502
		return $this->lasth;
2503
	}
2504
2505
	/**
2506
	 * Set the adjusting factor to convert pixels to user units.
2507
	 * @param float $scale adjusting factor to convert pixels to user units.
2508
	 * @author Nicola Asuni
2509
	 * @public
2510
	 * @since 1.5.2
2511
	 */
2512
	public function setImageScale($scale) {
2513
		$this->imgscale = $scale;
2514
	}
2515
2516
	/**
2517
	 * Returns the adjusting factor to convert pixels to user units.
2518
	 * @return float adjusting factor to convert pixels to user units.
2519
	 * @author Nicola Asuni
2520
	 * @public
2521
	 * @since 1.5.2
2522
	 */
2523
	public function getImageScale() {
2524
		return $this->imgscale;
2525
	}
2526
2527
	/**
2528
	 * Returns an array of page dimensions:
2529
	 * <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>
2530
	 * @param int|null $pagenum page number (empty = current page)
2531
	 * @return array of page dimensions.
2532
	 * @author Nicola Asuni
2533
	 * @public
2534
	 * @since 4.5.027 (2009-03-16)
2535
	 */
2536
	public function getPageDimensions($pagenum=null) {
2537
		if (empty($pagenum)) {
2538
			$pagenum = $this->page;
2539
		}
2540
		return $this->pagedim[$pagenum];
2541
	}
2542
2543
	/**
2544
	 * Returns the page width in units.
2545
	 * @param int|null $pagenum page number (empty = current page)
2546
	 * @return int|float page width.
2547
	 * @author Nicola Asuni
2548
	 * @public
2549
	 * @since 1.5.2
2550
	 * @see getPageDimensions()
2551
	 */
2552
	public function getPageWidth($pagenum=null) {
2553
		if (empty($pagenum)) {
2554
			return $this->w;
2555
		}
2556
		return $this->pagedim[$pagenum]['w'];
2557
	}
2558
2559
	/**
2560
	 * Returns the page height in units.
2561
	 * @param int|null $pagenum page number (empty = current page)
2562
	 * @return int|float page height.
2563
	 * @author Nicola Asuni
2564
	 * @public
2565
	 * @since 1.5.2
2566
	 * @see getPageDimensions()
2567
	 */
2568
	public function getPageHeight($pagenum=null) {
2569
		if (empty($pagenum)) {
2570
			return $this->h;
2571
		}
2572
		return $this->pagedim[$pagenum]['h'];
2573
	}
2574
2575
	/**
2576
	 * Returns the page break margin.
2577
	 * @param int|null $pagenum page number (empty = current page)
2578
	 * @return int|float page break margin.
2579
	 * @author Nicola Asuni
2580
	 * @public
2581
	 * @since 1.5.2
2582
	 * @see getPageDimensions()
2583
	 */
2584
	public function getBreakMargin($pagenum=null) {
2585
		if (empty($pagenum)) {
2586
			return $this->bMargin;
2587
		}
2588
		return $this->pagedim[$pagenum]['bm'];
2589
	}
2590
2591
	/**
2592
	 * Returns the scale factor (number of points in user unit).
2593
	 * @return int scale factor.
2594
	 * @author Nicola Asuni
2595
	 * @public
2596
	 * @since 1.5.2
2597
	 */
2598
	public function getScaleFactor() {
2599
		return $this->k;
2600
	}
2601
2602
	/**
2603
	 * Defines the left, top and right margins.
2604
	 * @param int|float $left Left margin.
2605
	 * @param int|float $top Top margin.
2606
	 * @param int|float|null $right Right margin. Default value is the left one.
2607
	 * @param boolean $keepmargins if true overwrites the default page margins
2608
	 * @public
2609
	 * @since 1.0
2610
	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2611
	 */
2612
	public function setMargins($left, $top, $right=null, $keepmargins=false) {
2613
		//Set left, top and right margins
2614
		$this->lMargin = $left;
2615
		$this->tMargin = $top;
2616
		if ($right == -1 OR $right === null) {
2617
			$right = $left;
2618
		}
2619
		$this->rMargin = $right;
2620
		if ($keepmargins) {
2621
			// overwrite original values
2622
			$this->original_lMargin = $this->lMargin;
2623
			$this->original_rMargin = $this->rMargin;
2624
		}
2625
	}
2626
2627
	/**
2628
	 * 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.
2629
	 * @param int|float $margin The margin.
2630
	 * @public
2631
	 * @since 1.4
2632
	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2633
	 */
2634
	public function setLeftMargin($margin) {
2635
		//Set left margin
2636
		$this->lMargin = $margin;
2637
		if (($this->page > 0) AND ($this->x < $margin)) {
2638
			$this->x = $margin;
2639
		}
2640
	}
2641
2642
	/**
2643
	 * Defines the top margin. The method can be called before creating the first page.
2644
	 * @param int|float $margin The margin.
2645
	 * @public
2646
	 * @since 1.5
2647
	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2648
	 */
2649
	public function setTopMargin($margin) {
2650
		//Set top margin
2651
		$this->tMargin = $margin;
2652
		if (($this->page > 0) AND ($this->y < $margin)) {
2653
			$this->y = $margin;
2654
		}
2655
	}
2656
2657
	/**
2658
	 * Defines the right margin. The method can be called before creating the first page.
2659
	 * @param int|float $margin The margin.
2660
	 * @public
2661
	 * @since 1.5
2662
	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2663
	 */
2664
	public function setRightMargin($margin) {
2665
		$this->rMargin = $margin;
2666
		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2667
			$this->x = $this->w - $margin;
2668
		}
2669
	}
2670
2671
	/**
2672
	 * Set the same internal Cell padding for top, right, bottom, left-
2673
	 * @param int|float $pad internal padding.
2674
	 * @public
2675
	 * @since 2.1.000 (2008-01-09)
2676
	 * @see getCellPaddings(), setCellPaddings()
2677
	 */
2678
	public function setCellPadding($pad) {
2679
		if ($pad >= 0) {
2680
			$this->cell_padding['L'] = $pad;
2681
			$this->cell_padding['T'] = $pad;
2682
			$this->cell_padding['R'] = $pad;
2683
			$this->cell_padding['B'] = $pad;
2684
		}
2685
	}
2686
2687
	/**
2688
	 * Set the internal Cell paddings.
2689
	 * @param int|float|null $left left padding
2690
	 * @param int|float|null $top top padding
2691
	 * @param int|float|null $right right padding
2692
	 * @param int|float|null $bottom bottom padding
2693
	 * @public
2694
	 * @since 5.9.000 (2010-10-03)
2695
	 * @see getCellPaddings(), SetCellPadding()
2696
	 */
2697
	public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) {
2698
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2699
			$this->cell_padding['L'] = $left;
2700
		}
2701
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2702
			$this->cell_padding['T'] = $top;
2703
		}
2704
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2705
			$this->cell_padding['R'] = $right;
2706
		}
2707
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2708
			$this->cell_padding['B'] = $bottom;
2709
		}
2710
	}
2711
2712
	/**
2713
	 * Get the internal Cell padding array.
2714
	 * @return array of padding values
2715
	 * @public
2716
	 * @since 5.9.000 (2010-10-03)
2717
	 * @see setCellPaddings(), SetCellPadding()
2718
	 */
2719
	public function getCellPaddings() {
2720
		return $this->cell_padding;
2721
	}
2722
2723
	/**
2724
	 * Set the internal Cell margins.
2725
	 * @param int|float|null $left left margin
2726
	 * @param int|float|null $top top margin
2727
	 * @param int|float|null $right right margin
2728
	 * @param int|float|null $bottom bottom margin
2729
	 * @public
2730
	 * @since 5.9.000 (2010-10-03)
2731
	 * @see getCellMargins()
2732
	 */
2733
	public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) {
2734
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2735
			$this->cell_margin['L'] = $left;
2736
		}
2737
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2738
			$this->cell_margin['T'] = $top;
2739
		}
2740
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2741
			$this->cell_margin['R'] = $right;
2742
		}
2743
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2744
			$this->cell_margin['B'] = $bottom;
2745
		}
2746
	}
2747
2748
	/**
2749
	 * Get the internal Cell margin array.
2750
	 * @return array of margin values
2751
	 * @public
2752
	 * @since 5.9.000 (2010-10-03)
2753
	 * @see setCellMargins()
2754
	 */
2755
	public function getCellMargins() {
2756
		return $this->cell_margin;
2757
	}
2758
2759
	/**
2760
	 * Adjust the internal Cell padding array to take account of the line width.
2761
	 * @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)))
2762
	 * @return void|array array of adjustments
2763
	 * @public
2764
	 * @since 5.9.000 (2010-10-03)
2765
	 */
2766
	protected function adjustCellPadding($brd=0) {
2767
		if (empty($brd)) {
2768
			return;
2769
		}
2770
		if (is_string($brd)) {
2771
			// convert string to array
2772
			$slen = strlen($brd);
2773
			$newbrd = array();
2774
			for ($i = 0; $i < $slen; ++$i) {
2775
				$newbrd[$brd[$i]] = true;
2776
			}
2777
			$brd = $newbrd;
2778
		} elseif (
2779
			($brd === 1)
2780
			|| ($brd === true)
2781
			|| (is_numeric($brd) && ((int)$brd > 0))
2782
		) {
2783
			$brd = array('LRTB' => true);
2784
		}
2785
		if (!is_array($brd)) {
2786
			return;
2787
		}
2788
		// store current cell padding
2789
		$cp = $this->cell_padding;
2790
		// select border mode
2791
		if (isset($brd['mode'])) {
2792
			$mode = $brd['mode'];
2793
			unset($brd['mode']);
2794
		} else {
2795
			$mode = 'normal';
2796
		}
2797
		// process borders
2798
		foreach ($brd as $border => $style) {
2799
			$line_width = $this->LineWidth;
2800
			if (is_array($style) && isset($style['width'])) {
2801
				// get border width
2802
				$line_width = $style['width'];
2803
			}
2804
			$adj = 0; // line width inside the cell
2805
			switch ($mode) {
2806
				case 'ext': {
2807
					$adj = 0;
2808
					break;
2809
				}
2810
				case 'int': {
2811
					$adj = $line_width;
2812
					break;
2813
				}
2814
				case 'normal':
2815
				default: {
2816
					$adj = ($line_width / 2);
2817
					break;
2818
				}
2819
			}
2820
			// correct internal cell padding if required to avoid overlap between text and lines
2821
			if (
2822
				is_numeric($this->cell_padding['T'])
2823
				&& ($this->cell_padding['T'] < $adj)
2824
				&& (strpos($border, 'T') !== false)
2825
			) {
2826
				$this->cell_padding['T'] = $adj;
2827
			}
2828
			if (
2829
				is_numeric($this->cell_padding['R'])
2830
				&& ($this->cell_padding['R'] < $adj)
2831
				&& (strpos($border, 'R') !== false)
2832
			) {
2833
				$this->cell_padding['R'] = $adj;
2834
			}
2835
			if (
2836
				is_numeric($this->cell_padding['B'])
2837
				&& ($this->cell_padding['B'] < $adj)
2838
				&& (strpos($border, 'B') !== false)
2839
			) {
2840
				$this->cell_padding['B'] = $adj;
2841
			}
2842
			if (
2843
				is_numeric($this->cell_padding['L'])
2844
				&& ($this->cell_padding['L'] < $adj)
2845
				&& (strpos($border, 'L') !== false)
2846
			) {
2847
				$this->cell_padding['L'] = $adj;
2848
			}
2849
2850
		}
2851
2852
		return array(
2853
			'T' => ($this->cell_padding['T'] - $cp['T']),
2854
			'R' => ($this->cell_padding['R'] - $cp['R']),
2855
			'B' => ($this->cell_padding['B'] - $cp['B']),
2856
			'L' => ($this->cell_padding['L'] - $cp['L']),
2857
		);
2858
	}
2859
2860
	/**
2861
	 * 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.
2862
	 * @param boolean $auto Boolean indicating if mode should be on or off.
2863
	 * @param float $margin Distance from the bottom of the page.
2864
	 * @public
2865
	 * @since 1.0
2866
	 * @see Cell(), MultiCell(), AcceptPageBreak()
2867
	 */
2868
	public function setAutoPageBreak($auto, $margin=0) {
2869
		$this->AutoPageBreak = $auto ? true : false;
2870
		$this->bMargin = $margin;
2871
		$this->PageBreakTrigger = $this->h - $margin;
2872
	}
2873
2874
	/**
2875
	 * Return the auto-page-break mode (true or false).
2876
	 * @return bool auto-page-break mode
2877
	 * @public
2878
	 * @since 5.9.088
2879
	 */
2880
	public function getAutoPageBreak() {
2881
		return $this->AutoPageBreak;
2882
	}
2883
2884
	/**
2885
	 * Defines the way the document is to be displayed by the viewer.
2886
	 * @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>
2887
	 * @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>
2888
	 * @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>
2889
	 * @public
2890
	 * @since 1.2
2891
	 */
2892
	public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2893
		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2894
			$this->ZoomMode = $zoom;
2895
		} else {
2896
			$this->Error('Incorrect zoom display mode: '.$zoom);
2897
		}
2898
		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2899
		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2900
	}
2901
2902
	/**
2903
	 * 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.
2904
	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2905
	 * @param boolean $compress Boolean indicating if compression must be enabled.
2906
	 * @public
2907
	 * @since 1.4
2908
	 */
2909
	public function setCompression($compress=true) {
2910
		$this->compress = false;
2911
		if (function_exists('gzcompress')) {
2912
			if ($compress) {
2913
				if ( !$this->pdfa_mode) {
2914
					$this->compress = true;
2915
				}
2916
			}
2917
		}
2918
	}
2919
2920
	/**
2921
	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2922
	 * @param boolean $mode If true force sRGB output intent.
2923
	 * @public
2924
	 * @since 5.9.121 (2011-09-28)
2925
	 */
2926
	public function setSRGBmode($mode=false) {
2927
		$this->force_srgb = $mode ? true : false;
2928
	}
2929
2930
	/**
2931
	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2932
	 * This has effect only when unicode mode is set to false.
2933
	 * @param boolean $unicode if true set the meta information in Unicode
2934
	 * @since 5.9.027 (2010-12-01)
2935
	 * @public
2936
	 */
2937
	public function setDocInfoUnicode($unicode=true) {
2938
		$this->docinfounicode = $unicode ? true : false;
2939
	}
2940
2941
	/**
2942
	 * Defines the title of the document.
2943
	 * @param string $title The title.
2944
	 * @public
2945
	 * @since 1.2
2946
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2947
	 */
2948
	public function setTitle($title) {
2949
		$this->title = $title;
2950
	}
2951
2952
	/**
2953
	 * Defines the subject of the document.
2954
	 * @param string $subject The subject.
2955
	 * @public
2956
	 * @since 1.2
2957
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2958
	 */
2959
	public function setSubject($subject) {
2960
		$this->subject = $subject;
2961
	}
2962
2963
	/**
2964
	 * Defines the author of the document.
2965
	 * @param string $author The name of the author.
2966
	 * @public
2967
	 * @since 1.2
2968
	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2969
	 */
2970
	public function setAuthor($author) {
2971
		$this->author = $author;
2972
	}
2973
2974
	/**
2975
	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2976
	 * @param string $keywords The list of keywords.
2977
	 * @public
2978
	 * @since 1.2
2979
	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2980
	 */
2981
	public function setKeywords($keywords) {
2982
		$this->keywords = $keywords;
2983
	}
2984
2985
	/**
2986
	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2987
	 * @param string $creator The name of the creator.
2988
	 * @public
2989
	 * @since 1.2
2990
	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2991
	 */
2992
	public function setCreator($creator) {
2993
		$this->creator = $creator;
2994
	}
2995
2996
	/**
2997
	 * Whether to allow local file path in image html tags, when prefixed with file://
2998
	 *
2999
	 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
3000
	 * @public
3001
	 * @since 6.4
3002
	 */
3003
	public function setAllowLocalFiles($allowLocalFiles) {
3004
		$this->allowLocalFiles = (bool) $allowLocalFiles;
3005
	}
3006
3007
3008
	/**
3009
	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
3010
	 * @param string $msg The error message
3011
	 * @public
3012
	 * @since 1.0
3013
	 */
3014
	public function Error($msg) {
3015
		// unset all class variables
3016
		$this->_destroy(true);
3017
		$msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
3018
		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
3019
			die('<strong>TCPDF ERROR: </strong>'.$msg);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
3020
		} else {
3021
			throw new Exception('TCPDF ERROR: '.$msg);
3022
		}
3023
	}
3024
3025
	/**
3026
	 * This method begins the generation of the PDF document.
3027
	 * It is not necessary to call it explicitly because AddPage() does it automatically.
3028
	 * Note: no page is created by this method
3029
	 * @public
3030
	 * @since 1.0
3031
	 * @see AddPage(), Close()
3032
	 */
3033
	public function Open() {
3034
		$this->state = 1;
3035
	}
3036
3037
	/**
3038
	 * Terminates the PDF document.
3039
	 * It is not necessary to call this method explicitly because Output() does it automatically.
3040
	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3041
	 * @public
3042
	 * @since 1.0
3043
	 * @see Open(), Output()
3044
	 */
3045
	public function Close() {
3046
		if ($this->state == 3) {
3047
			return;
3048
		}
3049
		if ($this->page == 0) {
3050
			$this->AddPage();
3051
		}
3052
		$this->endLayer();
3053
		if ($this->tcpdflink) {
3054
			// save current graphic settings
3055
			$gvars = $this->getGraphicVars();
3056
			$this->setEqualColumns();
3057
			$this->lastpage(true);
3058
			$this->setAutoPageBreak(false);
3059
			$this->x = 0;
3060
			$this->y = $this->h - (1 / $this->k);
3061
			$this->lMargin = 0;
3062
			$this->_outSaveGraphicsState();
3063
			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3064
			$this->setFont($font, '', 1);
3065
			$this->setTextRenderingMode(0, false, false);
3066
			$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";
3067
			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3068
			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3069
			$this->_outRestoreGraphicsState();
3070
			// restore graphic settings
3071
			$this->setGraphicVars($gvars);
3072
		}
3073
		// close page
3074
		$this->endPage();
3075
		// close document
3076
		$this->_enddoc();
3077
		// unset all class variables (except critical ones)
3078
		$this->_destroy(false);
3079
	}
3080
3081
	/**
3082
	 * Move pointer at the specified document page and update page dimensions.
3083
	 * @param int $pnum page number (1 ... numpages)
3084
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3085
	 * @public
3086
	 * @since 2.1.000 (2008-01-07)
3087
	 * @see getPage(), lastpage(), getNumPages()
3088
	 */
3089
	public function setPage($pnum, $resetmargins=false) {
3090
		if (($pnum == $this->page) AND ($this->state == 2)) {
3091
			return;
3092
		}
3093
		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3094
			$this->state = 2;
3095
			// save current graphic settings
3096
			//$gvars = $this->getGraphicVars();
3097
			$oldpage = $this->page;
3098
			$this->page = $pnum;
3099
			$this->wPt = $this->pagedim[$this->page]['w'];
3100
			$this->hPt = $this->pagedim[$this->page]['h'];
3101
			$this->w = $this->pagedim[$this->page]['wk'];
3102
			$this->h = $this->pagedim[$this->page]['hk'];
3103
			$this->tMargin = $this->pagedim[$this->page]['tm'];
3104
			$this->bMargin = $this->pagedim[$this->page]['bm'];
3105
			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3106
			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3107
			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3108
			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3109
			$this->setAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3110
			// restore graphic settings
3111
			//$this->setGraphicVars($gvars);
3112
			if ($resetmargins) {
3113
				$this->lMargin = $this->pagedim[$this->page]['olm'];
3114
				$this->rMargin = $this->pagedim[$this->page]['orm'];
3115
				$this->setY($this->tMargin);
3116
			} else {
3117
				// account for booklet mode
3118
				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3119
					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3120
					$this->lMargin += $deltam;
3121
					$this->rMargin -= $deltam;
3122
				}
3123
			}
3124
		} else {
3125
			$this->Error('Wrong page number on setPage() function: '.$pnum);
3126
		}
3127
	}
3128
3129
	/**
3130
	 * Reset pointer to the last document page.
3131
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3132
	 * @public
3133
	 * @since 2.0.000 (2008-01-04)
3134
	 * @see setPage(), getPage(), getNumPages()
3135
	 */
3136
	public function lastPage($resetmargins=false) {
3137
		$this->setPage($this->getNumPages(), $resetmargins);
3138
	}
3139
3140
	/**
3141
	 * Get current document page number.
3142
	 * @return int page number
3143
	 * @public
3144
	 * @since 2.1.000 (2008-01-07)
3145
	 * @see setPage(), lastpage(), getNumPages()
3146
	 */
3147
	public function getPage() {
3148
		return $this->page;
3149
	}
3150
3151
	/**
3152
	 * Get the total number of insered pages.
3153
	 * @return int number of pages
3154
	 * @public
3155
	 * @since 2.1.000 (2008-01-07)
3156
	 * @see setPage(), getPage(), lastpage()
3157
	 */
3158
	public function getNumPages() {
3159
		return $this->numpages;
3160
	}
3161
3162
	/**
3163
	 * Adds a new TOC (Table Of Content) page to the document.
3164
	 * @param string $orientation page orientation.
3165
	 * @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().
3166
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3167
	 * @public
3168
	 * @since 5.0.001 (2010-05-06)
3169
	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3170
	 */
3171
	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3172
		$this->AddPage($orientation, $format, $keepmargins, true);
3173
	}
3174
3175
	/**
3176
	 * Terminate the current TOC (Table Of Content) page
3177
	 * @public
3178
	 * @since 5.0.001 (2010-05-06)
3179
	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3180
	 */
3181
	public function endTOCPage() {
3182
		$this->endPage(true);
3183
	}
3184
3185
	/**
3186
	 * 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).
3187
	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3188
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3189
	 * @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().
3190
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3191
	 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3192
	 * @public
3193
	 * @since 1.0
3194
	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3195
	 */
3196
	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3197
		if ($this->inxobj) {
3198
			// we are inside an XObject template
3199
			return;
3200
		}
3201
		if (!isset($this->original_lMargin) OR $keepmargins) {
3202
			$this->original_lMargin = $this->lMargin;
3203
		}
3204
		if (!isset($this->original_rMargin) OR $keepmargins) {
3205
			$this->original_rMargin = $this->rMargin;
3206
		}
3207
		// terminate previous page
3208
		$this->endPage();
3209
		// start new page
3210
		$this->startPage($orientation, $format, $tocpage);
3211
	}
3212
3213
	/**
3214
	 * Terminate the current page
3215
	 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3216
	 * @public
3217
	 * @since 4.2.010 (2008-11-14)
3218
	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3219
	 */
3220
	public function endPage($tocpage=false) {
3221
		// check if page is already closed
3222
		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3223
			return;
3224
		}
3225
		// print page footer
3226
		$this->setFooter();
3227
		// close page
3228
		$this->_endpage();
3229
		// mark page as closed
3230
		$this->pageopen[$this->page] = false;
3231
		if ($tocpage) {
3232
			$this->tocpage = false;
3233
		}
3234
	}
3235
3236
	/**
3237
	 * Starts a new page to the document. The page must be closed using the endPage() function.
3238
	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3239
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3240
	 * @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().
3241
	 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3242
	 * @since 4.2.010 (2008-11-14)
3243
	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3244
	 * @public
3245
	 */
3246
	public function startPage($orientation='', $format='', $tocpage=false) {
3247
		if ($tocpage) {
3248
			$this->tocpage = true;
3249
		}
3250
		// move page numbers of documents to be attached
3251
		if ($this->tocpage) {
3252
			// move reference to unexistent pages (used for page attachments)
3253
			// adjust outlines
3254
			$tmpoutlines = $this->outlines;
3255
			foreach ($tmpoutlines as $key => $outline) {
3256
				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3257
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3258
				}
3259
			}
3260
			// adjust dests
3261
			$tmpdests = $this->dests;
3262
			foreach ($tmpdests as $key => $dest) {
3263
				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3264
					$this->dests[$key]['p'] = ($dest['p'] + 1);
3265
				}
3266
			}
3267
			// adjust links
3268
			$tmplinks = $this->links;
3269
			foreach ($tmplinks as $key => $link) {
3270
				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3271
					$this->links[$key]['p'] = ($link['p'] + 1);
3272
				}
3273
			}
3274
		}
3275
		if ($this->numpages > $this->page) {
3276
			// this page has been already added
3277
			$this->setPage($this->page + 1);
3278
			$this->setY($this->tMargin);
3279
			return;
3280
		}
3281
		// start a new page
3282
		if ($this->state == 0) {
3283
			$this->Open();
3284
		}
3285
		++$this->numpages;
3286
		$this->swapMargins($this->booklet);
3287
		// save current graphic settings
3288
		$gvars = $this->getGraphicVars();
3289
		// start new page
3290
		$this->_beginpage($orientation, $format);
3291
		// mark page as open
3292
		$this->pageopen[$this->page] = true;
3293
		// restore graphic settings
3294
		$this->setGraphicVars($gvars);
3295
		// mark this point
3296
		$this->setPageMark();
3297
		// print page header
3298
		$this->setHeader();
3299
		// restore graphic settings
3300
		$this->setGraphicVars($gvars);
3301
		// mark this point
3302
		$this->setPageMark();
3303
		// print table header (if any)
3304
		$this->setTableHeader();
3305
		// set mark for empty page check
3306
		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3307
	}
3308
3309
	/**
3310
	 * Set start-writing mark on current page stream used to put borders and fills.
3311
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3312
	 * This function must be called after calling Image() function for a background image.
3313
	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3314
	 * @public
3315
	 * @since 4.0.016 (2008-07-30)
3316
	 */
3317
	public function setPageMark() {
3318
		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3319
		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3320
		$this->setContentMark();
3321
	}
3322
3323
	/**
3324
	 * Set start-writing mark on selected page.
3325
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3326
	 * @param int $page page number (default is the current page)
3327
	 * @protected
3328
	 * @since 4.6.021 (2009-07-20)
3329
	 */
3330
	protected function setContentMark($page=0) {
3331
		if ($page <= 0) {
3332
			$page = $this->page;
3333
		}
3334
		if (isset($this->footerlen[$page])) {
3335
			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3336
		} else {
3337
			$this->cntmrk[$page] = $this->pagelen[$page];
3338
		}
3339
	}
3340
3341
	/**
3342
	 * Set header data.
3343
	 * @param string $ln header image logo
3344
	 * @param int $lw header image logo width in mm
3345
	 * @param string $ht string to print as title on document header
3346
	 * @param string $hs string to print on document header
3347
	 * @param int[] $tc RGB array color for text.
3348
	 * @param int[] $lc RGB array color for line.
3349
	 * @public
3350
	 */
3351
	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3352
		$this->header_logo = $ln;
3353
		$this->header_logo_width = $lw;
3354
		$this->header_title = $ht;
3355
		$this->header_string = $hs;
3356
		$this->header_text_color = $tc;
3357
		$this->header_line_color = $lc;
3358
	}
3359
3360
	/**
3361
	 * Set footer data.
3362
	 * @param int[] $tc RGB array color for text.
3363
	 * @param int[] $lc RGB array color for line.
3364
	 * @public
3365
	 */
3366
	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3367
		$this->footer_text_color = $tc;
3368
		$this->footer_line_color = $lc;
3369
	}
3370
3371
	/**
3372
	 * Returns header data:
3373
	 * <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>
3374
	 * @return array<string,mixed>
3375
	 * @public
3376
	 * @since 4.0.012 (2008-07-24)
3377
	 */
3378
	public function getHeaderData() {
3379
		$ret = array();
3380
		$ret['logo'] = $this->header_logo;
3381
		$ret['logo_width'] = $this->header_logo_width;
3382
		$ret['title'] = $this->header_title;
3383
		$ret['string'] = $this->header_string;
3384
		$ret['text_color'] = $this->header_text_color;
3385
		$ret['line_color'] = $this->header_line_color;
3386
		return $ret;
3387
	}
3388
3389
	/**
3390
	 * Set header margin.
3391
	 * (minimum distance between header and top page margin)
3392
	 * @param float $hm distance in user units
3393
	 * @public
3394
	 */
3395
	public function setHeaderMargin($hm=10) {
3396
		$this->header_margin = $hm;
3397
	}
3398
3399
	/**
3400
	 * Returns header margin in user units.
3401
	 * @return float
3402
	 * @since 4.0.012 (2008-07-24)
3403
	 * @public
3404
	 */
3405
	public function getHeaderMargin() {
3406
		return $this->header_margin;
3407
	}
3408
3409
	/**
3410
	 * Set footer margin.
3411
	 * (minimum distance between footer and bottom page margin)
3412
	 * @param float $fm distance in user units
3413
	 * @public
3414
	 */
3415
	public function setFooterMargin($fm=10) {
3416
		$this->footer_margin = $fm;
3417
	}
3418
3419
	/**
3420
	 * Returns footer margin in user units.
3421
	 * @return float
3422
	 * @since 4.0.012 (2008-07-24)
3423
	 * @public
3424
	 */
3425
	public function getFooterMargin() {
3426
		return $this->footer_margin;
3427
	}
3428
	/**
3429
	 * Set a flag to print page header.
3430
	 * @param boolean $val set to true to print the page header (default), false otherwise.
3431
	 * @public
3432
	 */
3433
	public function setPrintHeader($val=true) {
3434
		$this->print_header = $val ? true : false;
3435
	}
3436
3437
	/**
3438
	 * Set a flag to print page footer.
3439
	 * @param boolean $val set to true to print the page footer (default), false otherwise.
3440
	 * @public
3441
	 */
3442
	public function setPrintFooter($val=true) {
3443
		$this->print_footer = $val ? true : false;
3444
	}
3445
3446
	/**
3447
	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3448
	 * @return float
3449
	 * @public
3450
	 */
3451
	public function getImageRBX() {
3452
		return $this->img_rb_x;
3453
	}
3454
3455
	/**
3456
	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3457
	 * @return float
3458
	 * @public
3459
	 */
3460
	public function getImageRBY() {
3461
		return $this->img_rb_y;
3462
	}
3463
3464
	/**
3465
	 * Reset the xobject template used by Header() method.
3466
	 * @public
3467
	 */
3468
	public function resetHeaderTemplate() {
3469
		$this->header_xobjid = false;
3470
	}
3471
3472
	/**
3473
	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3474
	 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3475
	 * @public
3476
	 */
3477
	public function setHeaderTemplateAutoreset($val=true) {
3478
		$this->header_xobj_autoreset = $val ? true : false;
3479
	}
3480
3481
	/**
3482
	 * This method is used to render the page header.
3483
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3484
	 * @public
3485
	 */
3486
	public function Header() {
3487
		if ($this->header_xobjid === false) {
3488
			// start a new XObject Template
3489
			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3490
			$headerfont = $this->getHeaderFont();
3491
			$headerdata = $this->getHeaderData();
3492
			$this->y = $this->header_margin;
3493
			if ($this->rtl) {
3494
				$this->x = $this->w - $this->original_rMargin;
3495
			} else {
3496
				$this->x = $this->original_lMargin;
3497
			}
3498
			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3499
				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3500
				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3501
					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3502
				} elseif ($imgtype == 'svg') {
3503
					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3504
				} else {
3505
					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3506
				}
3507
				$imgy = $this->getImageRBY();
3508
			} else {
3509
				$imgy = $this->y;
3510
			}
3511
			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3512
			// set starting margin for text data cell
3513
			if ($this->getRTL()) {
3514
				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3515
			} else {
3516
				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3517
			}
3518
			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3519
			$this->setTextColorArray($this->header_text_color);
3520
			// header title
3521
			$this->setFont($headerfont[0], 'B', $headerfont[2] + 1);
3522
			$this->setX($header_x);
3523
			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3524
			// header string
3525
			$this->setFont($headerfont[0], $headerfont[1], $headerfont[2]);
3526
			$this->setX($header_x);
3527
			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3528
			// print an ending header line
3529
			$this->setLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3530
			$this->setY((2.835 / $this->k) + max($imgy, $this->y));
3531
			if ($this->rtl) {
3532
				$this->setX($this->original_rMargin);
3533
			} else {
3534
				$this->setX($this->original_lMargin);
3535
			}
3536
			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3537
			$this->endTemplate();
3538
		}
3539
		// print header template
3540
		$x = 0;
3541
		$dx = 0;
3542
		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3543
			// adjust margins for booklet mode
3544
			$dx = ($this->original_lMargin - $this->original_rMargin);
3545
		}
3546
		if ($this->rtl) {
3547
			$x = $this->w + $dx;
3548
		} else {
3549
			$x = 0 + $dx;
3550
		}
3551
		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3552
		if ($this->header_xobj_autoreset) {
3553
			// reset header xobject template at each page
3554
			$this->header_xobjid = false;
3555
		}
3556
	}
3557
3558
	/**
3559
	 * This method is used to render the page footer.
3560
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3561
	 * @public
3562
	 */
3563
	public function Footer() {
3564
		$cur_y = $this->y;
3565
		$this->setTextColorArray($this->footer_text_color);
3566
		//set style for cell border
3567
		$line_width = (0.85 / $this->k);
3568
		$this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3569
		//print document barcode
3570
		$barcode = $this->getBarcode();
3571
		if (!empty($barcode)) {
3572
			$this->Ln($line_width);
3573
			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3574
			$style = array(
3575
				'position' => $this->rtl?'R':'L',
3576
				'align' => $this->rtl?'R':'L',
3577
				'stretch' => false,
3578
				'fitwidth' => true,
3579
				'cellfitalign' => '',
3580
				'border' => false,
3581
				'padding' => 0,
3582
				'fgcolor' => array(0,0,0),
3583
				'bgcolor' => false,
3584
				'text' => false
3585
			);
3586
			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3587
		}
3588
		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3589
		if (empty($this->pagegroups)) {
3590
			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3591
		} else {
3592
			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3593
		}
3594
		$this->setY($cur_y);
3595
		//Print page number
3596
		if ($this->getRTL()) {
3597
			$this->setX($this->original_rMargin);
3598
			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3599
		} else {
3600
			$this->setX($this->original_lMargin);
3601
			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3602
		}
3603
	}
3604
3605
	/**
3606
	 * This method is used to render the page header.
3607
	 * @protected
3608
	 * @since 4.0.012 (2008-07-24)
3609
	 */
3610
	protected function setHeader() {
3611
		if (!$this->print_header OR ($this->state != 2)) {
3612
			return;
3613
		}
3614
		$this->InHeader = true;
3615
		$this->setGraphicVars($this->default_graphic_vars);
3616
		$temp_thead = $this->thead;
3617
		$temp_theadMargins = $this->theadMargins;
3618
		$lasth = $this->lasth;
3619
		$newline = $this->newline;
3620
		$this->_outSaveGraphicsState();
3621
		$this->rMargin = $this->original_rMargin;
3622
		$this->lMargin = $this->original_lMargin;
3623
		$this->setCellPadding(0);
3624
		//set current position
3625
		if ($this->rtl) {
3626
			$this->setXY($this->original_rMargin, $this->header_margin);
3627
		} else {
3628
			$this->setXY($this->original_lMargin, $this->header_margin);
3629
		}
3630
		$this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3631
		$this->Header();
3632
		//restore position
3633
		if ($this->rtl) {
3634
			$this->setXY($this->original_rMargin, $this->tMargin);
3635
		} else {
3636
			$this->setXY($this->original_lMargin, $this->tMargin);
3637
		}
3638
		$this->_outRestoreGraphicsState();
3639
		$this->lasth = $lasth;
3640
		$this->thead = $temp_thead;
3641
		$this->theadMargins = $temp_theadMargins;
3642
		$this->newline = $newline;
3643
		$this->InHeader = false;
3644
	}
3645
3646
	/**
3647
	 * This method is used to render the page footer.
3648
	 * @protected
3649
	 * @since 4.0.012 (2008-07-24)
3650
	 */
3651
	protected function setFooter() {
3652
		if ($this->state != 2) {
3653
			return;
3654
		}
3655
		$this->InFooter = true;
3656
		// save current graphic settings
3657
		$gvars = $this->getGraphicVars();
3658
		// mark this point
3659
		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3660
		$this->_out("\n");
3661
		if ($this->print_footer) {
3662
			$this->setGraphicVars($this->default_graphic_vars);
3663
			$this->current_column = 0;
3664
			$this->num_columns = 1;
3665
			$temp_thead = $this->thead;
3666
			$temp_theadMargins = $this->theadMargins;
3667
			$lasth = $this->lasth;
3668
			$this->_outSaveGraphicsState();
3669
			$this->rMargin = $this->original_rMargin;
3670
			$this->lMargin = $this->original_lMargin;
3671
			$this->setCellPadding(0);
3672
			//set current position
3673
			$footer_y = $this->h - $this->footer_margin;
3674
			if ($this->rtl) {
3675
				$this->setXY($this->original_rMargin, $footer_y);
3676
			} else {
3677
				$this->setXY($this->original_lMargin, $footer_y);
3678
			}
3679
			$this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3680
			$this->Footer();
3681
			//restore position
3682
			if ($this->rtl) {
3683
				$this->setXY($this->original_rMargin, $this->tMargin);
3684
			} else {
3685
				$this->setXY($this->original_lMargin, $this->tMargin);
3686
			}
3687
			$this->_outRestoreGraphicsState();
3688
			$this->lasth = $lasth;
3689
			$this->thead = $temp_thead;
3690
			$this->theadMargins = $temp_theadMargins;
3691
		}
3692
		// restore graphic settings
3693
		$this->setGraphicVars($gvars);
3694
		$this->current_column = $gvars['current_column'];
3695
		$this->num_columns = $gvars['num_columns'];
3696
		// calculate footer length
3697
		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3698
		$this->InFooter = false;
3699
	}
3700
3701
	/**
3702
	 * Check if we are on the page body (excluding page header and footer).
3703
	 * @return bool true if we are not in page header nor in page footer, false otherwise.
3704
	 * @protected
3705
	 * @since 5.9.091 (2011-06-15)
3706
	 */
3707
	protected function inPageBody() {
3708
		return (($this->InHeader === false) AND ($this->InFooter === false));
3709
	}
3710
3711
	/**
3712
	 * This method is used to render the table header on new page (if any).
3713
	 * @protected
3714
	 * @since 4.5.030 (2009-03-25)
3715
	 */
3716
	protected function setTableHeader() {
3717
		if ($this->num_columns > 1) {
3718
			// multi column mode
3719
			return;
3720
		}
3721
		if (isset($this->theadMargins['top'])) {
3722
			// restore the original top-margin
3723
			$this->tMargin = $this->theadMargins['top'];
3724
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3725
			$this->y = $this->tMargin;
3726
		}
3727
		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3728
			// set margins
3729
			$prev_lMargin = $this->lMargin;
3730
			$prev_rMargin = $this->rMargin;
3731
			$prev_cell_padding = $this->cell_padding;
3732
			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3733
			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3734
			$this->cell_padding = $this->theadMargins['cell_padding'];
3735
			if ($this->rtl) {
3736
				$this->x = $this->w - $this->rMargin;
3737
			} else {
3738
				$this->x = $this->lMargin;
3739
			}
3740
			// account for special "cell" mode
3741
			if ($this->theadMargins['cell']) {
3742
				if ($this->rtl) {
3743
					$this->x -= $this->cell_padding['R'];
3744
				} else {
3745
					$this->x += $this->cell_padding['L'];
3746
				}
3747
			}
3748
			$gvars = $this->getGraphicVars();
3749
			if (!empty($this->theadMargins['gvars'])) {
3750
				// set the correct graphic style
3751
				$this->setGraphicVars($this->theadMargins['gvars']);
3752
				$this->rMargin = $gvars['rMargin'];
3753
				$this->lMargin = $gvars['lMargin'];
3754
			}
3755
			// print table header
3756
			$this->writeHTML($this->thead, false, false, false, false, '');
3757
			$this->setGraphicVars($gvars);
3758
			// set new top margin to skip the table headers
3759
			if (!isset($this->theadMargins['top'])) {
3760
				$this->theadMargins['top'] = $this->tMargin;
3761
			}
3762
			// store end of header position
3763
			if (!isset($this->columns[0]['th'])) {
3764
				$this->columns[0]['th'] = array();
3765
			}
3766
			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3767
			$this->tMargin = $this->y;
3768
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3769
			$this->lasth = 0;
3770
			$this->lMargin = $prev_lMargin;
3771
			$this->rMargin = $prev_rMargin;
3772
			$this->cell_padding = $prev_cell_padding;
3773
		}
3774
	}
3775
3776
	/**
3777
	 * Returns the current page number.
3778
	 * @return int page number
3779
	 * @public
3780
	 * @since 1.0
3781
	 * @see getAliasNbPages()
3782
	 */
3783
	public function PageNo() {
3784
		return $this->page;
3785
	}
3786
3787
	/**
3788
	 * Returns the array of spot colors.
3789
	 * @return array Spot colors array.
3790
	 * @public
3791
	 * @since 6.0.038 (2013-09-30)
3792
	 */
3793
	public function getAllSpotColors() {
3794
		return $this->spot_colors;
3795
	}
3796
3797
	/**
3798
	 * Defines a new spot color.
3799
	 * It can be expressed in RGB components or gray scale.
3800
	 * The method can be called before the first page is created and the value is retained from page to page.
3801
	 * @param string $name Full name of the spot color.
3802
	 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3803
	 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3804
	 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3805
	 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3806
	 * @public
3807
	 * @since 4.0.024 (2008-09-12)
3808
	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3809
	 */
3810
	public function AddSpotColor($name, $c, $m, $y, $k) {
3811
		if (!isset($this->spot_colors[$name])) {
3812
			$i = (1 + count($this->spot_colors));
3813
			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3814
		}
3815
	}
3816
3817
	/**
3818
	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3819
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3820
	 * @param string $name Name of the spot color.
3821
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3822
	 * @return string PDF color command.
3823
	 * @public
3824
	 * @since 5.9.125 (2011-10-03)
3825
	 */
3826
	public function setSpotColor($type, $name, $tint=100) {
3827
		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3828
		if ($spotcolor === false) {
3829
			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3830
		}
3831
		$tint = (max(0, min(100, $tint)) / 100);
3832
		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3833
		switch ($type) {
3834
			case 'draw': {
3835
				$pdfcolor .= sprintf('CS %F SCN', $tint);
3836
				$this->DrawColor = $pdfcolor;
3837
				$this->strokecolor = $spotcolor;
3838
				break;
3839
			}
3840
			case 'fill': {
3841
				$pdfcolor .= sprintf('cs %F scn', $tint);
3842
				$this->FillColor = $pdfcolor;
3843
				$this->bgcolor = $spotcolor;
3844
				break;
3845
			}
3846
			case 'text': {
3847
				$pdfcolor .= sprintf('cs %F scn', $tint);
3848
				$this->TextColor = $pdfcolor;
3849
				$this->fgcolor = $spotcolor;
3850
				break;
3851
			}
3852
		}
3853
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3854
		if ($this->state == 2) {
3855
			$this->_out($pdfcolor);
3856
		}
3857
		if ($this->inxobj) {
3858
			// we are inside an XObject template
3859
			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3860
		}
3861
		return $pdfcolor;
3862
	}
3863
3864
	/**
3865
	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3866
	 * @param string $name Name of the spot color.
3867
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3868
	 * @public
3869
	 * @since 4.0.024 (2008-09-12)
3870
	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3871
	 */
3872
	public function setDrawSpotColor($name, $tint=100) {
3873
		$this->setSpotColor('draw', $name, $tint);
3874
	}
3875
3876
	/**
3877
	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3878
	 * @param string $name Name of the spot color.
3879
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3880
	 * @public
3881
	 * @since 4.0.024 (2008-09-12)
3882
	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3883
	 */
3884
	public function setFillSpotColor($name, $tint=100) {
3885
		$this->setSpotColor('fill', $name, $tint);
3886
	}
3887
3888
	/**
3889
	 * Defines the spot color used for text.
3890
	 * @param string $name Name of the spot color.
3891
	 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3892
	 * @public
3893
	 * @since 4.0.024 (2008-09-12)
3894
	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3895
	 */
3896
	public function setTextSpotColor($name, $tint=100) {
3897
		$this->setSpotColor('text', $name, $tint);
3898
	}
3899
3900
	/**
3901
	 * Set the color array for the specified type ('draw', 'fill', 'text').
3902
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3903
	 * The method can be called before the first page is created and the value is retained from page to page.
3904
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3905
	 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3906
	 * @param boolean $ret If true do not send the PDF command.
3907
	 * @return string The PDF command or empty string.
3908
	 * @public
3909
	 * @since 3.1.000 (2008-06-11)
3910
	 */
3911
	public function setColorArray($type, $color, $ret=false) {
3912
		if (is_array($color)) {
3913
			$color = array_values($color);
3914
			// component: grey, RGB red or CMYK cyan
3915
			$c = isset($color[0]) ? $color[0] : -1;
3916
			// component: RGB green or CMYK magenta
3917
			$m = isset($color[1]) ? $color[1] : -1;
3918
			// component: RGB blue or CMYK yellow
3919
			$y = isset($color[2]) ? $color[2] : -1;
3920
			// component: CMYK black
3921
			$k = isset($color[3]) ? $color[3] : -1;
3922
			// color name
3923
			$name = isset($color[4]) ? $color[4] : '';
3924
			if ($c >= 0) {
3925
				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3926
			}
3927
		}
3928
		return '';
3929
	}
3930
3931
	/**
3932
	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
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
	 * @return string the PDF command
3938
	 * @public
3939
	 * @since 3.1.000 (2008-06-11)
3940
	 * @see SetDrawColor()
3941
	 */
3942
	public function setDrawColorArray($color, $ret=false) {
3943
		return $this->setColorArray('draw', $color, $ret);
3944
	}
3945
3946
	/**
3947
	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3948
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3949
	 * The method can be called before the first page is created and the value is retained from page to page.
3950
	 * @param array $color Array of colors (1, 3 or 4 values).
3951
	 * @param boolean $ret If true do not send the PDF command.
3952
	 * @public
3953
	 * @since 3.1.000 (2008-6-11)
3954
	 * @see SetFillColor()
3955
	 */
3956
	public function setFillColorArray($color, $ret=false) {
3957
		return $this->setColorArray('fill', $color, $ret);
3958
	}
3959
3960
	/**
3961
	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3962
	 * The method can be called before the first page is created and the value is retained from page to page.
3963
	 * @param array $color Array of colors (1, 3 or 4 values).
3964
	 * @param boolean $ret If true do not send the PDF command.
3965
	 * @public
3966
	 * @since 3.1.000 (2008-6-11)
3967
	 * @see SetFillColor()
3968
	 */
3969
	public function setTextColorArray($color, $ret=false) {
3970
		return $this->setColorArray('text', $color, $ret);
3971
	}
3972
3973
	/**
3974
	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3975
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3976
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3977
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3978
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3979
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3980
	 * @param boolean $ret If true do not send the command.
3981
	 * @param string $name spot color name (if any)
3982
	 * @return string The PDF command or empty string.
3983
	 * @public
3984
	 * @since 5.9.125 (2011-10-03)
3985
	 */
3986
	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3987
		// set default values
3988
		if (!is_numeric($col1)) {
3989
			$col1 = 0;
3990
		}
3991
		if (!is_numeric($col2)) {
3992
			$col2 = -1;
3993
		}
3994
		if (!is_numeric($col3)) {
3995
			$col3 = -1;
3996
		}
3997
		if (!is_numeric($col4)) {
3998
			$col4 = -1;
3999
		}
4000
		// set color by case
4001
		$suffix = '';
4002
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4003
			// Grey scale
4004
			$col1 = max(0, min(255, $col1));
4005
			$intcolor = array('G' => $col1);
4006
			$pdfcolor = sprintf('%F ', ($col1 / 255));
4007
			$suffix = 'g';
4008
		} elseif ($col4 == -1) {
4009
			// RGB
4010
			$col1 = max(0, min(255, $col1));
4011
			$col2 = max(0, min(255, $col2));
4012
			$col3 = max(0, min(255, $col3));
4013
			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4014
			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4015
			$suffix = 'rg';
4016
		} else {
4017
			$col1 = max(0, min(100, $col1));
4018
			$col2 = max(0, min(100, $col2));
4019
			$col3 = max(0, min(100, $col3));
4020
			$col4 = max(0, min(100, $col4));
4021
			if (empty($name)) {
4022
				// CMYK
4023
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4024
				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4025
				$suffix = 'k';
4026
			} else {
4027
				// SPOT COLOR
4028
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4029
				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4030
				$pdfcolor = $this->setSpotColor($type, $name, 100);
4031
			}
4032
		}
4033
		switch ($type) {
4034
			case 'draw': {
4035
				$pdfcolor .= strtoupper($suffix);
4036
				$this->DrawColor = $pdfcolor;
4037
				$this->strokecolor = $intcolor;
4038
				break;
4039
			}
4040
			case 'fill': {
4041
				$pdfcolor .= $suffix;
4042
				$this->FillColor = $pdfcolor;
4043
				$this->bgcolor = $intcolor;
4044
				break;
4045
			}
4046
			case 'text': {
4047
				$pdfcolor .= $suffix;
4048
				$this->TextColor = $pdfcolor;
4049
				$this->fgcolor = $intcolor;
4050
				break;
4051
			}
4052
		}
4053
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4054
		if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4055
			if (!$ret) {
4056
				$this->_out($pdfcolor);
4057
			}
4058
			return $pdfcolor;
4059
		}
4060
		return '';
4061
	}
4062
4063
	/**
4064
	 * 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.
4065
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4066
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4067
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4068
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4069
	 * @param boolean $ret If true do not send the command.
4070
	 * @param string $name spot color name (if any)
4071
	 * @return string the PDF command
4072
	 * @public
4073
	 * @since 1.3
4074
	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4075
	 */
4076
	public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4077
		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4078
	}
4079
4080
	/**
4081
	 * 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.
4082
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4083
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4084
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4085
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4086
	 * @param boolean $ret If true do not send the command.
4087
	 * @param string $name Spot color name (if any).
4088
	 * @return string The PDF command.
4089
	 * @public
4090
	 * @since 1.3
4091
	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4092
	 */
4093
	public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4094
		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4095
	}
4096
4097
	/**
4098
	 * 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.
4099
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4100
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4101
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4102
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4103
	 * @param boolean $ret If true do not send the command.
4104
	 * @param string $name Spot color name (if any).
4105
	 * @return string Empty string.
4106
	 * @public
4107
	 * @since 1.3
4108
	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4109
	 */
4110
	public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4111
		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4112
	}
4113
4114
	/**
4115
	 * Returns the length of a string in user unit. A font must be selected.<br>
4116
	 * @param string $s The string whose length is to be computed
4117
	 * @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.
4118
	 * @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.
4119
	 * @param float $fontsize Font size in points. The default value is the current size.
4120
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4121
	 * @return float[]|float total string length or array of characted widths
4122
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4123
	 * @author Nicola Asuni
4124
	 * @public
4125
	 * @since 1.2
4126
	 */
4127
	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4128
		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);
4129
	}
4130
4131
	/**
4132
	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4133
	 * @param array $sa The array of chars whose total length is to be computed
4134
	 * @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.
4135
	 * @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.
4136
	 * @param float $fontsize Font size in points. The default value is the current size.
4137
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4138
	 * @return float[]|float total string length or array of characted widths
4139
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4140
	 * @author Nicola Asuni
4141
	 * @public
4142
	 * @since 2.4.000 (2008-03-06)
4143
	 */
4144
	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4145
		// store current values
4146
		if (!TCPDF_STATIC::empty_string($fontname)) {
4147
			$prev_FontFamily = $this->FontFamily;
4148
			$prev_FontStyle = $this->FontStyle;
4149
			$prev_FontSizePt = $this->FontSizePt;
4150
			$this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4151
		}
4152
		// convert UTF-8 array to Latin1 if required
4153
		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4154
			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4155
		}
4156
		$w = 0; // total width
4157
		$wa = array(); // array of characters widths
4158
		foreach ($sa as $ck => $char) {
4159
			// character width
4160
			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4161
			$wa[] = $cw;
4162
			$w += $cw;
4163
		}
4164
		// restore previous values
4165
		if (!TCPDF_STATIC::empty_string($fontname)) {
4166
			$this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4167
		}
4168
		if ($getarray) {
4169
			return $wa;
4170
		}
4171
		return $w;
4172
	}
4173
4174
	/**
4175
	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4176
	 * @param int $char The char code whose length is to be returned
4177
	 * @param boolean $notlast If false ignore the font-spacing.
4178
	 * @return float char width
4179
	 * @author Nicola Asuni
4180
	 * @public
4181
	 * @since 2.4.000 (2008-03-06)
4182
	 */
4183
	public function GetCharWidth($char, $notlast=true) {
4184
		// get raw width
4185
		$chw = $this->getRawCharWidth($char);
4186
		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4187
			// increase/decrease font spacing
4188
			$chw += $this->font_spacing;
4189
		}
4190
		if ($this->font_stretching != 100) {
4191
			// fixed stretching mode
4192
			$chw *= ($this->font_stretching / 100);
4193
		}
4194
		return $chw;
4195
	}
4196
4197
	/**
4198
	 * Returns the length of the char in user unit for the current font.
4199
	 * @param int $char The char code whose length is to be returned
4200
	 * @return float char width
4201
	 * @author Nicola Asuni
4202
	 * @public
4203
	 * @since 5.9.000 (2010-09-28)
4204
	 */
4205
	public function getRawCharWidth($char) {
4206
		if ($char == 173) {
4207
			// SHY character will not be printed
4208
			return (0);
4209
		}
4210
		if (isset($this->CurrentFont['cw'][intval($char)])) {
4211
			$w = $this->CurrentFont['cw'][intval($char)];
4212
		} elseif (isset($this->CurrentFont['dw'])) {
4213
			// default width
4214
			$w = $this->CurrentFont['dw'];
4215
		} elseif (isset($this->CurrentFont['cw'][32])) {
4216
			// default width
4217
			$w = $this->CurrentFont['cw'][32];
4218
		} else {
4219
			$w = 600;
4220
		}
4221
		return $this->getAbsFontMeasure($w);
4222
	}
4223
4224
	/**
4225
	 * Returns the numbero of characters in a string.
4226
	 * @param string $s The input string.
4227
	 * @return int number of characters
4228
	 * @public
4229
	 * @since 2.0.0001 (2008-01-07)
4230
	 */
4231
	public function GetNumChars($s) {
4232
		if ($this->isUnicodeFont()) {
4233
			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4234
		}
4235
		return strlen($s);
4236
	}
4237
4238
	/**
4239
	 * Fill the list of available fonts ($this->fontlist).
4240
	 * @protected
4241
	 * @since 4.0.013 (2008-07-28)
4242
	 */
4243
	protected function getFontsList() {
4244
		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4245
			while (($file = readdir($fontsdir)) !== false) {
4246
				if (substr($file, -4) == '.php') {
4247
					array_push($this->fontlist, strtolower(basename($file, '.php')));
4248
				}
4249
			}
4250
			closedir($fontsdir);
4251
		}
4252
	}
4253
4254
	/**
4255
	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4256
	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4257
	 * 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.
4258
	 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4259
	 * @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>
4260
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4261
	 * @return array|false array containing the font data, or false in case of error.
4262
	 * @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.
4263
	 * @public
4264
	 * @since 1.5
4265
	 * @see SetFont(), setFontSubsetting()
4266
	 */
4267
	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4268
		if ($subset === 'default') {
4269
			$subset = $this->font_subsetting;
4270
		}
4271
		if ($this->pdfa_mode) {
4272
			$subset = false;
4273
		}
4274
		if (TCPDF_STATIC::empty_string($family)) {
4275
			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4276
				$family = $this->FontFamily;
4277
			} else {
4278
				$this->Error('Empty font family');
4279
			}
4280
		}
4281
		// move embedded styles on $style
4282
		if (substr($family, -1) == 'I') {
4283
			$style .= 'I';
4284
			$family = substr($family, 0, -1);
4285
		}
4286
		if (substr($family, -1) == 'B') {
4287
			$style .= 'B';
4288
			$family = substr($family, 0, -1);
4289
		}
4290
		// normalize family name
4291
		$family = strtolower($family);
4292
		if ((!$this->isunicode) AND ($family == 'arial')) {
4293
			$family = 'helvetica';
4294
		}
4295
		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4296
			$style = '';
4297
		}
4298
		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4299
			// all fonts must be embedded
4300
			$family = 'pdfa'.$family;
4301
		}
4302
		$tempstyle = strtoupper($style === null ? '' : $style);
4303
		$style = '';
4304
		// underline
4305
		if (strpos($tempstyle, 'U') !== false) {
4306
			$this->underline = true;
4307
		} else {
4308
			$this->underline = false;
4309
		}
4310
		// line-through (deleted)
4311
		if (strpos($tempstyle, 'D') !== false) {
4312
			$this->linethrough = true;
4313
		} else {
4314
			$this->linethrough = false;
4315
		}
4316
		// overline
4317
		if (strpos($tempstyle, 'O') !== false) {
4318
			$this->overline = true;
4319
		} else {
4320
			$this->overline = false;
4321
		}
4322
		// bold
4323
		if (strpos($tempstyle, 'B') !== false) {
4324
			$style .= 'B';
4325
		}
4326
		// oblique
4327
		if (strpos($tempstyle, 'I') !== false) {
4328
			$style .= 'I';
4329
		}
4330
		$bistyle = $style;
4331
		$fontkey = $family.$style;
4332
		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4333
		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4334
		// check if the font has been already added
4335
		$fb = $this->getFontBuffer($fontkey);
4336
		if ($fb !== false) {
4337
			if ($this->inxobj) {
4338
				// we are inside an XObject template
4339
				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4340
			}
4341
			return $fontdata;
4342
		}
4343
		// get specified font directory (if any)
4344
		$fontdir = false;
4345
		if (!TCPDF_STATIC::empty_string($fontfile)) {
4346
			$fontdir = dirname($fontfile);
4347
			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4348
				$fontdir = '';
4349
			} else {
4350
				$fontdir .= '/';
4351
			}
4352
		}
4353
		// true when the font style variation is missing
4354
		$missing_style = false;
4355
		// search and include font file
4356
		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4357
			// build a standard filenames for specified font
4358
			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4359
			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4360
			if (TCPDF_STATIC::empty_string($fontfile)) {
4361
				$missing_style = true;
4362
				// try to remove the style part
4363
				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4364
				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4365
			}
4366
		}
4367
		// include font file
4368
		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4369
			$type=null;
4370
			$name=null;
4371
			$desc=null;
4372
			$up=-null;
4373
			$ut=null;
4374
			$cw=null;
4375
			$cbbox=null;
4376
			$dw=null;
4377
			$enc=null;
4378
			$cidinfo=null;
4379
			$file=null;
4380
			$ctg=null;
4381
			$diff=null;
4382
			$originalsize=null;
4383
			$size1=null;
4384
			$size2=null;
4385
			include($fontfile);
4386
		} else {
4387
			$this->Error('Could not include font definition file: '.$family.'');
4388
		}
4389
		// check font parameters
4390
		if ((!isset($type)) OR (!isset($cw))) {
4391
			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4392
		}
4393
		// SET default parameters
4394
		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4395
			$file = '';
4396
		}
4397
		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4398
			$enc = '';
4399
		}
4400
		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4401
			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4402
			$cidinfo['uni2cid'] = array();
4403
		}
4404
		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4405
			$ctg = '';
4406
		}
4407
		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4408
			$desc = array();
4409
		}
4410
		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4411
			$up = -100;
4412
		}
4413
		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4414
			$ut = 50;
4415
		}
4416
		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4417
			$cw = array();
4418
		}
4419
		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4420
			// set default width
4421
			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4422
				$dw = $desc['MissingWidth'];
4423
			} elseif (isset($cw[32])) {
4424
				$dw = $cw[32];
4425
			} else {
4426
				$dw = 600;
4427
			}
4428
		}
4429
		++$this->numfonts;
4430
		if ($type == 'core') {
4431
			$name = $this->CoreFonts[$fontkey];
4432
			$subset = false;
4433
		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4434
			$subset = false;
4435
		} elseif ($type == 'TrueTypeUnicode') {
4436
			$enc = 'Identity-H';
4437
		} elseif ($type == 'cidfont0') {
4438
			if ($this->pdfa_mode) {
4439
				$this->Error('All fonts must be embedded in PDF/A mode!');
4440
			}
4441
		} else {
4442
			$this->Error('Unknow font type: '.$type.'');
4443
		}
4444
		// set name if unset
4445
		if (empty($name)) {
4446
			$name = $fontkey;
4447
		}
4448
		// create artificial font style variations if missing (only works with non-embedded fonts)
4449
		if (($type != 'core') AND $missing_style) {
4450
			// style variations
4451
			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4452
			$name .= $styles[$bistyle];
4453
			// artificial bold
4454
			if (strpos($bistyle, 'B') !== false) {
4455
				if (isset($desc['StemV'])) {
4456
					// from normal to bold
4457
					$desc['StemV'] = round($desc['StemV'] * 1.75);
4458
				} else {
4459
					// bold
4460
					$desc['StemV'] = 123;
4461
				}
4462
			}
4463
			// artificial italic
4464
			if (strpos($bistyle, 'I') !== false) {
4465
				if (isset($desc['ItalicAngle'])) {
4466
					$desc['ItalicAngle'] -= 11;
4467
				} else {
4468
					$desc['ItalicAngle'] = -11;
4469
				}
4470
				if (isset($desc['Flags'])) {
4471
					$desc['Flags'] |= 64; //bit 7
4472
				} else {
4473
					$desc['Flags'] = 64;
4474
				}
4475
			}
4476
		}
4477
		// check if the array of characters bounding boxes is defined
4478
		if (!isset($cbbox)) {
4479
			$cbbox = array();
4480
		}
4481
		// initialize subsetchars
4482
		$subsetchars = array_fill(0, 255, true);
4483
		$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));
4484
		if ($this->inxobj) {
4485
			// we are inside an XObject template
4486
			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4487
		}
4488
		if (!empty($diff)) {
4489
			//Search existing encodings
4490
			$d = 0;
4491
			$nb = count($this->diffs);
4492
			for ($i=1; $i <= $nb; ++$i) {
4493
				if ($this->diffs[$i] == $diff) {
4494
					$d = $i;
4495
					break;
4496
				}
4497
			}
4498
			if ($d == 0) {
4499
				$d = $nb + 1;
4500
				$this->diffs[$d] = $diff;
4501
			}
4502
			$this->setFontSubBuffer($fontkey, 'diff', $d);
4503
		}
4504
		if (!TCPDF_STATIC::empty_string($file)) {
4505
			if (!isset($this->FontFiles[$file])) {
4506
				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4507
					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4508
				} elseif ($type != 'core') {
4509
					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4510
				}
4511
			} else {
4512
				// update fontkeys that are sharing this font file
4513
				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4514
				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4515
					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4516
				}
4517
			}
4518
		}
4519
		return $fontdata;
4520
	}
4521
4522
	/**
4523
	 * Sets the font used to print character strings.
4524
	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4525
	 * The method can be called before the first page is created and the font is retained from page to page.
4526
	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4527
	 * 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 />
4528
	 * @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.
4529
	 * @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.
4530
	 * @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
4531
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4532
	 * @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.
4533
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4534
	 * @author Nicola Asuni
4535
	 * @public
4536
	 * @since 1.0
4537
	 * @see AddFont(), SetFontSize()
4538
	 */
4539
	public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4540
		//Select a font; size given in points
4541
		if ($size === null) {
4542
			$size = $this->FontSizePt;
4543
		}
4544
		if ($size < 0) {
4545
			$size = 0;
4546
		}
4547
		// try to add font (if not already added)
4548
		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4549
		$this->FontFamily = $fontdata['family'];
4550
		$this->FontStyle = $fontdata['style'];
4551
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4552
			// save subset chars of the previous font
4553
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4554
		}
4555
		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4556
		$this->setFontSize($size, $out);
4557
	}
4558
4559
	/**
4560
	 * Defines the size of the current font.
4561
	 * @param float $size The font size in points.
4562
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4563
	 * @public
4564
	 * @since 1.0
4565
	 * @see SetFont()
4566
	 */
4567
	public function setFontSize($size, $out=true) {
4568
		$size = (float)$size;
4569
		// font size in points
4570
		$this->FontSizePt = $size;
4571
		// font size in user units
4572
		$this->FontSize = $size / $this->k;
4573
		// calculate some font metrics
4574
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4575
			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4576
			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4577
		} else {
4578
			$font_height = $size * 1.219;
4579
		}
4580
		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4581
			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4582
		}
4583
		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4584
			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4585
		}
4586
		if (!isset($font_ascent) AND !isset($font_descent)) {
4587
			// core font
4588
			$font_ascent = 0.76 * $font_height;
4589
			$font_descent = $font_height - $font_ascent;
4590
		} elseif (!isset($font_descent)) {
4591
			$font_descent = $font_height - $font_ascent;
4592
		} elseif (!isset($font_ascent)) {
4593
			$font_ascent = $font_height - $font_descent;
4594
		}
4595
		$this->FontAscent = ($font_ascent / $this->k);
4596
		$this->FontDescent = ($font_descent / $this->k);
4597
		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4598
			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4599
		}
4600
	}
4601
4602
	/**
4603
	 * Returns the bounding box of the current font in user units.
4604
	 * @return array
4605
	 * @public
4606
	 * @since 5.9.152 (2012-03-23)
4607
	 */
4608
	public function getFontBBox() {
4609
		$fbbox = array();
4610
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4611
			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4612
			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4613
		} else {
4614
			// Find max width
4615
			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4616
				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4617
			} else {
4618
				$maxw = 0;
4619
				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4620
					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4621
				}
4622
				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4623
					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4624
				}
4625
				if (isset($this->CurrentFont['dw'])) {
4626
					$maxw = max($maxw, $this->CurrentFont['dw']);
4627
				}
4628
				foreach ($this->CurrentFont['cw'] as $char => $w) {
4629
					$maxw = max($maxw, $w);
4630
				}
4631
				if ($maxw == 0) {
4632
					$maxw = 600;
4633
				}
4634
				$maxw = $this->getAbsFontMeasure($maxw);
4635
			}
4636
			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4637
		}
4638
		return $fbbox;
4639
	}
4640
4641
	/**
4642
	 * Convert a relative font measure into absolute value.
4643
	 * @param int $s Font measure.
4644
	 * @return float Absolute measure.
4645
	 * @since 5.9.186 (2012-09-13)
4646
	 */
4647
	public function getAbsFontMeasure($s) {
4648
		return ($s * $this->FontSize / 1000);
4649
	}
4650
4651
	/**
4652
	 * Returns the glyph bounding box of the specified character in the current font in user units.
4653
	 * @param int $char Input character code.
4654
	 * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4655
	 * @since 5.9.186 (2012-09-13)
4656
	 */
4657
	public function getCharBBox($char) {
4658
		$c = intval($char);
4659
		if (isset($this->CurrentFont['cw'][$c])) {
4660
			// glyph is defined ... use zero width & height for glyphs without outlines
4661
			$result = array(0,0,0,0);
4662
			if (isset($this->CurrentFont['cbbox'][$c])) {
4663
				$result = $this->CurrentFont['cbbox'][$c];
4664
			}
4665
			return array_map(array($this,'getAbsFontMeasure'), $result);
4666
		}
4667
		return false;
4668
	}
4669
4670
	/**
4671
	 * Return the font descent value
4672
	 * @param string $font font name
4673
	 * @param string $style font style
4674
	 * @param float $size The size (in points)
4675
	 * @return int font descent
4676
	 * @public
4677
	 * @author Nicola Asuni
4678
	 * @since 4.9.003 (2010-03-30)
4679
	 */
4680
	public function getFontDescent($font, $style='', $size=0) {
4681
		$fontdata = $this->AddFont($font, $style);
4682
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4683
		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4684
			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4685
		} else {
4686
			$descent = (1.219 * 0.24 * $size);
4687
		}
4688
		return ($descent / $this->k);
4689
	}
4690
4691
	/**
4692
	 * Return the font ascent value.
4693
	 * @param string $font font name
4694
	 * @param string $style font style
4695
	 * @param float $size The size (in points)
4696
	 * @return int font ascent
4697
	 * @public
4698
	 * @author Nicola Asuni
4699
	 * @since 4.9.003 (2010-03-30)
4700
	 */
4701
	public function getFontAscent($font, $style='', $size=0) {
4702
		$fontdata = $this->AddFont($font, $style);
4703
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4704
		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4705
			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4706
		} else {
4707
			$ascent = 1.219 * 0.76 * $size;
4708
		}
4709
		return ($ascent / $this->k);
4710
	}
4711
4712
	/**
4713
	 * Return true in the character is present in the specified font.
4714
	 * @param mixed $char Character to check (integer value or string)
4715
	 * @param string $font Font name (family name).
4716
	 * @param string $style Font style.
4717
	 * @return bool true if the char is defined, false otherwise.
4718
	 * @public
4719
	 * @since 5.9.153 (2012-03-28)
4720
	 */
4721
	public function isCharDefined($char, $font='', $style='') {
4722
		if (is_string($char)) {
4723
			// get character code
4724
			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4725
			$char = $char[0];
4726
		}
4727
		if (TCPDF_STATIC::empty_string($font)) {
4728
			if (TCPDF_STATIC::empty_string($style)) {
4729
				return (isset($this->CurrentFont['cw'][intval($char)]));
4730
			}
4731
			$font = $this->FontFamily;
4732
		}
4733
		$fontdata = $this->AddFont($font, $style);
4734
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4735
		return (isset($fontinfo['cw'][intval($char)]));
4736
	}
4737
4738
	/**
4739
	 * Replace missing font characters on selected font with specified substitutions.
4740
	 * @param string $text Text to process.
4741
	 * @param string $font Font name (family name).
4742
	 * @param string $style Font style.
4743
	 * @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.
4744
	 * @return string Processed text.
4745
	 * @public
4746
	 * @since 5.9.153 (2012-03-28)
4747
	 */
4748
	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4749
		if (empty($subs)) {
4750
			return $text;
4751
		}
4752
		if (TCPDF_STATIC::empty_string($font)) {
4753
			$font = $this->FontFamily;
4754
		}
4755
		$fontdata = $this->AddFont($font, $style);
4756
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4757
		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4758
		foreach ($uniarr as $k => $chr) {
4759
			if (!isset($fontinfo['cw'][$chr])) {
4760
				// this character is missing on the selected font
4761
				if (isset($subs[$chr])) {
4762
					// we have available substitutions
4763
					if (is_array($subs[$chr])) {
4764
						foreach($subs[$chr] as $s) {
4765
							if (isset($fontinfo['cw'][$s])) {
4766
								$uniarr[$k] = $s;
4767
								break;
4768
							}
4769
						}
4770
					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4771
						$uniarr[$k] = $subs[$chr];
4772
					}
4773
				}
4774
			}
4775
		}
4776
		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4777
	}
4778
4779
	/**
4780
	 * Defines the default monospaced font.
4781
	 * @param string $font Font name.
4782
	 * @public
4783
	 * @since 4.5.025
4784
	 */
4785
	public function setDefaultMonospacedFont($font) {
4786
		$this->default_monospaced_font = $font;
4787
	}
4788
4789
	/**
4790
	 * 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 />
4791
	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4792
	 * @public
4793
	 * @since 1.5
4794
	 * @see Cell(), Write(), Image(), Link(), SetLink()
4795
	 */
4796
	public function AddLink() {
4797
		// create a new internal link
4798
		$n = count($this->links) + 1;
4799
		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4800
		return $n;
4801
	}
4802
4803
	/**
4804
	 * Defines the page and position a link points to.
4805
	 * @param int $link The link identifier returned by AddLink()
4806
	 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4807
	 * @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.
4808
	 * @public
4809
	 * @since 1.5
4810
	 * @see AddLink()
4811
	 */
4812
	public function setLink($link, $y=0, $page=-1) {
4813
		$fixed = false;
4814
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4815
			$page = intval(substr($page, 1));
4816
			// this page number will not be changed when moving/add/deleting pages
4817
			$fixed = true;
4818
		}
4819
		if ($page < 0) {
4820
			$page = $this->page;
4821
		}
4822
		if ($y == -1) {
4823
			$y = $this->y;
4824
		}
4825
		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4826
	}
4827
4828
	/**
4829
	 * Puts a link on a rectangular area of the page.
4830
	 * 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.
4831
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4832
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4833
	 * @param float $w Width of the rectangle
4834
	 * @param float $h Height of the rectangle
4835
	 * @param mixed $link URL or identifier returned by AddLink()
4836
	 * @param int $spaces number of spaces on the text to link
4837
	 * @public
4838
	 * @since 1.5
4839
	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4840
	 */
4841
	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4842
		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4843
	}
4844
4845
	/**
4846
	 * Puts a markup annotation on a rectangular area of the page.
4847
	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4848
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4849
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4850
	 * @param float $w Width of the rectangle
4851
	 * @param float $h Height of the rectangle
4852
	 * @param string $text annotation text or alternate content
4853
	 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4854
	 * @param int $spaces number of spaces on the text to link
4855
	 * @public
4856
	 * @since 4.0.018 (2008-08-06)
4857
	 */
4858
	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4859
		if ($this->inxobj) {
4860
			// store parameters for later use on template
4861
			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4862
			return;
4863
		}
4864
		if ($x === '') {
4865
			$x = $this->x;
4866
		}
4867
		if ($y === '') {
4868
			$y = $this->y;
4869
		}
4870
		// check page for no-write regions and adapt page margins if necessary
4871
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4872
		// recalculate coordinates to account for graphic transformations
4873
		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4874
			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4875
				$maxid = count($this->transfmatrix[$i]) - 1;
4876
				for ($j=$maxid; $j >= 0; --$j) {
4877
					$ctm = $this->transfmatrix[$i][$j];
4878
					if (isset($ctm['a'])) {
4879
						$x = $x * $this->k;
4880
						$y = ($this->h - $y) * $this->k;
4881
						$w = $w * $this->k;
4882
						$h = $h * $this->k;
4883
						// top left
4884
						$xt = $x;
4885
						$yt = $y;
4886
						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4887
						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4888
						// top right
4889
						$xt = $x + $w;
4890
						$yt = $y;
4891
						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4892
						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4893
						// bottom left
4894
						$xt = $x;
4895
						$yt = $y - $h;
4896
						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4897
						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4898
						// bottom right
4899
						$xt = $x + $w;
4900
						$yt = $y - $h;
4901
						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4902
						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4903
						// new coordinates (rectangle area)
4904
						$x = min($x1, $x2, $x3, $x4);
4905
						$y = max($y1, $y2, $y3, $y4);
4906
						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4907
						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4908
						$x = $x / $this->k;
4909
						$y = $this->h - ($y / $this->k);
4910
					}
4911
				}
4912
			}
4913
		}
4914
		if ($this->page <= 0) {
4915
			$page = 1;
4916
		} else {
4917
			$page = $this->page;
4918
		}
4919
		if (!isset($this->PageAnnots[$page])) {
4920
			$this->PageAnnots[$page] = array();
4921
		}
4922
		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4923
		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4924
			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4925
				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4926
				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4927
				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4928
			}
4929
		}
4930
		// Add widgets annotation's icons
4931
		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4932
			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4933
		}
4934
		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4935
			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4936
		}
4937
		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4938
			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4939
		}
4940
	}
4941
4942
	/**
4943
	 * Embed the attached files.
4944
	 * @since 6.9.000 (2025-02-11)
4945
	 * @public
4946
	 */
4947
	public function EmbedFile($opt) {
4948
		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4949
			if ((($opt['Subtype'] == 'FileAttachment')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4950
				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4951
				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4952
				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4953
			}
4954
		}
4955
	}
4956
4957
	/**
4958
	 * Embed the attached files.
4959
	 * @since 6.9.000 (2025-02-11)
4960
	 * @public
4961
	 */
4962
	public function EmbedFileFromString($filename, $content) {
4963
		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4964
			$this->embeddedfiles[$filename] = array('f' => ++$this->n, 'n' => ++$this->n, 'content' => $content );
4965
		}
4966
	}
4967
4968
	/**
4969
	 * Embedd the attached files.
4970
	 * @since 4.4.000 (2008-12-07)
4971
	 * @protected
4972
	 * @see Annotation()
4973
	 */
4974
	protected function _putEmbeddedFiles() {
4975
		if ($this->pdfa_mode && $this->pdfa_version != 3)  {
4976
			// embedded files are not allowed in PDF/A mode version 1 and 2
4977
			return;
4978
		}
4979
		reset($this->embeddedfiles);
4980
		foreach ($this->embeddedfiles as $filename => $filedata) {
4981
			$data = false;
4982
			if (isset($filedata['file']) && !empty($filedata['file'])) {
4983
				$data = $this->getCachedFileContents($filedata['file']);
4984
			} elseif ($filedata['content'] && !empty($filedata['content'])) {
4985
				$data = $filedata['content'];
4986
			}
4987
			if ($data !== FALSE) {
4988
				$rawsize = strlen($data);
4989
				if ($rawsize > 0) {
4990
					// update name tree
4991
					$this->efnames[$filename] = $filedata['f'].' 0 R';
4992
					// embedded file specification object
4993
					$out = $this->_getobj($filedata['f'])."\n";
4994
					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4995
					$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4996
					$out .= ' /AFRelationship /Source';
4997
					$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4998
					$out .= "\n".'endobj';
4999
					$this->_out($out);
5000
					// embedded file object
5001
					$filter = '';
5002
					if ($this->compress) {
5003
						$data = gzcompress($data);
5004
						$filter = ' /Filter /FlateDecode';
5005
					}
5006
5007
					if ($this->pdfa_version == 3) {
5008
						$filter = ' /Subtype /text#2Fxml';
5009
					}
5010
5011
					$stream = $this->_getrawstream($data, $filedata['n']);
5012
					$out = $this->_getobj($filedata['n'])."\n";
5013
					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
5014
					$out .= ' stream'."\n".$stream."\n".'endstream';
5015
					$out .= "\n".'endobj';
5016
					$this->_out($out);
5017
				}
5018
			}
5019
		}
5020
	}
5021
5022
	/**
5023
	 * Prints a text cell at the specified position.
5024
	 * This method allows to place a string precisely on the page.
5025
	 * @param float $x Abscissa of the cell origin
5026
	 * @param float $y Ordinate of the cell origin
5027
	 * @param string $txt String to print
5028
	 * @param int $fstroke outline size in user units (0 = disable)
5029
	 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
5030
	 * @param boolean $ffill if true fills the text
5031
	 * @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)))
5032
	 * @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.
5033
	 * @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>
5034
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5035
	 * @param mixed $link URL or identifier returned by AddLink().
5036
	 * @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.
5037
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5038
	 * @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>
5039
	 * @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>
5040
	 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
5041
	 * @public
5042
	 * @since 1.0
5043
	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5044
	 */
5045
	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) {
5046
		$textrendermode = $this->textrendermode;
5047
		$textstrokewidth = $this->textstrokewidth;
5048
		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
5049
		$this->setXY($x, $y, $rtloff);
5050
		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5051
		// restore previous rendering mode
5052
		$this->textrendermode = $textrendermode;
5053
		$this->textstrokewidth = $textstrokewidth;
5054
	}
5055
5056
	/**
5057
	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5058
	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5059
	 * This method is called automatically and should not be called directly by the application.
5060
	 * @return bool
5061
	 * @public
5062
	 * @since 1.4
5063
	 * @see SetAutoPageBreak()
5064
	 */
5065
	public function AcceptPageBreak() {
5066
		if ($this->num_columns > 1) {
5067
			// multi column mode
5068
			if ($this->current_column < ($this->num_columns - 1)) {
5069
				// go to next column
5070
				$this->selectColumn($this->current_column + 1);
5071
			} elseif ($this->AutoPageBreak) {
5072
				// add a new page
5073
				$this->AddPage();
5074
				// set first column
5075
				$this->selectColumn(0);
5076
			}
5077
			// avoid page breaking from checkPageBreak()
5078
			return false;
5079
		}
5080
		return $this->AutoPageBreak;
5081
	}
5082
5083
	/**
5084
	 * Add page if needed.
5085
	 * @param float $h Cell height. Default value: 0.
5086
	 * @param float|null $y starting y position, leave empty for current position.
5087
	 * @param bool  $addpage if true add a page, otherwise only return the true/false state
5088
	 * @return bool true in case of page break, false otherwise.
5089
	 * @since 3.2.000 (2008-07-01)
5090
	 * @protected
5091
	 */
5092
	protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5093
		if (TCPDF_STATIC::empty_string($y)) {
5094
			$y = $this->y;
5095
		}
5096
		$current_page = $this->page;
5097
		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5098
			if ($addpage) {
5099
				//Automatic page break
5100
				$x = $this->x;
5101
				$this->AddPage($this->CurOrientation);
5102
				$this->y = $this->tMargin;
5103
				$oldpage = $this->page - 1;
5104
				if ($this->rtl) {
5105
					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5106
						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5107
					} else {
5108
						$this->x = $x;
5109
					}
5110
				} else {
5111
					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5112
						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5113
					} else {
5114
						$this->x = $x;
5115
					}
5116
				}
5117
			}
5118
			return true;
5119
		}
5120
		if ($current_page != $this->page) {
5121
			// account for columns mode
5122
			return true;
5123
		}
5124
		return false;
5125
	}
5126
5127
	/**
5128
	 * 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 />
5129
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5130
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5131
	 * @param float $h Cell height. Default value: 0.
5132
	 * @param string $txt String to print. Default value: empty string.
5133
	 * @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)))
5134
	 * @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.
5135
	 * @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>
5136
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5137
	 * @param mixed $link URL or identifier returned by AddLink().
5138
	 * @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.
5139
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5140
	 * @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>
5141
	 * @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>
5142
	 * @public
5143
	 * @since 1.0
5144
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5145
	 */
5146
	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') {
5147
		$prev_cell_margin = $this->cell_margin;
5148
		$prev_cell_padding = $this->cell_padding;
5149
		$this->adjustCellPadding($border);
5150
		if (!$ignore_min_height) {
5151
			$min_cell_height = $this->getCellHeight($this->FontSize);
5152
			if ($h < $min_cell_height) {
5153
				$h = $min_cell_height;
5154
			}
5155
		}
5156
		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5157
		// apply text shadow if enabled
5158
		if ($this->txtshadow['enabled']) {
5159
			// save data
5160
			$x = $this->x;
5161
			$y = $this->y;
5162
			$bc = $this->bgcolor;
5163
			$fc = $this->fgcolor;
5164
			$sc = $this->strokecolor;
5165
			$alpha = $this->alpha;
5166
			// print shadow
5167
			$this->x += $this->txtshadow['depth_w'];
5168
			$this->y += $this->txtshadow['depth_h'];
5169
			$this->setFillColorArray($this->txtshadow['color']);
5170
			$this->setTextColorArray($this->txtshadow['color']);
5171
			$this->setDrawColorArray($this->txtshadow['color']);
5172
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5173
				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5174
			}
5175
			if ($this->state == 2) {
5176
				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5177
			}
5178
			//restore data
5179
			$this->x = $x;
5180
			$this->y = $y;
5181
			$this->setFillColorArray($bc);
5182
			$this->setTextColorArray($fc);
5183
			$this->setDrawColorArray($sc);
5184
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5185
				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5186
			}
5187
		}
5188
		if ($this->state == 2) {
5189
			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5190
		}
5191
		$this->cell_padding = $prev_cell_padding;
5192
		$this->cell_margin = $prev_cell_margin;
5193
	}
5194
5195
	/**
5196
	 * 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 />
5197
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5198
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5199
	 * @param float $h Cell height. Default value: 0.
5200
	 * @param string $txt String to print. Default value: empty string.
5201
	 * @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)))
5202
	 * @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.
5203
	 * @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>
5204
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5205
	 * @param mixed $link URL or identifier returned by AddLink().
5206
	 * @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.
5207
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5208
	 * @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>
5209
	 * @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>
5210
	 * @return string containing cell code
5211
	 * @protected
5212
	 * @since 1.0
5213
	 * @see Cell()
5214
	 */
5215
	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') {
5216
		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5217
		$txt = is_null($txt) ? '' : $txt;
5218
		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5219
		$prev_cell_margin = $this->cell_margin;
5220
		$prev_cell_padding = $this->cell_padding;
5221
		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5222
		$rs = ''; //string to be returned
5223
		$this->adjustCellPadding($border);
5224
		if (!$ignore_min_height) {
5225
			$min_cell_height = $this->getCellHeight($this->FontSize);
5226
			if ($h < $min_cell_height) {
5227
				$h = $min_cell_height;
5228
			}
5229
		}
5230
		$k = $this->k;
5231
		// check page for no-write regions and adapt page margins if necessary
5232
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5233
		if ($this->rtl) {
5234
			$x = $this->x - $this->cell_margin['R'];
5235
		} else {
5236
			$x = $this->x + $this->cell_margin['L'];
5237
		}
5238
		$y = $this->y + $this->cell_margin['T'];
5239
		$prev_font_stretching = $this->font_stretching;
5240
		$prev_font_spacing = $this->font_spacing;
5241
		// cell vertical alignment
5242
		switch ($calign) {
5243
			case 'A': {
5244
				// font top
5245
				switch ($valign) {
5246
					case 'T': {
5247
						// top
5248
						$y -= $this->cell_padding['T'];
5249
						break;
5250
					}
5251
					case 'B': {
5252
						// bottom
5253
						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
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 'L': {
5267
				// font baseline
5268
				switch ($valign) {
5269
					case 'T': {
5270
						// top
5271
						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5272
						break;
5273
					}
5274
					case 'B': {
5275
						// bottom
5276
						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5277
						break;
5278
					}
5279
					default:
5280
					case 'C':
5281
					case 'M': {
5282
						// center
5283
						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5284
						break;
5285
					}
5286
				}
5287
				break;
5288
			}
5289
			case 'D': {
5290
				// font bottom
5291
				switch ($valign) {
5292
					case 'T': {
5293
						// top
5294
						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5295
						break;
5296
					}
5297
					case 'B': {
5298
						// bottom
5299
						$y -= ($h - $this->cell_padding['B']);
5300
						break;
5301
					}
5302
					default:
5303
					case 'C':
5304
					case 'M': {
5305
						// center
5306
						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5307
						break;
5308
					}
5309
				}
5310
				break;
5311
			}
5312
			case 'B': {
5313
				// cell bottom
5314
				$y -= $h;
5315
				break;
5316
			}
5317
			case 'C':
5318
			case 'M': {
5319
				// cell center
5320
				$y -= ($h / 2);
5321
				break;
5322
			}
5323
			default:
5324
			case 'T': {
5325
				// cell top
5326
				break;
5327
			}
5328
		}
5329
		// text vertical alignment
5330
		switch ($valign) {
5331
			case 'T': {
5332
				// top
5333
				$yt = $y + $this->cell_padding['T'];
5334
				break;
5335
			}
5336
			case 'B': {
5337
				// bottom
5338
				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5339
				break;
5340
			}
5341
			default:
5342
			case 'C':
5343
			case 'M': {
5344
				// center
5345
				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5346
				break;
5347
			}
5348
		}
5349
		$basefonty = $yt + $this->FontAscent;
5350
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5351
			if ($this->rtl) {
5352
				$w = $x - $this->lMargin;
5353
			} else {
5354
				$w = $this->w - $this->rMargin - $x;
5355
			}
5356
		}
5357
		$s = '';
5358
		// fill and borders
5359
		if (is_string($border) AND (strlen($border) == 4)) {
5360
			// full border
5361
			$border = 1;
5362
		}
5363
		if ($fill OR ($border == 1)) {
5364
			if ($fill) {
5365
				$op = ($border == 1) ? 'B' : 'f';
5366
			} else {
5367
				$op = 'S';
5368
			}
5369
			if ($this->rtl) {
5370
				$xk = (($x - $w) * $k);
5371
			} else {
5372
				$xk = ($x * $k);
5373
			}
5374
			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5375
		}
5376
		// draw borders
5377
		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5378
		if ($txt != '') {
5379
			$txt2 = $txt;
5380
			if ($this->isunicode) {
5381
				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5382
					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5383
				} else {
5384
					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5385
					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5386
					// replace thai chars (if any)
5387
					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5388
						// number of chars
5389
						$numchars = count($unicode);
5390
						// po pla, for far, for fan
5391
						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5392
						// do chada, to patak
5393
						$lowtail = array(0x0e0e, 0x0e0f);
5394
						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5395
						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5396
						// mai ek, mai tho, mai tri, mai chattawa, karan
5397
						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5398
						// sara u, sara uu, pinthu
5399
						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5400
						$output = array();
5401
						for ($i = 0; $i < $numchars; $i++) {
5402
							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5403
								$ch0 = $unicode[$i];
5404
								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5405
								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5406
								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5407
								if (in_array($ch0, $tonemark)) {
5408
									if ($chn == 0x0e33) {
5409
										// sara um
5410
										if (in_array($ch1, $longtail)) {
5411
											// tonemark at upper left
5412
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5413
										} else {
5414
											// tonemark at upper right (normal position)
5415
											$output[] = $ch0;
5416
										}
5417
									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5418
										// tonemark at lower left
5419
										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5420
									} elseif (in_array($ch1, $upvowel)) {
5421
										if (in_array($ch2, $longtail)) {
5422
											// tonemark at upper left
5423
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5424
										} else {
5425
											// tonemark at upper right (normal position)
5426
											$output[] = $ch0;
5427
										}
5428
									} else {
5429
										// tonemark at lower right
5430
										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5431
									}
5432
								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5433
									// add lower left nikhahit and sara aa
5434
									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5435
										$output[] = 0xf711;
5436
										$this->CurrentFont['subsetchars'][0xf711] = true;
5437
										$output[] = 0x0e32;
5438
										$this->CurrentFont['subsetchars'][0x0e32] = true;
5439
									} else {
5440
										$output[] = $ch0;
5441
									}
5442
								} elseif (in_array($ch1, $longtail)) {
5443
									if ($ch0 == 0x0e31) {
5444
										// lower left mai hun arkad
5445
										$output[] = $this->replaceChar($ch0, 0xf710);
5446
									} elseif (in_array($ch0, $upvowel)) {
5447
										// lower left
5448
										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5449
									} elseif ($ch0 == 0x0e47) {
5450
										// lower left mai tai koo
5451
										$output[] = $this->replaceChar($ch0, 0xf712);
5452
									} else {
5453
										// normal character
5454
										$output[] = $ch0;
5455
									}
5456
								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5457
									// lower vowel
5458
									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5459
								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5460
									// yo ying without lower part
5461
									$output[] = $this->replaceChar($ch0, 0xf70f);
5462
								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5463
									// tho santan without lower part
5464
									$output[] = $this->replaceChar($ch0, 0xf700);
5465
								} else {
5466
									$output[] = $ch0;
5467
								}
5468
							} else {
5469
								// non-thai character
5470
								$output[] = $unicode[$i];
5471
							}
5472
						}
5473
						$unicode = $output;
5474
						// update font subsetchars
5475
						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5476
					} // end of K_THAI_TOPCHARS
5477
					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5478
				}
5479
			}
5480
			$txt2 = TCPDF_STATIC::_escape($txt2);
5481
			// get current text width (considering general font stretching and spacing)
5482
			$txwidth = $this->GetStringWidth($txt);
5483
			$width = $txwidth;
5484
			// check for stretch mode
5485
			if ($stretch > 0) {
5486
				// calculate ratio between cell width and text width
5487
				if ($width <= 0) {
5488
					$ratio = 1;
5489
				} else {
5490
					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5491
				}
5492
				// check if stretching is required
5493
				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5494
					// the text will be stretched to fit cell width
5495
					if ($stretch > 2) {
5496
						// set new character spacing
5497
						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5498
					} else {
5499
						// set new horizontal stretching
5500
						$this->font_stretching *= $ratio;
5501
					}
5502
					// recalculate text width (the text fills the entire cell)
5503
					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5504
					// reset alignment
5505
					$align = '';
5506
				}
5507
			}
5508
			if ($this->font_stretching != 100) {
5509
				// apply font stretching
5510
				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5511
			}
5512
			if ($this->font_spacing != 0) {
5513
				// increase/decrease font spacing
5514
				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5515
			}
5516
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5517
				$s .= 'q '.$this->TextColor.' ';
5518
			}
5519
			// rendering mode
5520
			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5521
			// count number of spaces
5522
			$ns = substr_count($txt, chr(32));
5523
			// Justification
5524
			$spacewidth = 0;
5525
			if (($align == 'J') AND ($ns > 0)) {
5526
				if ($this->isUnicodeFont()) {
5527
					// get string width without spaces
5528
					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5529
					// calculate average space width
5530
					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5531
					if ($this->font_stretching != 100) {
5532
						// word spacing is affected by stretching
5533
						$spacewidth /= ($this->font_stretching / 100);
5534
					}
5535
					// set word position to be used with TJ operator
5536
					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5537
					$unicode_justification = true;
5538
				} else {
5539
					// get string width
5540
					$width = $txwidth;
5541
					// new space width
5542
					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5543
					if ($this->font_stretching != 100) {
5544
						// word spacing (Tw) is affected by stretching
5545
						$spacewidth /= ($this->font_stretching / 100);
5546
					}
5547
					// set word spacing
5548
					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5549
				}
5550
				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5551
			}
5552
			// replace carriage return characters
5553
			$txt2 = str_replace("\r", ' ', $txt2);
5554
			switch ($align) {
5555
				case 'C': {
5556
					$dx = ($w - $width) / 2;
5557
					break;
5558
				}
5559
				case 'R': {
5560
					if ($this->rtl) {
5561
						$dx = $this->cell_padding['R'];
5562
					} else {
5563
						$dx = $w - $width - $this->cell_padding['R'];
5564
					}
5565
					break;
5566
				}
5567
				case 'L': {
5568
					if ($this->rtl) {
5569
						$dx = $w - $width - $this->cell_padding['L'];
5570
					} else {
5571
						$dx = $this->cell_padding['L'];
5572
					}
5573
					break;
5574
				}
5575
				case 'J':
5576
				default: {
5577
					if ($this->rtl) {
5578
						$dx = $this->cell_padding['R'];
5579
					} else {
5580
						$dx = $this->cell_padding['L'];
5581
					}
5582
					break;
5583
				}
5584
			}
5585
			if ($this->rtl) {
5586
				$xdx = $x - $dx - $width;
5587
			} else {
5588
				$xdx = $x + $dx;
5589
			}
5590
			$xdk = $xdx * $k;
5591
			// print text
5592
			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5593
			if (isset($uniblock)) { // @phpstan-ignore-line
5594
				// print overlapping characters as separate string
5595
				$xshift = 0; // horizontal shift
5596
				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5597
				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5598
				foreach ($uniblock as $uk => $uniarr) { // @phpstan-ignore-line
5599
					if (($uk % 2) == 0) {
5600
						// x space to skip
5601
						if ($spacewidth != 0) {
5602
							// justification shift
5603
							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5604
						}
5605
						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5606
					} else {
5607
						// character to print
5608
						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5609
						$topchr = TCPDF_STATIC::_escape($topchr);
5610
						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5611
					}
5612
				}
5613
			}
5614
			if ($this->underline) {
5615
				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5616
			}
5617
			if ($this->linethrough) {
5618
				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5619
			}
5620
			if ($this->overline) {
5621
				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5622
			}
5623
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5624
				$s .= ' Q';
5625
			}
5626
			if ($link) {
5627
				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5628
			}
5629
		}
5630
		// output cell
5631
		if ($s) {
5632
			// output cell
5633
			$rs .= $s;
5634
			if ($this->font_spacing != 0) {
5635
				// reset font spacing mode
5636
				$rs .= ' BT 0 Tc ET';
5637
			}
5638
			if ($this->font_stretching != 100) {
5639
				// reset font stretching mode
5640
				$rs .= ' BT 100 Tz ET';
5641
			}
5642
		}
5643
		// reset word spacing
5644
		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5645
			$rs .= ' BT 0 Tw ET';
5646
		}
5647
		// reset stretching and spacing
5648
		$this->font_stretching = $prev_font_stretching;
5649
		$this->font_spacing = $prev_font_spacing;
5650
		$this->lasth = $h;
5651
		if ($ln > 0) {
5652
			//Go to the beginning of the next line
5653
			$this->y = $y + $h + $this->cell_margin['B'];
5654
			if ($ln == 1) {
5655
				if ($this->rtl) {
5656
					$this->x = $this->w - $this->rMargin;
5657
				} else {
5658
					$this->x = $this->lMargin;
5659
				}
5660
			}
5661
		} else {
5662
			// go left or right by case
5663
			if ($this->rtl) {
5664
				$this->x = $x - $w - $this->cell_margin['L'];
5665
			} else {
5666
				$this->x = $x + $w + $this->cell_margin['R'];
5667
			}
5668
		}
5669
		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5670
		$rs = $gstyles.$rs;
5671
		$this->cell_padding = $prev_cell_padding;
5672
		$this->cell_margin = $prev_cell_margin;
5673
		return $rs;
5674
	}
5675
5676
	/**
5677
	 * Replace a char if is defined on the current font.
5678
	 * @param int $oldchar Integer code (unicode) of the character to replace.
5679
	 * @param int $newchar Integer code (unicode) of the new character.
5680
	 * @return int the replaced char or the old char in case the new char i not defined
5681
	 * @protected
5682
	 * @since 5.9.167 (2012-06-22)
5683
	 */
5684
	protected function replaceChar($oldchar, $newchar) {
5685
		if ($this->isCharDefined($newchar)) {
5686
			// add the new char on the subset list
5687
			$this->CurrentFont['subsetchars'][$newchar] = true;
5688
			// return the new character
5689
			return $newchar;
5690
		}
5691
		// return the old char
5692
		return $oldchar;
5693
	}
5694
5695
	/**
5696
	 * Returns the code to draw the cell border
5697
	 * @param float $x X coordinate.
5698
	 * @param float $y Y coordinate.
5699
	 * @param float $w Cell width.
5700
	 * @param float $h Cell height.
5701
	 * @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)))
5702
	 * @return string containing cell border code
5703
	 * @protected
5704
	 * @see SetLineStyle()
5705
	 * @since 5.7.000 (2010-08-02)
5706
	 */
5707
	protected function getCellBorder($x, $y, $w, $h, $brd) {
5708
		$s = ''; // string to be returned
5709
		if (empty($brd)) {
5710
			return $s;
5711
		}
5712
		if ($brd == 1) {
5713
			$brd = array('LRTB' => true);
5714
		}
5715
		// calculate coordinates for border
5716
		$k = $this->k;
5717
		if ($this->rtl) {
5718
			$xeL = ($x - $w) * $k;
5719
			$xeR = $x * $k;
5720
		} else {
5721
			$xeL = $x * $k;
5722
			$xeR = ($x + $w) * $k;
5723
		}
5724
		$yeL = (($this->h - ($y + $h)) * $k);
5725
		$yeT = (($this->h - $y) * $k);
5726
		$xeT = $xeL;
5727
		$xeB = $xeR;
5728
		$yeR = $yeT;
5729
		$yeB = $yeL;
5730
		if (is_string($brd)) {
5731
			// convert string to array
5732
			$slen = strlen($brd);
5733
			$newbrd = array();
5734
			for ($i = 0; $i < $slen; ++$i) {
5735
				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5736
			}
5737
			$brd = $newbrd;
5738
		}
5739
		if (isset($brd['mode'])) {
5740
			$mode = $brd['mode'];
5741
			unset($brd['mode']);
5742
		} else {
5743
			$mode = 'normal';
5744
		}
5745
		foreach ($brd as $border => $style) {
5746
			if (is_array($style) AND !empty($style)) {
5747
				// apply border style
5748
				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5749
				$s .= $this->setLineStyle($style, true)."\n";
5750
			}
5751
			switch ($mode) {
5752
				case 'ext': {
5753
					$off = (($this->LineWidth / 2) * $k);
5754
					$xL = $xeL - $off;
5755
					$xR = $xeR + $off;
5756
					$yT = $yeT + $off;
5757
					$yL = $yeL - $off;
5758
					$xT = $xL;
5759
					$xB = $xR;
5760
					$yR = $yT;
5761
					$yB = $yL;
5762
					$w += $this->LineWidth;
5763
					$h += $this->LineWidth;
5764
					break;
5765
				}
5766
				case 'int': {
5767
					$off = ($this->LineWidth / 2) * $k;
5768
					$xL = $xeL + $off;
5769
					$xR = $xeR - $off;
5770
					$yT = $yeT - $off;
5771
					$yL = $yeL + $off;
5772
					$xT = $xL;
5773
					$xB = $xR;
5774
					$yR = $yT;
5775
					$yB = $yL;
5776
					$w -= $this->LineWidth;
5777
					$h -= $this->LineWidth;
5778
					break;
5779
				}
5780
				case 'normal':
5781
				default: {
5782
					$xL = $xeL;
5783
					$xT = $xeT;
5784
					$xB = $xeB;
5785
					$xR = $xeR;
5786
					$yL = $yeL;
5787
					$yT = $yeT;
5788
					$yB = $yeB;
5789
					$yR = $yeR;
5790
					break;
5791
				}
5792
			}
5793
			// draw borders by case
5794
			if (strlen($border) == 4) {
5795
				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5796
			} elseif (strlen($border) == 3) {
5797
				if (strpos($border,'B') === false) { // LTR
5798
					$s .= sprintf('%F %F m ', $xL, $yL);
5799
					$s .= sprintf('%F %F l ', $xT, $yT);
5800
					$s .= sprintf('%F %F l ', $xR, $yR);
5801
					$s .= sprintf('%F %F l ', $xB, $yB);
5802
					$s .= 'S ';
5803
				} elseif (strpos($border,'L') === false) { // TRB
5804
					$s .= sprintf('%F %F m ', $xT, $yT);
5805
					$s .= sprintf('%F %F l ', $xR, $yR);
5806
					$s .= sprintf('%F %F l ', $xB, $yB);
5807
					$s .= sprintf('%F %F l ', $xL, $yL);
5808
					$s .= 'S ';
5809
				} elseif (strpos($border,'T') === false) { // RBL
5810
					$s .= sprintf('%F %F m ', $xR, $yR);
5811
					$s .= sprintf('%F %F l ', $xB, $yB);
5812
					$s .= sprintf('%F %F l ', $xL, $yL);
5813
					$s .= sprintf('%F %F l ', $xT, $yT);
5814
					$s .= 'S ';
5815
				} elseif (strpos($border,'R') === false) { // BLT
5816
					$s .= sprintf('%F %F m ', $xB, $yB);
5817
					$s .= sprintf('%F %F l ', $xL, $yL);
5818
					$s .= sprintf('%F %F l ', $xT, $yT);
5819
					$s .= sprintf('%F %F l ', $xR, $yR);
5820
					$s .= 'S ';
5821
				}
5822
			} elseif (strlen($border) == 2) {
5823
				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5824
					$s .= sprintf('%F %F m ', $xL, $yL);
5825
					$s .= sprintf('%F %F l ', $xT, $yT);
5826
					$s .= sprintf('%F %F l ', $xR, $yR);
5827
					$s .= 'S ';
5828
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5829
					$s .= sprintf('%F %F m ', $xT, $yT);
5830
					$s .= sprintf('%F %F l ', $xR, $yR);
5831
					$s .= sprintf('%F %F l ', $xB, $yB);
5832
					$s .= 'S ';
5833
				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5834
					$s .= sprintf('%F %F m ', $xR, $yR);
5835
					$s .= sprintf('%F %F l ', $xB, $yB);
5836
					$s .= sprintf('%F %F l ', $xL, $yL);
5837
					$s .= 'S ';
5838
				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5839
					$s .= sprintf('%F %F m ', $xB, $yB);
5840
					$s .= sprintf('%F %F l ', $xL, $yL);
5841
					$s .= sprintf('%F %F l ', $xT, $yT);
5842
					$s .= 'S ';
5843
				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5844
					$s .= sprintf('%F %F m ', $xL, $yL);
5845
					$s .= sprintf('%F %F l ', $xT, $yT);
5846
					$s .= 'S ';
5847
					$s .= sprintf('%F %F m ', $xR, $yR);
5848
					$s .= sprintf('%F %F l ', $xB, $yB);
5849
					$s .= 'S ';
5850
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5851
					$s .= sprintf('%F %F m ', $xT, $yT);
5852
					$s .= sprintf('%F %F l ', $xR, $yR);
5853
					$s .= 'S ';
5854
					$s .= sprintf('%F %F m ', $xB, $yB);
5855
					$s .= sprintf('%F %F l ', $xL, $yL);
5856
					$s .= 'S ';
5857
				}
5858
			} else { // strlen($border) == 1
5859
				if (strpos($border,'L') !== false) { // L
5860
					$s .= sprintf('%F %F m ', $xL, $yL);
5861
					$s .= sprintf('%F %F l ', $xT, $yT);
5862
					$s .= 'S ';
5863
				} elseif (strpos($border,'T') !== false) { // T
5864
					$s .= sprintf('%F %F m ', $xT, $yT);
5865
					$s .= sprintf('%F %F l ', $xR, $yR);
5866
					$s .= 'S ';
5867
				} elseif (strpos($border,'R') !== false) { // R
5868
					$s .= sprintf('%F %F m ', $xR, $yR);
5869
					$s .= sprintf('%F %F l ', $xB, $yB);
5870
					$s .= 'S ';
5871
				} elseif (strpos($border,'B') !== false) { // B
5872
					$s .= sprintf('%F %F m ', $xB, $yB);
5873
					$s .= sprintf('%F %F l ', $xL, $yL);
5874
					$s .= 'S ';
5875
				}
5876
			}
5877
			if (is_array($style) AND !empty($style)) {
5878
				// reset border style to previous value
5879
				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5880
			}
5881
		}
5882
		return $s;
5883
	}
5884
5885
	/**
5886
	 * This method allows printing text with line breaks.
5887
	 * 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 />
5888
	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5889
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5890
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
5891
	 * @param string $txt String to print
5892
	 * @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)))
5893
	 * @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>
5894
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5895
	 * @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>
5896
	 * @param float|null $x x position in user units
5897
	 * @param float|null $y y position in user units
5898
	 * @param boolean $reseth if true reset the last cell height (default true).
5899
	 * @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.
5900
	 * @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.
5901
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5902
	 * @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.
5903
	 * @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.
5904
	 * @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.
5905
	 * @return int Return the number of cells or 1 for html mode.
5906
	 * @public
5907
	 * @since 1.3
5908
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5909
	 */
5910
	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) {
5911
		$prev_cell_margin = $this->cell_margin;
5912
		$prev_cell_padding = $this->cell_padding;
5913
		// adjust internal padding
5914
		$this->adjustCellPadding($border);
5915
		$mc_padding = $this->cell_padding;
5916
		$mc_margin = $this->cell_margin;
5917
		$this->cell_padding['T'] = 0;
5918
		$this->cell_padding['B'] = 0;
5919
		$this->setCellMargins(0, 0, 0, 0);
5920
		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5921
			// reset row height
5922
			$this->resetLastH();
5923
		}
5924
		if (!TCPDF_STATIC::empty_string($y)) {
5925
			$this->setY($y); // set y in order to convert negative y values to positive ones
5926
		}
5927
		$y = $this->GetY();
5928
		$resth = 0;
5929
		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5930
			// spit cell in more pages/columns
5931
			$newh = ($this->PageBreakTrigger - $y);
5932
			$resth = ($h - $newh); // cell to be printed on the next page/column
5933
			$h = $newh;
5934
		}
5935
		// get current page number
5936
		$startpage = $this->page;
5937
		// get current column
5938
		$startcolumn = $this->current_column;
5939
		if (!TCPDF_STATIC::empty_string($x)) {
5940
			$this->setX($x);
5941
		} else {
5942
			$x = $this->GetX();
5943
		}
5944
		// check page for no-write regions and adapt page margins if necessary
5945
		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5946
		// apply margins
5947
		$oy = $y + $mc_margin['T'];
5948
		if ($this->rtl) {
5949
			$ox = ($this->w - $x - $mc_margin['R']);
5950
		} else {
5951
			$ox = ($x + $mc_margin['L']);
5952
		}
5953
		$this->x = $ox;
5954
		$this->y = $oy;
5955
		// set width
5956
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5957
			if ($this->rtl) {
5958
				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5959
			} else {
5960
				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5961
			}
5962
		}
5963
		// store original margin values
5964
		$lMargin = $this->lMargin;
5965
		$rMargin = $this->rMargin;
5966
		if ($this->rtl) {
5967
			$this->rMargin = ($this->w - $this->x);
5968
			$this->lMargin = ($this->x - $w);
5969
		} else {
5970
			$this->lMargin = ($this->x);
5971
			$this->rMargin = ($this->w - $this->x - $w);
5972
		}
5973
		$this->clMargin = $this->lMargin;
5974
		$this->crMargin = $this->rMargin;
5975
		if ($autopadding) {
5976
			// add top padding
5977
			$this->y += $mc_padding['T'];
5978
		}
5979
		if ($ishtml) { // ******* Write HTML text
5980
			$this->writeHTML($txt, true, false, $reseth, true, $align);
5981
			$nl = 1;
5982
		} else { // ******* Write simple text
5983
			$prev_FontSizePt = $this->FontSizePt;
5984
			if ($fitcell) {
5985
				// ajust height values
5986
				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5987
				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5988
			}
5989
			// vertical alignment
5990
			if ($maxh > 0) {
5991
				// get text height
5992
				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5993
				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5994
					// try to reduce font size to fit text on cell (use a quick search algorithm)
5995
					$fmin = 1;
5996
					$fmax = $this->FontSizePt;
5997
					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5998
					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5999
					while ($maxit >= 0) {
6000
						$fmid = (($fmax + $fmin) / 2);
6001
						$this->setFontSize($fmid, false);
6002
						$this->resetLastH();
6003
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6004
						$diff = ($maxh - $text_height);
6005
						if ($diff >= 0) {
6006
							if ($diff <= $diff_epsilon) {
6007
								break;
6008
							}
6009
							$fmin = $fmid;
6010
						} else {
6011
							$fmax = $fmid;
6012
						}
6013
						--$maxit;
6014
					}
6015
					if ($maxit < 0) {
6016
						// premature exit, we get the minimum font value to fit the cell
6017
						$this->setFontSize($fmin);
6018
						$this->resetLastH();
6019
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6020
					} else {
6021
						$this->setFontSize($fmid);
6022
						$this->resetLastH();
6023
					}
6024
				}
6025
				if ($text_height < $maxh) {
6026
					if ($valign == 'M') {
6027
						// text vertically centered
6028
						$this->y += (($maxh - $text_height) / 2);
6029
					} elseif ($valign == 'B') {
6030
						// text vertically aligned on bottom
6031
						$this->y += ($maxh - $text_height);
6032
					}
6033
				}
6034
			}
6035
			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6036
			if ($fitcell) {
6037
				// restore font size
6038
				$this->setFontSize($prev_FontSizePt);
6039
			}
6040
		}
6041
		if ($autopadding) {
6042
			// add bottom padding
6043
			$this->y += $mc_padding['B'];
6044
		}
6045
		// Get end-of-text Y position
6046
		$currentY = $this->y;
6047
		// get latest page number
6048
		$endpage = $this->page;
6049
		if ($resth > 0) {
6050
			$skip = ($endpage - $startpage);
6051
			$tmpresth = $resth;
6052
			while ($tmpresth > 0) {
6053
				if ($skip <= 0) {
6054
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
6055
					$this->checkPageBreak($this->PageBreakTrigger + 1);
6056
				}
6057
				if ($this->num_columns > 1) {
6058
					$tmpresth -= ($this->h - $this->y - $this->bMargin);
6059
				} else {
6060
					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6061
				}
6062
				--$skip;
6063
			}
6064
			$currentY = $this->y;
6065
			$endpage = $this->page;
6066
		}
6067
		// get latest column
6068
		$endcolumn = $this->current_column;
6069
		if ($this->num_columns == 0) {
6070
			$this->num_columns = 1;
6071
		}
6072
		// disable page regions check
6073
		$check_page_regions = $this->check_page_regions;
6074
		$this->check_page_regions = false;
6075
		// get border modes
6076
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
6077
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
6078
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6079
		// design borders around HTML cells.
6080
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6081
			$ccode = '';
6082
			$this->setPage($page);
6083
			if ($this->num_columns < 2) {
6084
				// single-column mode
6085
				$this->setX($x);
6086
				$this->y = $this->tMargin;
6087
			}
6088
			// account for margin changes
6089
			if ($page > $startpage) {
6090
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6091
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6092
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6093
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6094
				}
6095
			}
6096
			if ($startpage == $endpage) {
6097
				// single page
6098
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6099
					if ($column != $this->current_column) {
6100
						$this->selectColumn($column);
6101
					}
6102
					if ($this->rtl) {
6103
						$this->x -= $mc_margin['R'];
6104
					} else {
6105
						$this->x += $mc_margin['L'];
6106
					}
6107
					if ($startcolumn == $endcolumn) { // single column
6108
						$cborder = $border;
6109
						$h = max($h, ($currentY - $oy));
6110
						$this->y = $oy;
6111
					} elseif ($column == $startcolumn) { // first column
6112
						$cborder = $border_start;
6113
						$this->y = $oy;
6114
						$h = $this->h - $this->y - $this->bMargin;
6115
					} elseif ($column == $endcolumn) { // end column
6116
						$cborder = $border_end;
6117
						$h = $currentY - $this->y;
6118
						if ($resth > $h) {
6119
							$h = $resth;
6120
						}
6121
					} else { // 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
			} elseif ($page == $startpage) { // first page
6129
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6130
					if ($column != $this->current_column) {
6131
						$this->selectColumn($column);
6132
					}
6133
					if ($this->rtl) {
6134
						$this->x -= $mc_margin['R'];
6135
					} else {
6136
						$this->x += $mc_margin['L'];
6137
					}
6138
					if ($column == $startcolumn) { // first column
6139
						$cborder = $border_start;
6140
						$this->y = $oy;
6141
						$h = $this->h - $this->y - $this->bMargin;
6142
					} else { // middle column
6143
						$cborder = $border_middle;
6144
						$h = $this->h - $this->y - $this->bMargin;
6145
						$resth -= $h;
6146
					}
6147
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6148
				} // end for each column
6149
			} elseif ($page == $endpage) { // last page
6150
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6151
					if ($column != $this->current_column) {
6152
						$this->selectColumn($column);
6153
					}
6154
					if ($this->rtl) {
6155
						$this->x -= $mc_margin['R'];
6156
					} else {
6157
						$this->x += $mc_margin['L'];
6158
					}
6159
					if ($column == $endcolumn) {
6160
						// end column
6161
						$cborder = $border_end;
6162
						$h = $currentY - $this->y;
6163
						if ($resth > $h) {
6164
							$h = $resth;
6165
						}
6166
					} else {
6167
						// middle column
6168
						$cborder = $border_middle;
6169
						$h = $this->h - $this->y - $this->bMargin;
6170
						$resth -= $h;
6171
					}
6172
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6173
				} // end for each column
6174
			} else { // middle page
6175
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6176
					$this->selectColumn($column);
6177
					if ($this->rtl) {
6178
						$this->x -= $mc_margin['R'];
6179
					} else {
6180
						$this->x += $mc_margin['L'];
6181
					}
6182
					$cborder = $border_middle;
6183
					$h = $this->h - $this->y - $this->bMargin;
6184
					$resth -= $h;
6185
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6186
				} // end for each column
6187
			}
6188
			if ($cborder OR $fill) {
6189
				$offsetlen = strlen($ccode);
6190
				// draw border and fill
6191
				if ($this->inxobj) {
6192
					// we are inside an XObject template
6193
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6194
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6195
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6196
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6197
					} else {
6198
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6199
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6200
					}
6201
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6202
					$pstart = substr($pagebuff, 0, $pagemark);
6203
					$pend = substr($pagebuff, $pagemark);
6204
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6205
				} else {
6206
					if (end($this->transfmrk[$this->page]) !== false) {
6207
						$pagemarkkey = key($this->transfmrk[$this->page]);
6208
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6209
						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6210
					} elseif ($this->InFooter) {
6211
						$pagemark = $this->footerpos[$this->page];
6212
						$this->footerpos[$this->page] += $offsetlen;
6213
					} else {
6214
						$pagemark = $this->intmrk[$this->page];
6215
						$this->intmrk[$this->page] += $offsetlen;
6216
					}
6217
					$pagebuff = $this->getPageBuffer($this->page);
6218
					$pstart = substr($pagebuff, 0, $pagemark);
6219
					$pend = substr($pagebuff, $pagemark);
6220
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6221
				}
6222
			}
6223
		} // end for each page
6224
		// restore page regions check
6225
		$this->check_page_regions = $check_page_regions;
6226
		// Get end-of-cell Y position
6227
		$currentY = $this->GetY();
6228
		// restore previous values
6229
		if ($this->num_columns > 1) {
6230
			$this->selectColumn();
6231
		} else {
6232
			// restore original margins
6233
			$this->lMargin = $lMargin;
6234
			$this->rMargin = $rMargin;
6235
			if ($this->page > $startpage) {
6236
				// check for margin variations between pages (i.e. booklet mode)
6237
				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6238
				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6239
				if (($dl != 0) OR ($dr != 0)) {
6240
					$this->lMargin += $dl;
6241
					$this->rMargin += $dr;
6242
				}
6243
			}
6244
		}
6245
		if ($ln > 0) {
6246
			//Go to the beginning of the next line
6247
			$this->setY($currentY + $mc_margin['B']);
6248
			if ($ln == 2) {
6249
				$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6250
			}
6251
		} else {
6252
			// go left or right by case
6253
			$this->setPage($startpage);
6254
			$this->y = $y;
6255
			$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6256
		}
6257
		$this->setContentMark();
6258
		$this->cell_padding = $prev_cell_padding;
6259
		$this->cell_margin = $prev_cell_margin;
6260
		$this->clMargin = $this->lMargin;
6261
		$this->crMargin = $this->rMargin;
6262
		return $nl;
6263
	}
6264
6265
	/**
6266
	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6267
	 * @param string $txt String for calculating his height
6268
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6269
	 * @param boolean $reseth if true reset the last cell height (default false).
6270
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6271
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6272
	 * @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)))
6273
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6274
	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6275
	 * @public
6276
	 * @since 4.5.011
6277
	 */
6278
	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6279
		if ($txt === NULL) {
6280
			return 0;
6281
		}
6282
		if ($txt === '') {
6283
			// empty string
6284
			return 1;
6285
		}
6286
		// adjust internal padding
6287
		$prev_cell_padding = $this->cell_padding;
6288
		$prev_lasth = $this->lasth;
6289
		if (is_array($cellpadding)) {
6290
			$this->cell_padding = $cellpadding;
6291
		}
6292
		$this->adjustCellPadding($border);
6293
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6294
			if ($this->rtl) {
6295
				$w = $this->x - $this->lMargin;
6296
			} else {
6297
				$w = $this->w - $this->rMargin - $this->x;
6298
			}
6299
		}
6300
		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6301
		if ($reseth) {
6302
			// reset row height
6303
			$this->resetLastH();
6304
		}
6305
		$lines = 1;
6306
		$sum = 0;
6307
		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6308
		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6309
		$length = count($chars);
6310
		$lastSeparator = -1;
6311
		for ($i = 0; $i < $length; ++$i) {
6312
			$c = $chars[$i];
6313
			$charWidth = $charsWidth[$i];
6314
			if (($c != 160)
6315
					AND (($c == 173)
6316
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6317
						OR (($c == 45)
6318
							AND ($i > 0) AND ($i < ($length - 1))
6319
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6320
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6321
						)
6322
					)
6323
				) {
6324
				$lastSeparator = $i;
6325
			}
6326
			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6327
				++$lines;
6328
				if ($c == 10) {
6329
					$lastSeparator = -1;
6330
					$sum = 0;
6331
				} elseif ($lastSeparator != -1) {
6332
					$i = $lastSeparator;
6333
					$lastSeparator = -1;
6334
					$sum = 0;
6335
				} else {
6336
					$sum = $charWidth;
6337
				}
6338
			} else {
6339
				$sum += $charWidth;
6340
			}
6341
		}
6342
		if ($chars[($length - 1)] == 10) {
6343
			--$lines;
6344
		}
6345
		$this->cell_padding = $prev_cell_padding;
6346
		$this->lasth = $prev_lasth;
6347
		return $lines;
6348
	}
6349
6350
	/**
6351
	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6352
	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6353
	 * @pre
6354
	 *  // store current object
6355
	 *  $pdf->startTransaction();
6356
	 *  // store starting values
6357
	 *  $start_y = $pdf->GetY();
6358
	 *  $start_page = $pdf->getPage();
6359
	 *  // call your printing functions with your parameters
6360
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6361
	 *  $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);
6362
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6363
	 *  // get the new Y
6364
	 *  $end_y = $pdf->GetY();
6365
	 *  $end_page = $pdf->getPage();
6366
	 *  // calculate height
6367
	 *  $height = 0;
6368
	 *  if ($end_page == $start_page) {
6369
	 *  	$height = $end_y - $start_y;
6370
	 *  } else {
6371
	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6372
	 *  		$this->setPage($page);
6373
	 *  		if ($page == $start_page) {
6374
	 *  			// first page
6375
	 *  			$height += $this->h - $start_y - $this->bMargin;
6376
	 *  		} elseif ($page == $end_page) {
6377
	 *  			// last page
6378
	 *  			$height += $end_y - $this->tMargin;
6379
	 *  		} else {
6380
	 *  			$height += $this->h - $this->tMargin - $this->bMargin;
6381
	 *  		}
6382
	 *  	}
6383
	 *  }
6384
	 *  // restore previous object
6385
	 *  $pdf = $pdf->rollbackTransaction();
6386
	 *
6387
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6388
	 * @param string $txt String for calculating his height
6389
	 * @param boolean $reseth if true reset the last cell height (default false).
6390
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6391
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6392
	 * @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)))
6393
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6394
	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6395
	 * @public
6396
	 */
6397
	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6398
		// adjust internal padding
6399
		$prev_cell_padding = $this->cell_padding;
6400
		$prev_lasth = $this->lasth;
6401
		if (is_array($cellpadding)) {
6402
			$this->cell_padding = $cellpadding;
6403
		}
6404
		$this->adjustCellPadding($border);
6405
		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6406
		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6407
		$this->cell_padding = $prev_cell_padding;
6408
		$this->lasth = $prev_lasth;
6409
		return $height;
6410
	}
6411
6412
	/**
6413
	 * This method prints text from the current position.<br />
6414
	 * @param float $h Line height
6415
	 * @param string $txt String to print
6416
	 * @param mixed $link URL or identifier returned by AddLink()
6417
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6418
	 * @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>
6419
	 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6420
	 * @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.
6421
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
6422
	 * @param boolean $firstblock if true the string is the starting of a line.
6423
	 * @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.
6424
	 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6425
	 * @param array|null $margin margin array of the parent container
6426
	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6427
	 * @public
6428
	 * @since 1.5
6429
	 */
6430
	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) {
6431
		// check page for no-write regions and adapt page margins if necessary
6432
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6433
		if (strlen($txt) == 0) {
6434
			// fix empty text
6435
			$txt = ' ';
6436
		}
6437
		if (!is_array($margin)) {
6438
			// set default margins
6439
			$margin = $this->cell_margin;
6440
		}
6441
		// remove carriage returns
6442
		$s = str_replace("\r", '', $txt);
6443
		// check if string contains arabic text
6444
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6445
			$arabic = true;
6446
		} else {
6447
			$arabic = false;
6448
		}
6449
		// check if string contains RTL text
6450
		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6451
			$rtlmode = true;
6452
		} else {
6453
			$rtlmode = false;
6454
		}
6455
		// get a char width
6456
		$chrwidth = $this->GetCharWidth(46); // dot character
6457
		// get array of unicode values
6458
		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6459
		// calculate maximum width for a single character on string
6460
		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6461
		array_walk($chrw, array($this, 'getRawCharWidth'));
6462
		$maxchwidth = ((is_array($chrw) || $chrw instanceof Countable) && count($chrw) > 0) ? max($chrw) : 0;
6463
		// get array of chars
6464
		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6465
		// get the number of characters
6466
		$nb = count($chars);
6467
		// replacement for SHY character (minus symbol)
6468
		$shy_replacement = 45;
6469
		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6470
		// widht for SHY replacement
6471
		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6472
		// page width
6473
		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6474
		// calculate remaining line width ($w)
6475
		if ($this->rtl) {
6476
			$w = $this->x - $this->lMargin;
6477
		} else {
6478
			$w = $this->w - $this->rMargin - $this->x;
6479
		}
6480
		// max column width
6481
		$wmax = ($w - $wadj);
6482
		if (!$firstline) {
6483
			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6484
		}
6485
		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6486
			// the maximum width character do not fit on column
6487
			return '';
6488
		}
6489
		// minimum row height
6490
		$row_height = max($h, $this->getCellHeight($this->FontSize));
6491
		// max Y
6492
		$maxy = $this->y + $maxh - max($row_height, $h);
6493
		$start_page = $this->page;
6494
		$i = 0; // character position
6495
		$j = 0; // current starting position
6496
		$sep = -1; // position of the last blank space
6497
		$prevsep = $sep; // previous separator
6498
		$shy = false; // true if the last blank is a soft hypen (SHY)
6499
		$prevshy = $shy; // previous shy mode
6500
		$l = 0; // current string length
6501
		$nl = 0; //number of lines
6502
		$linebreak = false;
6503
		$pc = 0; // previous character
6504
		// for each character
6505
		while ($i < $nb) {
6506
			if (($maxh > 0) AND ($this->y > $maxy) ) {
6507
				break;
6508
			}
6509
			//Get the current character
6510
			$c = $chars[$i];
6511
			if ($c == 10) { // 10 = "\n" = new line
6512
				//Explicit line break
6513
				if ($align == 'J') {
6514
					if ($this->rtl) {
6515
						$talign = 'R';
6516
					} else {
6517
						$talign = 'L';
6518
					}
6519
				} else {
6520
					$talign = $align;
6521
				}
6522
				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6523
				if ($firstline) {
6524
					$startx = $this->x;
6525
					$tmparr = array_slice($chars, $j, ($i - $j));
6526
					if ($rtlmode) {
6527
						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6528
					}
6529
					$linew = $this->GetArrStringWidth($tmparr);
6530
					unset($tmparr);
6531
					if ($this->rtl) {
6532
						$this->endlinex = $startx - $linew;
6533
					} else {
6534
						$this->endlinex = $startx + $linew;
6535
					}
6536
					$w = $linew;
6537
					$tmpcellpadding = $this->cell_padding;
6538
					if ($maxh == 0) {
6539
						$this->setCellPadding(0);
6540
					}
6541
				}
6542
				if ($firstblock AND $this->isRTLTextDir()) {
6543
					$tmpstr = $this->stringRightTrim($tmpstr);
6544
				}
6545
				// Skip newlines at the beginning of a page or column
6546
				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6547
					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6548
				}
6549
				unset($tmpstr);
6550
				if ($firstline) {
6551
					$this->cell_padding = $tmpcellpadding;
6552
					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6553
				}
6554
				++$nl;
6555
				$j = $i + 1;
6556
				$l = 0;
6557
				$sep = -1;
6558
				$prevsep = $sep;
6559
				$shy = false;
6560
				// account for margin changes
6561
				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6562
					if ($this->AcceptPageBreak())
6563
					{
6564
						if ($this->rtl) {
6565
							$this->x -= $margin['R'];
6566
						} else {
6567
							$this->x += $margin['L'];
6568
						}
6569
						$this->lMargin += $margin['L'];
6570
						$this->rMargin += $margin['R'];
6571
					}
6572
				}
6573
				$w = $this->getRemainingWidth();
6574
				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6575
			} else {
6576
				// 160 is the non-breaking space.
6577
				// 173 is SHY (Soft Hypen).
6578
				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6579
				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6580
				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6581
				if (($c != 160)
6582
					AND (($c == 173)
6583
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6584
						OR (($c == 45)
6585
							AND ($i < ($nb - 1))
6586
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6587
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6588
						)
6589
					)
6590
				) {
6591
					// update last blank space position
6592
					$prevsep = $sep;
6593
					$sep = $i;
6594
					// check if is a SHY
6595
					if (($c == 173) OR ($c == 45)) {
6596
						$prevshy = $shy;
6597
						$shy = true;
6598
						if ($pc == 45) {
6599
							$tmp_shy_replacement_width = 0;
6600
							$tmp_shy_replacement_char = '';
6601
						} else {
6602
							$tmp_shy_replacement_width = $shy_replacement_width;
6603
							$tmp_shy_replacement_char = $shy_replacement_char;
6604
						}
6605
					} else {
6606
						$shy = false;
6607
					}
6608
				}
6609
				// update string length
6610
				if ($this->isUnicodeFont() AND ($arabic)) {
6611
					// with bidirectional algorithm some chars may be changed affecting the line length
6612
					// *** very slow ***
6613
					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6614
				} else {
6615
					$l += $this->GetCharWidth($c, ($i+1 < $nb));
6616
				}
6617
				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6618
					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6619
						$sep = $prevsep;
6620
						$shy = $prevshy;
6621
					}
6622
					// we have reached the end of column
6623
					if ($sep == -1) {
6624
						// check if the line was already started
6625
						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6626
							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6627
							// print a void cell and go to next line
6628
							$this->Cell($w, $h, '', 0, 1);
6629
							$linebreak = true;
6630
							if ($firstline) {
6631
								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6632
							}
6633
						} else {
6634
							// truncate the word because do not fit on column
6635
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6636
							if ($firstline) {
6637
								$startx = $this->x;
6638
								$tmparr = array_slice($chars, $j, ($i - $j));
6639
								if ($rtlmode) {
6640
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6641
								}
6642
								$linew = $this->GetArrStringWidth($tmparr);
6643
								unset($tmparr);
6644
								if ($this->rtl) {
6645
									$this->endlinex = $startx - $linew;
6646
								} else {
6647
									$this->endlinex = $startx + $linew;
6648
								}
6649
								$w = $linew;
6650
								$tmpcellpadding = $this->cell_padding;
6651
								if ($maxh == 0) {
6652
									$this->setCellPadding(0);
6653
								}
6654
							}
6655
							if ($firstblock AND $this->isRTLTextDir()) {
6656
								$tmpstr = $this->stringRightTrim($tmpstr);
6657
							}
6658
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6659
							unset($tmpstr);
6660
							if ($firstline) {
6661
								$this->cell_padding = $tmpcellpadding;
6662
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6663
							}
6664
							$j = $i;
6665
							--$i;
6666
						}
6667
					} else {
6668
						// word wrapping
6669
						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6670
							$endspace = 1;
6671
						} else {
6672
							$endspace = 0;
6673
						}
6674
						// check the length of the next string
6675
						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6676
						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6677
						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6678
							// truncate the word because do not fit on a full page width
6679
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6680
							if ($firstline) {
6681
								$startx = $this->x;
6682
								$tmparr = array_slice($chars, $j, ($i - $j));
6683
								if ($rtlmode) {
6684
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6685
								}
6686
								$linew = $this->GetArrStringWidth($tmparr);
6687
								unset($tmparr);
6688
								if ($this->rtl) {
6689
									$this->endlinex = ($startx - $linew);
6690
								} else {
6691
									$this->endlinex = ($startx + $linew);
6692
								}
6693
								$w = $linew;
6694
								$tmpcellpadding = $this->cell_padding;
6695
								if ($maxh == 0) {
6696
									$this->setCellPadding(0);
6697
								}
6698
							}
6699
							if ($firstblock AND $this->isRTLTextDir()) {
6700
								$tmpstr = $this->stringRightTrim($tmpstr);
6701
							}
6702
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6703
							unset($tmpstr);
6704
							if ($firstline) {
6705
								$this->cell_padding = $tmpcellpadding;
6706
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6707
							}
6708
							$j = $i;
6709
							--$i;
6710
						} else {
6711
							// word wrapping
6712
							if ($shy) {
6713
								// add hypen (minus symbol) at the end of the line
6714
								$shy_width = $tmp_shy_replacement_width;
6715
								if ($this->rtl) {
6716
									$shy_char_left = $tmp_shy_replacement_char;
6717
									$shy_char_right = '';
6718
								} else {
6719
									$shy_char_left = '';
6720
									$shy_char_right = $tmp_shy_replacement_char;
6721
								}
6722
							} else {
6723
								$shy_width = 0;
6724
								$shy_char_left = '';
6725
								$shy_char_right = '';
6726
							}
6727
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6728
							if ($firstline) {
6729
								$startx = $this->x;
6730
								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6731
								if ($rtlmode) {
6732
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6733
								}
6734
								$linew = $this->GetArrStringWidth($tmparr);
6735
								unset($tmparr);
6736
								if ($this->rtl) {
6737
									$this->endlinex = $startx - $linew - $shy_width;
6738
								} else {
6739
									$this->endlinex = $startx + $linew + $shy_width;
6740
								}
6741
								$w = $linew;
6742
								$tmpcellpadding = $this->cell_padding;
6743
								if ($maxh == 0) {
6744
									$this->setCellPadding(0);
6745
								}
6746
							}
6747
							// print the line
6748
							if ($firstblock AND $this->isRTLTextDir()) {
6749
								$tmpstr = $this->stringRightTrim($tmpstr);
6750
							}
6751
							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6752
							unset($tmpstr);
6753
							if ($firstline) {
6754
								if ($chars[$sep] == 45) {
6755
									$endspace += 1;
6756
								}
6757
								// return the remaining text
6758
								$this->cell_padding = $tmpcellpadding;
6759
								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6760
							}
6761
							$i = $sep;
6762
							$sep = -1;
6763
							$shy = false;
6764
							$j = ($i + 1);
6765
						}
6766
					}
6767
					// account for margin changes
6768
					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6769
						if ($this->AcceptPageBreak())
6770
						{
6771
							if ($this->rtl) {
6772
								$this->x -= $margin['R'];
6773
							} else {
6774
								$this->x += $margin['L'];
6775
							}
6776
							$this->lMargin += $margin['L'];
6777
							$this->rMargin += $margin['R'];
6778
						}
6779
					}
6780
					$w = $this->getRemainingWidth();
6781
					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6782
					if ($linebreak) {
6783
						$linebreak = false;
6784
					} else {
6785
						++$nl;
6786
						$l = 0;
6787
					}
6788
				}
6789
			}
6790
			// save last character
6791
			$pc = $c;
6792
			++$i;
6793
		} // end while i < nb
6794
		// print last substring (if any)
6795
		if ($l > 0) {
6796
			switch ($align) {
6797
				case 'J':
6798
				case 'C': {
6799
					break;
6800
				}
6801
				case 'L': {
6802
					if (!$this->rtl) {
6803
						$w = $l;
6804
					}
6805
					break;
6806
				}
6807
				case 'R': {
6808
					if ($this->rtl) {
6809
						$w = $l;
6810
					}
6811
					break;
6812
				}
6813
				default: {
6814
					$w = $l;
6815
					break;
6816
				}
6817
			}
6818
			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6819
			if ($firstline) {
6820
				$startx = $this->x;
6821
				$tmparr = array_slice($chars, $j, ($nb - $j));
6822
				if ($rtlmode) {
6823
					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6824
				}
6825
				$linew = $this->GetArrStringWidth($tmparr);
6826
				unset($tmparr);
6827
				if ($this->rtl) {
6828
					$this->endlinex = $startx - $linew;
6829
				} else {
6830
					$this->endlinex = $startx + $linew;
6831
				}
6832
				$w = $linew;
6833
				$tmpcellpadding = $this->cell_padding;
6834
				if ($maxh == 0) {
6835
					$this->setCellPadding(0);
6836
				}
6837
			}
6838
			if ($firstblock AND $this->isRTLTextDir()) {
6839
				$tmpstr = $this->stringRightTrim($tmpstr);
6840
			}
6841
			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6842
			unset($tmpstr);
6843
			if ($firstline) {
6844
				$this->cell_padding = $tmpcellpadding;
6845
				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6846
			}
6847
			++$nl;
6848
		}
6849
		if ($firstline) {
6850
			return '';
6851
		}
6852
		return $nl;
6853
	}
6854
6855
	/**
6856
	 * Returns the remaining width between the current position and margins.
6857
	 * @return float Return the remaining width
6858
	 * @protected
6859
	 */
6860
	protected function getRemainingWidth() {
6861
		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6862
		if ($this->rtl) {
6863
			return ($this->x - $this->lMargin);
6864
		} else {
6865
			return ($this->w - $this->rMargin - $this->x);
6866
		}
6867
	}
6868
6869
	/**
6870
	 * Set the block dimensions accounting for page breaks and page/column fitting
6871
	 * @param float $w width
6872
	 * @param float $h height
6873
	 * @param float $x X coordinate
6874
	 * @param float $y Y coodiante
6875
	 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6876
	 * @return array array($w, $h, $x, $y)
6877
	 * @protected
6878
	 * @since 5.5.009 (2010-07-05)
6879
	 */
6880
	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6881
		if ($w <= 0) {
6882
			// set maximum width
6883
			$w = ($this->w - $this->lMargin - $this->rMargin);
6884
			if ($w <= 0) {
6885
				$w = 1;
6886
			}
6887
		}
6888
		if ($h <= 0) {
6889
			// set maximum height
6890
			$h = ($this->PageBreakTrigger - $this->tMargin);
6891
			if ($h <= 0) {
6892
				$h = 1;
6893
			}
6894
		}
6895
		// resize the block to be vertically contained on a single page or single column
6896
		if ($fitonpage OR $this->AutoPageBreak) {
6897
			$ratio_wh = ($w / $h);
6898
			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6899
				$h = $this->PageBreakTrigger - $this->tMargin;
6900
				$w = ($h * $ratio_wh);
6901
			}
6902
			// resize the block to be horizontally contained on a single page or single column
6903
			if ($fitonpage) {
6904
				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6905
				if ($w > $maxw) {
6906
					$w = $maxw;
6907
					$h = ($w / $ratio_wh);
6908
				}
6909
			}
6910
		}
6911
		// Check whether we need a new page or new column first as this does not fit
6912
		$prev_x = $this->x;
6913
		$prev_y = $this->y;
6914
		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6915
			$y = $this->y;
6916
			if ($this->rtl) {
6917
				$x += ($prev_x - $this->x);
6918
			} else {
6919
				$x += ($this->x - $prev_x);
6920
			}
6921
			$this->newline = true;
6922
		}
6923
		// resize the block to be contained on the remaining available page or column space
6924
		if ($fitonpage) {
6925
			// fallback to avoid division by zero
6926
			$h = $h == 0 ? 1 : $h;
6927
			$ratio_wh = ($w / $h);
6928
			if (($y + $h) > $this->PageBreakTrigger) {
6929
				$h = $this->PageBreakTrigger - $y;
6930
				$w = ($h * $ratio_wh);
6931
			}
6932
			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6933
				$w = $this->w - $this->rMargin - $x;
6934
				$h = ($w / $ratio_wh);
6935
			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6936
				$w = $x - $this->lMargin;
6937
				$h = ($w / $ratio_wh);
6938
			}
6939
		}
6940
		return array($w, $h, $x, $y);
6941
	}
6942
6943
	/**
6944
	 * Puts an image in the page.
6945
	 * The upper-left corner must be given.
6946
	 * The dimensions can be specified in different ways:<ul>
6947
	 * <li>explicit width and height (expressed in user unit)</li>
6948
	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6949
	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6950
	 * 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;
6951
	 * The format can be specified explicitly or inferred from the file extension.<br />
6952
	 * It is possible to put a link on the image.<br />
6953
	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6954
	 * @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').
6955
	 * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6956
	 * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6957
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6958
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6959
	 * @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.
6960
	 * @param mixed $link URL or identifier returned by AddLink().
6961
	 * @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>
6962
	 * @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).
6963
	 * @param int $dpi dot-per-inch resolution used on resize
6964
	 * @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>
6965
	 * @param boolean $ismask true if this image is a mask, false otherwise
6966
	 * @param mixed $imgmask image object returned by this function or false
6967
	 * @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)))
6968
	 * @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).
6969
	 * @param boolean $hidden If true do not display the image.
6970
	 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6971
	 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6972
	 * @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.
6973
	 * @return mixed|false image information
6974
	 * @public
6975
	 * @since 1.1
6976
	 */
6977
	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()) {
6978
		if ($this->state != 2) {
6979
			return false;
6980
		}
6981
		if (TCPDF_STATIC::empty_string($x)) {
6982
			$x = $this->x;
6983
		}
6984
		if (TCPDF_STATIC::empty_string($y)) {
6985
			$y = $this->y;
6986
		}
6987
		// check page for no-write regions and adapt page margins if necessary
6988
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6989
		$exurl = ''; // external streams
6990
		$imsize = FALSE;
6991
6992
        // Make sure the file variable is not empty or null because accessing $file[0] later
6993
        // results in error when running PHP 7.4
6994
        if (empty($file)) {
6995
            return false;
6996
        }
6997
		// check if we are passing an image as file or string
6998
		if ($file[0] === '@') {
6999
			// image from string
7000
			$imgdata = substr($file, 1);
7001
		} else { // image file
7002
			if ($file[0] === '*') {
7003
				// image as external stream
7004
				$file = substr($file, 1);
7005
				$exurl = $file;
7006
			}
7007
			// check if file exist and it is valid
7008
			if (!@$this->fileExists($file)) {
7009
				return false;
7010
			}
7011
            if (false !== $info = $this->getImageBuffer($file)) {
7012
                $imsize = array($info['w'], $info['h']);
7013
            } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
7014
                $imgdata = $this->getCachedFileContents($file);
7015
            }
7016
		}
7017
		if (!empty($imgdata)) {
7018
			// copy image to cache
7019
			$original_file = $file;
7020
			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7021
			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
7022
			if (!$fp) {
7023
				$this->Error('Unable to write file: '.$file);
7024
			}
7025
			fwrite($fp, $imgdata);
7026
			fclose($fp);
7027
			unset($imgdata);
7028
			$imsize = @getimagesize($file);
7029
			if ($imsize === FALSE) {
7030
				$this->_unlink($file);
7031
				$file = $original_file;
7032
			}
7033
		}
7034
		if ($imsize === FALSE) {
7035
			if (($w > 0) AND ($h > 0)) {
7036
				// get measures from specified data
7037
				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7038
				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7039
				$imsize = array($pw, $ph);
7040
			} else {
7041
				$this->Error('[Image] Unable to get the size of the image: '.$file);
7042
			}
7043
		}
7044
		// file hash
7045
		$filehash = md5($file);
7046
		// get original image width and height in pixels
7047
		list($pixw, $pixh) = $imsize;
7048
		// calculate image width and height on document
7049
		if (($w <= 0) AND ($h <= 0)) {
7050
			// convert image size to document unit
7051
			$w = $this->pixelsToUnits($pixw);
7052
			$h = $this->pixelsToUnits($pixh);
7053
		} elseif ($w <= 0) {
7054
			$w = $h * $pixw / $pixh;
7055
		} elseif ($h <= 0) {
7056
			$h = $w * $pixh / $pixw;
7057
		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7058
			if (strlen($fitbox) !== 2) {
7059
				// set default alignment
7060
				$fitbox = '--';
7061
			}
7062
			// scale image dimensions proportionally to fit within the ($w, $h) box
7063
			if ((($w * $pixh) / ($h * $pixw)) < 1) {
7064
				// store current height
7065
				$oldh = $h;
7066
				// calculate new height
7067
				$h = $w * $pixh / $pixw;
7068
				// height difference
7069
				$hdiff = ($oldh - $h);
7070
				// vertical alignment
7071
				switch (strtoupper($fitbox[1])) {
7072
					case 'T': {
7073
						break;
7074
					}
7075
					case 'M': {
7076
						$y += ($hdiff / 2);
7077
						break;
7078
					}
7079
					case 'B': {
7080
						$y += $hdiff;
7081
						break;
7082
					}
7083
				}
7084
			} else {
7085
				// store current width
7086
				$oldw = $w;
7087
				// calculate new width
7088
				$w = $h * $pixw / $pixh;
7089
				// width difference
7090
				$wdiff = ($oldw - $w);
7091
				// horizontal alignment
7092
				switch (strtoupper($fitbox[0])) {
7093
					case 'L': {
7094
						if ($this->rtl) {
7095
							$x -= $wdiff;
7096
						}
7097
						break;
7098
					}
7099
					case 'C': {
7100
						if ($this->rtl) {
7101
							$x -= ($wdiff / 2);
7102
						} else {
7103
							$x += ($wdiff / 2);
7104
						}
7105
						break;
7106
					}
7107
					case 'R': {
7108
						if (!$this->rtl) {
7109
							$x += $wdiff;
7110
						}
7111
						break;
7112
					}
7113
				}
7114
			}
7115
		}
7116
		// fit the image on available space
7117
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7118
		// calculate new minimum dimensions in pixels
7119
		$neww = round($w * $this->k * $dpi / $this->dpi);
7120
		$newh = round($h * $this->k * $dpi / $this->dpi);
7121
		// check if resize is necessary (resize is used only to reduce the image)
7122
		$newsize = ($neww * $newh);
7123
		$pixsize = ($pixw * $pixh);
7124
		if (intval($resize) == 2) {
7125
			$resize = true;
7126
		} elseif ($newsize >= $pixsize) {
7127
			$resize = false;
7128
		}
7129
		// check if image has been already added on document
7130
		$newimage = true;
7131
		if (in_array($file, $this->imagekeys)) {
7132
			$newimage = false;
7133
			// get existing image data
7134
			$info = $this->getImageBuffer($file);
7135
			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7136
				// check if the newer image is larger
7137
				$oldsize = ($info['w'] * $info['h']);
7138
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7139
					$newimage = true;
7140
				}
7141
			}
7142
		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7143
			// create temp image file (without alpha channel)
7144
			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7145
			// create temp alpha file
7146
			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7147
			// check for cached images
7148
			if (in_array($tempfile_plain, $this->imagekeys)) {
7149
				// get existing image data
7150
				$info = $this->getImageBuffer($tempfile_plain);
7151
				// check if the newer image is larger
7152
				$oldsize = ($info['w'] * $info['h']);
7153
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7154
					$newimage = true;
7155
				} else {
7156
					$newimage = false;
7157
					// embed mask image
7158
					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7159
					// embed image, masked with previously embedded mask
7160
					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7161
				}
7162
			}
7163
		}
7164
		if ($newimage) {
7165
			//First use of image, get info
7166
			$type = strtolower($type);
7167
			if ($type == '') {
7168
				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7169
			} elseif ($type == 'jpg') {
7170
				$type = 'jpeg';
7171
			}
7172
			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7173
			$mtd = '_parse'.$type;
7174
			// GD image handler function
7175
			$gdfunction = 'imagecreatefrom'.$type;
7176
			$info = false;
7177
			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7178
				// TCPDF image functions
7179
				$info = TCPDF_IMAGES::$mtd($file);
7180
				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7181
					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7182
					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7183
				}
7184
			}
7185
			if (($info === false) AND function_exists($gdfunction)) {
7186
				try {
7187
					// GD library
7188
					$img = $gdfunction($file);
7189
					if ($img !== false) {
7190
						if ($resize) {
7191
							$imgr = imagecreatetruecolor($neww, $newh);
7192
							if (($type == 'gif') OR ($type == 'png')) {
7193
								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7194
							}
7195
							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7196
							$img = $imgr;
7197
						}
7198
						if (($type == 'gif') OR ($type == 'png')) {
7199
							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7200
						} else {
7201
							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7202
						}
7203
					}
7204
				} catch(Exception $e) {
7205
					$info = false;
7206
				}
7207
			}
7208
			if (($info === false) AND extension_loaded('imagick')) {
7209
				try {
7210
					// ImageMagick library
7211
					$img = new Imagick();
7212
					if ($type == 'svg') {
7213
						if ($file[0] === '@') {
7214
							// image from string
7215
							$svgimg = substr($file, 1);
7216
						} else {
7217
							// get SVG file content
7218
                            $svgimg = $this->getCachedFileContents($file);
7219
						}
7220
						if ($svgimg !== FALSE) {
7221
							// get width and height
7222
							$regs = array();
7223
							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7224
								$svgtag = $regs[1];
7225
								$tmp = array();
7226
								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7227
									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7228
									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7229
									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7230
								} else {
7231
									$ow = $w;
7232
								}
7233
								$tmp = array();
7234
								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7235
									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7236
									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7237
									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7238
								} else {
7239
									$oh = $h;
7240
								}
7241
								$tmp = array();
7242
								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7243
									$vbw = ($ow * $this->imgscale * $this->k);
7244
									$vbh = ($oh * $this->imgscale * $this->k);
7245
									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7246
									$svgtag = $vbox.$svgtag;
7247
								}
7248
								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7249
							}
7250
							$img->readImageBlob($svgimg);
7251
						}
7252
					} else {
7253
						$img->readImage($file);
7254
					}
7255
					if ($resize) {
7256
						$img->resizeImage($neww, $newh, 10, 1, false);
7257
					}
7258
					$img->setCompressionQuality($this->jpeg_quality);
7259
					$img->setImageFormat('jpeg');
7260
					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7261
					$img->writeImage($tempname);
7262
					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7263
					$this->_unlink($tempname);
7264
					$img->destroy();
7265
				} catch(Exception $e) {
7266
					$info = false;
7267
				}
7268
			}
7269
			if ($info === false) {
7270
				// unable to process image
7271
				return false;
7272
			}
7273
			if ($ismask) {
7274
				// force grayscale
7275
				$info['cs'] = 'DeviceGray';
7276
			}
7277
			if ($imgmask !== false) {
7278
				$info['masked'] = $imgmask;
7279
			}
7280
			if (!empty($exurl)) {
7281
				$info['exurl'] = $exurl;
7282
			}
7283
			// array of alternative images
7284
			$info['altimgs'] = $altimgs;
7285
			// add image to document
7286
			$info['i'] = $this->setImageBuffer($file, $info);
7287
		}
7288
		// set alignment
7289
		$this->img_rb_x = $x + $w;
7290
		$this->img_rb_y = $y + $h;
7291
7292
		// set alignment
7293
		if ($palign == 'L') {
7294
			$ximg = $this->lMargin;
7295
		} elseif ($palign == 'C') {
7296
			$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7297
		} elseif ($palign == 'R') {
7298
			$ximg = $this->w - $this->rMargin - $w;
7299
		} else {
7300
			$ximg = $this->rtl ? $x - $w : $x;
7301
		}
7302
7303
		if ($ismask OR $hidden) {
7304
			// image is not displayed
7305
			return $info['i'];
7306
		}
7307
		$xkimg = $ximg * $this->k;
7308
		if (!$alt) {
7309
			// only non-alternative immages will be set
7310
			$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']));
7311
		}
7312
		if (!empty($border)) {
7313
			$bx = $this->x;
7314
			$by = $this->y;
7315
			$this->x = $ximg;
7316
			if ($this->rtl) {
7317
				$this->x += $w;
7318
			}
7319
			$this->y = $y;
7320
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7321
			$this->x = $bx;
7322
			$this->y = $by;
7323
		}
7324
		if ($link) {
7325
			$this->Link($ximg, $y, $w, $h, $link, 0);
7326
		}
7327
		// set pointer to align the next text/objects
7328
		switch($align) {
7329
			case 'T': {
7330
				$this->y = $y;
7331
				$this->x = $this->img_rb_x;
7332
				break;
7333
			}
7334
			case 'M': {
7335
				$this->y = $y + round($h/2);
7336
				$this->x = $this->img_rb_x;
7337
				break;
7338
			}
7339
			case 'B': {
7340
				$this->y = $this->img_rb_y;
7341
				$this->x = $this->img_rb_x;
7342
				break;
7343
			}
7344
			case 'N': {
7345
				$this->setY($this->img_rb_y);
7346
				break;
7347
			}
7348
			default:{
7349
				break;
7350
			}
7351
		}
7352
		$this->endlinex = $this->img_rb_x;
7353
		if ($this->inxobj) {
7354
			// we are inside an XObject template
7355
			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7356
		}
7357
		return $info['i'];
7358
	}
7359
7360
	/**
7361
	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7362
	 * @param string $file Name of the file containing the image.
7363
	 * @param float $x Abscissa of the upper-left corner.
7364
	 * @param float $y Ordinate of the upper-left corner.
7365
	 * @param float $wpx Original width of the image in pixels.
7366
	 * @param float $hpx original height of the image in pixels.
7367
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7368
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7369
	 * @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.
7370
	 * @param mixed $link URL or identifier returned by AddLink().
7371
	 * @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>
7372
	 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7373
	 * @param int $dpi dot-per-inch resolution used on resize
7374
	 * @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>
7375
	 * @param string $filehash File hash used to build unique file names.
7376
	 * @author Nicola Asuni
7377
	 * @protected
7378
	 * @since 4.3.007 (2008-12-04)
7379
	 * @see Image()
7380
	 */
7381
	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7382
		// create temp images
7383
		if (empty($filehash)) {
7384
			$filehash = md5($file);
7385
		}
7386
		// create temp image file (without alpha channel)
7387
		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7388
		// create temp alpha file
7389
		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7390
		$parsed = false;
7391
		$parse_error = '';
7392
		// ImageMagick extension
7393
		if (($parsed === false) AND extension_loaded('imagick')) {
7394
			try {
7395
				// ImageMagick library
7396
				$img = new Imagick();
7397
				$img->readImage($file);
7398
				// clone image object
7399
				$imga = TCPDF_STATIC::objclone($img);
7400
				// extract alpha channel
7401
				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7402
					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7403
				} else {
7404
					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7405
					$img->negateImage(true);
7406
				}
7407
				$img->setImageFormat('png');
7408
				$img->writeImage($tempfile_alpha);
7409
				// remove alpha channel
7410
				if (method_exists($imga, 'setImageMatte')) {
7411
					$imga->setImageMatte(false);
7412
				} else {
7413
					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7414
				}
7415
				$imga->setImageFormat('png');
7416
				$imga->writeImage($tempfile_plain);
7417
				$parsed = true;
7418
			} catch (Exception $e) {
7419
				// Imagemagick fails, try with GD
7420
				$parse_error = 'Imagick library error: '.$e->getMessage();
7421
			}
7422
		}
7423
		// GD extension
7424
		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7425
			try {
7426
				// generate images
7427
				$img = imagecreatefrompng($file);
7428
				$imgalpha = imagecreate($wpx, $hpx);
7429
				// generate gray scale palette (0 -> 255)
7430
				for ($c = 0; $c < 256; ++$c) {
7431
					ImageColorAllocate($imgalpha, $c, $c, $c);
7432
				}
7433
				// extract alpha channel
7434
				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7435
					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7436
						$color = imagecolorat($img, $xpx, $ypx);
7437
						// get and correct gamma color
7438
						$alpha = $this->getGDgamma($img, $color);
7439
						imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha);
7440
					}
7441
				}
7442
				imagepng($imgalpha, $tempfile_alpha);
7443
				imagedestroy($imgalpha);
7444
				// extract image without alpha channel
7445
				$imgplain = imagecreatetruecolor($wpx, $hpx);
7446
				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7447
				imagepng($imgplain, $tempfile_plain);
7448
				imagedestroy($imgplain);
7449
				$parsed = true;
7450
			} catch (Exception $e) {
7451
				// GD fails
7452
				$parse_error = 'GD library error: '.$e->getMessage();
7453
			}
7454
		}
7455
		if ($parsed === false) {
7456
			if (empty($parse_error)) {
7457
				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7458
			} else {
7459
				$this->Error($parse_error);
7460
			}
7461
		}
7462
		// embed mask image
7463
		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7464
		// embed image, masked with previously embedded mask
7465
		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7466
	}
7467
7468
	/**
7469
	 * Get the GD-corrected PNG gamma value from alpha color
7470
	 * @param resource $img GD image Resource ID.
7471
	 * @param int $c alpha color
7472
	 * @protected
7473
	 * @since 4.3.007 (2008-12-04)
7474
	 */
7475
	protected function getGDgamma($img, $c) {
7476
		if (!isset($this->gdgammacache['#'.$c])) {
7477
			$colors = imagecolorsforindex($img, $c);
7478
			// GD alpha is only 7 bit (0 -> 127)
7479
			$this->gdgammacache['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255);
7480
			// correct gamma
7481
			$this->gdgammacache['#'.$c] = (int) (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7482
			// store the latest values on cache to improve performances
7483
			if (count($this->gdgammacache) > 8) {
7484
				// remove one element from the cache array
7485
				array_shift($this->gdgammacache);
7486
			}
7487
		}
7488
		return $this->gdgammacache['#'.$c];
7489
	}
7490
7491
	/**
7492
	 * Performs a line break.
7493
	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7494
	 * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell.
7495
	 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7496
	 * @public
7497
	 * @since 1.0
7498
	 * @see Cell()
7499
	 */
7500
	public function Ln($h=null, $cell=false) {
7501
		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'])) {
7502
			// revove vertical space from the top of the column
7503
			return;
7504
		}
7505
		if ($cell) {
7506
			if ($this->rtl) {
7507
				$cellpadding = $this->cell_padding['R'];
7508
			} else {
7509
				$cellpadding = $this->cell_padding['L'];
7510
			}
7511
		} else {
7512
			$cellpadding = 0;
7513
		}
7514
		if ($this->rtl) {
7515
			$this->x = $this->w - $this->rMargin - $cellpadding;
7516
		} else {
7517
			$this->x = $this->lMargin + $cellpadding;
7518
		}
7519
		if (TCPDF_STATIC::empty_string($h)) {
7520
			$h = $this->lasth;
7521
		}
7522
		$this->y += $h;
7523
		$this->newline = true;
7524
	}
7525
7526
	/**
7527
	 * Returns the relative X value of current position.
7528
	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7529
	 * @return float
7530
	 * @public
7531
	 * @since 1.2
7532
	 * @see SetX(), GetY(), SetY()
7533
	 */
7534
	public function GetX() {
7535
		//Get x position
7536
		if ($this->rtl) {
7537
			return ($this->w - $this->x);
7538
		} else {
7539
			return $this->x;
7540
		}
7541
	}
7542
7543
	/**
7544
	 * Returns the absolute X value of current position.
7545
	 * @return float
7546
	 * @public
7547
	 * @since 1.2
7548
	 * @see SetX(), GetY(), SetY()
7549
	 */
7550
	public function GetAbsX() {
7551
		return $this->x;
7552
	}
7553
7554
	/**
7555
	 * Returns the ordinate of the current position.
7556
	 * @return float
7557
	 * @public
7558
	 * @since 1.0
7559
	 * @see SetY(), GetX(), SetX()
7560
	 */
7561
	public function GetY() {
7562
		return $this->y;
7563
	}
7564
7565
	/**
7566
	 * Defines the abscissa of the current position.
7567
	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7568
	 * @param float $x The value of the abscissa in user units.
7569
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7570
	 * @public
7571
	 * @since 1.2
7572
	 * @see GetX(), GetY(), SetY(), SetXY()
7573
	 */
7574
	public function setX($x, $rtloff=false) {
7575
		$x = floatval($x);
7576
		if (!$rtloff AND $this->rtl) {
7577
			if ($x >= 0) {
7578
				$this->x = $this->w - $x;
7579
			} else {
7580
				$this->x = abs($x);
7581
			}
7582
		} else {
7583
			if ($x >= 0) {
7584
				$this->x = $x;
7585
			} else {
7586
				$this->x = $this->w + $x;
7587
			}
7588
		}
7589
		if ($this->x < 0) {
7590
			$this->x = 0;
7591
		}
7592
		if ($this->x > $this->w) {
7593
			$this->x = $this->w;
7594
		}
7595
	}
7596
7597
	/**
7598
	 * Moves the current abscissa back to the left margin and sets the ordinate.
7599
	 * If the passed value is negative, it is relative to the bottom of the page.
7600
	 * @param float $y The value of the ordinate in user units.
7601
	 * @param bool $resetx if true (default) reset the X position.
7602
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7603
	 * @public
7604
	 * @since 1.0
7605
	 * @see GetX(), GetY(), SetY(), SetXY()
7606
	 */
7607
	public function setY($y, $resetx=true, $rtloff=false) {
7608
		$y = floatval($y);
7609
		if ($resetx) {
7610
			//reset x
7611
			if (!$rtloff AND $this->rtl) {
7612
				$this->x = $this->w - $this->rMargin;
7613
			} else {
7614
				$this->x = $this->lMargin;
7615
			}
7616
		}
7617
		if ($y >= 0) {
7618
			$this->y = $y;
7619
		} else {
7620
			$this->y = $this->h + $y;
7621
		}
7622
		if ($this->y < 0) {
7623
			$this->y = 0;
7624
		}
7625
		if ($this->y > $this->h) {
7626
			$this->y = $this->h;
7627
		}
7628
	}
7629
7630
	/**
7631
	 * Defines the abscissa and ordinate of the current position.
7632
	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7633
	 * @param float $x The value of the abscissa.
7634
	 * @param float $y The value of the ordinate.
7635
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7636
	 * @public
7637
	 * @since 1.2
7638
	 * @see SetX(), SetY()
7639
	 */
7640
	public function setXY($x, $y, $rtloff=false) {
7641
		$this->setY($y, false, $rtloff);
7642
		$this->setX($x, $rtloff);
7643
	}
7644
7645
	/**
7646
	 * Set the absolute X coordinate of the current pointer.
7647
	 * @param float $x The value of the abscissa in user units.
7648
	 * @public
7649
	 * @since 5.9.186 (2012-09-13)
7650
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7651
	 */
7652
	public function setAbsX($x) {
7653
		$this->x = floatval($x);
7654
	}
7655
7656
	/**
7657
	 * Set the absolute Y coordinate of the current pointer.
7658
	 * @param float $y (float) The value of the ordinate in user units.
7659
	 * @public
7660
	 * @since 5.9.186 (2012-09-13)
7661
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7662
	 */
7663
	public function setAbsY($y) {
7664
		$this->y = floatval($y);
7665
	}
7666
7667
	/**
7668
	 * Set the absolute X and Y coordinates of the current pointer.
7669
	 * @param float $x The value of the abscissa in user units.
7670
	 * @param float $y (float) The value of the ordinate in user units.
7671
	 * @public
7672
	 * @since 5.9.186 (2012-09-13)
7673
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7674
	 */
7675
	public function setAbsXY($x, $y) {
7676
		$this->setAbsX($x);
7677
		$this->setAbsY($y);
7678
	}
7679
7680
	/**
7681
	 * Send the document to a given destination: string, local file or browser.
7682
	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7683
	 * The method first calls Close() if necessary to terminate the document.
7684
	 * @param string $name The name of the file when saved
7685
	 * @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>
7686
	 * @return string
7687
	 * @public
7688
	 * @since 1.0
7689
	 * @see Close()
7690
	 */
7691
	public function Output($name='doc.pdf', $dest='I') {
7692
		//Output PDF to some destination
7693
		//Finish document if necessary
7694
		if ($this->state < 3) {
7695
			$this->Close();
7696
		}
7697
		//Normalize parameters
7698
		if (is_bool($dest)) {
7699
			$dest = $dest ? 'D' : 'F';
7700
		}
7701
		$dest = strtoupper($dest);
7702
7703
		if ($this->sign) {
7704
			// *** apply digital signature to the document ***
7705
			// get the document content
7706
			$pdfdoc = $this->getBuffer();
7707
			// remove last newline
7708
			$pdfdoc = substr($pdfdoc, 0, -1);
7709
			// remove filler space
7710
			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7711
			// define the ByteRange
7712
			$byte_range = array();
7713
			$byte_range[0] = 0;
7714
			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7715
			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7716
			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7717
			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7718
			// replace the ByteRange
7719
			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7720
			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7721
			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7722
			// write the document to a temporary folder
7723
			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7724
			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7725
			if (!$f) {
7726
				$this->Error('Unable to create temporary file: '.$tempdoc);
7727
			}
7728
			$pdfdoc_length = strlen($pdfdoc);
7729
			fwrite($f, $pdfdoc, $pdfdoc_length);
7730
			fclose($f);
7731
			// get digital signature via openssl library
7732
			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7733
			if (empty($this->signature_data['extracerts'])) {
7734
				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7735
			} else {
7736
				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']);
7737
			}
7738
			// read signature
7739
			$signature = file_get_contents($tempsign);
7740
			// extract signature
7741
			$signature = substr($signature, $pdfdoc_length);
7742
			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7743
			$tmparr = explode("\n\n", $signature);
7744
			$signature = $tmparr[1];
7745
			// decode signature
7746
			$signature = base64_decode(trim($signature));
7747
			// add TSA timestamp to signature
7748
			$signature = $this->applyTSA($signature);
7749
			// convert signature to hex
7750
			$signature = current(unpack('H*', $signature));
7751
			$signature = str_pad($signature, $this->signature_max_length, '0');
7752
			// Add signature to the document
7753
			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7754
			$this->bufferlen = strlen($this->buffer);
7755
		}
7756
		switch($dest) {
7757
			case 'I': {
7758
				// Send PDF to the standard output
7759
				if (ob_get_contents()) {
7760
					$this->Error('Some data has already been output, can\'t send PDF file');
7761
				}
7762
				if (php_sapi_name() != 'cli') {
7763
					// send output to a browser
7764
					header('Content-Type: application/pdf');
7765
					if (headers_sent()) {
7766
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7767
					}
7768
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7769
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7770
					header('Pragma: public');
7771
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7772
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7773
					header('Content-Disposition: inline; filename="' . rawurlencode(basename($name)) . '"; ' .
7774
						'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7775
					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7776
				} else {
7777
					echo $this->getBuffer();
7778
				}
7779
				break;
7780
			}
7781
			case 'D': {
7782
				// download PDF as file
7783
				if (ob_get_contents()) {
7784
					$this->Error('Some data has already been output, can\'t send PDF file');
7785
				}
7786
				header('Content-Description: File Transfer');
7787
				if (headers_sent()) {
7788
					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7789
				}
7790
				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7791
				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7792
				header('Pragma: public');
7793
				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7794
				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7795
				// force download dialog
7796
				if (strpos(php_sapi_name(), 'cgi') === false) {
7797
					header('Content-Type: application/force-download');
7798
					header('Content-Type: application/octet-stream', false);
7799
					header('Content-Type: application/download', false);
7800
					header('Content-Type: application/pdf', false);
7801
				} else {
7802
					header('Content-Type: application/pdf');
7803
				}
7804
				// use the Content-Disposition header to supply a recommended filename
7805
				header('Content-Disposition: attachment; filename="' . rawurlencode(basename($name)) . '"; ' .
7806
					'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7807
				header('Content-Transfer-Encoding: binary');
7808
				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7809
				break;
7810
			}
7811
			case 'F':
7812
			case 'FI':
7813
			case 'FD': {
7814
				// save PDF to a local file
7815
				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7816
				if (!$f) {
7817
					$this->Error('Unable to create output file: '.$name);
7818
				}
7819
				fwrite($f, $this->getBuffer(), $this->bufferlen);
7820
				fclose($f);
7821
				if ($dest == 'FI') {
7822
					// send headers to browser
7823
					header('Content-Type: application/pdf');
7824
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7825
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7826
					header('Pragma: public');
7827
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7828
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7829
					header('Content-Disposition: inline; filename="'.basename($name).'"');
7830
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7831
				} elseif ($dest == 'FD') {
7832
					// send headers to browser
7833
					if (ob_get_contents()) {
7834
						$this->Error('Some data has already been output, can\'t send PDF file');
7835
					}
7836
					header('Content-Description: File Transfer');
7837
					if (headers_sent()) {
7838
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7839
					}
7840
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7841
					header('Pragma: public');
7842
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7843
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7844
					// force download dialog
7845
					if (strpos(php_sapi_name(), 'cgi') === false) {
7846
						header('Content-Type: application/force-download');
7847
						header('Content-Type: application/octet-stream', false);
7848
						header('Content-Type: application/download', false);
7849
						header('Content-Type: application/pdf', false);
7850
					} else {
7851
						header('Content-Type: application/pdf');
7852
					}
7853
					// use the Content-Disposition header to supply a recommended filename
7854
					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7855
					header('Content-Transfer-Encoding: binary');
7856
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7857
				}
7858
				break;
7859
			}
7860
			case 'E': {
7861
				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7862
				$retval = 'Content-Type: application/pdf;'."\r\n";
7863
				$retval .= ' name="'.$name.'"'."\r\n";
7864
				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7865
				$retval .= 'Content-Disposition: attachment;'."\r\n";
7866
				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7867
				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7868
				return $retval;
7869
			}
7870
			case 'S': {
7871
				// returns PDF as a string
7872
				return $this->getBuffer();
7873
			}
7874
			default: {
7875
				$this->Error('Incorrect output destination: '.$dest);
7876
			}
7877
		}
7878
		return '';
7879
	}
7880
7881
	protected static $cleaned_ids = array();
7882
	/**
7883
	 * Unset all class variables except the following critical variables.
7884
	 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7885
	 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7886
	 * @public
7887
	 * @since 4.5.016 (2009-02-24)
7888
	 */
7889
	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7890
		if (isset(self::$cleaned_ids[$this->file_id])) {
7891
			$destroyall = false;
7892
		}
7893
		if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7894
			self::$cleaned_ids[$this->file_id] = true;
7895
			// remove all temporary files
7896
			if ($handle = @opendir(K_PATH_CACHE)) {
7897
				while ( false !== ( $file_name = readdir( $handle ) ) ) {
7898
					if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7899
						$this->_unlink(K_PATH_CACHE.$file_name);
7900
					}
7901
				}
7902
				closedir($handle);
7903
			}
7904
			if (isset($this->imagekeys)) {
7905
				foreach($this->imagekeys as $file) {
7906
					if ((strpos($file,  K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_') === 0)
7907
						&& TCPDF_STATIC::file_exists($file)) {
7908
							$this->_unlink($file);
7909
					}
7910
				}
7911
			}
7912
		}
7913
		$preserve = array(
7914
			'file_id',
7915
			'state',
7916
			'bufferlen',
7917
			'buffer',
7918
			'cached_files',
7919
			'imagekeys',
7920
			'sign',
7921
			'signature_data',
7922
			'signature_max_length',
7923
			'byterange_string',
7924
			'tsa_timestamp',
7925
			'tsa_data'
7926
		);
7927
		foreach (array_keys(get_object_vars($this)) as $val) {
7928
			if ($destroyall OR !in_array($val, $preserve)) {
7929
				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7930
					unset($this->$val);
7931
				}
7932
			}
7933
		}
7934
	}
7935
7936
	/**
7937
	 * Check for locale-related bug
7938
	 * @protected
7939
	 */
7940
	protected function _dochecks() {
7941
		//Check for locale-related bug
7942
		if (1.1 == 1) {
7943
			$this->Error('Don\'t alter the locale before including class file');
7944
		}
7945
		//Check for decimal separator
7946
		if (sprintf('%.1F', 1.0) != '1.0') {
7947
			setlocale(LC_NUMERIC, 'C');
7948
		}
7949
	}
7950
7951
	/**
7952
	 * Return an array containing variations for the basic page number alias.
7953
	 * @param string $a Base alias.
7954
	 * @return array of page number aliases
7955
	 * @protected
7956
	 */
7957
	protected function getInternalPageNumberAliases($a= '') {
7958
		$alias = array();
7959
		// build array of Unicode + ASCII variants (the order is important)
7960
		$alias = array('u' => array(), 'a' => array());
7961
		$u = '{'.$a.'}';
7962
		$alias['u'][] = TCPDF_STATIC::_escape($u);
7963
		if ($this->isunicode) {
7964
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7965
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7966
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7967
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7968
		}
7969
		$alias['a'][] = TCPDF_STATIC::_escape($a);
7970
		return $alias;
7971
	}
7972
7973
	/**
7974
	 * Return an array containing all internal page aliases.
7975
	 * @return array of page number aliases
7976
	 * @protected
7977
	 */
7978
	protected function getAllInternalPageNumberAliases() {
7979
		$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);
7980
		$pnalias = array();
7981
		foreach($basic_alias as $k => $a) {
7982
			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7983
		}
7984
		return $pnalias;
7985
	}
7986
7987
	/**
7988
	 * Replace right shift page number aliases with spaces to correct right alignment.
7989
	 * This works perfectly only when using monospaced fonts.
7990
	 * @param string $page Page content.
7991
	 * @param array $aliases Array of page aliases.
7992
	 * @param int $diff initial difference to add.
7993
	 * @return string replaced page content.
7994
	 * @protected
7995
	 */
7996
	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7997
		foreach ($aliases as $type => $alias) {
7998
			foreach ($alias as $a) {
7999
				// find position of compensation factor
8000
				$startnum = (strpos($a, ':') + 1);
8001
				$a = substr($a, 0, $startnum);
8002
				if (($pos = strpos($page, $a)) !== false) {
8003
					// end of alias
8004
					$endnum = strpos($page, '}', $pos);
8005
					// string to be replaced
8006
					$aa = substr($page, $pos, ($endnum - $pos + 1));
8007
					// get compensation factor
8008
					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
8009
					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
8010
					$ratio = floatval($ratio);
8011
					if ($type == 'u') {
8012
						$chrdiff = floor(($diff + 12) * $ratio);
8013
						$shift = str_repeat(' ', $chrdiff);
8014
						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
8015
					} else {
8016
						$chrdiff = floor(($diff + 11) * $ratio);
8017
						$shift = str_repeat(' ', $chrdiff);
8018
					}
8019
					$page = str_replace($aa, $shift, $page);
8020
				}
8021
			}
8022
		}
8023
		return $page;
8024
	}
8025
8026
	/**
8027
	 * Set page boxes to be included on page descriptions.
8028
	 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
8029
	 * @protected
8030
	 */
8031
	protected function setPageBoxTypes($boxes) {
8032
		$this->page_boxes = array();
8033
		foreach ($boxes as $box) {
8034
			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
8035
				$this->page_boxes[] = $box;
8036
			}
8037
		}
8038
	}
8039
8040
	/**
8041
	 * Output pages (and replace page number aliases).
8042
	 * @protected
8043
	 */
8044
	protected function _putpages() {
8045
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8046
		// get internal aliases for page numbers
8047
		$pnalias = $this->getAllInternalPageNumberAliases();
8048
		$num_pages = $this->numpages;
8049
		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
8050
		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
8051
		$ptp_num_chars = $this->GetNumChars($ptpa);
8052
		$pagegroupnum = 0;
8053
		$groupnum = 0;
8054
		$ptgu = 1;
8055
		$ptga = 1;
8056
		$ptg_num_chars = 1;
8057
		for ($n = 1; $n <= $num_pages; ++$n) {
8058
			// get current page
8059
			$temppage = $this->getPageBuffer($n);
8060
			$pagelen = strlen($temppage);
8061
			// set replacements for total pages number
8062
			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
8063
			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
8064
			$pnp_num_chars = $this->GetNumChars($pnpa);
8065
			$pdiff = 0; // difference used for right shift alignment of page numbers
8066
			$gdiff = 0; // difference used for right shift alignment of page group numbers
8067
			if (!empty($this->pagegroups)) {
8068
				if (isset($this->newpagegroup[$n])) {
8069
					$pagegroupnum = 0;
8070
					++$groupnum;
8071
					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
8072
					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
8073
					$ptg_num_chars = $this->GetNumChars($ptga);
8074
				}
8075
				++$pagegroupnum;
8076
				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
8077
				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
8078
				$png_num_chars = $this->GetNumChars($pnga);
8079
				// replace page numbers
8080
				$replace = array();
8081
				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8082
				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8083
				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8084
				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8085
				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8086
			}
8087
			// replace page numbers
8088
			$replace = array();
8089
			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8090
			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8091
			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8092
			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8093
			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8094
			// replace right shift alias
8095
			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8096
			// replace EPS marker
8097
			$temppage = str_replace($this->epsmarker, '', $temppage);
8098
			//Page
8099
			$this->page_obj_id[$n] = $this->_newobj();
8100
			$out = '<<';
8101
			$out .= ' /Type /Page';
8102
			$out .= ' /Parent 1 0 R';
8103
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8104
				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8105
			}
8106
			$out .= ' /Resources 2 0 R';
8107
			foreach ($this->page_boxes as $box) {
8108
				$out .= ' /'.$box;
8109
				$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']);
8110
			}
8111
			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8112
				$out .= ' /BoxColorInfo <<';
8113
				foreach ($this->page_boxes as $box) {
8114
					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8115
						$out .= ' /'.$box.' <<';
8116
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8117
							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8118
							$out .= ' /C [';
8119
							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8120
							$out .= ' ]';
8121
						}
8122
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8123
							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8124
						}
8125
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8126
							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8127
						}
8128
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8129
							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8130
							$out .= ' /D [';
8131
							foreach ($dashes as $dash) {
8132
								$out .= sprintf(' %F', ($dash * $this->k));
8133
							}
8134
							$out .= ' ]';
8135
						}
8136
						$out .= ' >>';
8137
					}
8138
				}
8139
				$out .= ' >>';
8140
			}
8141
			$out .= ' /Contents '.($this->n + 1).' 0 R';
8142
			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8143
			if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
8144
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8145
			}
8146
			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8147
				// page transitions
8148
				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8149
					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8150
				}
8151
				$out .= ' /Trans <<';
8152
				$out .= ' /Type /Trans';
8153
				if (isset($this->pagedim[$n]['trans']['S'])) {
8154
					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8155
				}
8156
				if (isset($this->pagedim[$n]['trans']['D'])) {
8157
					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8158
				}
8159
				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8160
					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8161
				}
8162
				if (isset($this->pagedim[$n]['trans']['M'])) {
8163
					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8164
				}
8165
				if (isset($this->pagedim[$n]['trans']['Di'])) {
8166
					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8167
				}
8168
				if (isset($this->pagedim[$n]['trans']['SS'])) {
8169
					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8170
				}
8171
				if (isset($this->pagedim[$n]['trans']['B'])) {
8172
					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8173
				}
8174
				$out .= ' >>';
8175
			}
8176
			$out .= $this->_getannotsrefs($n);
8177
			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8178
			$out .= ' >>';
8179
			$out .= "\n".'endobj';
8180
			$this->_out($out);
8181
			//Page content
8182
			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8183
			$this->_newobj();
8184
			$p = $this->_getrawstream($p);
8185
			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8186
		}
8187
		//Pages root
8188
		$out = $this->_getobj(1)."\n";
8189
		$out .= '<< /Type /Pages /Kids [';
8190
		foreach($this->page_obj_id as $page_obj) {
8191
			$out .= ' '.$page_obj.' 0 R';
8192
		}
8193
		$out .= ' ] /Count '.$num_pages.' >>';
8194
		$out .= "\n".'endobj';
8195
		$this->_out($out);
8196
	}
8197
8198
	/**
8199
	 * Get references to page annotations.
8200
	 * @param int $n page number
8201
	 * @return string
8202
	 * @protected
8203
	 * @author Nicola Asuni
8204
	 * @since 5.0.010 (2010-05-17)
8205
	 */
8206
	protected function _getannotsrefs($n) {
8207
		if (!(isset($this->PageAnnots[$n]) OR count($this->empty_signature_appearance)>0 OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8208
			return '';
8209
		}
8210
		$out = ' /Annots [';
8211
		if (isset($this->PageAnnots[$n])) {
8212
			foreach ($this->PageAnnots[$n] as $key => $val) {
8213
				if (!in_array($val['n'], $this->radio_groups)) {
8214
					$out .= ' '.$val['n'].' 0 R';
8215
				}
8216
			}
8217
			// add radiobutton groups
8218
			if (isset($this->radiobutton_groups[$n])) {
8219
				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8220
					if (isset($data['n'])) {
8221
						$out .= ' '.$data['n'].' 0 R';
8222
					}
8223
				}
8224
			}
8225
		}
8226
		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8227
			// set reference for signature object
8228
			$out .= ' '.$this->sig_obj_id.' 0 R';
8229
		}
8230
		if (!empty($this->empty_signature_appearance)) {
8231
			foreach ($this->empty_signature_appearance as $esa) {
8232
				if ($esa['page'] == $n) {
8233
					// set reference for empty signature objects
8234
					$out .= ' '.$esa['objid'].' 0 R';
8235
				}
8236
			}
8237
		}
8238
		$out .= ' ]';
8239
		return $out;
8240
	}
8241
8242
	/**
8243
	 * Output annotations objects for all pages.
8244
	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8245
	 * See section 12.5 of PDF 32000_2008 reference.
8246
	 * @protected
8247
	 * @author Nicola Asuni
8248
	 * @since 4.0.018 (2008-08-06)
8249
	 */
8250
	protected function _putannotsobjs() {
8251
		// reset object counter
8252
		for ($n=1; $n <= $this->numpages; ++$n) {
8253
			if (isset($this->PageAnnots[$n])) {
8254
				// set page annotations
8255
				foreach ($this->PageAnnots[$n] as $key => $pl) {
8256
					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8257
					// create annotation object for grouping radiobuttons
8258
					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8259
						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8260
						$annots = '<<';
8261
						$annots .= ' /Type /Annot';
8262
						$annots .= ' /Subtype /Widget';
8263
						$annots .= ' /Rect [0 0 0 0]';
8264
						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8265
							// read only
8266
							$annots .= ' /F 68';
8267
							$annots .= ' /Ff 49153';
8268
						} else {
8269
							$annots .= ' /F 4'; // default print for PDF/A
8270
							$annots .= ' /Ff 49152';
8271
						}
8272
						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8273
						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8274
							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8275
						}
8276
						$annots .= ' /FT /Btn';
8277
						$annots .= ' /Kids [';
8278
						$defval = '';
8279
						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8280
							if (isset($data['kid'])) {
8281
								$annots .= ' '.$data['kid'].' 0 R';
8282
								if ($data['def'] !== 'Off') {
8283
									$defval = $data['def'];
8284
								}
8285
							}
8286
						}
8287
						$annots .= ' ]';
8288
						if (!empty($defval)) {
8289
							$annots .= ' /V /'.$defval;
8290
						}
8291
						$annots .= ' >>';
8292
						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8293
						$this->form_obj_id[] = $radio_button_obj_id;
8294
						// store object id to be used on Parent entry of Kids
8295
						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8296
					}
8297
					$formfield = false;
8298
					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8299
					$a = $pl['x'] * $this->k;
8300
					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8301
					$c = $pl['w'] * $this->k;
8302
					$d = $pl['h'] * $this->k;
8303
					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8304
					// create new annotation object
8305
					$annots = '<</Type /Annot';
8306
					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8307
					$annots .= ' /Rect ['.$rect.']';
8308
					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8309
					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8310
						$annots .= ' /FT /'.$pl['opt']['ft'];
8311
						$formfield = true;
8312
					}
8313
					if ($pl['opt']['subtype'] !== 'Link') {
8314
						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8315
					}
8316
					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8317
					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8318
					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8319
					if (isset($pl['opt']['f'])) {
8320
						$fval = 0;
8321
						if (is_array($pl['opt']['f'])) {
8322
							foreach ($pl['opt']['f'] as $f) {
8323
								switch (strtolower($f)) {
8324
									case 'invisible': {
8325
										$fval += 1 << 0;
8326
										break;
8327
									}
8328
									case 'hidden': {
8329
										$fval += 1 << 1;
8330
										break;
8331
									}
8332
									case 'print': {
8333
										$fval += 1 << 2;
8334
										break;
8335
									}
8336
									case 'nozoom': {
8337
										$fval += 1 << 3;
8338
										break;
8339
									}
8340
									case 'norotate': {
8341
										$fval += 1 << 4;
8342
										break;
8343
									}
8344
									case 'noview': {
8345
										$fval += 1 << 5;
8346
										break;
8347
									}
8348
									case 'readonly': {
8349
										$fval += 1 << 6;
8350
										break;
8351
									}
8352
									case 'locked': {
8353
										$fval += 1 << 7;
8354
										break;
8355
									}
8356
									case 'togglenoview': {
8357
										$fval += 1 << 8;
8358
										break;
8359
									}
8360
									case 'lockedcontents': {
8361
										$fval += 1 << 9;
8362
										break;
8363
									}
8364
									default: {
8365
										break;
8366
									}
8367
								}
8368
							}
8369
						} else {
8370
							$fval = intval($pl['opt']['f']);
8371
						}
8372
					} else {
8373
						$fval = 4;
8374
					}
8375
					if ($this->pdfa_mode) {
8376
						// force print flag for PDF/A mode
8377
						$fval |= 4;
8378
					}
8379
					$annots .= ' /F '.intval($fval);
8380
					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8381
						$annots .= ' /AS /'.$pl['opt']['as'];
8382
					}
8383
					if (isset($pl['opt']['ap'])) {
8384
						// appearance stream
8385
						$annots .= ' /AP <<';
8386
						if (is_array($pl['opt']['ap'])) {
8387
							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8388
								// $apmode can be: n = normal; r = rollover; d = down;
8389
								$annots .= ' /'.strtoupper($apmode);
8390
								if (is_array($apdef)) {
8391
									$annots .= ' <<';
8392
									foreach ($apdef as $apstate => $stream) {
8393
										// reference to XObject that define the appearance for this mode-state
8394
										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8395
										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8396
									}
8397
									$annots .= ' >>';
8398
								} else {
8399
									// reference to XObject that define the appearance for this mode
8400
									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8401
									$annots .= ' '.$apsobjid.' 0 R';
8402
								}
8403
							}
8404
						} else {
8405
							$annots .= $pl['opt']['ap'];
8406
						}
8407
						$annots .= ' >>';
8408
					}
8409
					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8410
						$annots .= ' /BS <<';
8411
						$annots .= ' /Type /Border';
8412
						if (isset($pl['opt']['bs']['w'])) {
8413
							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8414
						}
8415
						$bstyles = array('S', 'D', 'B', 'I', 'U');
8416
						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8417
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8418
						}
8419
						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8420
							$annots .= ' /D [';
8421
							foreach ($pl['opt']['bs']['d'] as $cord) {
8422
								$annots .= ' '.intval($cord);
8423
							}
8424
							$annots .= ']';
8425
						}
8426
						$annots .= ' >>';
8427
					} else {
8428
						$annots .= ' /Border [';
8429
						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8430
							$annots .= intval($pl['opt']['border'][0]).' ';
8431
							$annots .= intval($pl['opt']['border'][1]).' ';
8432
							$annots .= intval($pl['opt']['border'][2]);
8433
							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8434
								$annots .= ' [';
8435
								foreach ($pl['opt']['border'][3] as $dash) {
8436
									$annots .= intval($dash).' ';
8437
								}
8438
								$annots .= ']';
8439
							}
8440
						} else {
8441
							$annots .= '0 0 0';
8442
						}
8443
						$annots .= ']';
8444
					}
8445
					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8446
						$annots .= ' /BE <<';
8447
						$bstyles = array('S', 'C');
8448
						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8449
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8450
						} else {
8451
							$annots .= ' /S /S';
8452
						}
8453
						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8454
							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8455
						}
8456
						$annots .= '>>';
8457
					}
8458
					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8459
						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8460
					}
8461
					//$annots .= ' /StructParent ';
8462
					//$annots .= ' /OC ';
8463
					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8464
					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8465
						// this is a markup type
8466
						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8467
							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8468
						}
8469
						//$annots .= ' /Popup ';
8470
						if (isset($pl['opt']['ca'])) {
8471
							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8472
						}
8473
						if (isset($pl['opt']['rc'])) {
8474
							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8475
						}
8476
						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8477
						//$annots .= ' /IRT ';
8478
						if (isset($pl['opt']['subj'])) {
8479
							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8480
						}
8481
						//$annots .= ' /RT ';
8482
						//$annots .= ' /IT ';
8483
						//$annots .= ' /ExData ';
8484
					}
8485
					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8486
					// Annotation types
8487
					switch (strtolower($pl['opt']['subtype'])) {
8488
						case 'text': {
8489
							if (isset($pl['opt']['open'])) {
8490
								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8491
							}
8492
							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8493
							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8494
								$annots .= ' /Name /'.$pl['opt']['name'];
8495
							} else {
8496
								$annots .= ' /Name /Note';
8497
							}
8498
							$hasStateModel = isset($pl['opt']['statemodel']);
8499
							$hasState = isset($pl['opt']['state']);
8500
							$statemodels = array('Marked', 'Review');
8501
							if (!$hasStateModel && !$hasState) {
8502
								break;
8503
							}
8504
							if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8505
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8506
							} else {
8507
								$pl['opt']['statemodel'] = 'Marked';
8508
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8509
							}
8510
							if ($pl['opt']['statemodel'] == 'Marked') {
8511
								$states = array('Accepted', 'Unmarked');
8512
							} else {
8513
								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8514
							}
8515
							if ($hasState AND in_array($pl['opt']['state'], $states)) {
8516
								$annots .= ' /State /'.$pl['opt']['state'];
8517
							} else {
8518
								if ($pl['opt']['statemodel'] == 'Marked') {
8519
									$annots .= ' /State /Unmarked';
8520
								} else {
8521
									$annots .= ' /State /None';
8522
								}
8523
							}
8524
							break;
8525
						}
8526
						case 'link': {
8527
							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8528
								if ($pl['txt'][0] == '#') {
8529
									// internal destination
8530
									$annots .= ' /A <</S /GoTo /D /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8531
								} elseif ($pl['txt'][0] == '%') {
8532
									// embedded PDF file
8533
									$filename = basename(substr($pl['txt'], 1));
8534
									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8535
								} elseif ($pl['txt'][0] == '*') {
8536
									// embedded generic file
8537
									$filename = basename(substr($pl['txt'], 1));
8538
									$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});';
8539
									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8540
								} else {
8541
									$parsedUrl = parse_url($pl['txt']);
8542
									if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8543
										// relative link to a PDF file
8544
										$dest = '[0 /Fit]'; // default page 0
8545
										if (!empty($parsedUrl['fragment'])) {
8546
											// check for named destination
8547
											$tmp = explode('=', $parsedUrl['fragment']);
8548
											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8549
										}
8550
										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8551
									} else {
8552
										// external URI link
8553
										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8554
									}
8555
								}
8556
							} elseif (isset($this->links[$pl['txt']])) {
8557
								// internal link ID
8558
								$l = $this->links[$pl['txt']];
8559
								if (isset($this->page_obj_id[($l['p'])])) {
8560
									$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)));
8561
								}
8562
							}
8563
							$hmodes = array('N', 'I', 'O', 'P');
8564
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8565
								$annots .= ' /H /'.$pl['opt']['h'];
8566
							} else {
8567
								$annots .= ' /H /I';
8568
							}
8569
							//$annots .= ' /PA ';
8570
							//$annots .= ' /Quadpoints ';
8571
							break;
8572
						}
8573
						case 'freetext': {
8574
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8575
								$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
8576
							}
8577
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8578
								$annots .= ' /Q '.intval($pl['opt']['q']);
8579
							}
8580
							if (isset($pl['opt']['rc'])) {
8581
								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8582
							}
8583
							if (isset($pl['opt']['ds'])) {
8584
								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8585
							}
8586
							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8587
								$annots .= ' /CL [';
8588
								foreach ($pl['opt']['cl'] as $cl) {
8589
									$annots .= sprintf('%F ', $cl * $this->k);
8590
								}
8591
								$annots .= ']';
8592
							}
8593
							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8594
							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8595
								$annots .= ' /IT /'.$pl['opt']['it'];
8596
							}
8597
							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8598
								$l = $pl['opt']['rd'][0] * $this->k;
8599
								$r = $pl['opt']['rd'][1] * $this->k;
8600
								$t = $pl['opt']['rd'][2] * $this->k;
8601
								$b = $pl['opt']['rd'][3] * $this->k;
8602
								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8603
							}
8604
							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8605
								$annots .= ' /LE /'.$pl['opt']['le'];
8606
							}
8607
							break;
8608
						}
8609
						case 'line': {
8610
							break;
8611
						}
8612
						case 'square': {
8613
							break;
8614
						}
8615
						case 'circle': {
8616
							break;
8617
						}
8618
						case 'polygon': {
8619
							break;
8620
						}
8621
						case 'polyline': {
8622
							break;
8623
						}
8624
						case 'highlight': {
8625
							break;
8626
						}
8627
						case 'underline': {
8628
							break;
8629
						}
8630
						case 'squiggly': {
8631
							break;
8632
						}
8633
						case 'strikeout': {
8634
							break;
8635
						}
8636
						case 'stamp': {
8637
							break;
8638
						}
8639
						case 'caret': {
8640
							break;
8641
						}
8642
						case 'ink': {
8643
							break;
8644
						}
8645
						case 'popup': {
8646
							break;
8647
						}
8648
						case 'fileattachment': {
8649
							if ($this->pdfa_mode && $this->pdfa_version != 3) {
8650
								// embedded files are not allowed in PDF/A mode version 1 and 2
8651
								break;
8652
							}
8653
							if (!isset($pl['opt']['fs'])) {
8654
								break;
8655
							}
8656
							$filename = basename($pl['opt']['fs']);
8657
							if (isset($this->embeddedfiles[$filename]['f'])) {
8658
								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8659
								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8660
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8661
									$annots .= ' /Name /'.$pl['opt']['name'];
8662
								} else {
8663
									$annots .= ' /Name /PushPin';
8664
								}
8665
								// index (zero-based) of the annotation in the Annots array of this page
8666
								$this->embeddedfiles[$filename]['a'] = $key;
8667
							}
8668
							break;
8669
						}
8670
						case 'sound': {
8671
							if (!isset($pl['opt']['fs'])) {
8672
								break;
8673
							}
8674
							$filename = basename($pl['opt']['fs']);
8675
							if (isset($this->embeddedfiles[$filename]['f'])) {
8676
								// ... TO BE COMPLETED ...
8677
								// /R /C /B /E /CO /CP
8678
								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8679
								$iconsapp = array('Speaker', 'Mic');
8680
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8681
									$annots .= ' /Name /'.$pl['opt']['name'];
8682
								} else {
8683
									$annots .= ' /Name /Speaker';
8684
								}
8685
							}
8686
							break;
8687
						}
8688
						case 'movie': {
8689
							break;
8690
						}
8691
						case 'widget': {
8692
							$hmode = array('N', 'I', 'O', 'P', 'T');
8693
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8694
								$annots .= ' /H /'.$pl['opt']['h'];
8695
							}
8696
							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8697
								$annots .= ' /MK <<';
8698
								if (isset($pl['opt']['mk']['r'])) {
8699
									$annots .= ' /R '.$pl['opt']['mk']['r'];
8700
								}
8701
								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8702
									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8703
								}
8704
								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8705
									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8706
								}
8707
								if (isset($pl['opt']['mk']['ca'])) {
8708
									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8709
								}
8710
								if (isset($pl['opt']['mk']['rc'])) {
8711
									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8712
								}
8713
								if (isset($pl['opt']['mk']['ac'])) {
8714
									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8715
								}
8716
								if (isset($pl['opt']['mk']['i'])) {
8717
									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8718
									if ($info !== false) {
8719
										$annots .= ' /I '.$info['n'].' 0 R';
8720
									}
8721
								}
8722
								if (isset($pl['opt']['mk']['ri'])) {
8723
									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8724
									if ($info !== false) {
8725
										$annots .= ' /RI '.$info['n'].' 0 R';
8726
									}
8727
								}
8728
								if (isset($pl['opt']['mk']['ix'])) {
8729
									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8730
									if ($info !== false) {
8731
										$annots .= ' /IX '.$info['n'].' 0 R';
8732
									}
8733
								}
8734
								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8735
									$annots .= ' /IF <<';
8736
									$if_sw = array('A', 'B', 'S', 'N');
8737
									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8738
										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8739
									}
8740
									$if_s = array('A', 'P');
8741
									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8742
										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8743
									}
8744
									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8745
										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8746
									}
8747
									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8748
										$annots .= ' /FB true';
8749
									}
8750
									$annots .= '>>';
8751
								}
8752
								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8753
									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8754
								}
8755
								$annots .= '>>';
8756
							} // end MK
8757
							// --- Entries for field dictionaries ---
8758
							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8759
								// set parent
8760
								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8761
							}
8762
							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8763
								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8764
							}
8765
							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8766
								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8767
							}
8768
							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8769
								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8770
							}
8771
							if (isset($pl['opt']['ff'])) {
8772
								if (is_array($pl['opt']['ff'])) {
8773
									// array of bit settings
8774
									$flag = 0;
8775
									foreach($pl['opt']['ff'] as $val) {
8776
										$flag += 1 << ($val - 1);
8777
									}
8778
								} else {
8779
									$flag = intval($pl['opt']['ff']);
8780
								}
8781
								$annots .= ' /Ff '.$flag;
8782
							}
8783
							if (isset($pl['opt']['maxlen'])) {
8784
								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8785
							}
8786
							if (isset($pl['opt']['v'])) {
8787
								$annots .= ' /V';
8788
								if (is_array($pl['opt']['v'])) {
8789
									foreach ($pl['opt']['v'] AS $optval) {
8790
										if (is_float($optval)) {
8791
											$optval = sprintf('%F', $optval);
8792
										}
8793
										$annots .= ' '.$optval;
8794
									}
8795
								} else {
8796
									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8797
								}
8798
							}
8799
							if (isset($pl['opt']['dv'])) {
8800
								$annots .= ' /DV';
8801
								if (is_array($pl['opt']['dv'])) {
8802
									foreach ($pl['opt']['dv'] AS $optval) {
8803
										if (is_float($optval)) {
8804
											$optval = sprintf('%F', $optval);
8805
										}
8806
										$annots .= ' '.$optval;
8807
									}
8808
								} else {
8809
									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8810
								}
8811
							}
8812
							if (isset($pl['opt']['rv'])) {
8813
								$annots .= ' /RV';
8814
								if (is_array($pl['opt']['rv'])) {
8815
									foreach ($pl['opt']['rv'] AS $optval) {
8816
										if (is_float($optval)) {
8817
											$optval = sprintf('%F', $optval);
8818
										}
8819
										$annots .= ' '.$optval;
8820
									}
8821
								} else {
8822
									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8823
								}
8824
							}
8825
							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8826
								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8827
							}
8828
							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8829
								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8830
							}
8831
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8832
								$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
8833
							}
8834
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8835
								$annots .= ' /Q '.intval($pl['opt']['q']);
8836
							}
8837
							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8838
								$annots .= ' /Opt [';
8839
								foreach($pl['opt']['opt'] AS $copt) {
8840
									if (is_array($copt)) {
8841
										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8842
									} else {
8843
										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8844
									}
8845
								}
8846
								$annots .= ']';
8847
							}
8848
							if (isset($pl['opt']['ti'])) {
8849
								$annots .= ' /TI '.intval($pl['opt']['ti']);
8850
							}
8851
							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8852
								$annots .= ' /I [';
8853
								foreach($pl['opt']['i'] AS $copt) {
8854
									$annots .= intval($copt).' ';
8855
								}
8856
								$annots .= ']';
8857
							}
8858
							break;
8859
						}
8860
						case 'screen': {
8861
							break;
8862
						}
8863
						case 'printermark': {
8864
							break;
8865
						}
8866
						case 'trapnet': {
8867
							break;
8868
						}
8869
						case 'watermark': {
8870
							break;
8871
						}
8872
						case '3d': {
8873
							break;
8874
						}
8875
						default: {
8876
							break;
8877
						}
8878
					}
8879
					$annots .= '>>';
8880
					// create new annotation object
8881
					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8882
					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8883
						// store reference of form object
8884
						$this->form_obj_id[] = $annot_obj_id;
8885
					}
8886
				}
8887
			}
8888
		} // end for each page
8889
	}
8890
8891
	/**
8892
	 * Put appearance streams XObject used to define annotation's appearance states.
8893
	 * @param int $w annotation width
8894
	 * @param int $h annotation height
8895
	 * @param string $stream appearance stream
8896
	 * @return int object ID
8897
	 * @protected
8898
	 * @since 4.8.001 (2009-09-09)
8899
	 */
8900
	protected function _putAPXObject($w=0, $h=0, $stream='') {
8901
		$stream = trim($stream);
8902
		$out = $this->_getobj()."\n";
8903
		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8904
		$out .= '<<';
8905
		$out .= ' /Type /XObject';
8906
		$out .= ' /Subtype /Form';
8907
		$out .= ' /FormType 1';
8908
		if ($this->compress) {
8909
			$stream = gzcompress($stream);
8910
			$out .= ' /Filter /FlateDecode';
8911
		}
8912
		$rect = sprintf('%F %F', $w, $h);
8913
		$out .= ' /BBox [0 0 '.$rect.']';
8914
		$out .= ' /Matrix [1 0 0 1 0 0]';
8915
		$out .= ' /Resources 2 0 R';
8916
		$stream = $this->_getrawstream($stream);
8917
		$out .= ' /Length '.strlen($stream);
8918
		$out .= ' >>';
8919
		$out .= ' stream'."\n".$stream."\n".'endstream';
8920
		$out .= "\n".'endobj';
8921
		$this->_out($out);
8922
		return $this->n;
8923
	}
8924
8925
	/**
8926
	 * Output fonts.
8927
	 * @author Nicola Asuni
8928
	 * @protected
8929
	 */
8930
	protected function _putfonts() {
8931
		$nf = $this->n;
8932
		foreach ($this->diffs as $diff) {
8933
			//Encodings
8934
			$this->_newobj();
8935
			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8936
		}
8937
		foreach ($this->FontFiles as $file => $info) {
8938
			// search and get font file to embedd
8939
			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8940
			if (!TCPDF_STATIC::empty_string($fontfile)) {
8941
				$font = file_get_contents($fontfile);
8942
				$compressed = (substr($file, -2) == '.z');
8943
				if ((!$compressed) AND (isset($info['length2']))) {
8944
					$header = (ord($font[0]) == 128);
8945
					if ($header) {
8946
						// strip first binary header
8947
						$font = substr($font, 6);
8948
					}
8949
					if ($header AND (ord($font[$info['length1']]) == 128)) {
8950
						// strip second binary header
8951
						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8952
					}
8953
				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8954
					if ($compressed) {
8955
						// uncompress font
8956
						$font = gzuncompress($font);
8957
					}
8958
					// merge subset characters
8959
					$subsetchars = array(); // used chars
8960
					foreach ($info['fontkeys'] as $fontkey) {
8961
						$fontinfo = $this->getFontBuffer($fontkey);
8962
						$subsetchars += $fontinfo['subsetchars'];
8963
					}
8964
					// rebuild a font subset
8965
					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8966
					// calculate new font length
8967
					$info['length1'] = strlen($font);
8968
					if ($compressed) {
8969
						// recompress font
8970
						$font = gzcompress($font);
8971
					}
8972
				}
8973
				$this->_newobj();
8974
				$this->FontFiles[$file]['n'] = $this->n;
8975
				$stream = $this->_getrawstream($font);
8976
				$out = '<< /Length '.strlen($stream);
8977
				if ($compressed) {
8978
					$out .= ' /Filter /FlateDecode';
8979
				}
8980
				$out .= ' /Length1 '.$info['length1'];
8981
				if (isset($info['length2'])) {
8982
					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8983
				}
8984
				$out .= ' >>';
8985
				$out .= ' stream'."\n".$stream."\n".'endstream';
8986
				$out .= "\n".'endobj';
8987
				$this->_out($out);
8988
			}
8989
		}
8990
		foreach ($this->fontkeys as $k) {
8991
			//Font objects
8992
			$font = $this->getFontBuffer($k);
8993
			$type = $font['type'];
8994
			$name = $font['name'];
8995
			if ($type == 'core') {
8996
				// standard core font
8997
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8998
				$out .= '<</Type /Font';
8999
				$out .= ' /Subtype /Type1';
9000
				$out .= ' /BaseFont /'.$name;
9001
				$out .= ' /Name /F'.$font['i'];
9002
				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
9003
					$out .= ' /Encoding /WinAnsiEncoding';
9004
				}
9005
				if ($k == 'helvetica') {
9006
					// add default font for annotations
9007
					$this->annotation_fonts[$k] = $font['i'];
9008
				}
9009
				$out .= ' >>';
9010
				$out .= "\n".'endobj';
9011
				$this->_out($out);
9012
			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
9013
				// additional Type1 or TrueType font
9014
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
9015
				$out .= '<</Type /Font';
9016
				$out .= ' /Subtype /'.$type;
9017
				$out .= ' /BaseFont /'.$name;
9018
				$out .= ' /Name /F'.$font['i'];
9019
				$out .= ' /FirstChar 32 /LastChar 255';
9020
				$out .= ' /Widths '.($this->n + 1).' 0 R';
9021
				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
9022
				if ($font['enc']) {
9023
					if (isset($font['diff'])) {
9024
						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
9025
					} else {
9026
						$out .= ' /Encoding /WinAnsiEncoding';
9027
					}
9028
				}
9029
				$out .= ' >>';
9030
				$out .= "\n".'endobj';
9031
				$this->_out($out);
9032
				// Widths
9033
				$this->_newobj();
9034
				$s = '[';
9035
				for ($i = 32; $i < 256; ++$i) {
9036
					if (isset($font['cw'][$i])) {
9037
						$s .= $font['cw'][$i].' ';
9038
					} else {
9039
						$s .= $font['dw'].' ';
9040
					}
9041
				}
9042
				$s .= ']';
9043
				$s .= "\n".'endobj';
9044
				$this->_out($s);
9045
				//Descriptor
9046
				$this->_newobj();
9047
				$s = '<</Type /FontDescriptor /FontName /'.$name;
9048
				foreach ($font['desc'] as $fdk => $fdv) {
9049
					if (is_float($fdv)) {
9050
						$fdv = sprintf('%F', $fdv);
9051
					}
9052
					$s .= ' /'.$fdk.' '.$fdv.'';
9053
				}
9054
				if (!TCPDF_STATIC::empty_string($font['file'])) {
9055
					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9056
				}
9057
				$s .= '>>';
9058
				$s .= "\n".'endobj';
9059
				$this->_out($s);
9060
			} else {
9061
				// additional types
9062
				$mtd = '_put'.strtolower($type);
9063
				if (!method_exists($this, $mtd)) {
9064
					$this->Error('Unsupported font type: '.$type);
9065
				}
9066
				$this->$mtd($font);
9067
			}
9068
		}
9069
	}
9070
9071
	/**
9072
	 * Adds unicode fonts.<br>
9073
	 * Based on PDF Reference 1.3 (section 5)
9074
	 * @param array $font font data
9075
	 * @protected
9076
	 * @author Nicola Asuni
9077
	 * @since 1.52.0.TC005 (2005-01-05)
9078
	 */
9079
	protected function _puttruetypeunicode($font) {
9080
		$fontname = '';
9081
		if ($font['subset']) {
9082
			// change name for font subsetting
9083
			$subtag = sprintf('%06u', $font['i']);
9084
			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9085
			$fontname .= $subtag.'+';
9086
		}
9087
		$fontname .= $font['name'];
9088
		// Type0 Font
9089
		// A composite font composed of other fonts, organized hierarchically
9090
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9091
		$out .= '<< /Type /Font';
9092
		$out .= ' /Subtype /Type0';
9093
		$out .= ' /BaseFont /'.$fontname;
9094
		$out .= ' /Name /F'.$font['i'];
9095
		$out .= ' /Encoding /'.$font['enc'];
9096
		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9097
		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9098
		$out .= ' >>';
9099
		$out .= "\n".'endobj';
9100
		$this->_out($out);
9101
		// ToUnicode map for Identity-H
9102
		$stream = TCPDF_FONT_DATA::$uni_identity_h;
9103
		// ToUnicode Object
9104
		$this->_newobj();
9105
		$stream = ($this->compress) ? gzcompress($stream) : $stream;
9106
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9107
		$stream = $this->_getrawstream($stream);
9108
		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9109
		// CIDFontType2
9110
		// A CIDFont whose glyph descriptions are based on TrueType font technology
9111
		$oid = $this->_newobj();
9112
		$out = '<< /Type /Font';
9113
		$out .= ' /Subtype /CIDFontType2';
9114
		$out .= ' /BaseFont /'.$fontname;
9115
		// A dictionary containing entries that define the character collection of the CIDFont.
9116
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9117
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9118
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9119
		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9120
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9121
		$out .= ' /DW '.$font['dw']; // default width
9122
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9123
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9124
			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9125
		}
9126
		$out .= ' >>';
9127
		$out .= "\n".'endobj';
9128
		$this->_out($out);
9129
		// Font descriptor
9130
		// A font descriptor describing the CIDFont default metrics other than its glyph widths
9131
		$this->_newobj();
9132
		$out = '<< /Type /FontDescriptor';
9133
		$out .= ' /FontName /'.$fontname;
9134
		foreach ($font['desc'] as $key => $value) {
9135
			if (is_float($value)) {
9136
				$value = sprintf('%F', $value);
9137
			}
9138
			$out .= ' /'.$key.' '.$value;
9139
		}
9140
		$fontdir = false;
9141
		if (!TCPDF_STATIC::empty_string($font['file'])) {
9142
			// A stream containing a TrueType font
9143
			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9144
			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
9145
		}
9146
		$out .= ' >>';
9147
		$out .= "\n".'endobj';
9148
		$this->_out($out);
9149
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9150
			$this->_newobj();
9151
			// Embed CIDToGIDMap
9152
			// A specification of the mapping from CIDs to glyph indices
9153
			// search and get CTG font file to embedd
9154
			$ctgfile = strtolower($font['ctg']);
9155
			// search and get ctg font file to embedd
9156
			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9157
			if (TCPDF_STATIC::empty_string($fontfile)) {
9158
				$this->Error('Font file not found: '.$ctgfile);
9159
			}
9160
			$stream = $this->_getrawstream(file_get_contents($fontfile));
9161
			$out = '<< /Length '.strlen($stream).'';
9162
			if (substr($fontfile, -2) == '.z') { // check file extension
9163
				// Decompresses data encoded using the public-domain
9164
				// zlib/deflate compression method, reproducing the
9165
				// original text or binary data
9166
				$out .= ' /Filter /FlateDecode';
9167
			}
9168
			$out .= ' >>';
9169
			$out .= ' stream'."\n".$stream."\n".'endstream';
9170
			$out .= "\n".'endobj';
9171
			$this->_out($out);
9172
		}
9173
	}
9174
9175
	/**
9176
	 * Output CID-0 fonts.
9177
	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9178
	 * @param array $font font data
9179
	 * @protected
9180
	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9181
	 * @since 3.2.000 (2008-06-23)
9182
	 */
9183
	protected function _putcidfont0($font) {
9184
		$cidoffset = 0;
9185
		if (!isset($font['cw'][1])) {
9186
			$cidoffset = 31;
9187
		}
9188
		if (isset($font['cidinfo']['uni2cid'])) {
9189
			// convert unicode to cid.
9190
			$uni2cid = $font['cidinfo']['uni2cid'];
9191
			$cw = array();
9192
			foreach ($font['cw'] as $uni => $width) {
9193
				if (isset($uni2cid[$uni])) {
9194
					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9195
				} elseif ($uni < 256) {
9196
					$cw[$uni] = $width;
9197
				} // else unknown character
9198
			}
9199
			$font = array_merge($font, array('cw' => $cw));
9200
		}
9201
		$name = $font['name'];
9202
		$enc = $font['enc'];
9203
		if ($enc) {
9204
			$longname = $name.'-'.$enc;
9205
		} else {
9206
			$longname = $name;
9207
		}
9208
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9209
		$out .= '<</Type /Font';
9210
		$out .= ' /Subtype /Type0';
9211
		$out .= ' /BaseFont /'.$longname;
9212
		$out .= ' /Name /F'.$font['i'];
9213
		if ($enc) {
9214
			$out .= ' /Encoding /'.$enc;
9215
		}
9216
		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9217
		$out .= ' >>';
9218
		$out .= "\n".'endobj';
9219
		$this->_out($out);
9220
		$oid = $this->_newobj();
9221
		$out = '<</Type /Font';
9222
		$out .= ' /Subtype /CIDFontType0';
9223
		$out .= ' /BaseFont /'.$name;
9224
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9225
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9226
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9227
		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9228
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9229
		$out .= ' /DW '.$font['dw'];
9230
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9231
		$out .= ' >>';
9232
		$out .= "\n".'endobj';
9233
		$this->_out($out);
9234
		$this->_newobj();
9235
		$s = '<</Type /FontDescriptor /FontName /'.$name;
9236
		foreach ($font['desc'] as $k => $v) {
9237
			if ($k != 'Style') {
9238
				if (is_float($v)) {
9239
					$v = sprintf('%F', $v);
9240
				}
9241
				$s .= ' /'.$k.' '.$v.'';
9242
			}
9243
		}
9244
		$s .= '>>';
9245
		$s .= "\n".'endobj';
9246
		$this->_out($s);
9247
	}
9248
9249
	/**
9250
	 * Output images.
9251
	 * @protected
9252
	 */
9253
	protected function _putimages() {
9254
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9255
		foreach ($this->imagekeys as $file) {
9256
			$info = $this->getImageBuffer($file);
9257
			// set object for alternate images array
9258
			$altoid = null;
9259
			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9260
				$altoid = $this->_newobj();
9261
				$out = '[';
9262
				foreach ($info['altimgs'] as $altimage) {
9263
					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9264
						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9265
						$out .= ' /DefaultForPrinting';
9266
						if ($altimage[1] === true) {
9267
							$out .= ' true';
9268
						} else {
9269
							$out .= ' false';
9270
						}
9271
						$out .= ' >>';
9272
					}
9273
				}
9274
				$out .= ' ]';
9275
				$out .= "\n".'endobj';
9276
				$this->_out($out);
9277
			}
9278
			// set image object
9279
			$oid = $this->_newobj();
9280
			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9281
			$this->setImageSubBuffer($file, 'n', $this->n);
9282
			$out = '<</Type /XObject';
9283
			$out .= ' /Subtype /Image';
9284
			$out .= ' /Width '.$info['w'];
9285
			$out .= ' /Height '.$info['h'];
9286
			if (array_key_exists('masked', $info)) {
9287
				$out .= ' /SMask '.($this->n - 1).' 0 R';
9288
			}
9289
			// set color space
9290
			$icc = false;
9291
			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9292
				// ICC Colour Space
9293
				$icc = true;
9294
				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9295
			} elseif ($info['cs'] == 'Indexed') {
9296
				// Indexed Colour Space
9297
				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9298
			} else {
9299
				// Device Colour Space
9300
				$out .= ' /ColorSpace /'.$info['cs'];
9301
			}
9302
			if ($info['cs'] == 'DeviceCMYK') {
9303
				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9304
			}
9305
			$out .= ' /BitsPerComponent '.$info['bpc'];
9306
			if ($altoid > 0) {
9307
				// reference to alternate images dictionary
9308
				$out .= ' /Alternates '.$altoid.' 0 R';
9309
			}
9310
			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9311
				// external stream
9312
				$out .= ' /Length 0';
9313
				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9314
				if (isset($info['f'])) {
9315
					$out .= ' /FFilter /'.$info['f'];
9316
				}
9317
				$out .= ' >>';
9318
				$out .= ' stream'."\n".'endstream';
9319
			} else {
9320
				if (isset($info['f'])) {
9321
					$out .= ' /Filter /'.$info['f'];
9322
				}
9323
				if (isset($info['parms'])) {
9324
					$out .= ' '.$info['parms'];
9325
				}
9326
				if (isset($info['trns']) AND is_array($info['trns'])) {
9327
					$trns = '';
9328
					$count_info = count($info['trns']);
9329
					if ($info['cs'] == 'Indexed') {
9330
						$maxval =(pow(2, $info['bpc']) - 1);
9331
						for ($i = 0; $i < $count_info; ++$i) {
9332
							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9333
								// this is not a binary type mask @TODO: create a SMask
9334
								$trns = '';
9335
								break;
9336
							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9337
								// store the first fully transparent value
9338
								$trns .= $i.' '.$i.' ';
9339
							}
9340
						}
9341
					} else {
9342
						// grayscale or RGB
9343
						for ($i = 0; $i < $count_info; ++$i) {
9344
							if ($info['trns'][$i] == 0) {
9345
								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9346
							}
9347
						}
9348
					}
9349
					// Colour Key Masking
9350
					if (!empty($trns)) {
9351
						$out .= ' /Mask ['.$trns.']';
9352
					}
9353
				}
9354
				$stream = $this->_getrawstream($info['data']);
9355
				$out .= ' /Length '.strlen($stream).' >>';
9356
				$out .= ' stream'."\n".$stream."\n".'endstream';
9357
			}
9358
			$out .= "\n".'endobj';
9359
			$this->_out($out);
9360
			if ($icc) {
9361
				// ICC colour profile
9362
				$this->_newobj();
9363
				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9364
				$icc = $this->_getrawstream($icc);
9365
				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9366
			} elseif ($info['cs'] == 'Indexed') {
9367
				// colour palette
9368
				$this->_newobj();
9369
				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9370
				$pal = $this->_getrawstream($pal);
9371
				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9372
			}
9373
		}
9374
	}
9375
9376
	/**
9377
	 * Output Form XObjects Templates.
9378
	 * @author Nicola Asuni
9379
	 * @since 5.8.017 (2010-08-24)
9380
	 * @protected
9381
	 * @see startTemplate(), endTemplate(), printTemplate()
9382
	 */
9383
	protected function _putxobjects() {
9384
		foreach ($this->xobjects as $key => $data) {
9385
			if (isset($data['outdata'])) {
9386
				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9387
				$out = $this->_getobj($data['n'])."\n";
9388
				$out .= '<<';
9389
				$out .= ' /Type /XObject';
9390
				$out .= ' /Subtype /Form';
9391
				$out .= ' /FormType 1';
9392
				if ($this->compress) {
9393
					$stream = gzcompress($stream);
9394
					$out .= ' /Filter /FlateDecode';
9395
				}
9396
				$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));
9397
				$out .= ' /Matrix [1 0 0 1 0 0]';
9398
				$out .= ' /Resources <<';
9399
				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9400
				if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9401
					// transparency
9402
					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9403
						$out .= ' /ExtGState <<';
9404
						foreach ($data['extgstates'] as $k => $extgstate) {
9405
							if (isset($this->extgstates[$k]['name'])) {
9406
								$out .= ' /'.$this->extgstates[$k]['name'];
9407
							} else {
9408
								$out .= ' /GS'.$k;
9409
							}
9410
							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9411
						}
9412
						$out .= ' >>';
9413
					}
9414
					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9415
						$gp = '';
9416
						$gs = '';
9417
						foreach ($data['gradients'] as $id => $grad) {
9418
							// gradient patterns
9419
							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9420
							// gradient shadings
9421
							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9422
						}
9423
						$out .= ' /Pattern <<'.$gp.' >>';
9424
						$out .= ' /Shading <<'.$gs.' >>';
9425
					}
9426
				}
9427
				// spot colors
9428
				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9429
					$out .= ' /ColorSpace <<';
9430
					foreach ($data['spot_colors'] as $name => $color) {
9431
						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9432
					}
9433
					$out .= ' >>';
9434
				}
9435
				// fonts
9436
				if (!empty($data['fonts'])) {
9437
					$out .= ' /Font <<';
9438
					foreach ($data['fonts'] as $fontkey => $fontid) {
9439
						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9440
					}
9441
					$out .= ' >>';
9442
				}
9443
				// images or nested xobjects
9444
				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9445
					$out .= ' /XObject <<';
9446
					foreach ($data['images'] as $imgid) {
9447
						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9448
					}
9449
					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9450
						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9451
					}
9452
					$out .= ' >>';
9453
				}
9454
				$out .= ' >>'; //end resources
9455
				if (isset($data['group']) AND ($data['group'] !== false)) {
9456
					// set transparency group
9457
					$out .= ' /Group << /Type /Group /S /Transparency';
9458
					if (is_array($data['group'])) {
9459
						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9460
							$out .= ' /CS /'.$data['group']['CS'];
9461
						}
9462
						if (isset($data['group']['I'])) {
9463
							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9464
						}
9465
						if (isset($data['group']['K'])) {
9466
							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9467
						}
9468
					}
9469
					$out .= ' >>';
9470
				}
9471
				$stream = $this->_getrawstream($stream, $data['n']);
9472
				$out .= ' /Length '.strlen($stream);
9473
				$out .= ' >>';
9474
				$out .= ' stream'."\n".$stream."\n".'endstream';
9475
				$out .= "\n".'endobj';
9476
				$this->_out($out);
9477
			}
9478
		}
9479
	}
9480
9481
	/**
9482
	 * Output Spot Colors Resources.
9483
	 * @protected
9484
	 * @since 4.0.024 (2008-09-12)
9485
	 */
9486
	protected function _putspotcolors() {
9487
		foreach ($this->spot_colors as $name => $color) {
9488
			$this->_newobj();
9489
			$this->spot_colors[$name]['n'] = $this->n;
9490
			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9491
			$out .= ' /DeviceCMYK <<';
9492
			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9493
			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9494
			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9495
			$out .= "\n".'endobj';
9496
			$this->_out($out);
9497
		}
9498
	}
9499
9500
	/**
9501
	 * Return XObjects Dictionary.
9502
	 * @return string XObjects dictionary
9503
	 * @protected
9504
	 * @since 5.8.014 (2010-08-23)
9505
	 */
9506
	protected function _getxobjectdict() {
9507
		$out = '';
9508
		foreach ($this->xobjects as $id => $objid) {
9509
			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9510
		}
9511
		return $out;
9512
	}
9513
9514
	/**
9515
	 * Output Resources Dictionary.
9516
	 * @protected
9517
	 */
9518
	protected function _putresourcedict() {
9519
		$out = $this->_getobj(2)."\n";
9520
		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9521
		$out .= ' /Font <<';
9522
		foreach ($this->fontkeys as $fontkey) {
9523
			$font = $this->getFontBuffer($fontkey);
9524
			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9525
		}
9526
		$out .= ' >>';
9527
		$out .= ' /XObject <<';
9528
		$out .= $this->_getxobjectdict();
9529
		$out .= ' >>';
9530
		// layers
9531
		if (!empty($this->pdflayers)) {
9532
			$out .= ' /Properties <<';
9533
			foreach ($this->pdflayers as $layer) {
9534
				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9535
			}
9536
			$out .= ' >>';
9537
		}
9538
		if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9539
			// transparency
9540
			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9541
				$out .= ' /ExtGState <<';
9542
				foreach ($this->extgstates as $k => $extgstate) {
9543
					if (isset($extgstate['name'])) {
9544
						$out .= ' /'.$extgstate['name'];
9545
					} else {
9546
						$out .= ' /GS'.$k;
9547
					}
9548
					$out .= ' '.$extgstate['n'].' 0 R';
9549
				}
9550
				$out .= ' >>';
9551
			}
9552
			if (isset($this->gradients) AND !empty($this->gradients)) {
9553
				$gp = '';
9554
				$gs = '';
9555
				foreach ($this->gradients as $id => $grad) {
9556
					// gradient patterns
9557
					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9558
					// gradient shadings
9559
					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9560
				}
9561
				$out .= ' /Pattern <<'.$gp.' >>';
9562
				$out .= ' /Shading <<'.$gs.' >>';
9563
			}
9564
		}
9565
		// spot colors
9566
		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9567
			$out .= ' /ColorSpace <<';
9568
			foreach ($this->spot_colors as $color) {
9569
				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9570
			}
9571
			$out .= ' >>';
9572
		}
9573
		$out .= ' >>';
9574
		$out .= "\n".'endobj';
9575
		$this->_out($out);
9576
	}
9577
9578
	/**
9579
	 * Output Resources.
9580
	 * @protected
9581
	 */
9582
	protected function _putresources() {
9583
		$this->_putextgstates();
9584
		$this->_putocg();
9585
		$this->_putfonts();
9586
		$this->_putimages();
9587
		$this->_putspotcolors();
9588
		$this->_putshaders();
9589
		$this->_putxobjects();
9590
		$this->_putresourcedict();
9591
		$this->_putdests();
9592
		$this->_putEmbeddedFiles();
9593
		$this->_putannotsobjs();
9594
		$this->_putjavascript();
9595
		$this->_putbookmarks();
9596
		$this->_putencryption();
9597
	}
9598
9599
	/**
9600
	 * Adds some Metadata information (Document Information Dictionary)
9601
	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9602
	 * @return int object id
9603
	 * @protected
9604
	 */
9605
	protected function _putinfo() {
9606
		$oid = $this->_newobj();
9607
		$out = '<<';
9608
		// store current isunicode value
9609
		$prev_isunicode = $this->isunicode;
9610
		if ($this->docinfounicode) {
9611
			$this->isunicode = true;
9612
		}
9613
		if (!TCPDF_STATIC::empty_string($this->title)) {
9614
			// The document's title.
9615
			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9616
		}
9617
		if (!TCPDF_STATIC::empty_string($this->author)) {
9618
			// The name of the person who created the document.
9619
			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9620
		}
9621
		if (!TCPDF_STATIC::empty_string($this->subject)) {
9622
			// The subject of the document.
9623
			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9624
		}
9625
		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9626
			// Keywords associated with the document.
9627
			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9628
		}
9629
		if (!TCPDF_STATIC::empty_string($this->creator)) {
9630
			// 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.
9631
			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9632
		}
9633
		// restore previous isunicode value
9634
		$this->isunicode = $prev_isunicode;
9635
		// default producer
9636
		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9637
		// The date and time the document was created, in human-readable form
9638
		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9639
		// The date and time the document was most recently modified, in human-readable form
9640
		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9641
		// A name object indicating whether the document has been modified to include trapping information
9642
		$out .= ' /Trapped /False';
9643
		$out .= ' >>';
9644
		$out .= "\n".'endobj';
9645
		$this->_out($out);
9646
		return $oid;
9647
	}
9648
9649
	/**
9650
	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9651
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9652
	 * @param string $xmp Custom XMP data.
9653
	 * @since 5.9.128 (2011-10-06)
9654
	 * @public
9655
	 */
9656
	public function setExtraXMP($xmp) {
9657
		$this->custom_xmp = $xmp;
9658
	}
9659
9660
	/**
9661
	 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9662
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9663
	 * @param string $xmp Custom XMP RDF data.
9664
	 * @since 6.3.0 (2019-09-19)
9665
	 * @public
9666
	 */
9667
	public function setExtraXMPRDF($xmp) {
9668
		$this->custom_xmp_rdf = $xmp;
9669
	}
9670
9671
	/**
9672
	 * Set additional XMP data to be added to the default XMP data for PDF/A extensions.
9673
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9674
	 * @param string $xmp Custom XMP RDF data.
9675
	 * @since 6.9.0 (2025-02-14)
9676
	 * @public
9677
	 */
9678
	public function setExtraXMPPdfaextension($xmp) {
9679
		$this->custom_xmp_rdf_pdfaExtension = $xmp;
9680
	}
9681
9682
	/**
9683
	 * Put XMP data object and return ID.
9684
	 * @return int The object ID.
9685
	 * @since 5.9.121 (2011-09-28)
9686
	 * @protected
9687
	 */
9688
	protected function _putXMP() {
9689
		$oid = $this->_newobj();
9690
		// store current isunicode value
9691
		$prev_isunicode = $this->isunicode;
9692
		$this->isunicode = true;
9693
		$prev_encrypted = $this->encrypted;
9694
		$this->encrypted = false;
9695
		// set XMP data
9696
		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9697
		$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";
9698
		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9699
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9700
		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9701
		$xmp .= "\t\t\t".'<dc:title>'."\n";
9702
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9703
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9704
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9705
		$xmp .= "\t\t\t".'</dc:title>'."\n";
9706
		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9707
		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9708
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9709
		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9710
		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9711
		$xmp .= "\t\t\t".'<dc:description>'."\n";
9712
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9713
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9714
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9715
		$xmp .= "\t\t\t".'</dc:description>'."\n";
9716
		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9717
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9718
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9719
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9720
		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9721
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9722
		// convert doc creation date format
9723
		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9724
		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9725
		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9726
		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9727
		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9728
		// convert doc modification date format
9729
		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9730
		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9731
		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9732
		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9733
		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9734
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9735
		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9736
		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9737
		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9738
		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9739
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9740
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9741
		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9742
		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9743
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9744
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9745
		$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);
9746
		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9747
		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9748
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9749
		if ($this->pdfa_mode) {
9750
			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9751
			$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9752
			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9753
			$xmp .= "\t\t".'</rdf:Description>'."\n";
9754
		}
9755
		// XMP extension schemas
9756
		$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";
9757
		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9758
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9759
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9760
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9761
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9762
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9763
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9764
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9765
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9766
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9767
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9768
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9769
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9770
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9771
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9772
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9773
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9774
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9775
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9776
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9777
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9778
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9779
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9780
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9781
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9782
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9783
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9784
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9785
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9786
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9787
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9788
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9789
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9790
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9791
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9792
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9793
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9794
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9795
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9796
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9797
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9798
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9799
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9800
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9801
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9802
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9803
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9804
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9805
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9806
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9807
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9808
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9809
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9810
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9811
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9812
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9813
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9814
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9815
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9816
		$xmp .= $this->custom_xmp_rdf_pdfaExtension;
9817
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9818
		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9819
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9820
		$xmp .= $this->custom_xmp_rdf;
9821
		$xmp .= "\t".'</rdf:RDF>'."\n";
9822
		$xmp .= $this->custom_xmp;
9823
		$xmp .= '</x:xmpmeta>'."\n";
9824
		$xmp .= '<?xpacket end="w"?>';
9825
		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9826
		// restore previous isunicode value
9827
		$this->isunicode = $prev_isunicode;
9828
		$this->encrypted = $prev_encrypted;
9829
		$this->_out($out);
9830
		return $oid;
9831
	}
9832
9833
	/**
9834
	 * Output Catalog.
9835
	 * @return int object id
9836
	 * @protected
9837
	 */
9838
	protected function _putcatalog() {
9839
		// put XMP
9840
		$xmpobj = $this->_putXMP();
9841
		// if required, add standard sRGB ICC colour profile
9842
		if ($this->pdfa_mode OR $this->force_srgb) {
9843
			$iccobj = $this->_newobj();
9844
			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9845
			$filter = '';
9846
			if ($this->compress) {
9847
				$filter = ' /Filter /FlateDecode';
9848
				$icc = gzcompress($icc);
9849
			}
9850
			$icc = $this->_getrawstream($icc);
9851
			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9852
		}
9853
		// start catalog
9854
		$oid = $this->_newobj();
9855
		$out = '<< ';
9856
		if (!empty($this->efnames)) {
9857
			$out .= ' /AF [ '. implode(' ', $this->efnames) .' ]';
9858
		}
9859
		$out .= ' /Type /Catalog';
9860
		$out .= ' /Version /'.$this->PDFVersion;
9861
		//$out .= ' /Extensions <<>>';
9862
		$out .= ' /Pages 1 0 R';
9863
		//$out .= ' /PageLabels ' //...;
9864
		$out .= ' /Names <<';
9865
		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9866
			$out .= ' /JavaScript '.$this->n_js;
9867
		}
9868
		if (!empty($this->efnames)) {
9869
			$out .= ' /EmbeddedFiles <</Names [';
9870
			foreach ($this->efnames AS $fn => $fref) {
9871
				$out .= ' '.$this->_datastring($fn).' '.$fref;
9872
			}
9873
			$out .= ' ]>>';
9874
		}
9875
		$out .= ' >>';
9876
		if (!empty($this->dests)) {
9877
			$out .= ' /Dests '.($this->n_dests).' 0 R';
9878
		}
9879
		$out .= $this->_putviewerpreferences();
9880
		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9881
			$out .= ' /PageLayout /'.$this->LayoutMode;
9882
		}
9883
		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9884
			$out .= ' /PageMode /'.$this->PageMode;
9885
		}
9886
		if (count($this->outlines) > 0) {
9887
			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9888
			$out .= ' /PageMode /UseOutlines';
9889
		}
9890
		//$out .= ' /Threads []';
9891
		if ($this->ZoomMode == 'fullpage') {
9892
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9893
		} elseif ($this->ZoomMode == 'fullwidth') {
9894
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9895
		} elseif ($this->ZoomMode == 'real') {
9896
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9897
		} elseif (!is_string($this->ZoomMode)) {
9898
			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9899
		}
9900
		//$out .= ' /AA <<>>';
9901
		//$out .= ' /URI <<>>';
9902
		$out .= ' /Metadata '.$xmpobj.' 0 R';
9903
		//$out .= ' /StructTreeRoot <<>>';
9904
		//$out .= ' /MarkInfo <<>>';
9905
		if (isset($this->l['a_meta_language'])) {
9906
			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9907
		}
9908
		//$out .= ' /SpiderInfo <<>>';
9909
		// set OutputIntent to sRGB IEC61966-2.1 if required
9910
		if ($this->pdfa_mode OR $this->force_srgb) {
9911
			$out .= ' /OutputIntents [<<';
9912
			$out .= ' /Type /OutputIntent';
9913
			$out .= ' /S /GTS_PDFA1';
9914
			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9915
			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9916
			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9917
			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9918
			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9919
			$out .= ' >>]';
9920
		}
9921
		//$out .= ' /PieceInfo <<>>';
9922
		if (!empty($this->pdflayers)) {
9923
			$lyrobjs = '';
9924
			$lyrobjs_off = '';
9925
			$lyrobjs_lock = '';
9926
			foreach ($this->pdflayers as $layer) {
9927
				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9928
				$lyrobjs .= $layer_obj_ref;
9929
				if ($layer['view'] === false) {
9930
					$lyrobjs_off .= $layer_obj_ref;
9931
				}
9932
				if ($layer['lock']) {
9933
					$lyrobjs_lock .= $layer_obj_ref;
9934
				}
9935
			}
9936
			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9937
			$out .= ' /D <<';
9938
			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9939
			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9940
			$out .= ' /BaseState /ON';
9941
			$out .= ' /OFF ['.$lyrobjs_off.']';
9942
			$out .= ' /Locked ['.$lyrobjs_lock.']';
9943
			$out .= ' /Intent /View';
9944
			$out .= ' /AS [';
9945
			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9946
			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9947
			$out .= ' ]';
9948
			$out .= ' /Order ['.$lyrobjs.']';
9949
			$out .= ' /ListMode /AllPages';
9950
			//$out .= ' /RBGroups ['..']';
9951
			//$out .= ' /Locked ['..']';
9952
			$out .= ' >>';
9953
			$out .= ' >>';
9954
		}
9955
		// AcroForm
9956
		if (!empty($this->form_obj_id)
9957
			OR ($this->sign AND isset($this->signature_data['cert_type']))
9958
			OR !empty($this->empty_signature_appearance)) {
9959
			$out .= ' /AcroForm <<';
9960
			$objrefs = '';
9961
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9962
				// set reference for signature object
9963
				$objrefs .= $this->sig_obj_id.' 0 R';
9964
			}
9965
			if (!empty($this->empty_signature_appearance)) {
9966
				foreach ($this->empty_signature_appearance as $esa) {
9967
					// set reference for empty signature objects
9968
					$objrefs .= ' '.$esa['objid'].' 0 R';
9969
				}
9970
			}
9971
			if (!empty($this->form_obj_id)) {
9972
				foreach($this->form_obj_id as $objid) {
9973
					$objrefs .= ' '.$objid.' 0 R';
9974
				}
9975
			}
9976
			$out .= ' /Fields ['.$objrefs.']';
9977
			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9978
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9979
				$out .= ' /NeedAppearances false';
9980
			}
9981
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9982
				if ($this->signature_data['cert_type'] > 0) {
9983
					$out .= ' /SigFlags 3';
9984
				} else {
9985
					$out .= ' /SigFlags 1';
9986
				}
9987
			}
9988
			//$out .= ' /CO ';
9989
			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9990
				$out .= ' /DR <<';
9991
				$out .= ' /Font <<';
9992
				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9993
					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9994
				}
9995
				$out .= ' >> >>';
9996
			}
9997
			$font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
9998
			$out .= ' /DA ' . $this->_datastring('/F'.$font['i'].' 0 Tf 0 g');
9999
			$out .= ' /Q '.(($this->rtl)?'2':'0');
10000
			//$out .= ' /XFA ';
10001
			$out .= ' >>';
10002
			// signatures
10003
			if ($this->sign AND isset($this->signature_data['cert_type'])
10004
				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
10005
				if ($this->signature_data['cert_type'] > 0) {
10006
					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
10007
				} else {
10008
					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
10009
				}
10010
			}
10011
		}
10012
		//$out .= ' /Legal <<>>';
10013
		//$out .= ' /Requirements []';
10014
		//$out .= ' /Collection <<>>';
10015
		//$out .= ' /NeedsRendering true';
10016
		$out .= ' >>';
10017
		$out .= "\n".'endobj';
10018
		$this->_out($out);
10019
		return $oid;
10020
	}
10021
10022
	/**
10023
	 * Output viewer preferences.
10024
	 * @return string for viewer preferences
10025
	 * @author Nicola asuni
10026
	 * @since 3.1.000 (2008-06-09)
10027
	 * @protected
10028
	 */
10029
	protected function _putviewerpreferences() {
10030
		$vp = $this->viewer_preferences;
10031
		$out = ' /ViewerPreferences <<';
10032
		if ($this->rtl) {
10033
			$out .= ' /Direction /R2L';
10034
		} else {
10035
			$out .= ' /Direction /L2R';
10036
		}
10037
		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
10038
			$out .= ' /HideToolbar true';
10039
		}
10040
		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
10041
			$out .= ' /HideMenubar true';
10042
		}
10043
		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
10044
			$out .= ' /HideWindowUI true';
10045
		}
10046
		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
10047
			$out .= ' /FitWindow true';
10048
		}
10049
		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
10050
			$out .= ' /CenterWindow true';
10051
		}
10052
		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
10053
			$out .= ' /DisplayDocTitle true';
10054
		}
10055
		if (isset($vp['NonFullScreenPageMode'])) {
10056
			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
10057
		}
10058
		if (isset($vp['ViewArea'])) {
10059
			$out .= ' /ViewArea /'.$vp['ViewArea'];
10060
		}
10061
		if (isset($vp['ViewClip'])) {
10062
			$out .= ' /ViewClip /'.$vp['ViewClip'];
10063
		}
10064
		if (isset($vp['PrintArea'])) {
10065
			$out .= ' /PrintArea /'.$vp['PrintArea'];
10066
		}
10067
		if (isset($vp['PrintClip'])) {
10068
			$out .= ' /PrintClip /'.$vp['PrintClip'];
10069
		}
10070
		if (isset($vp['PrintScaling'])) {
10071
			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
10072
		}
10073
		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
10074
			$out .= ' /Duplex /'.$vp['Duplex'];
10075
		}
10076
		if (isset($vp['PickTrayByPDFSize'])) {
10077
			if ($vp['PickTrayByPDFSize']) {
10078
				$out .= ' /PickTrayByPDFSize true';
10079
			} else {
10080
				$out .= ' /PickTrayByPDFSize false';
10081
			}
10082
		}
10083
		if (isset($vp['PrintPageRange'])) {
10084
			$PrintPageRangeNum = '';
10085
			foreach ($vp['PrintPageRange'] as $k => $v) {
10086
				$PrintPageRangeNum .= ' '.($v - 1).'';
10087
			}
10088
			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10089
		}
10090
		if (isset($vp['NumCopies'])) {
10091
			$out .= ' /NumCopies '.intval($vp['NumCopies']);
10092
		}
10093
		$out .= ' >>';
10094
		return $out;
10095
	}
10096
10097
	/**
10098
	 * Output PDF File Header (7.5.2).
10099
	 * @protected
10100
	 */
10101
	protected function _putheader() {
10102
		$this->_out('%PDF-'.$this->PDFVersion);
10103
		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10104
	}
10105
10106
	/**
10107
	 * Output end of document (EOF).
10108
	 * @protected
10109
	 */
10110
	protected function _enddoc() {
10111
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10112
			// save subset chars of the previous font
10113
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10114
		}
10115
		$this->state = 1;
10116
		$this->_putheader();
10117
		$this->_putpages();
10118
		$this->_putresources();
10119
		// empty signature fields
10120
		if (!empty($this->empty_signature_appearance)) {
10121
			foreach ($this->empty_signature_appearance as $key => $esa) {
10122
				// widget annotation for empty signature
10123
				$out = $this->_getobj($esa['objid'])."\n";
10124
				$out .= '<< /Type /Annot';
10125
				$out .= ' /Subtype /Widget';
10126
				$out .= ' /Rect ['.$esa['rect'].']';
10127
				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10128
				$out .= ' /F 4';
10129
				$out .= ' /FT /Sig';
10130
				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10131
				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10132
				$out .= ' /Ff 0';
10133
				$out .= ' >>';
10134
				$out .= "\n".'endobj';
10135
				$this->_out($out);
10136
			}
10137
		}
10138
		// Signature
10139
		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10140
			// widget annotation for signature
10141
			$out = $this->_getobj($this->sig_obj_id)."\n";
10142
			$out .= '<< /Type /Annot';
10143
			$out .= ' /Subtype /Widget';
10144
			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10145
			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10146
			$out .= ' /F 4';
10147
			$out .= ' /FT /Sig';
10148
			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10149
			$out .= ' /Ff 0';
10150
			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10151
			$out .= ' >>';
10152
			$out .= "\n".'endobj';
10153
			$this->_out($out);
10154
			// signature
10155
			$this->_putsignature();
10156
		}
10157
		// Info
10158
		$objid_info = $this->_putinfo();
10159
		// Catalog
10160
		$objid_catalog = $this->_putcatalog();
10161
		// Cross-ref
10162
		$o = $this->bufferlen;
10163
		// XREF section
10164
		$this->_out('xref');
10165
		$this->_out('0 '.($this->n + 1));
10166
		$this->_out('0000000000 65535 f ');
10167
		$freegen = ($this->n + 2);
10168
		for ($i=1; $i <= $this->n; ++$i) {
10169
			if (!isset($this->offsets[$i]) AND ($i > 1)) {
10170
				$this->_out(sprintf('0000000000 %05d f ', $freegen));
10171
				++$freegen;
10172
			} else {
10173
				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10174
			}
10175
		}
10176
		// TRAILER
10177
		$out = 'trailer'."\n";
10178
		$out .= '<<';
10179
		$out .= ' /Size '.($this->n + 1);
10180
		$out .= ' /Root '.$objid_catalog.' 0 R';
10181
		$out .= ' /Info '.$objid_info.' 0 R';
10182
		if ($this->encrypted) {
10183
			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10184
		}
10185
		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10186
		$out .= ' >>';
10187
		$this->_out($out);
10188
		$this->_out('startxref');
10189
		$this->_out($o);
10190
		$this->_out('%%EOF');
10191
		$this->state = 3; // end-of-doc
10192
	}
10193
10194
	/**
10195
	 * Initialize a new page.
10196
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10197
	 * @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().
10198
	 * @protected
10199
	 * @see getPageSizeFromFormat(), setPageFormat()
10200
	 */
10201
	protected function _beginpage($orientation='', $format='') {
10202
		++$this->page;
10203
		$this->pageobjects[$this->page] = array();
10204
		$this->setPageBuffer($this->page, '');
10205
		// initialize array for graphics tranformation positions inside a page buffer
10206
		$this->transfmrk[$this->page] = array();
10207
		$this->state = 2;
10208
		if (TCPDF_STATIC::empty_string($orientation)) {
10209
			if (isset($this->CurOrientation)) {
10210
				$orientation = $this->CurOrientation;
10211
			} elseif ($this->fwPt > $this->fhPt) {
10212
				// landscape
10213
				$orientation = 'L';
10214
			} else {
10215
				// portrait
10216
				$orientation = 'P';
10217
			}
10218
		}
10219
		if (TCPDF_STATIC::empty_string($format)) {
10220
			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10221
			$this->setPageOrientation($orientation);
10222
		} else {
10223
			$this->setPageFormat($format, $orientation);
10224
		}
10225
		if ($this->rtl) {
10226
			$this->x = $this->w - $this->rMargin;
10227
		} else {
10228
			$this->x = $this->lMargin;
10229
		}
10230
		$this->y = $this->tMargin;
10231
		if (isset($this->newpagegroup[$this->page])) {
10232
			// start a new group
10233
			$this->currpagegroup = $this->newpagegroup[$this->page];
10234
			$this->pagegroups[$this->currpagegroup] = 1;
10235
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10236
			++$this->pagegroups[$this->currpagegroup];
10237
		}
10238
	}
10239
10240
	/**
10241
	 * Mark end of page.
10242
	 * @protected
10243
	 */
10244
	protected function _endpage() {
10245
		$this->setVisibility('all');
10246
		$this->state = 1;
10247
	}
10248
10249
	/**
10250
	 * Begin a new object and return the object number.
10251
	 * @return int object number
10252
	 * @protected
10253
	 */
10254
	protected function _newobj() {
10255
		$this->_out($this->_getobj());
10256
		return $this->n;
10257
	}
10258
10259
	/**
10260
	 * Return the starting object string for the selected object ID.
10261
	 * @param int|null $objid Object ID (leave empty to get a new ID).
10262
	 * @return string the starting object string
10263
	 * @protected
10264
	 * @since 5.8.009 (2010-08-20)
10265
	 */
10266
	protected function _getobj($objid=null) {
10267
		if (TCPDF_STATIC::empty_string($objid)) {
10268
			++$this->n;
10269
			$objid = $this->n;
10270
		}
10271
		$this->offsets[$objid] = $this->bufferlen;
10272
		$this->pageobjects[$this->page][] = $objid;
10273
		return $objid.' 0 obj';
10274
	}
10275
10276
	/**
10277
	 * Underline text.
10278
	 * @param int $x X coordinate
10279
	 * @param int $y Y coordinate
10280
	 * @param string $txt text to underline
10281
	 * @protected
10282
	 */
10283
	protected function _dounderline($x, $y, $txt) {
10284
		$w = $this->GetStringWidth($txt);
10285
		return $this->_dounderlinew($x, $y, $w);
10286
	}
10287
10288
	/**
10289
	 * Underline for rectangular text area.
10290
	 * @param int $x X coordinate
10291
	 * @param int $y Y coordinate
10292
	 * @param int $w width to underline
10293
	 * @protected
10294
	 * @since 4.8.008 (2009-09-29)
10295
	 */
10296
	protected function _dounderlinew($x, $y, $w) {
10297
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10298
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10299
	}
10300
10301
	/**
10302
	 * Line through text.
10303
	 * @param int $x X coordinate
10304
	 * @param int $y Y coordinate
10305
	 * @param string $txt text to linethrough
10306
	 * @protected
10307
	 */
10308
	protected function _dolinethrough($x, $y, $txt) {
10309
		$w = $this->GetStringWidth($txt);
10310
		return $this->_dolinethroughw($x, $y, $w);
10311
	}
10312
10313
	/**
10314
	 * Line through for rectangular text area.
10315
	 * @param int $x X coordinate
10316
	 * @param int $y Y coordinate
10317
	 * @param int $w line length (width)
10318
	 * @protected
10319
	 * @since 4.9.008 (2009-09-29)
10320
	 */
10321
	protected function _dolinethroughw($x, $y, $w) {
10322
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10323
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10324
	}
10325
10326
	/**
10327
	 * Overline text.
10328
	 * @param int $x X coordinate
10329
	 * @param int $y Y coordinate
10330
	 * @param string $txt text to overline
10331
	 * @protected
10332
	 * @since 4.9.015 (2010-04-19)
10333
	 */
10334
	protected function _dooverline($x, $y, $txt) {
10335
		$w = $this->GetStringWidth($txt);
10336
		return $this->_dooverlinew($x, $y, $w);
10337
	}
10338
10339
	/**
10340
	 * Overline for rectangular text area.
10341
	 * @param int $x X coordinate
10342
	 * @param int $y Y coordinate
10343
	 * @param int $w width to overline
10344
	 * @protected
10345
	 * @since 4.9.015 (2010-04-19)
10346
	 */
10347
	protected function _dooverlinew($x, $y, $w) {
10348
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10349
		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10350
10351
	}
10352
10353
	/**
10354
	 * Format a data string for meta information
10355
	 * @param string $s data string to escape.
10356
	 * @param int $n object ID
10357
	 * @return string escaped string.
10358
	 * @protected
10359
	 */
10360
	protected function _datastring($s, $n=0) {
10361
		if ($n == 0) {
10362
			$n = $this->n;
10363
		}
10364
		$s = $this->_encrypt_data($n, $s);
10365
		return '('. TCPDF_STATIC::_escape($s).')';
10366
	}
10367
10368
	/**
10369
	 * Set the document creation timestamp
10370
	 * @param mixed $time Document creation timestamp in seconds or date-time string.
10371
	 * @public
10372
	 * @since 5.9.152 (2012-03-23)
10373
	 */
10374
	public function setDocCreationTimestamp($time) {
10375
		if (is_string($time)) {
10376
			$time = TCPDF_STATIC::getTimestamp($time);
10377
		}
10378
		$this->doc_creation_timestamp = intval($time);
10379
	}
10380
10381
	/**
10382
	 * Set the document modification timestamp
10383
	 * @param mixed $time Document modification timestamp in seconds or date-time string.
10384
	 * @public
10385
	 * @since 5.9.152 (2012-03-23)
10386
	 */
10387
	public function setDocModificationTimestamp($time) {
10388
		if (is_string($time)) {
10389
			$time = TCPDF_STATIC::getTimestamp($time);
10390
		}
10391
		$this->doc_modification_timestamp = intval($time);
10392
	}
10393
10394
	/**
10395
	 * Returns document creation timestamp in seconds.
10396
	 * @return int Creation timestamp in seconds.
10397
	 * @public
10398
	 * @since 5.9.152 (2012-03-23)
10399
	 */
10400
	public function getDocCreationTimestamp() {
10401
		return $this->doc_creation_timestamp;
10402
	}
10403
10404
	/**
10405
	 * Returns document modification timestamp in seconds.
10406
	 * @return int Modfication timestamp in seconds.
10407
	 * @public
10408
	 * @since 5.9.152 (2012-03-23)
10409
	 */
10410
	public function getDocModificationTimestamp() {
10411
		return $this->doc_modification_timestamp;
10412
	}
10413
10414
	/**
10415
	 * Returns a formatted date for meta information
10416
	 * @param int $n Object ID.
10417
	 * @param int $timestamp Timestamp to convert.
10418
	 * @return string escaped date string.
10419
	 * @protected
10420
	 * @since 4.6.028 (2009-08-25)
10421
	 */
10422
	protected function _datestring($n=0, $timestamp=0) {
10423
		if ((empty($timestamp)) OR ($timestamp < 0)) {
10424
			$timestamp = $this->doc_creation_timestamp;
10425
		}
10426
		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10427
	}
10428
10429
	/**
10430
	 * Format a text string for meta information
10431
	 * @param string $s string to escape.
10432
	 * @param int $n object ID
10433
	 * @return string escaped string.
10434
	 * @protected
10435
	 */
10436
	protected function _textstring($s, $n=0) {
10437
		if ($this->isunicode) {
10438
			//Convert string to UTF-16BE
10439
			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10440
		}
10441
		return $this->_datastring($s, $n);
10442
	}
10443
10444
	/**
10445
	 * get raw output stream.
10446
	 * @param string $s string to output.
10447
	 * @param int $n object reference for encryption mode
10448
	 * @protected
10449
	 * @author Nicola Asuni
10450
	 * @since 5.5.000 (2010-06-22)
10451
	 */
10452
	protected function _getrawstream($s, $n=0) {
10453
		if ($n <= 0) {
10454
			// default to current object
10455
			$n = $this->n;
10456
		}
10457
		return $this->_encrypt_data($n, $s);
10458
	}
10459
10460
	/**
10461
	 * Output a string to the document.
10462
	 * @param string $s string to output.
10463
	 * @protected
10464
	 */
10465
	protected function _out($s) {
10466
		if ($this->state == 2) {
10467
			if ($this->inxobj) {
10468
				// we are inside an XObject template
10469
				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10470
			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10471
				// puts data before page footer
10472
				$pagebuff = $this->getPageBuffer($this->page);
10473
				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10474
				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10475
				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10476
				// update footer position
10477
				$this->footerpos[$this->page] += strlen($s."\n");
10478
			} else {
10479
				// set page data
10480
				$this->setPageBuffer($this->page, $s."\n", true);
10481
			}
10482
		} elseif ($this->state > 0) {
10483
			// set general data
10484
			$this->setBuffer($s."\n");
10485
		}
10486
	}
10487
10488
	/**
10489
	 * Set header font.
10490
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10491
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10492
	 * @public
10493
	 * @since 1.1
10494
	 */
10495
	public function setHeaderFont($font) {
10496
		$this->header_font = $font;
10497
	}
10498
10499
	/**
10500
	 * Get header font.
10501
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10502
	 * @phpstan-return array{0: string, 1: string, 2: float|null}
10503
	 * @public
10504
	 * @since 4.0.012 (2008-07-24)
10505
	 */
10506
	public function getHeaderFont() {
10507
		return $this->header_font;
10508
	}
10509
10510
	/**
10511
	 * Set footer font.
10512
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10513
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10514
	 * @public
10515
	 * @since 1.1
10516
	 */
10517
	public function setFooterFont($font) {
10518
		$this->footer_font = $font;
10519
	}
10520
10521
	/**
10522
	 * Get Footer font.
10523
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10524
	 * @phpstan-return array{0: string, 1: string, 2: float|null} $font
10525
	 * @public
10526
	 * @since 4.0.012 (2008-07-24)
10527
	 */
10528
	public function getFooterFont() {
10529
		return $this->footer_font;
10530
	}
10531
10532
	/**
10533
	 * Set language array.
10534
	 * @param array $language
10535
	 * @public
10536
	 * @since 1.1
10537
	 */
10538
	public function setLanguageArray($language) {
10539
		$this->l = $language;
10540
		if (isset($this->l['a_meta_dir'])) {
10541
			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10542
		} else {
10543
			$this->rtl = false;
10544
		}
10545
	}
10546
10547
	/**
10548
	 * Returns the PDF data.
10549
	 * @public
10550
	 */
10551
	public function getPDFData() {
10552
		if ($this->state < 3) {
10553
			$this->Close();
10554
		}
10555
		return $this->buffer;
10556
	}
10557
10558
	/**
10559
	 * Output anchor link.
10560
	 * @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;)
10561
	 * @param string $name link name
10562
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10563
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
10564
	 * @param array|null $color array of RGB text color
10565
	 * @param string $style font style (U, D, B, I)
10566
	 * @param boolean $firstblock if true the string is the starting of a line.
10567
	 * @return int the number of cells used or the remaining text if $firstline = true;
10568
	 * @public
10569
	 */
10570
	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) {
10571
		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10572
			// convert url to internal link
10573
			$lnkdata = explode(',', $url);
10574
			if (isset($lnkdata[0]) ) {
10575
				$page = substr($lnkdata[0], 1);
10576
				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10577
					$lnky = floatval($lnkdata[1]);
10578
				} else {
10579
					$lnky = 0;
10580
				}
10581
				$url = $this->AddLink();
10582
				$this->setLink($url, $lnky, $page);
10583
			}
10584
		}
10585
		// store current settings
10586
		$prevcolor = $this->fgcolor;
10587
		$prevstyle = $this->FontStyle;
10588
		if (empty($color)) {
10589
			$this->setTextColorArray($this->htmlLinkColorArray);
10590
		} else {
10591
			$this->setTextColorArray($color);
10592
		}
10593
		if ($style == -1) {
10594
			$this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10595
		} else {
10596
			$this->setFont('', $this->FontStyle.$style);
10597
		}
10598
		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10599
		// restore settings
10600
		$this->setFont('', $prevstyle);
10601
		$this->setTextColorArray($prevcolor);
10602
		return $ret;
10603
	}
10604
10605
	/**
10606
	 * Converts pixels to User's Units.
10607
	 * @param int $px pixels
10608
	 * @return float value in user's unit
10609
	 * @public
10610
	 * @see setImageScale(), getImageScale()
10611
	 */
10612
	public function pixelsToUnits($px) {
10613
		return ($px / ($this->imgscale * $this->k));
10614
	}
10615
10616
	/**
10617
	 * Reverse function for htmlentities.
10618
	 * Convert entities in UTF-8.
10619
	 * @param string $text_to_convert Text to convert.
10620
	 * @return string converted text string
10621
	 * @public
10622
	 */
10623
	public function unhtmlentities($text_to_convert) {
10624
		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10625
	}
10626
10627
	// ENCRYPTION METHODS ----------------------------------
10628
10629
	/**
10630
	 * Compute encryption key depending on object number where the encrypted data is stored.
10631
	 * This is used for all strings and streams without crypt filter specifier.
10632
	 * @param int $n object number
10633
	 * @return int object key
10634
	 * @protected
10635
	 * @author Nicola Asuni
10636
	 * @since 2.0.000 (2008-01-02)
10637
	 */
10638
	protected function _objectkey($n) {
10639
		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10640
		if ($this->encryptdata['mode'] == 2) { // AES-128
10641
			// AES padding
10642
			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10643
		}
10644
		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10645
		$objkey = substr($objkey, 0, 16);
10646
		return $objkey;
10647
	}
10648
10649
	/**
10650
	 * Encrypt the input string.
10651
	 * @param int $n object number
10652
	 * @param string $s data string to encrypt
10653
	 * @return string encrypted string
10654
	 * @protected
10655
	 * @author Nicola Asuni
10656
	 * @since 5.0.005 (2010-05-11)
10657
	 */
10658
	protected function _encrypt_data($n, $s) {
10659
		if (!$this->encrypted) {
10660
			return $s;
10661
		}
10662
		switch ($this->encryptdata['mode']) {
10663
			case 0:   // RC4-40
10664
			case 1: { // RC4-128
10665
				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10666
				break;
10667
			}
10668
			case 2: { // AES-128
10669
				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10670
				break;
10671
			}
10672
			case 3: { // AES-256
10673
				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10674
				break;
10675
			}
10676
		}
10677
		return $s;
10678
	}
10679
10680
	/**
10681
	 * Put encryption on PDF document.
10682
	 * @protected
10683
	 * @author Nicola Asuni
10684
	 * @since 2.0.000 (2008-01-02)
10685
	 */
10686
	protected function _putencryption() {
10687
		if (!$this->encrypted) {
10688
			return;
10689
		}
10690
		$this->encryptdata['objid'] = $this->_newobj();
10691
		$out = '<<';
10692
		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10693
			$this->encryptdata['Filter'] = 'Standard';
10694
		}
10695
		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10696
		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10697
			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10698
		}
10699
		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10700
			$this->encryptdata['V'] = 1;
10701
		}
10702
		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10703
		$out .= ' /V '.$this->encryptdata['V'];
10704
		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10705
			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10706
			$out .= ' /Length '.$this->encryptdata['Length'];
10707
		} else {
10708
			$out .= ' /Length 40';
10709
		}
10710
		if ($this->encryptdata['V'] >= 4) {
10711
			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10712
				$this->encryptdata['StmF'] = 'Identity';
10713
			}
10714
			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10715
				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10716
				$this->encryptdata['StrF'] = 'Identity';
10717
			}
10718
			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10719
			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10720
				$out .= ' /CF <<';
10721
				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10722
				$out .= ' /Type /CryptFilter';
10723
				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10724
					// The method used
10725
					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10726
					if ($this->encryptdata['pubkey']) {
10727
						$out .= ' /Recipients [';
10728
						foreach ($this->encryptdata['Recipients'] as $rec) {
10729
							$out .= ' <'.$rec.'>';
10730
						}
10731
						$out .= ' ]';
10732
						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10733
							$out .= ' /EncryptMetadata false';
10734
						} else {
10735
							$out .= ' /EncryptMetadata true';
10736
						}
10737
					}
10738
				} else {
10739
					$out .= ' /CFM /None';
10740
				}
10741
				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10742
					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10743
					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10744
				} else {
10745
					$out .= ' /AuthEvent /DocOpen';
10746
				}
10747
				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10748
					// The bit length of the encryption key.
10749
					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10750
				}
10751
				$out .= ' >> >>';
10752
			}
10753
			// The name of the crypt filter that shall be used by default when decrypting streams.
10754
			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10755
			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10756
			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10757
			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10758
				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10759
				$out .= ' /EFF /'.$this->encryptdata[''];
10760
			}
10761
		}
10762
		// Additional encryption dictionary entries for the standard security handler
10763
		if ($this->encryptdata['pubkey']) {
10764
			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10765
				$out .= ' /Recipients [';
10766
				foreach ($this->encryptdata['Recipients'] as $rec) {
10767
					$out .= ' <'.$rec.'>';
10768
				}
10769
				$out .= ' ]';
10770
			}
10771
		} else {
10772
			$out .= ' /R';
10773
			if ($this->encryptdata['V'] == 5) { // AES-256
10774
				$out .= ' 5';
10775
				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10776
				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10777
				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10778
			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10779
				$out .= ' 4';
10780
			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10781
				$out .= ' 2';
10782
			} else { // RC-128
10783
				$out .= ' 3';
10784
			}
10785
			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10786
			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10787
			$out .= ' /P '.$this->encryptdata['P'];
10788
			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10789
				$out .= ' /EncryptMetadata false';
10790
			} else {
10791
				$out .= ' /EncryptMetadata true';
10792
			}
10793
		}
10794
		$out .= ' >>';
10795
		$out .= "\n".'endobj';
10796
		$this->_out($out);
10797
	}
10798
10799
	/**
10800
	 * Compute U value (used for encryption)
10801
	 * @return string U value
10802
	 * @protected
10803
	 * @since 2.0.000 (2008-01-02)
10804
	 * @author Nicola Asuni
10805
	 */
10806
	protected function _Uvalue() {
10807
		if ($this->encryptdata['mode'] == 0) { // RC4-40
10808
			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10809
		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10810
			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10811
			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10812
			$len = strlen($tmp);
10813
			for ($i = 1; $i <= 19; ++$i) {
10814
				$ek = '';
10815
				for ($j = 0; $j < $len; ++$j) {
10816
					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10817
				}
10818
				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10819
			}
10820
			$enc .= str_repeat("\x00", 16);
10821
			return substr($enc, 0, 32);
10822
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10823
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10824
			// User Validation Salt
10825
			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10826
			// User Key Salt
10827
			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10828
			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10829
		}
10830
	}
10831
10832
	/**
10833
	 * Compute UE value (used for encryption)
10834
	 * @return string UE value
10835
	 * @protected
10836
	 * @since 5.9.006 (2010-10-19)
10837
	 * @author Nicola Asuni
10838
	 */
10839
	protected function _UEvalue() {
10840
		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10841
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10842
	}
10843
10844
	/**
10845
	 * Compute O value (used for encryption)
10846
	 * @return string O value
10847
	 * @protected
10848
	 * @since 2.0.000 (2008-01-02)
10849
	 * @author Nicola Asuni
10850
	 */
10851
	protected function _Ovalue() {
10852
		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10853
			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10854
			if ($this->encryptdata['mode'] > 0) {
10855
				for ($i = 0; $i < 50; ++$i) {
10856
					$tmp = TCPDF_STATIC::_md5_16($tmp);
10857
				}
10858
			}
10859
			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10860
			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10861
			if ($this->encryptdata['mode'] > 0) {
10862
				$len = strlen($owner_key);
10863
				for ($i = 1; $i <= 19; ++$i) {
10864
					$ek = '';
10865
					for ($j = 0; $j < $len; ++$j) {
10866
						$ek .= chr(ord($owner_key[$j]) ^ $i);
10867
					}
10868
					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10869
				}
10870
			}
10871
			return $enc;
10872
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10873
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10874
			// Owner Validation Salt
10875
			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10876
			// Owner Key Salt
10877
			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10878
			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10879
		}
10880
	}
10881
10882
	/**
10883
	 * Compute OE value (used for encryption)
10884
	 * @return string OE value
10885
	 * @protected
10886
	 * @since 5.9.006 (2010-10-19)
10887
	 * @author Nicola Asuni
10888
	 */
10889
	protected function _OEvalue() {
10890
		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10891
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10892
	}
10893
10894
	/**
10895
	 * Convert password for AES-256 encryption mode
10896
	 * @param string $password password
10897
	 * @return string password
10898
	 * @protected
10899
	 * @since 5.9.006 (2010-10-19)
10900
	 * @author Nicola Asuni
10901
	 */
10902
	protected function _fixAES256Password($password) {
10903
		$psw = ''; // password to be returned
10904
		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10905
		foreach ($psw_array as $c) {
10906
			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10907
		}
10908
		return substr($psw, 0, 127);
10909
	}
10910
10911
	/**
10912
	 * Compute encryption key
10913
	 * @protected
10914
	 * @since 2.0.000 (2008-01-02)
10915
	 * @author Nicola Asuni
10916
	 */
10917
	protected function _generateencryptionkey() {
10918
		$keybytelen = ($this->encryptdata['Length'] / 8);
10919
		if (!$this->encryptdata['pubkey']) { // standard mode
10920
			if ($this->encryptdata['mode'] == 3) { // AES-256
10921
				// generate 256 bit random key
10922
				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10923
				// truncate passwords
10924
				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10925
				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10926
				// Compute U value
10927
				$this->encryptdata['U'] = $this->_Uvalue();
10928
				// Compute UE value
10929
				$this->encryptdata['UE'] = $this->_UEvalue();
10930
				// Compute O value
10931
				$this->encryptdata['O'] = $this->_Ovalue();
10932
				// Compute OE value
10933
				$this->encryptdata['OE'] = $this->_OEvalue();
10934
				// Compute P value
10935
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10936
				// Computing the encryption dictionary's Perms (permissions) value
10937
				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10938
				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10939
				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10940
					$perms .= 'F';
10941
				} else {
10942
					$perms .= 'T';
10943
				}
10944
				$perms .= 'adb'; // bytes 9-11
10945
				$perms .= 'nick'; // bytes 12-15
10946
				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10947
			} else { // RC4-40, RC4-128, AES-128
10948
				// Pad passwords
10949
				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10950
				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10951
				// Compute O value
10952
				$this->encryptdata['O'] = $this->_Ovalue();
10953
				// get default permissions (reverse byte order)
10954
				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10955
				// Compute encryption key
10956
				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10957
				if ($this->encryptdata['mode'] > 0) {
10958
					for ($i = 0; $i < 50; ++$i) {
10959
						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10960
					}
10961
				}
10962
				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10963
				// Compute U value
10964
				$this->encryptdata['U'] = $this->_Uvalue();
10965
				// Compute P value
10966
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10967
			}
10968
		} else { // Public-Key mode
10969
			// random 20-byte seed
10970
			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10971
			$recipient_bytes = '';
10972
			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10973
				// for each public certificate
10974
				if (isset($pubkey['p'])) {
10975
					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10976
				} else {
10977
					$pkprotection = $this->encryptdata['protection'];
10978
				}
10979
				// get default permissions (reverse byte order)
10980
				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10981
				// envelope data
10982
				$envelope = $seed.$pkpermissions;
10983
				// write the envelope data to a temporary file
10984
				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10985
				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10986
				if (!$f) {
10987
					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10988
				}
10989
				$envelope_length = strlen($envelope);
10990
				fwrite($f, $envelope, $envelope_length);
10991
				fclose($f);
10992
				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10993
				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10994
					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10995
				}
10996
				// read encryption signature
10997
				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10998
				// extract signature
10999
				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
11000
				$tmparr = explode("\n\n", $signature);
11001
				$signature = trim($tmparr[1]);
11002
				unset($tmparr);
11003
				// decode signature
11004
				$signature = base64_decode($signature);
11005
				// convert signature to hex
11006
				$hexsignature = current(unpack('H*', $signature));
11007
				// store signature on recipients array
11008
				$this->encryptdata['Recipients'][] = $hexsignature;
11009
				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
11010
				$recipient_bytes .= $signature;
11011
			}
11012
			// calculate encryption key
11013
			if ($this->encryptdata['mode'] == 3) { // AES-256
11014
				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
11015
			} else { // RC4-40, RC4-128, AES-128
11016
				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
11017
			}
11018
		}
11019
	}
11020
11021
	/**
11022
	 * Set document protection
11023
	 * Remark: the protection against modification is for people who have the full Acrobat product.
11024
	 * 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.
11025
	 * 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.
11026
	 * @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>
11027
	 * @param string $user_pass user password. Empty by default.
11028
	 * @param string|null $owner_pass owner password. If not specified, a random value is used.
11029
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
11030
	 * @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')))
11031
	 * @public
11032
	 * @since 2.0.000 (2008-01-02)
11033
	 * @author Nicola Asuni
11034
	 */
11035
	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) {
11036
		if ($this->pdfa_mode) {
11037
			// encryption is not allowed in PDF/A mode
11038
			return;
11039
		}
11040
		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
11041
		if (($pubkeys !== null) AND (is_array($pubkeys))) {
11042
			// public-key mode
11043
			$this->encryptdata['pubkeys'] = $pubkeys;
11044
			if ($mode == 0) {
11045
				// public-Key Security requires at least 128 bit
11046
				$mode = 1;
11047
			}
11048
			if (!function_exists('openssl_pkcs7_encrypt')) {
11049
				$this->Error('Public-Key Security requires openssl library.');
11050
			}
11051
			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
11052
			$this->encryptdata['pubkey'] = true;
11053
			$this->encryptdata['Filter'] = 'Adobe.PubSec';
11054
			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
11055
			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
11056
		} else {
11057
			// standard mode (password mode)
11058
			$this->encryptdata['pubkey'] = false;
11059
			$this->encryptdata['Filter'] = 'Standard';
11060
			$this->encryptdata['StmF'] = 'StdCF';
11061
			$this->encryptdata['StrF'] = 'StdCF';
11062
		}
11063
		if ($mode > 1) { // AES
11064
			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
11065
				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
11066
			}
11067
			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
11068
				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
11069
			}
11070
			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
11071
				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11072
			}
11073
			if (($mode == 3) AND !function_exists('hash')) {
11074
				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11075
				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11076
			}
11077
		}
11078
		if ($owner_pass === null) {
11079
			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11080
		}
11081
		$this->encryptdata['user_password'] = $user_pass;
11082
		$this->encryptdata['owner_password'] = $owner_pass;
11083
		$this->encryptdata['mode'] = $mode;
11084
		switch ($mode) {
11085
			case 0: { // RC4 40 bit
11086
				$this->encryptdata['V'] = 1;
11087
				$this->encryptdata['Length'] = 40;
11088
				$this->encryptdata['CF']['CFM'] = 'V2';
11089
				break;
11090
			}
11091
			case 1: { // RC4 128 bit
11092
				$this->encryptdata['V'] = 2;
11093
				$this->encryptdata['Length'] = 128;
11094
				$this->encryptdata['CF']['CFM'] = 'V2';
11095
				if ($this->encryptdata['pubkey']) {
11096
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11097
					$this->encryptdata['Recipients'] = array();
11098
				}
11099
				break;
11100
			}
11101
			case 2: { // AES 128 bit
11102
				$this->encryptdata['V'] = 4;
11103
				$this->encryptdata['Length'] = 128;
11104
				$this->encryptdata['CF']['CFM'] = 'AESV2';
11105
				$this->encryptdata['CF']['Length'] = 16;
11106
				if ($this->encryptdata['pubkey']) {
11107
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11108
					$this->encryptdata['Recipients'] = array();
11109
				}
11110
				break;
11111
			}
11112
			case 3: { // AES 256 bit
11113
				$this->encryptdata['V'] = 5;
11114
				$this->encryptdata['Length'] = 256;
11115
				$this->encryptdata['CF']['CFM'] = 'AESV3';
11116
				$this->encryptdata['CF']['Length'] = 32;
11117
				if ($this->encryptdata['pubkey']) {
11118
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11119
					$this->encryptdata['Recipients'] = array();
11120
				}
11121
				break;
11122
			}
11123
		}
11124
		$this->encrypted = true;
11125
		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11126
		$this->_generateencryptionkey();
11127
	}
11128
11129
	// END OF ENCRYPTION FUNCTIONS -------------------------
11130
11131
	// START TRANSFORMATIONS SECTION -----------------------
11132
11133
	/**
11134
	 * Starts a 2D tranformation saving current graphic state.
11135
	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11136
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11137
	 * @public
11138
	 * @since 2.1.000 (2008-01-07)
11139
	 * @see StartTransform(), StopTransform()
11140
	 */
11141
	public function StartTransform() {
11142
		if ($this->state != 2) {
11143
			return;
11144
		}
11145
		$this->_outSaveGraphicsState();
11146
		if ($this->inxobj) {
11147
			// we are inside an XObject template
11148
			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11149
		} else {
11150
			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11151
		}
11152
		++$this->transfmatrix_key;
11153
		$this->transfmatrix[$this->transfmatrix_key] = array();
11154
	}
11155
11156
	/**
11157
	 * Stops a 2D tranformation restoring previous graphic state.
11158
	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11159
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11160
	 * @public
11161
	 * @since 2.1.000 (2008-01-07)
11162
	 * @see StartTransform(), StopTransform()
11163
	 */
11164
	public function StopTransform() {
11165
		if ($this->state != 2) {
11166
			return;
11167
		}
11168
		$this->_outRestoreGraphicsState();
11169
		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11170
			array_pop($this->transfmatrix[$this->transfmatrix_key]);
11171
			--$this->transfmatrix_key;
11172
		}
11173
		if ($this->inxobj) {
11174
			// we are inside an XObject template
11175
			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11176
		} else {
11177
			array_pop($this->transfmrk[$this->page]);
11178
		}
11179
	}
11180
	/**
11181
	 * Horizontal Scaling.
11182
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11183
	 * @param int $x abscissa of the scaling center. Default is current x position
11184
	 * @param int $y ordinate of the scaling center. Default is current y position
11185
	 * @public
11186
	 * @since 2.1.000 (2008-01-07)
11187
	 * @see StartTransform(), StopTransform()
11188
	 */
11189
	public function ScaleX($s_x, $x='', $y='') {
11190
		$this->Scale($s_x, 100, $x, $y);
11191
	}
11192
11193
	/**
11194
	 * Vertical Scaling.
11195
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11196
	 * @param int $x abscissa of the scaling center. Default is current x position
11197
	 * @param int $y ordinate of the scaling center. Default is current y position
11198
	 * @public
11199
	 * @since 2.1.000 (2008-01-07)
11200
	 * @see StartTransform(), StopTransform()
11201
	 */
11202
	public function ScaleY($s_y, $x='', $y='') {
11203
		$this->Scale(100, $s_y, $x, $y);
11204
	}
11205
11206
	/**
11207
	 * Vertical and horizontal proportional Scaling.
11208
	 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11209
	 * @param int $x abscissa of the scaling center. Default is current x position
11210
	 * @param int $y ordinate of the scaling center. Default is current y position
11211
	 * @public
11212
	 * @since 2.1.000 (2008-01-07)
11213
	 * @see StartTransform(), StopTransform()
11214
	 */
11215
	public function ScaleXY($s, $x='', $y='') {
11216
		$this->Scale($s, $s, $x, $y);
11217
	}
11218
11219
	/**
11220
	 * Vertical and horizontal non-proportional Scaling.
11221
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11222
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11223
	 * @param float|null $x abscissa of the scaling center. Default is current x position
11224
	 * @param float|null $y ordinate of the scaling center. Default is current y position
11225
	 * @public
11226
	 * @since 2.1.000 (2008-01-07)
11227
	 * @see StartTransform(), StopTransform()
11228
	 */
11229
	public function Scale($s_x, $s_y, $x=null, $y=null) {
11230
		if (TCPDF_STATIC::empty_string($x)) {
11231
			$x = $this->x;
11232
		}
11233
		if (TCPDF_STATIC::empty_string($y)) {
11234
			$y = $this->y;
11235
		}
11236
		if (($s_x == 0) OR ($s_y == 0)) {
11237
			$this->Error('Please do not use values equal to zero for scaling');
11238
		}
11239
		$y = ($this->h - $y) * $this->k;
11240
		$x *= $this->k;
11241
		//calculate elements of transformation matrix
11242
		$s_x /= 100;
11243
		$s_y /= 100;
11244
		$tm = array();
11245
		$tm[0] = $s_x;
11246
		$tm[1] = 0;
11247
		$tm[2] = 0;
11248
		$tm[3] = $s_y;
11249
		$tm[4] = $x * (1 - $s_x);
11250
		$tm[5] = $y * (1 - $s_y);
11251
		//scale the coordinate system
11252
		$this->Transform($tm);
11253
	}
11254
11255
	/**
11256
	 * Horizontal Mirroring.
11257
	 * @param float|null $x abscissa of the point. Default is current x position
11258
	 * @public
11259
	 * @since 2.1.000 (2008-01-07)
11260
	 * @see StartTransform(), StopTransform()
11261
	 */
11262
	public function MirrorH($x=null) {
11263
		$this->Scale(-100, 100, $x);
11264
	}
11265
11266
	/**
11267
	 * Verical Mirroring.
11268
	 * @param float|null $y ordinate of the point. Default is current y position
11269
	 * @public
11270
	 * @since 2.1.000 (2008-01-07)
11271
	 * @see StartTransform(), StopTransform()
11272
	 */
11273
	public function MirrorV($y=null) {
11274
		$this->Scale(100, -100, null, $y);
11275
	}
11276
11277
	/**
11278
	 * Point reflection mirroring.
11279
	 * @param float|null $x abscissa of the point. Default is current x position
11280
	 * @param float|null $y ordinate of the point. Default is current y position
11281
	 * @public
11282
	 * @since 2.1.000 (2008-01-07)
11283
	 * @see StartTransform(), StopTransform()
11284
	 */
11285
	public function MirrorP($x=null,$y=null) {
11286
		$this->Scale(-100, -100, $x, $y);
11287
	}
11288
11289
	/**
11290
	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11291
	 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11292
	 * @param float|null $x abscissa of the point. Default is current x position
11293
	 * @param float|null $y ordinate of the point. Default is current y position
11294
	 * @public
11295
	 * @since 2.1.000 (2008-01-07)
11296
	 * @see StartTransform(), StopTransform()
11297
	 */
11298
	public function MirrorL($angle=0, $x=null,$y=null) {
11299
		$this->Scale(-100, 100, $x, $y);
11300
		$this->Rotate(-2*($angle-90), $x, $y);
11301
	}
11302
11303
	/**
11304
	 * Translate graphic object horizontally.
11305
	 * @param int $t_x movement to the right (or left for RTL)
11306
	 * @public
11307
	 * @since 2.1.000 (2008-01-07)
11308
	 * @see StartTransform(), StopTransform()
11309
	 */
11310
	public function TranslateX($t_x) {
11311
		$this->Translate($t_x, 0);
11312
	}
11313
11314
	/**
11315
	 * Translate graphic object vertically.
11316
	 * @param int $t_y movement to the bottom
11317
	 * @public
11318
	 * @since 2.1.000 (2008-01-07)
11319
	 * @see StartTransform(), StopTransform()
11320
	 */
11321
	public function TranslateY($t_y) {
11322
		$this->Translate(0, $t_y);
11323
	}
11324
11325
	/**
11326
	 * Translate graphic object horizontally and vertically.
11327
	 * @param int $t_x movement to the right
11328
	 * @param int $t_y movement to the bottom
11329
	 * @public
11330
	 * @since 2.1.000 (2008-01-07)
11331
	 * @see StartTransform(), StopTransform()
11332
	 */
11333
	public function Translate($t_x, $t_y) {
11334
		//calculate elements of transformation matrix
11335
		$tm = array();
11336
		$tm[0] = 1;
11337
		$tm[1] = 0;
11338
		$tm[2] = 0;
11339
		$tm[3] = 1;
11340
		$tm[4] = $t_x * $this->k;
11341
		$tm[5] = -$t_y * $this->k;
11342
		//translate the coordinate system
11343
		$this->Transform($tm);
11344
	}
11345
11346
	/**
11347
	 * Rotate object.
11348
	 * @param float $angle angle in degrees for counter-clockwise rotation
11349
	 * @param float|null $x abscissa of the rotation center. Default is current x position
11350
	 * @param float|null $y ordinate of the rotation center. Default is current y position
11351
	 * @public
11352
	 * @since 2.1.000 (2008-01-07)
11353
	 * @see StartTransform(), StopTransform()
11354
	 */
11355
	public function Rotate($angle, $x=null, $y=null) {
11356
		if (TCPDF_STATIC::empty_string($x)) {
11357
			$x = $this->x;
11358
		}
11359
		if (TCPDF_STATIC::empty_string($y)) {
11360
			$y = $this->y;
11361
		}
11362
		$y = ($this->h - $y) * $this->k;
11363
		$x *= $this->k;
11364
		//calculate elements of transformation matrix
11365
		$tm = array();
11366
		$tm[0] = cos(deg2rad($angle));
11367
		$tm[1] = sin(deg2rad($angle));
11368
		$tm[2] = -$tm[1];
11369
		$tm[3] = $tm[0];
11370
		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11371
		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11372
		//rotate the coordinate system around ($x,$y)
11373
		$this->Transform($tm);
11374
	}
11375
11376
	/**
11377
	 * Skew horizontally.
11378
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11379
	 * @param float|null $x abscissa of the skewing center. default is current x position
11380
	 * @param float|null $y ordinate of the skewing center. default is current y position
11381
	 * @public
11382
	 * @since 2.1.000 (2008-01-07)
11383
	 * @see StartTransform(), StopTransform()
11384
	 */
11385
	public function SkewX($angle_x, $x=null, $y=null) {
11386
		$this->Skew($angle_x, 0, $x, $y);
11387
	}
11388
11389
	/**
11390
	 * Skew vertically.
11391
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11392
	 * @param float|null $x abscissa of the skewing center. default is current x position
11393
	 * @param float|null $y ordinate of the skewing center. default is current y position
11394
	 * @public
11395
	 * @since 2.1.000 (2008-01-07)
11396
	 * @see StartTransform(), StopTransform()
11397
	 */
11398
	public function SkewY($angle_y, $x=null, $y=null) {
11399
		$this->Skew(0, $angle_y, $x, $y);
11400
	}
11401
11402
	/**
11403
	 * Skew.
11404
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11405
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11406
	 * @param float|null $x abscissa of the skewing center. default is current x position
11407
	 * @param float|null $y ordinate of the skewing center. default is current y position
11408
	 * @public
11409
	 * @since 2.1.000 (2008-01-07)
11410
	 * @see StartTransform(), StopTransform()
11411
	 */
11412
	public function Skew($angle_x, $angle_y, $x=null, $y=null) {
11413
		if (TCPDF_STATIC::empty_string($x)) {
11414
			$x = $this->x;
11415
		}
11416
		if (TCPDF_STATIC::empty_string($y)) {
11417
			$y = $this->y;
11418
		}
11419
		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11420
			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11421
		}
11422
		$x *= $this->k;
11423
		$y = ($this->h - $y) * $this->k;
11424
		//calculate elements of transformation matrix
11425
		$tm = array();
11426
		$tm[0] = 1;
11427
		$tm[1] = tan(deg2rad($angle_y));
11428
		$tm[2] = tan(deg2rad($angle_x));
11429
		$tm[3] = 1;
11430
		$tm[4] = -$tm[2] * $y;
11431
		$tm[5] = -$tm[1] * $x;
11432
		//skew the coordinate system
11433
		$this->Transform($tm);
11434
	}
11435
11436
	/**
11437
	 * Apply graphic transformations.
11438
	 * @param array $tm transformation matrix
11439
	 * @protected
11440
	 * @since 2.1.000 (2008-01-07)
11441
	 * @see StartTransform(), StopTransform()
11442
	 */
11443
	protected function Transform($tm) {
11444
		if ($this->state != 2) {
11445
			return;
11446
		}
11447
		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11448
		// add tranformation matrix
11449
		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11450
		// update transformation mark
11451
		if ($this->inxobj) {
11452
			// we are inside an XObject template
11453
			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11454
				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11455
				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11456
			}
11457
		} elseif (end($this->transfmrk[$this->page]) !== false) {
11458
			$key = key($this->transfmrk[$this->page]);
11459
			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11460
		}
11461
	}
11462
11463
	// END TRANSFORMATIONS SECTION -------------------------
11464
11465
	// START GRAPHIC FUNCTIONS SECTION ---------------------
11466
	// The following section is based on the code provided by David Hernandez Sanz
11467
11468
	/**
11469
	 * 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.
11470
	 * @param float $width The width.
11471
	 * @public
11472
	 * @since 1.0
11473
	 * @see Line(), Rect(), Cell(), MultiCell()
11474
	 */
11475
	public function setLineWidth($width) {
11476
		//Set line width
11477
		$this->LineWidth = $width;
11478
		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11479
		if ($this->state == 2) {
11480
			$this->_out($this->linestyleWidth);
11481
		}
11482
	}
11483
11484
	/**
11485
	 * Returns the current the line width.
11486
	 * @return int Line width
11487
	 * @public
11488
	 * @since 2.1.000 (2008-01-07)
11489
	 * @see Line(), SetLineWidth()
11490
	 */
11491
	public function GetLineWidth() {
11492
		return $this->LineWidth;
11493
	}
11494
11495
	/**
11496
	 * Set line style.
11497
	 * @param array $style Line style. Array with keys among the following:
11498
	 * <ul>
11499
	 *	 <li>width (float): Width of the line in user units.</li>
11500
	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11501
	 * butt, round, square. The difference between "square" and "butt" is that
11502
	 * "square" projects a flat end past the end of the line.</li>
11503
	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11504
	 * bevel.</li>
11505
	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11506
	 * series of length values, which are the lengths of the on and off dashes.
11507
	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11508
	 * 1 off, 2 on, 1 off, ...</li>
11509
	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11510
	 * the point at which the pattern starts.</li>
11511
	 *	 <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>
11512
	 * </ul>
11513
	 * @param boolean $ret if true do not send the command.
11514
	 * @return string the PDF command
11515
	 * @public
11516
	 * @since 2.1.000 (2008-01-08)
11517
	 */
11518
	public function setLineStyle($style, $ret=false) {
11519
		$s = ''; // string to be returned
11520
		if (!is_array($style)) {
11521
			return $s;
11522
		}
11523
		if (isset($style['width'])) {
11524
			$this->LineWidth = $style['width'];
11525
			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11526
			$s .= $this->linestyleWidth.' ';
11527
		}
11528
		if (isset($style['cap'])) {
11529
			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11530
			if (isset($ca[$style['cap']])) {
11531
				$this->linestyleCap = $ca[$style['cap']].' J';
11532
				$s .= $this->linestyleCap.' ';
11533
			}
11534
		}
11535
		if (isset($style['join'])) {
11536
			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11537
			if (isset($ja[$style['join']])) {
11538
				$this->linestyleJoin = $ja[$style['join']].' j';
11539
				$s .= $this->linestyleJoin.' ';
11540
			}
11541
		}
11542
		if (isset($style['dash'])) {
11543
			$dash_string = '';
11544
			if ($style['dash']) {
11545
				if (preg_match('/^.+,/', $style['dash']) > 0) {
11546
					$tab = explode(',', $style['dash']);
11547
				} else {
11548
					$tab = array($style['dash']);
11549
				}
11550
				$dash_string = '';
11551
				foreach ($tab as $i => $v) {
11552
					if ($i) {
11553
						$dash_string .= ' ';
11554
					}
11555
					$dash_string .= sprintf('%F', $v);
11556
				}
11557
			}
11558
			if (!isset($style['phase']) OR !$style['dash']) {
11559
				$style['phase'] = 0;
11560
			}
11561
			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11562
			$s .= $this->linestyleDash.' ';
11563
		}
11564
		if (isset($style['color'])) {
11565
			$s .= $this->setDrawColorArray($style['color'], true).' ';
11566
		}
11567
		if (!$ret AND ($this->state == 2)) {
11568
			$this->_out($s);
11569
		}
11570
		return $s;
11571
	}
11572
11573
	/**
11574
	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11575
	 * @param float $x Abscissa of point.
11576
	 * @param float $y Ordinate of point.
11577
	 * @protected
11578
	 * @since 2.1.000 (2008-01-08)
11579
	 */
11580
	protected function _outPoint($x, $y) {
11581
		if ($this->state == 2) {
11582
			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11583
		}
11584
	}
11585
11586
	/**
11587
	 * Append a straight line segment from the current point to the point (x, y).
11588
	 * The new current point shall be (x, y).
11589
	 * @param float $x Abscissa of end point.
11590
	 * @param float $y Ordinate of end point.
11591
	 * @protected
11592
	 * @since 2.1.000 (2008-01-08)
11593
	 */
11594
	protected function _outLine($x, $y) {
11595
		if ($this->state == 2) {
11596
			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11597
		}
11598
	}
11599
11600
	/**
11601
	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11602
	 * @param float $x Abscissa of upper-left corner.
11603
	 * @param float $y Ordinate of upper-left corner.
11604
	 * @param float $w Width.
11605
	 * @param float $h Height.
11606
	 * @param string $op options
11607
	 * @protected
11608
	 * @since 2.1.000 (2008-01-08)
11609
	 */
11610
	protected function _outRect($x, $y, $w, $h, $op) {
11611
		if ($this->state == 2) {
11612
			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11613
		}
11614
	}
11615
11616
	/**
11617
	 * 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.
11618
	 * The new current point shall be (x3, y3).
11619
	 * @param float $x1 Abscissa of control point 1.
11620
	 * @param float $y1 Ordinate of control point 1.
11621
	 * @param float $x2 Abscissa of control point 2.
11622
	 * @param float $y2 Ordinate of control point 2.
11623
	 * @param float $x3 Abscissa of end point.
11624
	 * @param float $y3 Ordinate of end point.
11625
	 * @protected
11626
	 * @since 2.1.000 (2008-01-08)
11627
	 */
11628
	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11629
		if ($this->state == 2) {
11630
			$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)));
11631
		}
11632
	}
11633
11634
	/**
11635
	 * 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.
11636
	 * The new current point shall be (x3, y3).
11637
	 * @param float $x2 Abscissa of control point 2.
11638
	 * @param float $y2 Ordinate of control point 2.
11639
	 * @param float $x3 Abscissa of end point.
11640
	 * @param float $y3 Ordinate of end point.
11641
	 * @protected
11642
	 * @since 4.9.019 (2010-04-26)
11643
	 */
11644
	protected function _outCurveV($x2, $y2, $x3, $y3) {
11645
		if ($this->state == 2) {
11646
			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11647
		}
11648
	}
11649
11650
	/**
11651
	 * 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.
11652
	 * The new current point shall be (x3, y3).
11653
	 * @param float $x1 Abscissa of control point 1.
11654
	 * @param float $y1 Ordinate of control point 1.
11655
	 * @param float $x3 Abscissa of end point.
11656
	 * @param float $y3 Ordinate of end point.
11657
	 * @protected
11658
	 * @since 2.1.000 (2008-01-08)
11659
	 */
11660
	protected function _outCurveY($x1, $y1, $x3, $y3) {
11661
		if ($this->state == 2) {
11662
			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11663
		}
11664
	}
11665
11666
	/**
11667
	 * Draws a line between two points.
11668
	 * @param float $x1 Abscissa of first point.
11669
	 * @param float $y1 Ordinate of first point.
11670
	 * @param float $x2 Abscissa of second point.
11671
	 * @param float $y2 Ordinate of second point.
11672
	 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11673
	 * @public
11674
	 * @since 1.0
11675
	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11676
	 */
11677
	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11678
		if ($this->state != 2) {
11679
			return;
11680
		}
11681
		if (is_array($style)) {
11682
			$this->setLineStyle($style);
11683
		}
11684
		$this->_outPoint($x1, $y1);
11685
		$this->_outLine($x2, $y2);
11686
		$this->_out('S');
11687
	}
11688
11689
	/**
11690
	 * Draws a rectangle.
11691
	 * @param float $x Abscissa of upper-left corner.
11692
	 * @param float $y Ordinate of upper-left corner.
11693
	 * @param float $w Width.
11694
	 * @param float $h Height.
11695
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11696
	 * @param array $border_style Border style of rectangle. Array with keys among the following:
11697
	 * <ul>
11698
	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11699
	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11700
	 * </ul>
11701
	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11702
	 * @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).
11703
	 * @public
11704
	 * @since 1.0
11705
	 * @see SetLineStyle()
11706
	 */
11707
	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11708
		if ($this->state != 2) {
11709
			return;
11710
		}
11711
		if (empty($style)) {
11712
			$style = 'S';
11713
		}
11714
		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11715
			// set background color
11716
			$this->setFillColorArray($fill_color);
11717
		}
11718
		if (!empty($border_style)) {
11719
			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11720
				//set global style for border
11721
				$this->setLineStyle($border_style['all']);
11722
				$border_style = array();
11723
			} else {
11724
				// remove stroke operator from style
11725
				$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*' );
11726
				if (isset($opnostroke[$style])) {
11727
					$style = $opnostroke[$style];
11728
				}
11729
			}
11730
		}
11731
		if (!empty($style)) {
11732
			$op = TCPDF_STATIC::getPathPaintOperator($style);
11733
			$this->_outRect($x, $y, $w, $h, $op);
11734
		}
11735
		if (!empty($border_style)) {
11736
			$border_style2 = array();
11737
			foreach ($border_style as $line => $value) {
11738
				$length = strlen($line);
11739
				for ($i = 0; $i < $length; ++$i) {
11740
					$border_style2[$line[$i]] = $value;
11741
				}
11742
			}
11743
			$border_style = $border_style2;
11744
			if (isset($border_style['L']) AND $border_style['L']) {
11745
				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11746
			}
11747
			if (isset($border_style['T']) AND $border_style['T']) {
11748
				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11749
			}
11750
			if (isset($border_style['R']) AND $border_style['R']) {
11751
				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11752
			}
11753
			if (isset($border_style['B']) AND $border_style['B']) {
11754
				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11755
			}
11756
		}
11757
	}
11758
11759
	/**
11760
	 * Draws a Bezier curve.
11761
	 * The Bezier curve is a tangent to the line between the control points at
11762
	 * either end of the curve.
11763
	 * @param float $x0 Abscissa of start point.
11764
	 * @param float $y0 Ordinate of start point.
11765
	 * @param float $x1 Abscissa of control point 1.
11766
	 * @param float $y1 Ordinate of control point 1.
11767
	 * @param float $x2 Abscissa of control point 2.
11768
	 * @param float $y2 Ordinate of control point 2.
11769
	 * @param float $x3 Abscissa of end point.
11770
	 * @param float $y3 Ordinate of end point.
11771
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11772
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11773
	 * @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).
11774
	 * @public
11775
	 * @see SetLineStyle()
11776
	 * @since 2.1.000 (2008-01-08)
11777
	 */
11778
	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11779
		if ($this->state != 2) {
11780
			return;
11781
		}
11782
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11783
			$this->setFillColorArray($fill_color);
11784
		}
11785
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11786
		if ($line_style) {
11787
			$this->setLineStyle($line_style);
11788
		}
11789
		$this->_outPoint($x0, $y0);
11790
		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11791
		$this->_out($op);
11792
	}
11793
11794
	/**
11795
	 * Draws a poly-Bezier curve.
11796
	 * Each Bezier curve segment is a tangent to the line between the control points at
11797
	 * either end of the curve.
11798
	 * @param float $x0 Abscissa of start point.
11799
	 * @param float $y0 Ordinate of start point.
11800
	 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11801
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11802
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11803
	 * @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).
11804
	 * @public
11805
	 * @see SetLineStyle()
11806
	 * @since 3.0008 (2008-05-12)
11807
	 */
11808
	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11809
		if ($this->state != 2) {
11810
			return;
11811
		}
11812
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11813
			$this->setFillColorArray($fill_color);
11814
		}
11815
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11816
		if ($op == 'f') {
11817
			$line_style = array();
11818
		}
11819
		if ($line_style) {
11820
			$this->setLineStyle($line_style);
11821
		}
11822
		$this->_outPoint($x0, $y0);
11823
		foreach ($segments as $segment) {
11824
			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11825
			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11826
		}
11827
		$this->_out($op);
11828
	}
11829
11830
	/**
11831
	 * Draws an ellipse.
11832
	 * An ellipse is formed from n Bezier curves.
11833
	 * @param float $x0 Abscissa of center point.
11834
	 * @param float $y0 Ordinate of center point.
11835
	 * @param float $rx Horizontal radius.
11836
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11837
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11838
	 * @param float $astart Angle start of draw line. Default value: 0.
11839
	 * @param float $afinish Angle finish of draw line. Default value: 360.
11840
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11841
	 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11842
	 * @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).
11843
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11844
	 * @author Nicola Asuni
11845
	 * @public
11846
	 * @since 2.1.000 (2008-01-08)
11847
	 */
11848
	public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11849
		if ($this->state != 2) {
11850
			return;
11851
		}
11852
		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11853
			$ry = $rx;
11854
		}
11855
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11856
			$this->setFillColorArray($fill_color);
11857
		}
11858
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11859
		if ($op == 'f') {
11860
			$line_style = array();
11861
		}
11862
		if ($line_style) {
11863
			$this->setLineStyle($line_style);
11864
		}
11865
		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11866
		$this->_out($op);
11867
	}
11868
11869
	/**
11870
	 * Append an elliptical arc to the current path.
11871
	 * An ellipse is formed from n Bezier curves.
11872
	 * @param float $xc Abscissa of center point.
11873
	 * @param float $yc Ordinate of center point.
11874
	 * @param float $rx Horizontal radius.
11875
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11876
	 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11877
	 * @param float $angs Angle start of draw line. Default value: 0.
11878
	 * @param float $angf Angle finish of draw line. Default value: 360.
11879
	 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11880
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11881
	 * @param boolean $startpoint if true output a starting point.
11882
	 * @param boolean $ccw if true draws in counter-clockwise.
11883
	 * @param boolean $svg if true the angles are in svg mode (already calculated).
11884
	 * @return array bounding box coordinates (x min, y min, x max, y max)
11885
	 * @author Nicola Asuni
11886
	 * @protected
11887
	 * @since 4.9.019 (2010-04-26)
11888
	 */
11889
	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11890
		if (($rx <= 0) OR ($ry < 0)) {
11891
			return;
11892
		}
11893
		$k = $this->k;
11894
		if ($nc < 2) {
11895
			$nc = 2;
11896
		}
11897
		$xmin = 2147483647;
11898
		$ymin = 2147483647;
11899
		$xmax = 0;
11900
		$ymax = 0;
11901
		if ($pie) {
11902
			// center of the arc
11903
			$this->_outPoint($xc, $yc);
11904
		}
11905
		$xang = deg2rad((float) $xang);
11906
		$angs = deg2rad((float) $angs);
11907
		$angf = deg2rad((float) $angf);
11908
		if ($svg) {
11909
			$as = $angs;
11910
			$af = $angf;
11911
		} else {
11912
			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11913
			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11914
		}
11915
		if ($as < 0) {
11916
			$as += (2 * M_PI);
11917
		}
11918
		if ($af < 0) {
11919
			$af += (2 * M_PI);
11920
		}
11921
		if ($ccw AND ($as > $af)) {
11922
			// reverse rotation
11923
			$as -= (2 * M_PI);
11924
		} elseif (!$ccw AND ($as < $af)) {
11925
			// reverse rotation
11926
			$af -= (2 * M_PI);
11927
		}
11928
		$total_angle = ($af - $as);
11929
		if ($nc < 2) {
11930
			$nc = 2;
11931
		}
11932
		// total arcs to draw
11933
		$nc *= (2 * abs($total_angle) / M_PI);
11934
		$nc = round($nc) + 1;
11935
		// angle of each arc
11936
		$arcang = ($total_angle / $nc);
11937
		// center point in PDF coordinates
11938
		$x0 = $xc;
11939
		$y0 = ($this->h - $yc);
11940
		// starting angle
11941
		$ang = $as;
11942
		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11943
		$cos_xang = cos($xang);
11944
		$sin_xang = sin($xang);
11945
		$cos_ang = cos($ang);
11946
		$sin_ang = sin($ang);
11947
		// first arc point
11948
		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11949
		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11950
		// first Bezier control point
11951
		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11952
		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11953
		if ($pie) {
11954
			// line from center to arc starting point
11955
			$this->_outLine($px1, $this->h - $py1);
11956
		} elseif ($startpoint) {
11957
			// arc starting point
11958
			$this->_outPoint($px1, $this->h - $py1);
11959
		}
11960
		// draw arcs
11961
		for ($i = 1; $i <= $nc; ++$i) {
11962
			// starting angle
11963
			$ang = $as + ($i * $arcang);
11964
			if ($i == $nc) {
11965
				$ang = $af;
11966
			}
11967
			$cos_ang = cos($ang);
11968
			$sin_ang = sin($ang);
11969
			// second arc point
11970
			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11971
			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11972
			// second Bezier control point
11973
			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11974
			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11975
			// draw arc
11976
			$cx1 = ($px1 + $qx1);
11977
			$cy1 = ($this->h - ($py1 + $qy1));
11978
			$cx2 = ($px2 - $qx2);
11979
			$cy2 = ($this->h - ($py2 - $qy2));
11980
			$cx3 = $px2;
11981
			$cy3 = ($this->h - $py2);
11982
			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11983
			// get bounding box coordinates
11984
			$xmin = min($xmin, $cx1, $cx2, $cx3);
11985
			$ymin = min($ymin, $cy1, $cy2, $cy3);
11986
			$xmax = max($xmax, $cx1, $cx2, $cx3);
11987
			$ymax = max($ymax, $cy1, $cy2, $cy3);
11988
			// move to next point
11989
			$px1 = $px2;
11990
			$py1 = $py2;
11991
			$qx1 = $qx2;
11992
			$qy1 = $qy2;
11993
		}
11994
		if ($pie) {
11995
			$this->_outLine($xc, $yc);
11996
			// get bounding box coordinates
11997
			$xmin = min($xmin, $xc);
11998
			$ymin = min($ymin, $yc);
11999
			$xmax = max($xmax, $xc);
12000
			$ymax = max($ymax, $yc);
12001
		}
12002
		return array($xmin, $ymin, $xmax, $ymax);
12003
	}
12004
12005
	/**
12006
	 * Draws a circle.
12007
	 * A circle is formed from n Bezier curves.
12008
	 * @param float $x0 Abscissa of center point.
12009
	 * @param float $y0 Ordinate of center point.
12010
	 * @param float $r Radius.
12011
	 * @param float $angstr Angle start of draw line. Default value: 0.
12012
	 * @param float $angend Angle finish of draw line. Default value: 360.
12013
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12014
	 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
12015
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12016
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
12017
	 * @public
12018
	 * @since 2.1.000 (2008-01-08)
12019
	 */
12020
	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
12021
		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
12022
	}
12023
12024
	/**
12025
	 * Draws a polygonal line
12026
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
12027
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12028
	 * @param array $line_style Line style of polygon. Array with keys among the following:
12029
	 * <ul>
12030
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
12031
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
12032
	 * </ul>
12033
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
12034
	 * @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).
12035
	 * @since 4.8.003 (2009-09-15)
12036
	 * @public
12037
	 */
12038
	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
12039
		$this->Polygon($p, $style, $line_style, $fill_color, false);
12040
	}
12041
12042
	/**
12043
	 * Draws a polygon.
12044
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
12045
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12046
	 * @param array $line_style Line style of polygon. Array with keys among the following:
12047
	 * <ul>
12048
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
12049
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
12050
	 * </ul>
12051
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
12052
	 * @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).
12053
	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
12054
	 * @public
12055
	 * @since 2.1.000 (2008-01-08)
12056
	 */
12057
	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
12058
		if ($this->state != 2) {
12059
			return;
12060
		}
12061
		$nc = count($p); // number of coordinates
12062
		$np = $nc / 2; // number of points
12063
		if ($closed) {
12064
			// close polygon by adding the first 2 points at the end (one line)
12065
			for ($i = 0; $i < 4; ++$i) {
12066
				$p[$nc + $i] = $p[$i];
12067
			}
12068
			// copy style for the last added line
12069
			if (isset($line_style[0])) {
12070
				$line_style[$np] = $line_style[0];
12071
			}
12072
			$nc += 4;
12073
		}
12074
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12075
			$this->setFillColorArray($fill_color);
12076
		}
12077
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12078
		if ($op == 'f') {
12079
			$line_style = array();
12080
		}
12081
		$draw = true;
12082
		if ($line_style) {
12083
			if (isset($line_style['all'])) {
12084
				$this->setLineStyle($line_style['all']);
12085
			} else {
12086
				$draw = false;
12087
				if ($op == 'B') {
12088
					// draw fill
12089
					$op = 'f';
12090
					$this->_outPoint($p[0], $p[1]);
12091
					for ($i = 2; $i < $nc; $i = $i + 2) {
12092
						$this->_outLine($p[$i], $p[$i + 1]);
12093
					}
12094
					$this->_out($op);
12095
				}
12096
				// draw outline
12097
				$this->_outPoint($p[0], $p[1]);
12098
				for ($i = 2; $i < $nc; $i = $i + 2) {
12099
					$line_num = ($i / 2) - 1;
12100
					if (isset($line_style[$line_num])) {
12101
						if ($line_style[$line_num] != 0) {
12102
							if (is_array($line_style[$line_num])) {
12103
								$this->_out('S');
12104
								$this->setLineStyle($line_style[$line_num]);
12105
								$this->_outPoint($p[$i - 2], $p[$i - 1]);
12106
								$this->_outLine($p[$i], $p[$i + 1]);
12107
								$this->_out('S');
12108
								$this->_outPoint($p[$i], $p[$i + 1]);
12109
							} else {
12110
								$this->_outLine($p[$i], $p[$i + 1]);
12111
							}
12112
						}
12113
					} else {
12114
						$this->_outLine($p[$i], $p[$i + 1]);
12115
					}
12116
				}
12117
				$this->_out($op);
12118
			}
12119
		}
12120
		if ($draw) {
12121
			$this->_outPoint($p[0], $p[1]);
12122
			for ($i = 2; $i < $nc; $i = $i + 2) {
12123
				$this->_outLine($p[$i], $p[$i + 1]);
12124
			}
12125
			$this->_out($op);
12126
		}
12127
	}
12128
12129
	/**
12130
	 * Draws a regular polygon.
12131
	 * @param float $x0 Abscissa of center point.
12132
	 * @param float $y0 Ordinate of center point.
12133
	 * @param float $r Radius of inscribed circle.
12134
	 * @param integer $ns Number of sides.
12135
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12136
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12137
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12138
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12139
	 * <ul>
12140
	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12141
	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12142
	 * </ul>
12143
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12144
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12145
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12146
	 * <ul>
12147
	 *	 <li>D or empty string: Draw (default).</li>
12148
	 *	 <li>F: Fill.</li>
12149
	 *	 <li>DF or FD: Draw and fill.</li>
12150
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12151
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12152
	 * </ul>
12153
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12154
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12155
	 * @public
12156
	 * @since 2.1.000 (2008-01-08)
12157
	 */
12158
	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()) {
12159
		if (3 > $ns) {
12160
			$ns = 3;
12161
		}
12162
		if ($draw_circle) {
12163
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12164
		}
12165
		$p = array();
12166
		for ($i = 0; $i < $ns; ++$i) {
12167
			$a = $angle + ($i * 360 / $ns);
12168
			$a_rad = deg2rad((float) $a);
12169
			$p[] = $x0 + ($r * sin($a_rad));
12170
			$p[] = $y0 + ($r * cos($a_rad));
12171
		}
12172
		$this->Polygon($p, $style, $line_style, $fill_color);
12173
	}
12174
12175
	/**
12176
	 * Draws a star polygon
12177
	 * @param float $x0 Abscissa of center point.
12178
	 * @param float $y0 Ordinate of center point.
12179
	 * @param float $r Radius of inscribed circle.
12180
	 * @param integer $nv Number of vertices.
12181
	 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12182
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12183
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12184
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12185
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12186
	 * <ul>
12187
	 *	 <li>all: Line style of all sides. Array like for
12188
	 * SetLineStyle().</li>
12189
	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12190
	 * </ul>
12191
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12192
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12193
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12194
	 * <ul>
12195
	 *	 <li>D or empty string: Draw (default).</li>
12196
	 *	 <li>F: Fill.</li>
12197
	 *	 <li>DF or FD: Draw and fill.</li>
12198
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12199
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12200
	 * </ul>
12201
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12202
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12203
	 * @public
12204
	 * @since 2.1.000 (2008-01-08)
12205
	 */
12206
	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()) {
12207
		if ($nv < 2) {
12208
			$nv = 2;
12209
		}
12210
		if ($draw_circle) {
12211
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12212
		}
12213
		$p2 = array();
12214
		$visited = array();
12215
		for ($i = 0; $i < $nv; ++$i) {
12216
			$a = $angle + ($i * 360 / $nv);
12217
			$a_rad = deg2rad((float) $a);
12218
			$p2[] = $x0 + ($r * sin($a_rad));
12219
			$p2[] = $y0 + ($r * cos($a_rad));
12220
			$visited[] = false;
12221
		}
12222
		$p = array();
12223
		$i = 0;
12224
		do {
12225
			$p[] = $p2[$i * 2];
12226
			$p[] = $p2[($i * 2) + 1];
12227
			$visited[$i] = true;
12228
			$i += $ng;
12229
			$i %= $nv;
12230
		} while (!$visited[$i]);
12231
		$this->Polygon($p, $style, $line_style, $fill_color);
12232
	}
12233
12234
	/**
12235
	 * Draws a rounded rectangle.
12236
	 * @param float $x Abscissa of upper-left corner.
12237
	 * @param float $y Ordinate of upper-left corner.
12238
	 * @param float $w Width.
12239
	 * @param float $h Height.
12240
	 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12241
	 * @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").
12242
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12243
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12244
	 * @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).
12245
	 * @public
12246
	 * @since 2.1.000 (2008-01-08)
12247
	 */
12248
	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12249
		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12250
	}
12251
12252
	/**
12253
	 * Draws a rounded rectangle.
12254
	 * @param float $x Abscissa of upper-left corner.
12255
	 * @param float $y Ordinate of upper-left corner.
12256
	 * @param float $w Width.
12257
	 * @param float $h Height.
12258
	 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12259
	 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12260
	 * @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").
12261
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12262
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12263
	 * @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).
12264
	 * @public
12265
	 * @since 4.9.019 (2010-04-22)
12266
	 */
12267
	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12268
		if ($this->state != 2) {
12269
			return;
12270
		}
12271
		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12272
			// Not rounded
12273
			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12274
			return;
12275
		}
12276
		// Rounded
12277
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12278
			$this->setFillColorArray($fill_color);
12279
		}
12280
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12281
		if ($op == 'f') {
12282
			$border_style = array();
12283
		}
12284
		if ($border_style) {
12285
			$this->setLineStyle($border_style);
12286
		}
12287
		$MyArc = 4 / 3 * (sqrt(2) - 1);
12288
		$this->_outPoint($x + $rx, $y);
12289
		$xc = $x + $w - $rx;
12290
		$yc = $y + $ry;
12291
		$this->_outLine($xc, $y);
12292
		if ($round_corner[0]) {
12293
			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12294
		} else {
12295
			$this->_outLine($x + $w, $y);
12296
		}
12297
		$xc = $x + $w - $rx;
12298
		$yc = $y + $h - $ry;
12299
		$this->_outLine($x + $w, $yc);
12300
		if ($round_corner[1]) {
12301
			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12302
		} else {
12303
			$this->_outLine($x + $w, $y + $h);
12304
		}
12305
		$xc = $x + $rx;
12306
		$yc = $y + $h - $ry;
12307
		$this->_outLine($xc, $y + $h);
12308
		if ($round_corner[2]) {
12309
			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12310
		} else {
12311
			$this->_outLine($x, $y + $h);
12312
		}
12313
		$xc = $x + $rx;
12314
		$yc = $y + $ry;
12315
		$this->_outLine($x, $yc);
12316
		if ($round_corner[3]) {
12317
			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12318
		} else {
12319
			$this->_outLine($x, $y);
12320
			$this->_outLine($x + $rx, $y);
12321
		}
12322
		$this->_out($op);
12323
	}
12324
12325
	/**
12326
	 * Draws a grahic arrow.
12327
	 * @param float $x0 Abscissa of first point.
12328
	 * @param float $y0 Ordinate of first point.
12329
	 * @param float $x1 Abscissa of second point.
12330
	 * @param float $y1 Ordinate of second point.
12331
	 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12332
	 * @param float $arm_size length of arrowhead arms
12333
	 * @param int $arm_angle angle between an arm and the shaft
12334
	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12335
	 * @since 4.6.018 (2009-07-10)
12336
	 */
12337
	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12338
		// getting arrow direction angle
12339
		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12340
		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12341
		if ($dir_angle < 0) {
12342
			$dir_angle += (2 * M_PI);
12343
		}
12344
		$arm_angle = deg2rad($arm_angle);
12345
		$sx1 = $x1;
12346
		$sy1 = $y1;
12347
		if ($head_style > 0) {
12348
			// calculate the stopping point for the arrow shaft
12349
			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12350
			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12351
		}
12352
		// main arrow line / shaft
12353
		$this->Line($x0, $y0, $sx1, $sy1);
12354
		// left arrowhead arm tip
12355
		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12356
		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12357
		// right arrowhead arm tip
12358
		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12359
		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12360
		$mode = 'D';
12361
		$style = array();
12362
		switch ($head_style) {
12363
			case 0: {
12364
				// draw only arrowhead arms
12365
				$mode = 'D';
12366
				$style = array(1, 1, 0);
12367
				break;
12368
			}
12369
			case 1: {
12370
				// draw closed arrowhead, but no fill
12371
				$mode = 'D';
12372
				break;
12373
			}
12374
			case 2: {
12375
				// closed and filled arrowhead
12376
				$mode = 'DF';
12377
				break;
12378
			}
12379
			case 3: {
12380
				// filled arrowhead
12381
				$mode = 'F';
12382
				break;
12383
			}
12384
		}
12385
		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12386
	}
12387
12388
	// END GRAPHIC FUNCTIONS SECTION -----------------------
12389
12390
	/**
12391
	 * Add a Named Destination.
12392
	 * NOTE: destination names are unique, so only last entry will be saved.
12393
	 * @param string $name Destination name.
12394
	 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12395
	 * @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.
12396
	 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12397
	 * @return string|false Stripped named destination identifier or false in case of error.
12398
	 * @public
12399
	 * @author Christian Deligant, Nicola Asuni
12400
	 * @since 5.9.097 (2011-06-23)
12401
	 */
12402
	public function setDestination($name, $y=-1, $page='', $x=-1) {
12403
		// remove unsupported characters
12404
		$name = TCPDF_STATIC::encodeNameObject($name);
12405
		if (TCPDF_STATIC::empty_string($name)) {
12406
			return false;
12407
		}
12408
		if ($y == -1) {
12409
			$y = $this->GetY();
12410
		} elseif ($y < 0) {
12411
			$y = 0;
12412
		} elseif ($y > $this->h) {
12413
			$y = $this->h;
12414
		}
12415
		if ($x == -1) {
12416
			$x = $this->GetX();
12417
		} elseif ($x < 0) {
12418
			$x = 0;
12419
		} elseif ($x > $this->w) {
12420
			$x = $this->w;
12421
		}
12422
		$fixed = false;
12423
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12424
			$page = intval(substr($page, 1));
12425
			// this page number will not be changed when moving/add/deleting pages
12426
			$fixed = true;
12427
		}
12428
		if (empty($page)) {
12429
			$page = $this->PageNo();
12430
			if (empty($page)) {
12431
				return;
12432
			}
12433
		}
12434
		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12435
		return $name;
12436
	}
12437
12438
	/**
12439
	 * Return the Named Destination array.
12440
	 * @return array Named Destination array.
12441
	 * @public
12442
	 * @author Nicola Asuni
12443
	 * @since 5.9.097 (2011-06-23)
12444
	 */
12445
	public function getDestination() {
12446
		return $this->dests;
12447
	}
12448
12449
	/**
12450
	 * Insert Named Destinations.
12451
	 * @protected
12452
	 * @author Johannes G\FCntert, Nicola Asuni
12453
	 * @since 5.9.098 (2011-06-23)
12454
	 */
12455
	protected function _putdests() {
12456
		if (empty($this->dests)) {
12457
			return;
12458
		}
12459
		$this->n_dests = $this->_newobj();
12460
		$out = ' <<';
12461
		foreach($this->dests as $name => $o) {
12462
			$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)));
12463
		}
12464
		$out .= ' >>';
12465
		$out .= "\n".'endobj';
12466
		$this->_out($out);
12467
	}
12468
12469
	/**
12470
	 * Adds a bookmark - alias for Bookmark().
12471
	 * @param string $txt Bookmark description.
12472
	 * @param int $level Bookmark level (minimum value is 0).
12473
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12474
	 * @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.
12475
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12476
	 * @param array $color RGB color array (values from 0 to 255).
12477
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12478
	 * @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).
12479
	 * @public
12480
	 */
12481
	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12482
		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12483
	}
12484
12485
	/**
12486
	 * Adds a bookmark.
12487
	 * @param string $txt Bookmark description.
12488
	 * @param int $level Bookmark level (minimum value is 0).
12489
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12490
	 * @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.
12491
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12492
	 * @param array $color RGB color array (values from 0 to 255).
12493
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12494
	 * @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).
12495
	 * @public
12496
	 * @since 2.1.002 (2008-02-12)
12497
	 */
12498
	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12499
		if ($level < 0) {
12500
			$level = 0;
12501
		}
12502
		if (isset($this->outlines[0])) {
12503
			$lastoutline = end($this->outlines);
12504
			$maxlevel = $lastoutline['l'] + 1;
12505
		} else {
12506
			$maxlevel = 0;
12507
		}
12508
		if ($level > $maxlevel) {
12509
			$level = $maxlevel;
12510
		}
12511
		if ($y == -1) {
12512
			$y = $this->GetY();
12513
		} elseif ($y < 0) {
12514
			$y = 0;
12515
		} elseif ($y > $this->h) {
12516
			$y = $this->h;
12517
		}
12518
		if ($x == -1) {
12519
			$x = $this->GetX();
12520
		} elseif ($x < 0) {
12521
			$x = 0;
12522
		} elseif ($x > $this->w) {
12523
			$x = $this->w;
12524
		}
12525
		$fixed = false;
12526
		$pageAsString = (string) $page;
12527
		if ($pageAsString && $pageAsString[0] == '*') {
12528
			$page = intval(substr($page, 1));
12529
			// this page number will not be changed when moving/add/deleting pages
12530
			$fixed = true;
12531
		}
12532
		if (empty($page)) {
12533
			$page = $this->PageNo();
12534
			if (empty($page)) {
12535
				return;
12536
			}
12537
		}
12538
		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12539
	}
12540
12541
	/**
12542
	 * Sort bookmarks for page and key.
12543
	 * @protected
12544
	 * @since 5.9.119 (2011-09-19)
12545
	 */
12546
	protected function sortBookmarks() {
12547
		// get sorting columns
12548
		$outline_p = array();
12549
		$outline_y = array();
12550
		foreach ($this->outlines as $key => $row) {
12551
			$outline_p[$key] = $row['p'];
12552
			$outline_k[$key] = $key;
12553
		}
12554
		// sort outlines by page and original position
12555
		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12556
	}
12557
12558
	/**
12559
	 * Create a bookmark PDF string.
12560
	 * @protected
12561
	 * @author Olivier Plathey, Nicola Asuni
12562
	 * @since 2.1.002 (2008-02-12)
12563
	 */
12564
	protected function _putbookmarks() {
12565
		$nb = count($this->outlines);
12566
		if ($nb == 0) {
12567
			return;
12568
		}
12569
		// sort bookmarks
12570
		$this->sortBookmarks();
12571
		$lru = array();
12572
		$level = 0;
12573
		foreach ($this->outlines as $i => $o) {
12574
			if ($o['l'] > 0) {
12575
				$parent = $lru[($o['l'] - 1)];
12576
				//Set parent and last pointers
12577
				$this->outlines[$i]['parent'] = $parent;
12578
				$this->outlines[$parent]['last'] = $i;
12579
				if ($o['l'] > $level) {
12580
					//Level increasing: set first pointer
12581
					$this->outlines[$parent]['first'] = $i;
12582
				}
12583
			} else {
12584
				$this->outlines[$i]['parent'] = $nb;
12585
			}
12586
			if (($o['l'] <= $level) AND ($i > 0)) {
12587
				//Set prev and next pointers
12588
				$prev = $lru[$o['l']];
12589
				$this->outlines[$prev]['next'] = $i;
12590
				$this->outlines[$i]['prev'] = $prev;
12591
			}
12592
			$lru[$o['l']] = $i;
12593
			$level = $o['l'];
12594
		}
12595
		//Outline items
12596
		$n = $this->n + 1;
12597
		$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';
12598
		foreach ($this->outlines as $i => $o) {
12599
			$oid = $this->_newobj();
12600
			// covert HTML title to string
12601
			$title = preg_replace($nltags, "\n", $o['t']);
12602
			$title = preg_replace("/[\r]+/si", '', $title);
12603
			$title = preg_replace("/[\n]+/si", "\n", $title);
12604
			$title = strip_tags($title);
12605
			$title = $this->stringTrim($title);
12606
			$out = '<</Title '.$this->_textstring($title, $oid);
12607
			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12608
			if (isset($o['prev'])) {
12609
				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12610
			}
12611
			if (isset($o['next'])) {
12612
				$out .= ' /Next '.($n + $o['next']).' 0 R';
12613
			}
12614
			if (isset($o['first'])) {
12615
				$out .= ' /First '.($n + $o['first']).' 0 R';
12616
			}
12617
			if (isset($o['last'])) {
12618
				$out .= ' /Last '.($n + $o['last']).' 0 R';
12619
			}
12620
			if (isset($o['u']) AND !empty($o['u'])) {
12621
				// link
12622
				if (is_string($o['u'])) {
12623
					if ($o['u'][0] == '#') {
12624
						// internal destination
12625
						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12626
					} elseif ($o['u'][0] == '%') {
12627
						// embedded PDF file
12628
						$filename = basename(substr($o['u'], 1));
12629
						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12630
					} elseif ($o['u'][0] == '*') {
12631
						// embedded generic file
12632
						$filename = basename(substr($o['u'], 1));
12633
						$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});';
12634
						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12635
					} else {
12636
						// external URI link
12637
						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12638
					}
12639
				} elseif (isset($this->links[$o['u']])) {
12640
					// internal link ID
12641
					$l = $this->links[$o['u']];
12642
					if (isset($this->page_obj_id[($l['p'])])) {
12643
						$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)));
12644
					}
12645
				}
12646
			} elseif (isset($this->page_obj_id[($o['p'])])) {
12647
				// link to a page
12648
				$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)));
12649
			}
12650
			// set font style
12651
			$style = 0;
12652
			if (!empty($o['s'])) {
12653
				// bold
12654
				if (strpos($o['s'], 'B') !== false) {
12655
					$style |= 2;
12656
				}
12657
				// oblique
12658
				if (strpos($o['s'], 'I') !== false) {
12659
					$style |= 1;
12660
				}
12661
			}
12662
			$out .= sprintf(' /F %d', $style);
12663
			// set bookmark color
12664
			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12665
				$color = array_values($o['c']);
12666
				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12667
			} else {
12668
				// black
12669
				$out .= ' /C [0.0 0.0 0.0]';
12670
			}
12671
			$out .= ' /Count 0'; // normally closed item
12672
			$out .= ' >>';
12673
			$out .= "\n".'endobj';
12674
			$this->_out($out);
12675
		}
12676
		//Outline root
12677
		$this->OutlineRoot = $this->_newobj();
12678
		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12679
	}
12680
12681
	// --- JAVASCRIPT ------------------------------------------------------
12682
12683
	/**
12684
	 * Adds a javascript
12685
	 * @param string $script Javascript code
12686
	 * @public
12687
	 * @author Johannes G\FCntert, Nicola Asuni
12688
	 * @since 2.1.002 (2008-02-12)
12689
	 */
12690
	public function IncludeJS($script) {
12691
		$this->javascript .= $script;
12692
	}
12693
12694
	/**
12695
	 * Adds a javascript object and return object ID
12696
	 * @param string $script Javascript code
12697
	 * @param boolean $onload if true executes this object when opening the document
12698
	 * @return int internal object ID
12699
	 * @public
12700
	 * @author Nicola Asuni
12701
	 * @since 4.8.000 (2009-09-07)
12702
	 */
12703
	public function addJavascriptObject($script, $onload=false) {
12704
		if ($this->pdfa_mode) {
12705
			// javascript is not allowed in PDF/A mode
12706
			return false;
12707
		}
12708
		++$this->n;
12709
		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12710
		return $this->n;
12711
	}
12712
12713
	/**
12714
	 * Create a javascript PDF string.
12715
	 * @protected
12716
	 * @author Johannes G\FCntert, Nicola Asuni
12717
	 * @since 2.1.002 (2008-02-12)
12718
	 */
12719
	protected function _putjavascript() {
12720
		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12721
			return;
12722
		}
12723
		if (strpos($this->javascript, 'this.addField') > 0) {
12724
			if (!$this->ur['enabled']) {
12725
				//$this->setUserRights();
12726
			}
12727
			// the following two lines are used to avoid form fields duplication after saving
12728
			// The addField method only works when releasing user rights (UR3)
12729
			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12730
			$jsb = "getField('tcpdfdocsaved').value='saved';";
12731
			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12732
		}
12733
		// name tree for javascript
12734
		$this->n_js = '<< /Names [';
12735
		if (!empty($this->javascript)) {
12736
			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12737
		}
12738
		if (!empty($this->js_objects)) {
12739
			foreach ($this->js_objects as $key => $val) {
12740
				if ($val['onload']) {
12741
					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12742
				}
12743
			}
12744
		}
12745
		$this->n_js .= ' ] >>';
12746
		// default Javascript object
12747
		if (!empty($this->javascript)) {
12748
			$obj_id = $this->_newobj();
12749
			$out = '<< /S /JavaScript';
12750
			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12751
			$out .= ' >>';
12752
			$out .= "\n".'endobj';
12753
			$this->_out($out);
12754
		}
12755
		// additional Javascript objects
12756
		if (!empty($this->js_objects)) {
12757
			foreach ($this->js_objects as $key => $val) {
12758
				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12759
				$this->_out($out);
12760
			}
12761
		}
12762
	}
12763
12764
	/**
12765
	 * Adds a javascript form field.
12766
	 * @param string $type field type
12767
	 * @param string $name field name
12768
	 * @param int $x horizontal position
12769
	 * @param int $y vertical position
12770
	 * @param int $w width
12771
	 * @param int $h height
12772
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12773
	 * @protected
12774
	 * @author Denis Van Nuffelen, Nicola Asuni
12775
	 * @since 2.1.002 (2008-02-12)
12776
	 */
12777
	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12778
		if ($this->rtl) {
12779
			$x = $x - $w;
12780
		}
12781
		// the followind avoid fields duplication after saving the document
12782
		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12783
		$k = $this->k;
12784
		$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";
12785
		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12786
		foreach($prop as $key => $val) {
12787
			if (strcmp(substr($key, -5), 'Color') == 0) {
12788
				$val = TCPDF_COLORS::_JScolor($val);
12789
			} else {
12790
				$val = "'".$val."'";
12791
			}
12792
			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12793
		}
12794
		if ($this->rtl) {
12795
			$this->x -= $w;
12796
		} else {
12797
			$this->x += $w;
12798
		}
12799
		$this->javascript .= '}';
12800
	}
12801
12802
	// --- FORM FIELDS -----------------------------------------------------
12803
12804
12805
12806
	/**
12807
	 * Set default properties for form fields.
12808
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12809
	 * @public
12810
	 * @author Nicola Asuni
12811
	 * @since 4.8.000 (2009-09-06)
12812
	 */
12813
	public function setFormDefaultProp($prop=array()) {
12814
		$this->default_form_prop = $prop;
12815
	}
12816
12817
	/**
12818
	 * Return the default properties for form fields.
12819
	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12820
	 * @public
12821
	 * @author Nicola Asuni
12822
	 * @since 4.8.000 (2009-09-06)
12823
	 */
12824
	public function getFormDefaultProp() {
12825
		return $this->default_form_prop;
12826
	}
12827
12828
	/**
12829
	 * Creates a text field
12830
	 * @param string $name field name
12831
	 * @param float $w Width of the rectangle
12832
	 * @param float $h Height of the rectangle
12833
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12834
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12835
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12836
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12837
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12838
	 * @public
12839
	 * @author Nicola Asuni
12840
	 * @since 4.8.000 (2009-09-07)
12841
	 */
12842
	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
12843
		if (TCPDF_STATIC::empty_string($x)) {
12844
			$x = $this->x;
12845
		}
12846
		if (TCPDF_STATIC::empty_string($y)) {
12847
			$y = $this->y;
12848
		}
12849
		// check page for no-write regions and adapt page margins if necessary
12850
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12851
		if ($js) {
12852
			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12853
			return;
12854
		}
12855
		// get default style
12856
		$prop = array_merge($this->getFormDefaultProp(), $prop);
12857
		// get annotation data
12858
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12859
		// set default appearance stream
12860
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12861
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12862
		$popt['da'] = $fontstyle;
12863
		// build appearance stream
12864
		$popt['ap'] = array();
12865
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12866
		$text = '';
12867
		if (isset($prop['value']) AND !empty($prop['value'])) {
12868
			$text = $prop['value'];
12869
		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12870
			$text = $opt['v'];
12871
		}
12872
		$tmpid = $this->startTemplate($w, $h, false);
12873
		$align = '';
12874
		if (isset($popt['q'])) {
12875
			switch ($popt['q']) {
12876
				case 0: {
12877
					$align = 'L';
12878
					break;
12879
				}
12880
				case 1: {
12881
					$align = 'C';
12882
					break;
12883
				}
12884
				case 2: {
12885
					$align = 'R';
12886
					break;
12887
				}
12888
				default: {
12889
					$align = '';
12890
					break;
12891
				}
12892
			}
12893
		}
12894
		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12895
		$this->endTemplate();
12896
		--$this->n;
12897
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12898
		unset($this->xobjects[$tmpid]);
12899
		$popt['ap']['n'] .= 'Q EMC';
12900
		// merge options
12901
		$opt = array_merge($popt, $opt);
12902
		// remove some conflicting options
12903
		unset($opt['bs']);
12904
		// set remaining annotation data
12905
		$opt['Subtype'] = 'Widget';
12906
		$opt['ft'] = 'Tx';
12907
		$opt['t'] = $name;
12908
		// Additional annotation's parameters (check _putannotsobj() method):
12909
		//$opt['f']
12910
		//$opt['as']
12911
		//$opt['bs']
12912
		//$opt['be']
12913
		//$opt['c']
12914
		//$opt['border']
12915
		//$opt['h']
12916
		//$opt['mk'];
12917
		//$opt['mk']['r']
12918
		//$opt['mk']['bc'];
12919
		//$opt['mk']['bg'];
12920
		unset($opt['mk']['ca']);
12921
		unset($opt['mk']['rc']);
12922
		unset($opt['mk']['ac']);
12923
		unset($opt['mk']['i']);
12924
		unset($opt['mk']['ri']);
12925
		unset($opt['mk']['ix']);
12926
		unset($opt['mk']['if']);
12927
		//$opt['mk']['if']['sw'];
12928
		//$opt['mk']['if']['s'];
12929
		//$opt['mk']['if']['a'];
12930
		//$opt['mk']['if']['fb'];
12931
		unset($opt['mk']['tp']);
12932
		//$opt['tu']
12933
		//$opt['tm']
12934
		//$opt['ff']
12935
		//$opt['v']
12936
		//$opt['dv']
12937
		//$opt['a']
12938
		//$opt['aa']
12939
		//$opt['q']
12940
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12941
		if ($this->rtl) {
12942
			$this->x -= $w;
12943
		} else {
12944
			$this->x += $w;
12945
		}
12946
	}
12947
12948
	/**
12949
	 * Creates a RadioButton field.
12950
	 * @param string $name Field name.
12951
	 * @param int $w Width of the radio button.
12952
	 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12953
	 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12954
	 * @param string $onvalue Value to be returned if selected.
12955
	 * @param boolean $checked Define the initial state.
12956
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12957
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12958
	 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12959
	 * @public
12960
	 * @author Nicola Asuni
12961
	 * @since 4.8.000 (2009-09-07)
12962
	 */
12963
	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) {
12964
		if (TCPDF_STATIC::empty_string($x)) {
12965
			$x = $this->x;
12966
		}
12967
		if (TCPDF_STATIC::empty_string($y)) {
12968
			$y = $this->y;
12969
		}
12970
		// check page for no-write regions and adapt page margins if necessary
12971
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12972
		if ($js) {
12973
			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12974
			return;
12975
		}
12976
		if (TCPDF_STATIC::empty_string($onvalue)) {
12977
			$onvalue = 'On';
12978
		}
12979
		if ($checked) {
12980
			$defval = $onvalue;
12981
		} else {
12982
			$defval = 'Off';
12983
		}
12984
		// set font
12985
		$font = 'zapfdingbats';
12986
		if ($this->pdfa_mode) {
12987
			// all fonts must be embedded
12988
			$font = 'pdfa'.$font;
12989
		}
12990
		$this->AddFont($font);
12991
		$tmpfont = $this->getFontBuffer($font);
12992
		// set data for parent group
12993
		if (!isset($this->radiobutton_groups[$this->page])) {
12994
			$this->radiobutton_groups[$this->page] = array();
12995
		}
12996
		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12997
			$this->radiobutton_groups[$this->page][$name] = array();
12998
			++$this->n;
12999
			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
13000
			$this->radio_groups[] = $this->n;
13001
		}
13002
		$kid = ($this->n + 1);
13003
		// save object ID to be added on Kids entry on parent object
13004
		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
13005
		// get default style
13006
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13007
		$prop['NoToggleToOff'] = 'true';
13008
		$prop['Radio'] = 'true';
13009
		$prop['borderStyle'] = 'inset';
13010
		// get annotation data
13011
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13012
		// set additional default options
13013
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13014
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13015
		$popt['da'] = $fontstyle;
13016
		// build appearance stream
13017
		$popt['ap'] = array();
13018
		$popt['ap']['n'] = array();
13019
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
13020
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13021
		$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);
13022
		$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);
13023
		if (!isset($popt['mk'])) {
13024
			$popt['mk'] = array();
13025
		}
13026
		$popt['mk']['ca'] = '(l)';
13027
		// merge options
13028
		$opt = array_merge($popt, $opt);
13029
		// set remaining annotation data
13030
		$opt['Subtype'] = 'Widget';
13031
		$opt['ft'] = 'Btn';
13032
		if ($checked) {
13033
			$opt['v'] = array('/'.$onvalue);
13034
			$opt['as'] = $onvalue;
13035
		} else {
13036
			$opt['as'] = 'Off';
13037
		}
13038
		// store readonly flag
13039
		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
13040
			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
13041
		}
13042
		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
13043
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13044
		if ($this->rtl) {
13045
			$this->x -= $w;
13046
		} else {
13047
			$this->x += $w;
13048
		}
13049
	}
13050
13051
	/**
13052
	 * Creates a List-box field
13053
	 * @param string $name field name
13054
	 * @param int $w width
13055
	 * @param int $h height
13056
	 * @param array $values array containing the list of values.
13057
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13058
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13059
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13060
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13061
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13062
	 * @public
13063
	 * @author Nicola Asuni
13064
	 * @since 4.8.000 (2009-09-07)
13065
	 */
13066
	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13067
		if (TCPDF_STATIC::empty_string($x)) {
13068
			$x = $this->x;
13069
		}
13070
		if (TCPDF_STATIC::empty_string($y)) {
13071
			$y = $this->y;
13072
		}
13073
		// check page for no-write regions and adapt page margins if necessary
13074
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13075
		if ($js) {
13076
			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13077
			$s = '';
13078
			foreach ($values as $value) {
13079
				if (is_array($value)) {
13080
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13081
				} else {
13082
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13083
				}
13084
			}
13085
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13086
			return;
13087
		}
13088
		// get default style
13089
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13090
		// get annotation data
13091
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13092
		// set additional default values
13093
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13094
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13095
		$popt['da'] = $fontstyle;
13096
		// build appearance stream
13097
		$popt['ap'] = array();
13098
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13099
		$text = '';
13100
		foreach($values as $item) {
13101
			if (is_array($item)) {
13102
				$text .= $item[1]."\n";
13103
			} else {
13104
				$text .= $item."\n";
13105
			}
13106
		}
13107
		$tmpid = $this->startTemplate($w, $h, false);
13108
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13109
		$this->endTemplate();
13110
		--$this->n;
13111
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13112
		unset($this->xobjects[$tmpid]);
13113
		$popt['ap']['n'] .= 'Q EMC';
13114
		// merge options
13115
		$opt = array_merge($popt, $opt);
13116
		// set remaining annotation data
13117
		$opt['Subtype'] = 'Widget';
13118
		$opt['ft'] = 'Ch';
13119
		$opt['t'] = $name;
13120
		$opt['opt'] = $values;
13121
		unset($opt['mk']['ca']);
13122
		unset($opt['mk']['rc']);
13123
		unset($opt['mk']['ac']);
13124
		unset($opt['mk']['i']);
13125
		unset($opt['mk']['ri']);
13126
		unset($opt['mk']['ix']);
13127
		unset($opt['mk']['if']);
13128
		unset($opt['mk']['tp']);
13129
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13130
		if ($this->rtl) {
13131
			$this->x -= $w;
13132
		} else {
13133
			$this->x += $w;
13134
		}
13135
	}
13136
13137
	/**
13138
	 * Creates a Combo-box field
13139
	 * @param string $name field name
13140
	 * @param int $w width
13141
	 * @param int $h height
13142
	 * @param array $values array containing the list of values.
13143
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13144
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13145
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13146
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13147
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13148
	 * @public
13149
	 * @author Nicola Asuni
13150
	 * @since 4.8.000 (2009-09-07)
13151
	 */
13152
	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13153
		if (TCPDF_STATIC::empty_string($x)) {
13154
			$x = $this->x;
13155
		}
13156
		if (TCPDF_STATIC::empty_string($y)) {
13157
			$y = $this->y;
13158
		}
13159
		// check page for no-write regions and adapt page margins if necessary
13160
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13161
		if ($js) {
13162
			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13163
			$s = '';
13164
			foreach ($values as $value) {
13165
				if (is_array($value)) {
13166
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13167
				} else {
13168
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13169
				}
13170
			}
13171
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13172
			return;
13173
		}
13174
		// get default style
13175
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13176
		$prop['Combo'] = true;
13177
		// get annotation data
13178
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13179
		// set additional default options
13180
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13181
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13182
		$popt['da'] = $fontstyle;
13183
		// build appearance stream
13184
		$popt['ap'] = array();
13185
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13186
		$text = '';
13187
		foreach($values as $item) {
13188
			if (is_array($item)) {
13189
				$text .= $item[1]."\n";
13190
			} else {
13191
				$text .= $item."\n";
13192
			}
13193
		}
13194
		$tmpid = $this->startTemplate($w, $h, false);
13195
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13196
		$this->endTemplate();
13197
		--$this->n;
13198
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13199
		unset($this->xobjects[$tmpid]);
13200
		$popt['ap']['n'] .= 'Q EMC';
13201
		// merge options
13202
		$opt = array_merge($popt, $opt);
13203
		// set remaining annotation data
13204
		$opt['Subtype'] = 'Widget';
13205
		$opt['ft'] = 'Ch';
13206
		$opt['t'] = $name;
13207
		$opt['opt'] = $values;
13208
		unset($opt['mk']['ca']);
13209
		unset($opt['mk']['rc']);
13210
		unset($opt['mk']['ac']);
13211
		unset($opt['mk']['i']);
13212
		unset($opt['mk']['ri']);
13213
		unset($opt['mk']['ix']);
13214
		unset($opt['mk']['if']);
13215
		unset($opt['mk']['tp']);
13216
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13217
		if ($this->rtl) {
13218
			$this->x -= $w;
13219
		} else {
13220
			$this->x += $w;
13221
		}
13222
	}
13223
13224
	/**
13225
	 * Creates a CheckBox field
13226
	 * @param string $name field name
13227
	 * @param int $w width
13228
	 * @param boolean $checked define the initial state.
13229
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13230
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13231
	 * @param string $onvalue value to be returned if selected.
13232
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13233
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13234
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13235
	 * @public
13236
	 * @author Nicola Asuni
13237
	 * @since 4.8.000 (2009-09-07)
13238
	 */
13239
	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) {
13240
		if (TCPDF_STATIC::empty_string($x)) {
13241
			$x = $this->x;
13242
		}
13243
		if (TCPDF_STATIC::empty_string($y)) {
13244
			$y = $this->y;
13245
		}
13246
		// check page for no-write regions and adapt page margins if necessary
13247
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13248
		if ($js) {
13249
			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13250
			return;
13251
		}
13252
		if (!isset($prop['value'])) {
13253
			$prop['value'] = array('Yes');
13254
		}
13255
		// get default style
13256
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13257
		$prop['borderStyle'] = 'inset';
13258
		// get annotation data
13259
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13260
		// set additional default options
13261
		$font = 'zapfdingbats';
13262
		if ($this->pdfa_mode) {
13263
			// all fonts must be embedded
13264
			$font = 'pdfa'.$font;
13265
		}
13266
		$this->AddFont($font);
13267
		$tmpfont = $this->getFontBuffer($font);
13268
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13269
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13270
		$popt['da'] = $fontstyle;
13271
		// build appearance stream
13272
		$popt['ap'] = array();
13273
		$popt['ap']['n'] = array();
13274
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13275
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13276
		$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);
13277
		$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);
13278
		// merge options
13279
		$opt = array_merge($popt, $opt);
13280
		// set remaining annotation data
13281
		$opt['Subtype'] = 'Widget';
13282
		$opt['ft'] = 'Btn';
13283
		$opt['t'] = $name;
13284
		if (TCPDF_STATIC::empty_string($onvalue)) {
13285
			$onvalue = 'Yes';
13286
		}
13287
		$opt['opt'] = array($onvalue);
13288
		if ($checked) {
13289
			$opt['v'] = array('/Yes');
13290
			$opt['as'] = 'Yes';
13291
		} else {
13292
			$opt['v'] = array('/Off');
13293
			$opt['as'] = 'Off';
13294
		}
13295
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13296
		if ($this->rtl) {
13297
			$this->x -= $w;
13298
		} else {
13299
			$this->x += $w;
13300
		}
13301
	}
13302
13303
	/**
13304
	 * Creates a button field
13305
	 * @param string $name field name
13306
	 * @param int $w width
13307
	 * @param int $h height
13308
	 * @param string $caption caption.
13309
	 * @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.
13310
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13311
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13312
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13313
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13314
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13315
	 * @public
13316
	 * @author Nicola Asuni
13317
	 * @since 4.8.000 (2009-09-07)
13318
	 */
13319
	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13320
		if (TCPDF_STATIC::empty_string($x)) {
13321
			$x = $this->x;
13322
		}
13323
		if (TCPDF_STATIC::empty_string($y)) {
13324
			$y = $this->y;
13325
		}
13326
		// check page for no-write regions and adapt page margins if necessary
13327
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13328
		if ($js) {
13329
			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13330
			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13331
			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13332
			$this->javascript .= 'f'.$name.".highlight='push';\n";
13333
			$this->javascript .= 'f'.$name.".print=false;\n";
13334
			return;
13335
		}
13336
		// get default style
13337
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13338
		$prop['Pushbutton'] = 'true';
13339
		$prop['highlight'] = 'push';
13340
		$prop['display'] = 'display.noPrint';
13341
		// get annotation data
13342
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13343
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13344
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13345
		$popt['da'] = $fontstyle;
13346
		// build appearance stream
13347
		$popt['ap'] = array();
13348
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13349
		$tmpid = $this->startTemplate($w, $h, false);
13350
		$bw = (2 / $this->k); // border width
13351
		$border = array(
13352
			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13353
			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13354
			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13355
			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13356
		$this->setFillColor(204);
13357
		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13358
		$this->endTemplate();
13359
		--$this->n;
13360
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13361
		unset($this->xobjects[$tmpid]);
13362
		$popt['ap']['n'] .= 'Q EMC';
13363
		// set additional default options
13364
		if (!isset($popt['mk'])) {
13365
			$popt['mk'] = array();
13366
		}
13367
		$ann_obj_id = ($this->n + 1);
13368
		if (!empty($action) AND !is_array($action)) {
13369
			$ann_obj_id = ($this->n + 2);
13370
		}
13371
		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13372
		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13373
		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13374
		// merge options
13375
		$opt = array_merge($popt, $opt);
13376
		// set remaining annotation data
13377
		$opt['Subtype'] = 'Widget';
13378
		$opt['ft'] = 'Btn';
13379
		$opt['t'] = $caption;
13380
		$opt['v'] = $name;
13381
		if (!empty($action)) {
13382
			if (is_array($action)) {
13383
				// form action options as on section 12.7.5 of PDF32000_2008.
13384
				$opt['aa'] = '/D <<';
13385
				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13386
				foreach ($action AS $key => $val) {
13387
					if (($key == 'S') AND in_array($val, $bmode)) {
13388
						$opt['aa'] .= ' /S /'.$val;
13389
					} elseif (($key == 'F') AND (!empty($val))) {
13390
						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13391
					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13392
						$opt['aa'] .= ' /Fields [';
13393
						foreach ($val AS $field) {
13394
							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13395
						}
13396
						$opt['aa'] .= ']';
13397
					} elseif (($key == 'Flags')) {
13398
						$ff = 0;
13399
						if (is_array($val)) {
13400
							foreach ($val AS $flag) {
13401
								switch ($flag) {
13402
									case 'Include/Exclude': {
13403
										$ff += 1 << 0;
13404
										break;
13405
									}
13406
									case 'IncludeNoValueFields': {
13407
										$ff += 1 << 1;
13408
										break;
13409
									}
13410
									case 'ExportFormat': {
13411
										$ff += 1 << 2;
13412
										break;
13413
									}
13414
									case 'GetMethod': {
13415
										$ff += 1 << 3;
13416
										break;
13417
									}
13418
									case 'SubmitCoordinates': {
13419
										$ff += 1 << 4;
13420
										break;
13421
									}
13422
									case 'XFDF': {
13423
										$ff += 1 << 5;
13424
										break;
13425
									}
13426
									case 'IncludeAppendSaves': {
13427
										$ff += 1 << 6;
13428
										break;
13429
									}
13430
									case 'IncludeAnnotations': {
13431
										$ff += 1 << 7;
13432
										break;
13433
									}
13434
									case 'SubmitPDF': {
13435
										$ff += 1 << 8;
13436
										break;
13437
									}
13438
									case 'CanonicalFormat': {
13439
										$ff += 1 << 9;
13440
										break;
13441
									}
13442
									case 'ExclNonUserAnnots': {
13443
										$ff += 1 << 10;
13444
										break;
13445
									}
13446
									case 'ExclFKey': {
13447
										$ff += 1 << 11;
13448
										break;
13449
									}
13450
									case 'EmbedForm': {
13451
										$ff += 1 << 13;
13452
										break;
13453
									}
13454
								}
13455
							}
13456
						} else {
13457
							$ff = intval($val);
13458
						}
13459
						$opt['aa'] .= ' /Flags '.$ff;
13460
					}
13461
				}
13462
				$opt['aa'] .= ' >>';
13463
			} else {
13464
				// Javascript action or raw action command
13465
				$js_obj_id = $this->addJavascriptObject($action);
13466
				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13467
			}
13468
		}
13469
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13470
		if ($this->rtl) {
13471
			$this->x -= $w;
13472
		} else {
13473
			$this->x += $w;
13474
		}
13475
	}
13476
13477
	// --- END FORMS FIELDS ------------------------------------------------
13478
13479
	/**
13480
	 * Add certification signature (DocMDP or UR3)
13481
	 * You can set only one signature type
13482
	 * @protected
13483
	 * @author Nicola Asuni
13484
	 * @since 4.6.008 (2009-05-07)
13485
	 */
13486
	protected function _putsignature() {
13487
		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13488
			return;
13489
		}
13490
		$sigobjid = ($this->sig_obj_id + 1);
13491
		$out = $this->_getobj($sigobjid)."\n";
13492
		$out .= '<< /Type /Sig';
13493
		$out .= ' /Filter /Adobe.PPKLite';
13494
		$out .= ' /SubFilter /adbe.pkcs7.detached';
13495
		$out .= ' '.TCPDF_STATIC::$byterange_string;
13496
		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13497
		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13498
			$out .= ' /Reference ['; // array of signature reference dictionaries
13499
			$out .= ' << /Type /SigRef';
13500
			if ($this->signature_data['cert_type'] > 0) {
13501
				$out .= ' /TransformMethod /DocMDP';
13502
				$out .= ' /TransformParams <<';
13503
				$out .= ' /Type /TransformParams';
13504
				$out .= ' /P '.$this->signature_data['cert_type'];
13505
				$out .= ' /V /1.2';
13506
			} else {
13507
				$out .= ' /TransformMethod /UR3';
13508
				$out .= ' /TransformParams <<';
13509
				$out .= ' /Type /TransformParams';
13510
				$out .= ' /V /2.2';
13511
				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13512
					$out .= ' /Document['.$this->ur['document'].']';
13513
				}
13514
				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13515
					$out .= ' /Form['.$this->ur['form'].']';
13516
				}
13517
				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13518
					$out .= ' /Signature['.$this->ur['signature'].']';
13519
				}
13520
				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13521
					$out .= ' /Annots['.$this->ur['annots'].']';
13522
				}
13523
				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13524
					$out .= ' /EF['.$this->ur['ef'].']';
13525
				}
13526
				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13527
					$out .= ' /FormEX['.$this->ur['formex'].']';
13528
				}
13529
			}
13530
			$out .= ' >>'; // close TransformParams
13531
			// optional digest data (values must be calculated and replaced later)
13532
			//$out .= ' /Data ********** 0 R';
13533
			//$out .= ' /DigestMethod/MD5';
13534
			//$out .= ' /DigestLocation[********** 34]';
13535
			//$out .= ' /DigestValue<********************************>';
13536
			$out .= ' >>';
13537
			$out .= ' ]'; // end of reference
13538
		}
13539
		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13540
			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13541
		}
13542
		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13543
			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13544
		}
13545
		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13546
			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13547
		}
13548
		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13549
			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13550
		}
13551
		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13552
		$out .= ' >>';
13553
		$out .= "\n".'endobj';
13554
		$this->_out($out);
13555
	}
13556
13557
	/**
13558
	 * Set User's Rights for PDF Reader
13559
	 * WARNING: This is experimental and currently do not work.
13560
	 * Check the PDF Reference 8.7.1 Transform Methods,
13561
	 * Table 8.105 Entries in the UR transform parameters dictionary
13562
	 * @param boolean $enable if true enable user's rights on PDF reader
13563
	 * @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.
13564
	 * @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.
13565
	 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13566
	 * @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.
13567
	 * @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
13568
	 Names specifying additional embedded-files-related usage rights for the document.
13569
	 * @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.
13570
	 * @public
13571
	 * @author Nicola Asuni
13572
	 * @since 2.9.000 (2008-03-26)
13573
	 */
13574
	public function setUserRights(
13575
			$enable=true,
13576
			$document='/FullSave',
13577
			$annots='/Create/Delete/Modify/Copy/Import/Export',
13578
			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13579
			$signature='/Modify',
13580
			$ef='/Create/Delete/Modify/Import',
13581
			$formex='') {
13582
		$this->ur['enabled'] = $enable;
13583
		$this->ur['document'] = $document;
13584
		$this->ur['annots'] = $annots;
13585
		$this->ur['form'] = $form;
13586
		$this->ur['signature'] = $signature;
13587
		$this->ur['ef'] = $ef;
13588
		$this->ur['formex'] = $formex;
13589
		if (!$this->sign) {
13590
			$this->setSignature('', '', '', '', 0, array());
13591
		}
13592
	}
13593
13594
	/**
13595
	 * Enable document signature (requires the OpenSSL Library).
13596
	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13597
	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13598
	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13599
	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13600
	 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13601
	 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13602
	 * @param string $private_key_password password
13603
	 * @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.
13604
	 * @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.
13605
	 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13606
	 * @param string $approval Enable approval signature eg. for PDF incremental update
13607
	 * @public
13608
	 * @author Nicola Asuni
13609
	 * @since 4.6.005 (2009-04-24)
13610
	 */
13611
	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13612
		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13613
		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13614
		// to convert pfx certificate to pem: openssl
13615
		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13616
		$this->sign = true;
13617
		++$this->n;
13618
		$this->sig_obj_id = $this->n; // signature widget
13619
		++$this->n; // signature object ($this->sig_obj_id + 1)
13620
		$this->signature_data = array();
13621
		if (strlen($signing_cert) == 0) {
13622
			$this->Error('Please provide a certificate file and password!');
13623
		}
13624
		if (strlen($private_key) == 0) {
13625
			$private_key = $signing_cert;
13626
		}
13627
		$this->signature_data['signcert'] = $signing_cert;
13628
		$this->signature_data['privkey'] = $private_key;
13629
		$this->signature_data['password'] = $private_key_password;
13630
		$this->signature_data['extracerts'] = $extracerts;
13631
		$this->signature_data['cert_type'] = $cert_type;
13632
		$this->signature_data['info'] = $info;
13633
		$this->signature_data['approval'] = $approval;
13634
	}
13635
13636
	/**
13637
	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13638
	 * @param float $x Abscissa of the upper-left corner.
13639
	 * @param float $y Ordinate of the upper-left corner.
13640
	 * @param float $w Width of the signature area.
13641
	 * @param float $h Height of the signature area.
13642
	 * @param int $page option page number (if < 0 the current page is used).
13643
	 * @param string $name Name of the signature.
13644
	 * @public
13645
	 * @author Nicola Asuni
13646
	 * @since 5.3.011 (2010-06-17)
13647
	 */
13648
	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13649
		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13650
	}
13651
13652
	/**
13653
	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13654
	 * @param float $x Abscissa of the upper-left corner.
13655
	 * @param float $y Ordinate of the upper-left corner.
13656
	 * @param float $w Width of the signature area.
13657
	 * @param float $h Height of the signature area.
13658
	 * @param int $page option page number (if < 0 the current page is used).
13659
	 * @param string $name Name of the signature.
13660
	 * @public
13661
	 * @author Nicola Asuni
13662
	 * @since 5.9.101 (2011-07-06)
13663
	 */
13664
	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13665
		++$this->n;
13666
		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13667
	}
13668
13669
	/**
13670
	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13671
	 * @param float $x Abscissa of the upper-left corner.
13672
	 * @param float $y Ordinate of the upper-left corner.
13673
	 * @param float $w Width of the signature area.
13674
	 * @param float $h Height of the signature area.
13675
	 * @param int $page option page number (if < 0 the current page is used).
13676
	 * @param string $name Name of the signature.
13677
	 * @return array Array defining page and rectangle coordinates of signature appearance.
13678
	 * @protected
13679
	 * @author Nicola Asuni
13680
	 * @since 5.9.101 (2011-07-06)
13681
	 */
13682
	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13683
		$sigapp = array();
13684
		if (($page < 1) OR ($page > $this->numpages)) {
13685
			$sigapp['page'] = $this->page;
13686
		} else {
13687
			$sigapp['page'] = intval($page);
13688
		}
13689
		if (empty($name)) {
13690
			$sigapp['name'] = 'Signature';
13691
		} else {
13692
			$sigapp['name'] = $name;
13693
		}
13694
		$a = $x * $this->k;
13695
		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13696
		$c = $w * $this->k;
13697
		$d = $h * $this->k;
13698
		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13699
		return $sigapp;
13700
	}
13701
13702
	/**
13703
	 * Enable document timestamping (requires the OpenSSL Library).
13704
	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13705
	 * Use with digital signature only!
13706
	 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13707
	 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13708
	 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13709
	 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13710
	 * @public
13711
	 * @author Richard Stockinger
13712
	 * @since 6.0.090 (2014-06-16)
13713
	 */
13714
	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13715
		$this->tsa_data = array();
13716
		if (!function_exists('curl_init')) {
13717
			$this->Error('Please enable cURL PHP extension!');
13718
		}
13719
		if (strlen($tsa_host) == 0) {
13720
			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13721
		}
13722
		$this->tsa_data['tsa_host'] = $tsa_host;
13723
		if (is_file($tsa_username)) {
13724
			$this->tsa_data['tsa_auth'] = $tsa_username;
13725
		} else {
13726
			$this->tsa_data['tsa_username'] = $tsa_username;
13727
		}
13728
		$this->tsa_data['tsa_password'] = $tsa_password;
13729
		$this->tsa_data['tsa_cert'] = $tsa_cert;
13730
		$this->tsa_timestamp = true;
13731
	}
13732
13733
	/**
13734
	 * NOT YET IMPLEMENTED
13735
	 * Request TSA for a timestamp
13736
	 * @param string $signature Digital signature as binary string
13737
	 * @return string Timestamped digital signature
13738
	 * @protected
13739
	 * @author Richard Stockinger
13740
	 * @since 6.0.090 (2014-06-16)
13741
	 */
13742
	protected function applyTSA($signature) {
13743
		if (!$this->tsa_timestamp) {
13744
			return $signature;
13745
		}
13746
		//@TODO: implement this feature
13747
		return $signature;
13748
	}
13749
13750
	/**
13751
	 * Create a new page group.
13752
	 * NOTE: call this function before calling AddPage()
13753
	 * @param int|null $page starting group page (leave empty for next page).
13754
	 * @public
13755
	 * @since 3.0.000 (2008-03-27)
13756
	 */
13757
	public function startPageGroup($page=null) {
13758
		if (empty($page)) {
13759
			$page = $this->page + 1;
13760
		}
13761
		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13762
	}
13763
13764
	/**
13765
	 * Set the starting page number.
13766
	 * @param int $num Starting page number.
13767
	 * @since 5.9.093 (2011-06-16)
13768
	 * @public
13769
	 */
13770
	public function setStartingPageNumber($num=1) {
13771
		$this->starting_page_number = max(0, intval($num));
13772
	}
13773
13774
	/**
13775
	 * Returns the string alias used right align page numbers.
13776
	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13777
	 * @return string
13778
	 * @since 5.9.099 (2011-06-27)
13779
	 * @public
13780
	 */
13781
	public function getAliasRightShift() {
13782
		// calculate aproximatively the ratio between widths of aliases and replacements.
13783
		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13784
		$rep = str_repeat(' ', $this->GetNumChars($ref));
13785
		$wrep = $this->GetStringWidth($rep);
13786
		if ($wrep > 0) {
13787
			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13788
		} else {
13789
			$wdiff = 1;
13790
		}
13791
		$sdiff = sprintf('%F', $wdiff);
13792
		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13793
		if ($this->isUnicodeFont()) {
13794
			$alias = '{'.$alias;
13795
		}
13796
		return $alias;
13797
	}
13798
13799
	/**
13800
	 * Returns the string alias used for the total number of pages.
13801
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13802
	 * This alias will be replaced by the total number of pages in the document.
13803
	 * @return string
13804
	 * @since 4.0.018 (2008-08-08)
13805
	 * @public
13806
	 */
13807
	public function getAliasNbPages() {
13808
		if ($this->isUnicodeFont()) {
13809
			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13810
		}
13811
		return TCPDF_STATIC::$alias_tot_pages;
13812
	}
13813
13814
	/**
13815
	 * Returns the string alias used for the page number.
13816
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13817
	 * This alias will be replaced by the page number.
13818
	 * @return string
13819
	 * @since 4.5.000 (2009-01-02)
13820
	 * @public
13821
	 */
13822
	public function getAliasNumPage() {
13823
		if ($this->isUnicodeFont()) {
13824
			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13825
		}
13826
		return TCPDF_STATIC::$alias_num_page;
13827
	}
13828
13829
	/**
13830
	 * Return the alias for the total number of pages in the current page group.
13831
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13832
	 * This alias will be replaced by the total number of pages in this group.
13833
	 * @return string alias of the current page group
13834
	 * @public
13835
	 * @since 3.0.000 (2008-03-27)
13836
	 */
13837
	public function getPageGroupAlias() {
13838
		if ($this->isUnicodeFont()) {
13839
			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13840
		}
13841
		return TCPDF_STATIC::$alias_group_tot_pages;
13842
	}
13843
13844
	/**
13845
	 * Return the alias for the page number on the current page group.
13846
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13847
	 * This alias will be replaced by the page number (relative to the belonging group).
13848
	 * @return string alias of the current page group
13849
	 * @public
13850
	 * @since 4.5.000 (2009-01-02)
13851
	 */
13852
	public function getPageNumGroupAlias() {
13853
		if ($this->isUnicodeFont()) {
13854
			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13855
		}
13856
		return TCPDF_STATIC::$alias_group_num_page;
13857
	}
13858
13859
	/**
13860
	 * Return the current page in the group.
13861
	 * @return int current page in the group
13862
	 * @public
13863
	 * @since 3.0.000 (2008-03-27)
13864
	 */
13865
	public function getGroupPageNo() {
13866
		return $this->pagegroups[$this->currpagegroup];
13867
	}
13868
13869
	/**
13870
	 * Returns the current group page number formatted as a string.
13871
	 * @public
13872
	 * @since 4.3.003 (2008-11-18)
13873
	 * @see PaneNo(), formatPageNumber()
13874
	 */
13875
	public function getGroupPageNoFormatted() {
13876
		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13877
	}
13878
13879
	/**
13880
	 * Returns the current page number formatted as a string.
13881
	 * @public
13882
	 * @since 4.2.005 (2008-11-06)
13883
	 * @see PaneNo(), formatPageNumber()
13884
	 */
13885
	public function PageNoFormatted() {
13886
		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13887
	}
13888
13889
	/**
13890
	 * Put pdf layers.
13891
	 * @protected
13892
	 * @since 3.0.000 (2008-03-27)
13893
	 */
13894
	protected function _putocg() {
13895
		if (empty($this->pdflayers)) {
13896
			return;
13897
		}
13898
		foreach ($this->pdflayers as $key => $layer) {
13899
			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13900
			 $out = '<< /Type /OCG';
13901
			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13902
			 $out .= ' /Usage <<';
13903
			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13904
				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13905
			 }
13906
			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13907
			 $out .= ' >> >>';
13908
			 $out .= "\n".'endobj';
13909
			 $this->_out($out);
13910
		}
13911
	}
13912
13913
	/**
13914
	 * Start a new pdf layer.
13915
	 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13916
	 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13917
	 * @param boolean $view Set to true to view this layer.
13918
	 * @param boolean $lock If true lock the layer
13919
	 * @public
13920
	 * @since 5.9.102 (2011-07-13)
13921
	 */
13922
	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13923
		if ($this->state != 2) {
13924
			return;
13925
		}
13926
		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13927
		if (empty($name)) {
13928
			$name = $layer;
13929
		} else {
13930
			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13931
		}
13932
		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13933
		$this->openMarkedContent = true;
13934
		$this->_out('/OC /'.$layer.' BDC');
13935
	}
13936
13937
	/**
13938
	 * End the current PDF layer.
13939
	 * @public
13940
	 * @since 5.9.102 (2011-07-13)
13941
	 */
13942
	public function endLayer() {
13943
		if ($this->state != 2) {
13944
			return;
13945
		}
13946
		if ($this->openMarkedContent) {
13947
			// close existing open marked-content layer
13948
			$this->_out('EMC');
13949
			$this->openMarkedContent = false;
13950
		}
13951
	}
13952
13953
	/**
13954
	 * Set the visibility of the successive elements.
13955
	 * This can be useful, for instance, to put a background
13956
	 * image or color that will show on screen but won't print.
13957
	 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13958
	 * @public
13959
	 * @since 3.0.000 (2008-03-27)
13960
	 */
13961
	public function setVisibility($v) {
13962
		if ($this->state != 2) {
13963
			return;
13964
		}
13965
		$this->endLayer();
13966
		switch($v) {
13967
			case 'print': {
13968
				$this->startLayer('Print', true, false);
13969
				break;
13970
			}
13971
			case 'view':
13972
			case 'screen': {
13973
				$this->startLayer('View', false, true);
13974
				break;
13975
			}
13976
			case 'all': {
13977
				$this->_out('');
13978
				break;
13979
			}
13980
			default: {
13981
				$this->Error('Incorrect visibility: '.$v);
13982
				break;
13983
			}
13984
		}
13985
	}
13986
13987
	/**
13988
	 * Add transparency parameters to the current extgstate
13989
	 * @param array $parms parameters
13990
	 * @return int|void the number of extgstates
13991
	 * @protected
13992
	 * @since 3.0.000 (2008-03-27)
13993
	 */
13994
	protected function addExtGState($parms) {
13995
		if (($this->pdfa_mode && $this->pdfa_version < 2) || ($this->state != 2)) {
13996
			// transparency is not allowed in PDF/A-1 mode
13997
			return;
13998
		}
13999
		// check if this ExtGState already exist
14000
		foreach ($this->extgstates as $i => $ext) {
14001
			if ($ext['parms'] == $parms) {
14002
				if ($this->inxobj) {
14003
					// we are inside an XObject template
14004
					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
14005
				}
14006
				// return reference to existing ExtGState
14007
				return $i;
14008
			}
14009
		}
14010
		$n = (count($this->extgstates) + 1);
14011
		$this->extgstates[$n] = array('parms' => $parms);
14012
		if ($this->inxobj) {
14013
			// we are inside an XObject template
14014
			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
14015
		}
14016
		return $n;
14017
	}
14018
14019
	/**
14020
	 * Add an extgstate
14021
	 * @param int $gs extgstate
14022
	 * @protected
14023
	 * @since 3.0.000 (2008-03-27)
14024
	 */
14025
	protected function setExtGState($gs) {
14026
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14027
			// transparency is not allowed in PDF/A-1 mode
14028
			return;
14029
		}
14030
		$this->_out(sprintf('/GS%d gs', $gs));
14031
	}
14032
14033
	/**
14034
	 * Put extgstates for object transparency
14035
	 * @protected
14036
	 * @since 3.0.000 (2008-03-27)
14037
	 */
14038
	protected function _putextgstates() {
14039
		foreach ($this->extgstates as $i => $ext) {
14040
			$this->extgstates[$i]['n'] = $this->_newobj();
14041
			$out = '<< /Type /ExtGState';
14042
			foreach ($ext['parms'] as $k => $v) {
14043
				if (is_float($v)) {
14044
					$v = sprintf('%F', $v);
14045
				} elseif ($v === true) {
14046
					$v = 'true';
14047
				} elseif ($v === false) {
14048
					$v = 'false';
14049
				}
14050
				$out .= ' /'.$k.' '.$v;
14051
			}
14052
			$out .= ' >>';
14053
			$out .= "\n".'endobj';
14054
			$this->_out($out);
14055
		}
14056
	}
14057
14058
	/**
14059
	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
14060
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14061
	 * @param boolean $stroking If true apply overprint for stroking operations.
14062
	 * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking.
14063
	 * @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).
14064
	 * @public
14065
	 * @since 5.9.152 (2012-03-23)
14066
	 */
14067
	public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14068
		if ($this->state != 2) {
14069
			return;
14070
		}
14071
		$stroking = $stroking ? true : false;
14072
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14073
			// default value if not set
14074
			$nonstroking = $stroking;
14075
		} else {
14076
			$nonstroking = $nonstroking ? true : false;
14077
		}
14078
		if (($mode != 0) AND ($mode != 1)) {
14079
			$mode = 0;
14080
		}
14081
		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14082
		$gs = $this->addExtGState($this->overprint);
14083
		$this->setExtGState($gs);
14084
	}
14085
14086
	/**
14087
	 * Get the overprint mode array (OP, op, OPM).
14088
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14089
	 * @return array<string,bool|int>
14090
	 * @public
14091
	 * @since 5.9.152 (2012-03-23)
14092
	 */
14093
	public function getOverprint() {
14094
		return $this->overprint;
14095
	}
14096
14097
	/**
14098
	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14099
	 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14100
	 * @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
14101
	 * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14102
	 * @param boolean $ais
14103
	 * @public
14104
	 * @since 3.0.000 (2008-03-27)
14105
	 */
14106
	public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) {
14107
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14108
			// transparency is not allowed in PDF/A-1 mode
14109
			return;
14110
		}
14111
		$stroking = floatval($stroking);
14112
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14113
			// default value if not set
14114
			$nonstroking = $stroking;
14115
		} else {
14116
			$nonstroking = floatval($nonstroking);
14117
		}
14118
		if ($bm[0] == '/') {
14119
			// remove trailing slash
14120
			$bm = substr($bm, 1);
14121
		}
14122
		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14123
			$bm = 'Normal';
14124
		}
14125
		$ais = $ais ? true : false;
14126
		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14127
		$gs = $this->addExtGState($this->alpha);
14128
		$this->setExtGState($gs);
14129
	}
14130
14131
	/**
14132
	 * Get the alpha mode array (CA, ca, BM, AIS).
14133
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14134
	 * @return array<string,bool|string>
14135
	 * @public
14136
	 * @since 5.9.152 (2012-03-23)
14137
	 */
14138
	public function getAlpha() {
14139
		return $this->alpha;
14140
	}
14141
14142
	/**
14143
	 * Set the default JPEG compression quality (1-100)
14144
	 * @param int $quality JPEG quality, integer between 1 and 100
14145
	 * @public
14146
	 * @since 3.0.000 (2008-03-27)
14147
	 */
14148
	public function setJPEGQuality($quality) {
14149
		if (($quality < 1) OR ($quality > 100)) {
14150
			$quality = 75;
14151
		}
14152
		$this->jpeg_quality = intval($quality);
14153
	}
14154
14155
	/**
14156
	 * Set the default number of columns in a row for HTML tables.
14157
	 * @param int $cols number of columns
14158
	 * @public
14159
	 * @since 3.0.014 (2008-06-04)
14160
	 */
14161
	public function setDefaultTableColumns($cols=4) {
14162
		$this->default_table_columns = intval($cols);
14163
	}
14164
14165
	/**
14166
	 * Set the height of the cell (line height) respect the font height.
14167
	 * @param float $h cell proportion respect font height (typical value = 1.25).
14168
	 * @public
14169
	 * @since 3.0.014 (2008-06-04)
14170
	 */
14171
	public function setCellHeightRatio($h) {
14172
		$this->cell_height_ratio = $h;
14173
	}
14174
14175
	/**
14176
	 * return the height of cell repect font height.
14177
	 * @public
14178
	 * @return float
14179
	 * @since 4.0.012 (2008-07-24)
14180
	 */
14181
	public function getCellHeightRatio() {
14182
		return $this->cell_height_ratio;
14183
	}
14184
14185
	/**
14186
	 * Set the PDF version (check PDF reference for valid values).
14187
	 * @param string $version PDF document version.
14188
	 * @public
14189
	 * @since 3.1.000 (2008-06-09)
14190
	 */
14191
	public function setPDFVersion($version='1.7') {
14192
		if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14193
			// PDF/A-1 mode
14194
			$this->PDFVersion = '1.4';
14195
		} elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14196
            // PDF/A-2 mode
14197
            $this->PDFVersion = '1.7';
14198
        } else {
14199
			$this->PDFVersion = $version;
14200
		}
14201
	}
14202
14203
	/**
14204
	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14205
	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14206
	 * <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>
14207
	 * @param array $preferences array of options.
14208
	 * @author Nicola Asuni
14209
	 * @public
14210
	 * @since 3.1.000 (2008-06-09)
14211
	 */
14212
	public function setViewerPreferences($preferences) {
14213
		$this->viewer_preferences = $preferences;
14214
	}
14215
14216
	/**
14217
	 * Paints color transition registration bars
14218
	 * @param float $x abscissa of the top left corner of the rectangle.
14219
	 * @param float $y ordinate of the top left corner of the rectangle.
14220
	 * @param float $w width of the rectangle.
14221
	 * @param float $h height of the rectangle.
14222
	 * @param boolean $transition if true prints tcolor transitions to white.
14223
	 * @param boolean $vertical if true prints bar vertically.
14224
	 * @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.
14225
	 * @author Nicola Asuni
14226
	 * @since 4.9.000 (2010-03-26)
14227
	 * @public
14228
	 */
14229
	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14230
		if (strpos($colors, 'ALLSPOT') !== false) {
14231
			// expand spot colors
14232
			$spot_colors = '';
14233
			foreach ($this->spot_colors as $spot_color_name => $v) {
14234
				$spot_colors .= ','.$spot_color_name;
14235
			}
14236
			if (!empty($spot_colors)) {
14237
				$spot_colors = substr($spot_colors, 1);
14238
				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14239
			} else {
14240
				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14241
			}
14242
		}
14243
		$bars = explode(',', $colors);
14244
		$numbars = count($bars); // number of bars to print
14245
		if ($numbars <= 0) {
14246
			return;
14247
		}
14248
		// set bar measures
14249
		if ($vertical) {
14250
			$coords = array(0, 0, 0, 1);
14251
			$wb = $w / $numbars; // bar width
14252
			$hb = $h; // bar height
14253
			$xd = $wb; // delta x
14254
			$yd = 0; // delta y
14255
		} else {
14256
			$coords = array(1, 0, 0, 0);
14257
			$wb = $w; // bar width
14258
			$hb = $h / $numbars; // bar height
14259
			$xd = 0; // delta x
14260
			$yd = $hb; // delta y
14261
		}
14262
		$xb = $x;
14263
		$yb = $y;
14264
		foreach ($bars as $col) {
14265
			switch ($col) {
14266
				// set transition colors
14267
				case 'A': { // BLACK (GRAYSCALE)
14268
					$col_a = array(255);
14269
					$col_b = array(0);
14270
					break;
14271
				}
14272
				case 'W': { // WHITE (GRAYSCALE)
14273
					$col_a = array(0);
14274
					$col_b = array(255);
14275
					break;
14276
				}
14277
				case 'R': { // RED (RGB)
14278
					$col_a = array(255,255,255);
14279
					$col_b = array(255,0,0);
14280
					break;
14281
				}
14282
				case 'G': { // GREEN (RGB)
14283
					$col_a = array(255,255,255);
14284
					$col_b = array(0,255,0);
14285
					break;
14286
				}
14287
				case 'B': { // BLUE (RGB)
14288
					$col_a = array(255,255,255);
14289
					$col_b = array(0,0,255);
14290
					break;
14291
				}
14292
				case 'C': { // CYAN (CMYK)
14293
					$col_a = array(0,0,0,0);
14294
					$col_b = array(100,0,0,0);
14295
					break;
14296
				}
14297
				case 'M': { // MAGENTA (CMYK)
14298
					$col_a = array(0,0,0,0);
14299
					$col_b = array(0,100,0,0);
14300
					break;
14301
				}
14302
				case 'Y': { // YELLOW (CMYK)
14303
					$col_a = array(0,0,0,0);
14304
					$col_b = array(0,0,100,0);
14305
					break;
14306
				}
14307
				case 'K': { // KEY - BLACK (CMYK)
14308
					$col_a = array(0,0,0,0);
14309
					$col_b = array(0,0,0,100);
14310
					break;
14311
				}
14312
				case 'RGB': { // BLACK REGISTRATION (RGB)
14313
					$col_a = array(255,255,255);
14314
					$col_b = array(0,0,0);
14315
					break;
14316
				}
14317
				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14318
					$col_a = array(0,0,0,0);
14319
					$col_b = array(100,100,100,100);
14320
					break;
14321
				}
14322
				case 'ALL': { // SPOT COLOR REGISTRATION
14323
					$col_a = array(0,0,0,0,'None');
14324
					$col_b = array(100,100,100,100,'All');
14325
					break;
14326
				}
14327
				case 'NONE': { // SKIP THIS COLOR
14328
					$col_a = array(0,0,0,0,'None');
14329
					$col_b = array(0,0,0,0,'None');
14330
					break;
14331
				}
14332
				default: { // SPECIFIC SPOT COLOR NAME
14333
					$col_a = array(0,0,0,0,'None');
14334
					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14335
					if ($col_b === false) {
14336
						// in case of error defaults to the registration color
14337
						$col_b = array(100,100,100,100,'All');
14338
					}
14339
					break;
14340
				}
14341
			}
14342
			if ($col != 'NONE') {
14343
				if ($transition) {
14344
					// color gradient
14345
					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14346
				} else {
14347
					$this->setFillColorArray($col_b);
14348
					// colored rectangle
14349
					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14350
				}
14351
				$xb += $xd;
14352
				$yb += $yd;
14353
			}
14354
		}
14355
	}
14356
14357
	/**
14358
	 * Paints crop marks.
14359
	 * @param float $x abscissa of the crop mark center.
14360
	 * @param float $y ordinate of the crop mark center.
14361
	 * @param float $w width of the crop mark.
14362
	 * @param float $h height of the crop mark.
14363
	 * @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.
14364
	 * @param array $color crop mark color (default spot registration color).
14365
	 * @author Nicola Asuni
14366
	 * @since 4.9.000 (2010-03-26)
14367
	 * @public
14368
	 */
14369
	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14370
		$this->setLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14371
		$type = strtoupper($type);
14372
		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14373
		// split type in single components
14374
		$type = str_replace('-', ',', $type);
14375
		$type = str_replace('TL', 'T,L', $type);
14376
		$type = str_replace('TR', 'T,R', $type);
14377
		$type = str_replace('BL', 'F,L', $type);
14378
		$type = str_replace('BR', 'F,R', $type);
14379
		$type = str_replace('A', 'T,L', $type);
14380
		$type = str_replace('B', 'T,R', $type);
14381
		$type = str_replace('T,RO', 'BO', $type);
14382
		$type = str_replace('C', 'F,L', $type);
14383
		$type = str_replace('D', 'F,R', $type);
14384
		$crops = explode(',', strtoupper($type));
14385
		// remove duplicates
14386
		$crops = array_unique($crops);
14387
		$dw = ($w / 4); // horizontal space to leave before the intersection point
14388
		$dh = ($h / 4); // vertical space to leave before the intersection point
14389
		foreach ($crops as $crop) {
14390
			switch ($crop) {
14391
				case 'T':
14392
				case 'TOP': {
14393
					$x1 = $x;
14394
					$y1 = ($y - $h);
14395
					$x2 = $x;
14396
					$y2 = ($y - $dh);
14397
					break;
14398
				}
14399
				case 'F':
14400
				case 'BOTTOM': {
14401
					$x1 = $x;
14402
					$y1 = ($y + $dh);
14403
					$x2 = $x;
14404
					$y2 = ($y + $h);
14405
					break;
14406
				}
14407
				case 'L':
14408
				case 'LEFT': {
14409
					$x1 = ($x - $w);
14410
					$y1 = $y;
14411
					$x2 = ($x - $dw);
14412
					$y2 = $y;
14413
					break;
14414
				}
14415
				case 'R':
14416
				case 'RIGHT': {
14417
					$x1 = ($x + $dw);
14418
					$y1 = $y;
14419
					$x2 = ($x + $w);
14420
					$y2 = $y;
14421
					break;
14422
				}
14423
			}
14424
			$this->Line($x1, $y1, $x2, $y2);
14425
		}
14426
	}
14427
14428
	/**
14429
	 * Paints a registration mark
14430
	 * @param float $x abscissa of the registration mark center.
14431
	 * @param float $y ordinate of the registration mark center.
14432
	 * @param float $r radius of the crop mark.
14433
	 * @param boolean $double if true print two concentric crop marks.
14434
	 * @param array $cola crop mark color (default spot registration color 'All').
14435
	 * @param array $colb second crop mark color (default spot registration color 'None').
14436
	 * @author Nicola Asuni
14437
	 * @since 4.9.000 (2010-03-26)
14438
	 * @public
14439
	 */
14440
	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14441
		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14442
		$this->setFillColorArray($cola);
14443
		$this->PieSector($x, $y, $r, 90, 180, 'F');
14444
		$this->PieSector($x, $y, $r, 270, 360, 'F');
14445
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14446
		if ($double) {
14447
			$ri = $r * 0.5;
14448
			$this->setFillColorArray($colb);
14449
			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14450
			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14451
			$this->setFillColorArray($cola);
14452
			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14453
			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14454
			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14455
		}
14456
	}
14457
14458
	/**
14459
	 * Paints a CMYK registration mark
14460
	 * @param float $x abscissa of the registration mark center.
14461
	 * @param float $y ordinate of the registration mark center.
14462
	 * @param float $r radius of the crop mark.
14463
	 * @author Nicola Asuni
14464
	 * @since 6.0.038 (2013-09-30)
14465
	 * @public
14466
	 */
14467
	public function registrationMarkCMYK($x, $y, $r) {
14468
		// line width
14469
		$lw = max((0.5 / $this->k),($r / 8));
14470
		// internal radius
14471
		$ri = ($r * 0.6);
14472
		// external radius
14473
		$re = ($r * 1.3);
14474
		// Cyan
14475
		$this->setFillColorArray(array(100,0,0,0));
14476
		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14477
		// Magenta
14478
		$this->setFillColorArray(array(0,100,0,0));
14479
		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14480
		// Yellow
14481
		$this->setFillColorArray(array(0,0,100,0));
14482
		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14483
		// Key - black
14484
		$this->setFillColorArray(array(0,0,0,100));
14485
		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14486
		// registration color
14487
		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14488
		$this->setFillColorArray(array(100,100,100,100,'All'));
14489
		// external circle
14490
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14491
		// cross lines
14492
		$this->Line($x, ($y - $re), $x, ($y - $ri));
14493
		$this->Line($x, ($y + $ri), $x, ($y + $re));
14494
		$this->Line(($x - $re), $y, ($x - $ri), $y);
14495
		$this->Line(($x + $ri), $y, ($x + $re), $y);
14496
	}
14497
14498
	/**
14499
	 * Paints a linear colour gradient.
14500
	 * @param float $x abscissa of the top left corner of the rectangle.
14501
	 * @param float $y ordinate of the top left corner of the rectangle.
14502
	 * @param float $w width of the rectangle.
14503
	 * @param float $h height of the rectangle.
14504
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14505
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14506
	 * @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).
14507
	 * @author Andreas W\FCrmser, Nicola Asuni
14508
	 * @since 3.1.000 (2008-06-09)
14509
	 * @public
14510
	 */
14511
	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14512
		$this->Clip($x, $y, $w, $h);
14513
		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14514
	}
14515
14516
	/**
14517
	 * Paints a radial colour gradient.
14518
	 * @param float $x abscissa of the top left corner of the rectangle.
14519
	 * @param float $y ordinate of the top left corner of the rectangle.
14520
	 * @param float $w width of the rectangle.
14521
	 * @param float $h height of the rectangle.
14522
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14523
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14524
	 * @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.
14525
	 * @author Andreas W\FCrmser, Nicola Asuni
14526
	 * @since 3.1.000 (2008-06-09)
14527
	 * @public
14528
	 */
14529
	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14530
		$this->Clip($x, $y, $w, $h);
14531
		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14532
	}
14533
14534
	/**
14535
	 * Paints a coons patch mesh.
14536
	 * @param float $x abscissa of the top left corner of the rectangle.
14537
	 * @param float $y ordinate of the top left corner of the rectangle.
14538
	 * @param float $w width of the rectangle.
14539
	 * @param float $h height of the rectangle.
14540
	 * @param array $col1 first color (lower left corner) (RGB components).
14541
	 * @param array $col2 second color (lower right corner) (RGB components).
14542
	 * @param array $col3 third color (upper right corner) (RGB components).
14543
	 * @param array $col4 fourth color (upper left corner) (RGB components).
14544
	 * @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>
14545
	 * @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
14546
	 * @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
14547
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14548
	 * @author Andreas W\FCrmser, Nicola Asuni
14549
	 * @since 3.1.000 (2008-06-09)
14550
	 * @public
14551
	 */
14552
	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) {
14553
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14554
			return;
14555
		}
14556
		$this->Clip($x, $y, $w, $h);
14557
		$n = count($this->gradients) + 1;
14558
		$this->gradients[$n] = array();
14559
		$this->gradients[$n]['type'] = 6; //coons patch mesh
14560
		$this->gradients[$n]['coords'] = array();
14561
		$this->gradients[$n]['antialias'] = $antialias;
14562
		$this->gradients[$n]['colors'] = array();
14563
		$this->gradients[$n]['transparency'] = false;
14564
		//check the coords array if it is the simple array or the multi patch array
14565
		if (!isset($coords[0]['f'])) {
14566
			//simple array -> convert to multi patch array
14567
			if (!isset($col1[1])) {
14568
				$col1[1] = $col1[2] = $col1[0];
14569
			}
14570
			if (!isset($col2[1])) {
14571
				$col2[1] = $col2[2] = $col2[0];
14572
			}
14573
			if (!isset($col3[1])) {
14574
				$col3[1] = $col3[2] = $col3[0];
14575
			}
14576
			if (!isset($col4[1])) {
14577
				$col4[1] = $col4[2] = $col4[0];
14578
			}
14579
			$patch_array[0]['f'] = 0;
14580
			$patch_array[0]['points'] = $coords;
14581
			$patch_array[0]['colors'][0]['r'] = $col1[0];
14582
			$patch_array[0]['colors'][0]['g'] = $col1[1];
14583
			$patch_array[0]['colors'][0]['b'] = $col1[2];
14584
			$patch_array[0]['colors'][1]['r'] = $col2[0];
14585
			$patch_array[0]['colors'][1]['g'] = $col2[1];
14586
			$patch_array[0]['colors'][1]['b'] = $col2[2];
14587
			$patch_array[0]['colors'][2]['r'] = $col3[0];
14588
			$patch_array[0]['colors'][2]['g'] = $col3[1];
14589
			$patch_array[0]['colors'][2]['b'] = $col3[2];
14590
			$patch_array[0]['colors'][3]['r'] = $col4[0];
14591
			$patch_array[0]['colors'][3]['g'] = $col4[1];
14592
			$patch_array[0]['colors'][3]['b'] = $col4[2];
14593
		} else {
14594
			//multi patch array
14595
			$patch_array = $coords;
14596
		}
14597
		$bpcd = 65535; //16 bits per coordinate
14598
		//build the data stream
14599
		$this->gradients[$n]['stream'] = '';
14600
		$count_patch = count($patch_array);
14601
		for ($i=0; $i < $count_patch; ++$i) {
14602
			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14603
			$count_points = count($patch_array[$i]['points']);
14604
			for ($j=0; $j < $count_points; ++$j) {
14605
				//each point as 16 bit
14606
				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14607
				if ($patch_array[$i]['points'][$j] < 0) {
14608
					$patch_array[$i]['points'][$j] = 0;
14609
				}
14610
				if ($patch_array[$i]['points'][$j] > $bpcd) {
14611
					$patch_array[$i]['points'][$j] = $bpcd;
14612
				}
14613
				$this->gradients[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256));
14614
				$this->gradients[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) % 256));
14615
			}
14616
			$count_cols = count($patch_array[$i]['colors']);
14617
			for ($j=0; $j < $count_cols; ++$j) {
14618
				//each color component as 8 bit
14619
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14620
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14621
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14622
			}
14623
		}
14624
		//paint the gradient
14625
		$this->_out('/Sh'.$n.' sh');
14626
		//restore previous Graphic State
14627
		$this->_outRestoreGraphicsState();
14628
		if ($this->inxobj) {
14629
			// we are inside an XObject template
14630
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14631
		}
14632
	}
14633
14634
	/**
14635
	 * Set a rectangular clipping area.
14636
	 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14637
	 * @param float $y ordinate of the top left corner of the rectangle.
14638
	 * @param float $w width of the rectangle.
14639
	 * @param float $h height of the rectangle.
14640
	 * @author Andreas W\FCrmser, Nicola Asuni
14641
	 * @since 3.1.000 (2008-06-09)
14642
	 * @protected
14643
	 */
14644
	protected function Clip($x, $y, $w, $h) {
14645
		if ($this->state != 2) {
14646
			 return;
14647
		}
14648
		if ($this->rtl) {
14649
			$x = $this->w - $x - $w;
14650
		}
14651
		//save current Graphic State
14652
		$s = 'q';
14653
		//set clipping area
14654
		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14655
		//set up transformation matrix for gradient
14656
		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14657
		$this->_out($s);
14658
	}
14659
14660
	/**
14661
	 * Output gradient.
14662
	 * @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)
14663
	 * @param array $coords array of coordinates.
14664
	 * @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).
14665
	 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14666
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14667
	 * @author Nicola Asuni
14668
	 * @since 3.1.000 (2008-06-09)
14669
	 * @public
14670
	 */
14671
	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14672
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14673
			return;
14674
		}
14675
		$n = count($this->gradients) + 1;
14676
		$this->gradients[$n] = array();
14677
		$this->gradients[$n]['type'] = $type;
14678
		$this->gradients[$n]['coords'] = $coords;
14679
		$this->gradients[$n]['antialias'] = $antialias;
14680
		$this->gradients[$n]['colors'] = array();
14681
		$this->gradients[$n]['transparency'] = false;
14682
		// color space
14683
		$numcolspace = count($stops[0]['color']);
14684
		$bcolor = array_values($background);
14685
		switch($numcolspace) {
14686
			case 5:   // SPOT
14687
			case 4: { // CMYK
14688
				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14689
				if (!empty($background)) {
14690
					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14691
				}
14692
				break;
14693
			}
14694
			case 3: { // RGB
14695
				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14696
				if (!empty($background)) {
14697
					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14698
				}
14699
				break;
14700
			}
14701
			case 1: { // GRAY SCALE
14702
				$this->gradients[$n]['colspace'] = 'DeviceGray';
14703
				if (!empty($background)) {
14704
					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14705
				}
14706
				break;
14707
			}
14708
		}
14709
		$num_stops = count($stops);
14710
		$last_stop_id = $num_stops - 1;
14711
		foreach ($stops as $key => $stop) {
14712
			$this->gradients[$n]['colors'][$key] = array();
14713
			// offset represents a location along the gradient vector
14714
			if (isset($stop['offset'])) {
14715
				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14716
			} else {
14717
				if ($key == 0) {
14718
					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14719
				} elseif ($key == $last_stop_id) {
14720
					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14721
				} else {
14722
					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14723
					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14724
				}
14725
			}
14726
			if (isset($stop['opacity'])) {
14727
				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14728
				if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) {
14729
					$this->gradients[$n]['transparency'] = true;
14730
				}
14731
			} else {
14732
				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14733
			}
14734
			// exponent for the exponential interpolation function
14735
			if (isset($stop['exponent'])) {
14736
				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14737
			} else {
14738
				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14739
			}
14740
			// set colors
14741
			$color = array_values($stop['color']);
14742
			switch($numcolspace) {
14743
				case 5:   // SPOT
14744
				case 4: { // CMYK
14745
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14746
					break;
14747
				}
14748
				case 3: { // RGB
14749
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14750
					break;
14751
				}
14752
				case 1: { // GRAY SCALE
14753
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14754
					break;
14755
				}
14756
			}
14757
		}
14758
		if ($this->gradients[$n]['transparency']) {
14759
			// paint luminosity gradient
14760
			$this->_out('/TGS'.$n.' gs');
14761
		}
14762
		//paint the gradient
14763
		$this->_out('/Sh'.$n.' sh');
14764
		//restore previous Graphic State
14765
		$this->_outRestoreGraphicsState();
14766
		if ($this->inxobj) {
14767
			// we are inside an XObject template
14768
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14769
		}
14770
	}
14771
14772
	/**
14773
	 * Output gradient shaders.
14774
	 * @author Nicola Asuni
14775
	 * @since 3.1.000 (2008-06-09)
14776
	 * @protected
14777
	 */
14778
	function _putshaders() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
14779
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14780
			return;
14781
		}
14782
		$idt = count($this->gradients); //index for transparency gradients
14783
		foreach ($this->gradients as $id => $grad) {
14784
			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14785
				$fc = $this->_newobj();
14786
				$out = '<<';
14787
				$out .= ' /FunctionType 3';
14788
				$out .= ' /Domain [0 1]';
14789
				$functions = '';
14790
				$bounds = '';
14791
				$encode = '';
14792
				$i = 1;
14793
				$num_cols = count($grad['colors']);
14794
				$lastcols = $num_cols - 1;
14795
				for ($i = 1; $i < $num_cols; ++$i) {
14796
					$functions .= ($fc + $i).' 0 R ';
14797
					if ($i < $lastcols) {
14798
						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14799
					}
14800
					$encode .= '0 1 ';
14801
				}
14802
				$out .= ' /Functions ['.trim($functions).']';
14803
				$out .= ' /Bounds ['.trim($bounds).']';
14804
				$out .= ' /Encode ['.trim($encode).']';
14805
				$out .= ' >>';
14806
				$out .= "\n".'endobj';
14807
				$this->_out($out);
14808
				for ($i = 1; $i < $num_cols; ++$i) {
14809
					$this->_newobj();
14810
					$out = '<<';
14811
					$out .= ' /FunctionType 2';
14812
					$out .= ' /Domain [0 1]';
14813
					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14814
					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14815
					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14816
					$out .= ' >>';
14817
					$out .= "\n".'endobj';
14818
					$this->_out($out);
14819
				}
14820
				// set transparency functions
14821
				if ($grad['transparency']) {
14822
					$ft = $this->_newobj();
14823
					$out = '<<';
14824
					$out .= ' /FunctionType 3';
14825
					$out .= ' /Domain [0 1]';
14826
					$functions = '';
14827
					$i = 1;
14828
					$num_cols = count($grad['colors']);
14829
					for ($i = 1; $i < $num_cols; ++$i) {
14830
						$functions .= ($ft + $i).' 0 R ';
14831
					}
14832
					$out .= ' /Functions ['.trim($functions).']';
14833
					$out .= ' /Bounds ['.trim($bounds).']';
14834
					$out .= ' /Encode ['.trim($encode).']';
14835
					$out .= ' >>';
14836
					$out .= "\n".'endobj';
14837
					$this->_out($out);
14838
					for ($i = 1; $i < $num_cols; ++$i) {
14839
						$this->_newobj();
14840
						$out = '<<';
14841
						$out .= ' /FunctionType 2';
14842
						$out .= ' /Domain [0 1]';
14843
						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14844
						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14845
						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14846
						$out .= ' >>';
14847
						$out .= "\n".'endobj';
14848
						$this->_out($out);
14849
					}
14850
				}
14851
			}
14852
			// set shading object
14853
			$this->_newobj();
14854
			$out = '<< /ShadingType '.$grad['type'];
14855
			if (isset($grad['colspace'])) {
14856
				$out .= ' /ColorSpace /'.$grad['colspace'];
14857
			} else {
14858
				$out .= ' /ColorSpace /DeviceRGB';
14859
			}
14860
			if (isset($grad['background']) AND !empty($grad['background'])) {
14861
				$out .= ' /Background ['.$grad['background'].']';
14862
			}
14863
			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14864
				$out .= ' /AntiAlias true';
14865
			}
14866
			if ($grad['type'] == 2) {
14867
				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14868
				$out .= ' /Domain [0 1]';
14869
				$out .= ' /Function '.$fc.' 0 R';
14870
				$out .= ' /Extend [true true]';
14871
				$out .= ' >>';
14872
			} elseif ($grad['type'] == 3) {
14873
				//x0, y0, r0, x1, y1, r1
14874
				//at this this time radius of inner circle is 0
14875
				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14876
				$out .= ' /Domain [0 1]';
14877
				$out .= ' /Function '.$fc.' 0 R';
14878
				$out .= ' /Extend [true true]';
14879
				$out .= ' >>';
14880
			} elseif ($grad['type'] == 6) {
14881
				$out .= ' /BitsPerCoordinate 16';
14882
				$out .= ' /BitsPerComponent 8';
14883
				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14884
				$out .= ' /BitsPerFlag 8';
14885
				$stream = $this->_getrawstream($grad['stream']);
14886
				$out .= ' /Length '.strlen($stream);
14887
				$out .= ' >>';
14888
				$out .= ' stream'."\n".$stream."\n".'endstream';
14889
			}
14890
			$out .= "\n".'endobj';
14891
			$this->_out($out);
14892
			if ($grad['transparency']) {
14893
				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14894
				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14895
			}
14896
			$this->gradients[$id]['id'] = $this->n;
14897
			// set pattern object
14898
			$this->_newobj();
14899
			$out = '<< /Type /Pattern /PatternType 2';
14900
			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14901
			$out .= ' >>';
14902
			$out .= "\n".'endobj';
14903
			$this->_out($out);
14904
			$this->gradients[$id]['pattern'] = $this->n;
14905
			// set shading and pattern for transparency mask
14906
			if ($grad['transparency']) {
14907
				// luminosity pattern
14908
				$idgs = $id + $idt;
14909
				$this->_newobj();
14910
				$this->_out($shading_transparency);
14911
				$this->gradients[$idgs]['id'] = $this->n;
14912
				$this->_newobj();
14913
				$out = '<< /Type /Pattern /PatternType 2';
14914
				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14915
				$out .= ' >>';
14916
				$out .= "\n".'endobj';
14917
				$this->_out($out);
14918
				$this->gradients[$idgs]['pattern'] = $this->n;
14919
				// luminosity XObject
14920
				$oid = $this->_newobj();
14921
				$this->xobjects['LX'.$oid] = array('n' => $oid);
14922
				$filter = '';
14923
				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14924
				if ($this->compress) {
14925
					$filter = ' /Filter /FlateDecode';
14926
					$stream = gzcompress($stream);
14927
				}
14928
				$stream = $this->_getrawstream($stream);
14929
				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14930
				$out .= ' /Length '.strlen($stream);
14931
				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14932
				$out .= ' /BBox [0 0 '.$rect.']';
14933
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14934
				$out .= ' /Resources <<';
14935
				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14936
				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14937
				$out .= ' >>';
14938
				$out .= ' >> ';
14939
				$out .= ' stream'."\n".$stream."\n".'endstream';
14940
				$out .= "\n".'endobj';
14941
				$this->_out($out);
14942
				// SMask
14943
				$this->_newobj();
14944
				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14945
				$this->_out($out);
14946
				// ExtGState
14947
				$this->_newobj();
14948
				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14949
				$this->_out($out);
14950
				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14951
			}
14952
		}
14953
	}
14954
14955
	/**
14956
	 * Draw the sector of a circle.
14957
	 * It can be used for instance to render pie charts.
14958
	 * @param float $xc abscissa of the center.
14959
	 * @param float $yc ordinate of the center.
14960
	 * @param float $r radius.
14961
	 * @param float $a start angle (in degrees).
14962
	 * @param float $b end angle (in degrees).
14963
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14964
	 * @param float $cw indicates whether to go clockwise (default: true).
14965
	 * @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.
14966
	 * @author Maxime Delorme, Nicola Asuni
14967
	 * @since 3.1.000 (2008-06-09)
14968
	 * @public
14969
	 */
14970
	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14971
		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14972
	}
14973
14974
	/**
14975
	 * Draw the sector of an ellipse.
14976
	 * It can be used for instance to render pie charts.
14977
	 * @param float $xc abscissa of the center.
14978
	 * @param float $yc ordinate of the center.
14979
	 * @param float $rx the x-axis radius.
14980
	 * @param float $ry the y-axis radius.
14981
	 * @param float $a start angle (in degrees).
14982
	 * @param float $b end angle (in degrees).
14983
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14984
	 * @param float $cw indicates whether to go clockwise.
14985
	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14986
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14987
	 * @author Maxime Delorme, Nicola Asuni
14988
	 * @since 3.1.000 (2008-06-09)
14989
	 * @public
14990
	 */
14991
	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14992
		if ($this->state != 2) {
14993
			 return;
14994
		}
14995
		if ($this->rtl) {
14996
			$xc = ($this->w - $xc);
14997
		}
14998
		$op = TCPDF_STATIC::getPathPaintOperator($style);
14999
		if ($op == 'f') {
15000
			$line_style = array();
15001
		}
15002
		if ($cw) {
15003
			$d = $b;
15004
			$b = (360 - $a + $o);
15005
			$a = (360 - $d + $o);
15006
		} else {
15007
			$b += $o;
15008
			$a += $o;
15009
		}
15010
		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
15011
		$this->_out($op);
15012
	}
15013
15014
	/**
15015
	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
15016
	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
15017
	 * Only vector drawing is supported, not text or bitmap.
15018
	 * 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).
15019
	 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
15020
	 * @param float|null $x Abscissa of the upper-left corner.
15021
	 * @param float|null $y Ordinate of the upper-left corner.
15022
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
15023
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
15024
	 * @param mixed $link URL or identifier returned by AddLink().
15025
	 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
15026
	 * @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>
15027
	 * @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>
15028
	 * @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)))
15029
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
15030
	 * @param boolean $fixoutvals if true remove values outside the bounding box.
15031
	 * @author Valentin Schmidt, Nicola Asuni
15032
	 * @since 3.1.000 (2008-06-09)
15033
	 * @public
15034
	 */
15035
	public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
15036
		if ($this->state != 2) {
15037
			 return;
15038
		}
15039
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
15040
			// convert EPS to raster image using GD or ImageMagick libraries
15041
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15042
		}
15043
		if (TCPDF_STATIC::empty_string($x)) {
15044
			$x = $this->x;
15045
		}
15046
		if (TCPDF_STATIC::empty_string($y)) {
15047
			$y = $this->y;
15048
		}
15049
		// check page for no-write regions and adapt page margins if necessary
15050
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15051
		$k = $this->k;
15052
		if ($file[0] === '@') { // image from string
15053
			$data = substr($file, 1);
15054
		} else { // EPS/AI file
15055
            $data = $this->getCachedFileContents($file);
15056
		}
15057
		if ($data === FALSE) {
15058
			$this->Error('EPS file not found: '.$file);
15059
		}
15060
		$regs = array();
15061
		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
15062
		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
15063
		if (count($regs) > 1) {
15064
			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
15065
			if (strpos($version_str, 'Adobe Illustrator') !== false) {
15066
				$versexp = explode(' ', $version_str);
15067
				$version = (float)array_pop($versexp);
15068
				if ($version >= 9) {
15069
					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
15070
				}
15071
			}
15072
		}
15073
		// strip binary bytes in front of PS-header
15074
		$start = strpos($data, '%!PS-Adobe');
15075
		if ($start > 0) {
15076
			$data = substr($data, $start);
15077
		}
15078
		// find BoundingBox params
15079
		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15080
		if (count($regs) > 1) {
15081
			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15082
		} else {
15083
			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
15084
		}
15085
		$start = strpos($data, '%%EndSetup');
15086
		if ($start === false) {
15087
			$start = strpos($data, '%%EndProlog');
15088
		}
15089
		if ($start === false) {
15090
			$start = strpos($data, '%%BoundingBox');
15091
		}
15092
		$data = substr($data, $start);
15093
		$end = strpos($data, '%%PageTrailer');
15094
		if ($end===false) {
15095
			$end = strpos($data, 'showpage');
15096
		}
15097
		if ($end) {
15098
			$data = substr($data, 0, $end);
15099
		}
15100
		// calculate image width and height on document
15101
		if (($w <= 0) AND ($h <= 0)) {
15102
			$w = ($x2 - $x1) / $k;
15103
			$h = ($y2 - $y1) / $k;
15104
		} elseif ($w <= 0) {
15105
			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15106
		} elseif ($h <= 0) {
15107
			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15108
		}
15109
		// fit the image on available space
15110
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15111
		if ($this->rasterize_vector_images) {
15112
			// convert EPS to raster image using GD or ImageMagick libraries
15113
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15114
		}
15115
		// set scaling factors
15116
		$scale_x = $w / (($x2 - $x1) / $k);
15117
		$scale_y = $h / (($y2 - $y1) / $k);
15118
		// set alignment
15119
		$this->img_rb_y = $y + $h;
15120
		// set alignment
15121
		if ($this->rtl) {
15122
			if ($palign == 'L') {
15123
				$ximg = $this->lMargin;
15124
			} elseif ($palign == 'C') {
15125
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15126
			} elseif ($palign == 'R') {
15127
				$ximg = $this->w - $this->rMargin - $w;
15128
			} else {
15129
				$ximg = $x - $w;
15130
			}
15131
			$this->img_rb_x = $ximg;
15132
		} else {
15133
			if ($palign == 'L') {
15134
				$ximg = $this->lMargin;
15135
			} elseif ($palign == 'C') {
15136
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15137
			} elseif ($palign == 'R') {
15138
				$ximg = $this->w - $this->rMargin - $w;
15139
			} else {
15140
				$ximg = $x;
15141
			}
15142
			$this->img_rb_x = $ximg + $w;
15143
		}
15144
		if ($useBoundingBox) {
15145
			$dx = $ximg * $k - $x1;
15146
			$dy = $y * $k - $y1;
15147
		} else {
15148
			$dx = $ximg * $k;
15149
			$dy = $y * $k;
15150
		}
15151
		// save the current graphic state
15152
		$this->_out('q'.$this->epsmarker);
15153
		// translate
15154
		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15155
		// scale
15156
		$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15157
		// handle pc/unix/mac line endings
15158
		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15159
		$u=0;
15160
		$cnt = count($lines);
15161
		for ($i=0; $i < $cnt; ++$i) {
15162
			$line = $lines[$i];
15163
			if (($line == '') OR ($line[0] == '%')) {
15164
				continue;
15165
			}
15166
			$len = strlen($line);
15167
			// check for spot color names
15168
			$color_name = '';
15169
			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15170
				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15171
					// extract spot color name
15172
					$color_name = $matches[0];
15173
					// remove color name from string
15174
					$line = str_replace(' '.$color_name, '', $line);
15175
					// remove pharentesis from color name
15176
					$color_name = substr($color_name, 1, -1);
15177
				}
15178
			}
15179
			$chunks = explode(' ', $line);
15180
			$cmd = trim(array_pop($chunks));
15181
			// RGB
15182
			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15183
				$b = array_pop($chunks);
15184
				$g = array_pop($chunks);
15185
				$r = array_pop($chunks);
15186
				$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!
15187
				continue;
15188
			}
15189
			$skip = false;
15190
			if ($fixoutvals) {
15191
				// check for values outside the bounding box
15192
				switch ($cmd) {
15193
					case 'm':
15194
					case 'l':
15195
					case 'L': {
15196
						// skip values outside bounding box
15197
						foreach ($chunks as $key => $val) {
15198
							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15199
								$skip = true;
15200
							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15201
								$skip = true;
15202
							}
15203
						}
15204
					}
15205
				}
15206
			}
15207
			switch ($cmd) {
15208
				case 'm':
15209
				case 'l':
15210
				case 'v':
15211
				case 'y':
15212
				case 'c':
15213
				case 'k':
15214
				case 'K':
15215
				case 'g':
15216
				case 'G':
15217
				case 's':
15218
				case 'S':
15219
				case 'J':
15220
				case 'j':
15221
				case 'w':
15222
				case 'M':
15223
				case 'd':
15224
				case 'n': {
15225
					if ($skip) {
15226
						break;
15227
					}
15228
					$this->_out($line);
15229
					break;
15230
				}
15231
				case 'x': {// custom fill color
15232
					if (empty($color_name)) {
15233
						// CMYK color
15234
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15235
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15236
					} else {
15237
						// Spot Color (CMYK + tint)
15238
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15239
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15240
						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15241
						$this->_out($color_cmd);
15242
					}
15243
					break;
15244
				}
15245
				case 'X': { // custom stroke color
15246
					if (empty($color_name)) {
15247
						// CMYK color
15248
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15249
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15250
					} else {
15251
						// Spot Color (CMYK + tint)
15252
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15253
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15254
						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15255
						$this->_out($color_cmd);
15256
					}
15257
					break;
15258
				}
15259
				case 'Y':
15260
				case 'N':
15261
				case 'V':
15262
				case 'L':
15263
				case 'C': {
15264
					if ($skip) {
15265
						break;
15266
					}
15267
					$line[($len - 1)] = strtolower($cmd);
15268
					$this->_out($line);
15269
					break;
15270
				}
15271
				case 'b':
15272
				case 'B': {
15273
					$this->_out($cmd . '*');
15274
					break;
15275
				}
15276
				case 'f':
15277
				case 'F': {
15278
					if ($u > 0) {
15279
						$isU = false;
15280
						$max = min(($i + 5), $cnt);
15281
						for ($j = ($i + 1); $j < $max; ++$j) {
15282
							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15283
						}
15284
						if ($isU) {
15285
							$this->_out('f*');
15286
						}
15287
					} else {
15288
						$this->_out('f*');
15289
					}
15290
					break;
15291
				}
15292
				case '*u': {
15293
					++$u;
15294
					break;
15295
				}
15296
				case '*U': {
15297
					--$u;
15298
					break;
15299
				}
15300
			}
15301
		}
15302
		// restore previous graphic state
15303
		$this->_out($this->epsmarker.'Q');
15304
		if (!empty($border)) {
15305
			$bx = $this->x;
15306
			$by = $this->y;
15307
			$this->x = $ximg;
15308
			if ($this->rtl) {
15309
				$this->x += $w;
15310
			}
15311
			$this->y = $y;
15312
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15313
			$this->x = $bx;
15314
			$this->y = $by;
15315
		}
15316
		if ($link) {
15317
			$this->Link($ximg, $y, $w, $h, $link, 0);
15318
		}
15319
		// set pointer to align the next text/objects
15320
		switch($align) {
15321
			case 'T':{
15322
				$this->y = $y;
15323
				$this->x = $this->img_rb_x;
15324
				break;
15325
			}
15326
			case 'M':{
15327
				$this->y = $y + round($h/2);
15328
				$this->x = $this->img_rb_x;
15329
				break;
15330
			}
15331
			case 'B':{
15332
				$this->y = $this->img_rb_y;
15333
				$this->x = $this->img_rb_x;
15334
				break;
15335
			}
15336
			case 'N':{
15337
				$this->setY($this->img_rb_y);
15338
				break;
15339
			}
15340
			default:{
15341
				break;
15342
			}
15343
		}
15344
		$this->endlinex = $this->img_rb_x;
15345
	}
15346
15347
	/**
15348
	 * Set document barcode.
15349
	 * @param string $bc barcode
15350
	 * @public
15351
	 */
15352
	public function setBarcode($bc='') {
15353
		$this->barcode = $bc;
15354
	}
15355
15356
	/**
15357
	 * Get current barcode.
15358
	 * @return string
15359
	 * @public
15360
	 * @since 4.0.012 (2008-07-24)
15361
	 */
15362
	public function getBarcode() {
15363
		return $this->barcode;
15364
	}
15365
15366
	/**
15367
	 * Print a Linear Barcode.
15368
	 * @param string $code code to print
15369
	 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15370
	 * @param float|null $x x position in user units (null = current x position)
15371
	 * @param float|null $y y position in user units (null = current y position)
15372
	 * @param float|null $w width in user units (null = remaining page width)
15373
	 * @param float|null $h height in user units (null = remaining page height)
15374
	 * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm)
15375
	 * @param array $style array of options:<ul>
15376
	 * <li>boolean $style['border'] if true prints a border</li>
15377
	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15378
	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15379
	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15380
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15381
	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15382
	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15383
	 * <li>string $style['label'] override default label</li>
15384
	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15385
	 * <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>
15386
	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15387
	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15388
	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15389
	 * <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>
15390
	 * <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>
15391
	 * @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>
15392
	 * @author Nicola Asuni
15393
	 * @since 3.1.000 (2008-06-09)
15394
	 * @public
15395
	 */
15396
	public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') {
15397
		if (TCPDF_STATIC::empty_string(trim($code))) {
15398
			return;
15399
		}
15400
		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15401
		// save current graphic settings
15402
		$gvars = $this->getGraphicVars();
15403
		// create new barcode object
15404
		$barcodeobj = new TCPDFBarcode($code, $type);
15405
		$arrcode = $barcodeobj->getBarcodeArray();
15406
		if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15407
			$this->Error('Error in 1D barcode string');
15408
		}
15409
		if ($arrcode['maxh'] <= 0) {
15410
			$arrcode['maxh'] = 1;
15411
		}
15412
		// set default values
15413
		if (!isset($style['position'])) {
15414
			$style['position'] = '';
15415
		} elseif ($style['position'] == 'S') {
15416
			// keep this for backward compatibility
15417
			$style['position'] = '';
15418
			$style['stretch'] = true;
15419
		}
15420
		if (!isset($style['fitwidth'])) {
15421
			if (!isset($style['stretch'])) {
15422
				$style['fitwidth'] = true;
15423
			} else {
15424
				$style['fitwidth'] = false;
15425
			}
15426
		}
15427
		if ($style['fitwidth']) {
15428
			// disable stretch
15429
			$style['stretch'] = false;
15430
		}
15431
		if (!isset($style['stretch'])) {
15432
			if (($w === '') OR ($w <= 0)) {
15433
				$style['stretch'] = false;
15434
			} else {
15435
				$style['stretch'] = true;
15436
			}
15437
		}
15438
		if (!isset($style['fgcolor'])) {
15439
			$style['fgcolor'] = array(0,0,0); // default black
15440
		}
15441
		if (!isset($style['bgcolor'])) {
15442
			$style['bgcolor'] = false; // default transparent
15443
		}
15444
		if (!isset($style['border'])) {
15445
			$style['border'] = false;
15446
		}
15447
		$fontsize = 0;
15448
		if (!isset($style['text'])) {
15449
			$style['text'] = false;
15450
		}
15451
		if ($style['text'] AND isset($style['font'])) {
15452
			if (isset($style['fontsize'])) {
15453
				$fontsize = $style['fontsize'];
15454
			}
15455
			$this->setFont($style['font'], '', $fontsize);
15456
		}
15457
		if (!isset($style['stretchtext'])) {
15458
			$style['stretchtext'] = 4;
15459
		}
15460
		if (TCPDF_STATIC::empty_string($x)) {
15461
			$x = $this->x;
15462
		}
15463
		if (TCPDF_STATIC::empty_string($y)) {
15464
			$y = $this->y;
15465
		}
15466
		// check page for no-write regions and adapt page margins if necessary
15467
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15468
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
15469
			if ($this->rtl) {
15470
				$w = $x - $this->lMargin;
15471
			} else {
15472
				$w = $this->w - $this->rMargin - $x;
15473
			}
15474
		}
15475
		// padding
15476
		if (!isset($style['padding'])) {
15477
			$padding = 0;
15478
		} elseif ($style['padding'] === 'auto') {
15479
			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15480
		} else {
15481
			$padding = floatval($style['padding']);
15482
		}
15483
		// horizontal padding
15484
		if (!isset($style['hpadding'])) {
15485
			$hpadding = $padding;
15486
		} elseif ($style['hpadding'] === 'auto') {
15487
			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15488
		} else {
15489
			$hpadding = floatval($style['hpadding']);
15490
		}
15491
		// vertical padding
15492
		if (!isset($style['vpadding'])) {
15493
			$vpadding = $padding;
15494
		} elseif ($style['vpadding'] === 'auto') {
15495
			$vpadding = ($hpadding / 2);
15496
		} else {
15497
			$vpadding = floatval($style['vpadding']);
15498
		}
15499
		// calculate xres (single bar width)
15500
		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15501
		if ($style['stretch']) {
15502
			$xres = $max_xres;
15503
		} else {
15504
			if (TCPDF_STATIC::empty_string($xres)) {
15505
				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15506
			}
15507
			if ($xres > $max_xres) {
15508
				// correct xres to fit on $w
15509
				$xres = $max_xres;
15510
			}
15511
			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15512
				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15513
				$hpadding = 10 * $xres;
15514
				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15515
					$vpadding = ($hpadding / 2);
15516
				}
15517
			}
15518
		}
15519
		if ($style['fitwidth']) {
15520
			$wold = $w;
15521
			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15522
			if (isset($style['cellfitalign'])) {
15523
				switch ($style['cellfitalign']) {
15524
					case 'L': {
15525
						if ($this->rtl) {
15526
							$x -= ($wold - $w);
15527
						}
15528
						break;
15529
					}
15530
					case 'R': {
15531
						if (!$this->rtl) {
15532
							$x += ($wold - $w);
15533
						}
15534
						break;
15535
					}
15536
					case 'C': {
15537
						if ($this->rtl) {
15538
							$x -= (($wold - $w) / 2);
15539
						} else {
15540
							$x += (($wold - $w) / 2);
15541
						}
15542
						break;
15543
					}
15544
					default : {
15545
						break;
15546
					}
15547
				}
15548
			}
15549
		}
15550
		$text_height = $this->getCellHeight($fontsize / $this->k);
15551
		// height
15552
		if (TCPDF_STATIC::empty_string($h) OR ($h <= 0)) {
15553
			// set default height
15554
			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15555
		}
15556
		$barh = $h - $text_height - (2 * $vpadding);
15557
		if ($barh <=0) {
15558
			// try to reduce font or padding to fit barcode on available height
15559
			if ($text_height > $h) {
15560
				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15561
				$text_height = $this->getCellHeight($fontsize / $this->k);
15562
				$this->setFont($style['font'], '', $fontsize);
15563
			}
15564
			if ($vpadding > 0) {
15565
				$vpadding = (($h - $text_height) / 4);
15566
			}
15567
			$barh = $h - $text_height - (2 * $vpadding);
15568
		}
15569
		// fit the barcode on available space
15570
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15571
		// set alignment
15572
		$this->img_rb_y = $y + $h;
15573
		// set alignment
15574
		if ($this->rtl) {
15575
			if ($style['position'] == 'L') {
15576
				$xpos = $this->lMargin;
15577
			} elseif ($style['position'] == 'C') {
15578
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15579
			} elseif ($style['position'] == 'R') {
15580
				$xpos = $this->w - $this->rMargin - $w;
15581
			} else {
15582
				$xpos = $x - $w;
15583
			}
15584
			$this->img_rb_x = $xpos;
15585
		} else {
15586
			if ($style['position'] == 'L') {
15587
				$xpos = $this->lMargin;
15588
			} elseif ($style['position'] == 'C') {
15589
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15590
			} elseif ($style['position'] == 'R') {
15591
				$xpos = $this->w - $this->rMargin - $w;
15592
			} else {
15593
				$xpos = $x;
15594
			}
15595
			$this->img_rb_x = $xpos + $w;
15596
		}
15597
		$xpos_rect = $xpos;
15598
		if (!isset($style['align'])) {
15599
			$style['align'] = 'C';
15600
		}
15601
		switch ($style['align']) {
15602
			case 'L': {
15603
				$xpos = $xpos_rect + $hpadding;
15604
				break;
15605
			}
15606
			case 'R': {
15607
				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15608
				break;
15609
			}
15610
			case 'C':
15611
			default : {
15612
				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15613
				break;
15614
			}
15615
		}
15616
		$xpos_text = $xpos;
15617
		// barcode is always printed in LTR direction
15618
		$tempRTL = $this->rtl;
15619
		$this->rtl = false;
15620
		// print background color
15621
		if ($style['bgcolor']) {
15622
			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15623
		} elseif ($style['border']) {
15624
			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15625
		}
15626
		// set foreground color
15627
		$this->setDrawColorArray($style['fgcolor']);
15628
		$this->setTextColorArray($style['fgcolor']);
15629
		// print bars
15630
		foreach ($arrcode['bcode'] as $k => $v) {
15631
			$bw = ($v['w'] * $xres);
15632
			if ($v['t']) {
15633
				// draw a vertical bar
15634
				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15635
				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15636
			}
15637
			$xpos += $bw;
15638
		}
15639
		// print text
15640
		if ($style['text']) {
15641
			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15642
				$label = $style['label'];
15643
			} else {
15644
				$label = $code;
15645
			}
15646
			$txtwidth = ($arrcode['maxw'] * $xres);
15647
			if ($this->GetStringWidth($label) > $txtwidth) {
15648
				$style['stretchtext'] = 2;
15649
			}
15650
			// print text
15651
			$this->x = $xpos_text;
15652
			$this->y = $y + $vpadding + $barh;
15653
			$cellpadding = $this->cell_padding;
15654
			$this->setCellPadding(0);
15655
			$this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15656
			$this->cell_padding = $cellpadding;
15657
		}
15658
		// restore original direction
15659
		$this->rtl = $tempRTL;
15660
		// restore previous settings
15661
		$this->setGraphicVars($gvars);
15662
		// set pointer to align the next text/objects
15663
		switch($align) {
15664
			case 'T':{
15665
				$this->y = $y;
15666
				$this->x = $this->img_rb_x;
15667
				break;
15668
			}
15669
			case 'M':{
15670
				$this->y = $y + round($h / 2);
15671
				$this->x = $this->img_rb_x;
15672
				break;
15673
			}
15674
			case 'B':{
15675
				$this->y = $this->img_rb_y;
15676
				$this->x = $this->img_rb_x;
15677
				break;
15678
			}
15679
			case 'N':{
15680
				$this->setY($this->img_rb_y);
15681
				break;
15682
			}
15683
			default:{
15684
				break;
15685
			}
15686
		}
15687
		$this->endlinex = $this->img_rb_x;
15688
	}
15689
15690
	/**
15691
	 * Print 2D Barcode.
15692
	 * @param string $code code to print
15693
	 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15694
	 * @param float|null $x x position in user units
15695
	 * @param float|null $y y position in user units
15696
	 * @param float|null $w width in user units
15697
	 * @param float|null $h height in user units
15698
	 * @param array $style array of options:<ul>
15699
	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15700
	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15701
	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15702
	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15703
	 * <li>int $style['module_width'] width of a single module in points</li>
15704
	 * <li>int $style['module_height'] height of a single module in points</li>
15705
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15706
	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15707
	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15708
	 * @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>
15709
	 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15710
	 * @author Nicola Asuni
15711
	 * @since 4.5.037 (2009-04-07)
15712
	 * @public
15713
	 */
15714
	public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) {
15715
		if (TCPDF_STATIC::empty_string(trim($code))) {
15716
			return;
15717
		}
15718
		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15719
		// save current graphic settings
15720
		$gvars = $this->getGraphicVars();
15721
		// create new barcode object
15722
		$barcodeobj = new TCPDF2DBarcode($code, $type);
15723
		$arrcode = $barcodeobj->getBarcodeArray();
15724
		if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15725
			$this->Error('Error in 2D barcode string');
15726
		}
15727
		// set default values
15728
		if (!isset($style['position'])) {
15729
			$style['position'] = '';
15730
		}
15731
		if (!isset($style['fgcolor'])) {
15732
			$style['fgcolor'] = array(0,0,0); // default black
15733
		}
15734
		if (!isset($style['bgcolor'])) {
15735
			$style['bgcolor'] = false; // default transparent
15736
		}
15737
		if (!isset($style['border'])) {
15738
			$style['border'] = false;
15739
		}
15740
		// padding
15741
		if (!isset($style['padding'])) {
15742
			$style['padding'] = 0;
15743
		} elseif ($style['padding'] === 'auto') {
15744
			$style['padding'] = 4;
15745
		}
15746
		if (!isset($style['hpadding'])) {
15747
			$style['hpadding'] = $style['padding'];
15748
		} elseif ($style['hpadding'] === 'auto') {
15749
			$style['hpadding'] = 4;
15750
		}
15751
		if (!isset($style['vpadding'])) {
15752
			$style['vpadding'] = $style['padding'];
15753
		} elseif ($style['vpadding'] === 'auto') {
15754
			$style['vpadding'] = 4;
15755
		}
15756
		$hpad = (2 * $style['hpadding']);
15757
		$vpad = (2 * $style['vpadding']);
15758
		// cell (module) dimension
15759
		if (!isset($style['module_width'])) {
15760
			$style['module_width'] = 1; // width of a single module in points
15761
		}
15762
		if (!isset($style['module_height'])) {
15763
			$style['module_height'] = 1; // height of a single module in points
15764
		}
15765
		if (TCPDF_STATIC::empty_string($x)) {
15766
			$x = $this->x;
15767
		}
15768
		if (TCPDF_STATIC::empty_string($y)) {
15769
			$y = $this->y;
15770
		}
15771
		// check page for no-write regions and adapt page margins if necessary
15772
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15773
		// number of barcode columns and rows
15774
		$rows = $arrcode['num_rows'];
15775
		$cols = $arrcode['num_cols'];
15776
		if (($rows <= 0) || ($cols <= 0)){
15777
			$this->Error('Error in 2D barcode string');
15778
		}
15779
		// module width and height
15780
		$mw = $style['module_width'];
15781
		$mh = $style['module_height'];
15782
		if (($mw <= 0) OR ($mh <= 0)) {
15783
			$this->Error('Error in 2D barcode string');
15784
		}
15785
		// get max dimensions
15786
		if ($this->rtl) {
15787
			$maxw = $x - $this->lMargin;
15788
		} else {
15789
			$maxw = $this->w - $this->rMargin - $x;
15790
		}
15791
		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15792
		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15793
		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15794
		if (!$distort) {
15795
			if (($maxw * $ratioHW) > $maxh) {
15796
				$maxw = $maxh * $ratioWH;
15797
			}
15798
			if (($maxh * $ratioWH) > $maxw) {
15799
				$maxh = $maxw * $ratioHW;
15800
			}
15801
		}
15802
		// set maximum dimensions
15803
		if ($w > $maxw) {
15804
			$w = $maxw;
15805
		}
15806
		if ($h > $maxh) {
15807
			$h = $maxh;
15808
		}
15809
		// set dimensions
15810
		if ((TCPDF_STATIC::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC::empty_string($h) OR ($h <= 0))) {
15811
			$w = ($cols + $hpad) * ($mw / $this->k);
15812
			$h = ($rows + $vpad) * ($mh / $this->k);
15813
		} elseif (($w === '') OR ($w <= 0)) {
15814
			$w = $h * $ratioWH;
15815
		} elseif (($h === '') OR ($h <= 0)) {
15816
			$h = $w * $ratioHW;
15817
		}
15818
		// barcode size (excluding padding)
15819
		$bw = ($w * $cols) / ($cols + $hpad);
15820
		$bh = ($h * $rows) / ($rows + $vpad);
15821
		// dimension of single barcode cell unit
15822
		$cw = $bw / $cols;
15823
		$ch = $bh / $rows;
15824
		if (!$distort) {
15825
			if (($cw / $ch) > ($mw / $mh)) {
15826
				// correct horizontal distortion
15827
				$cw = $ch * $mw / $mh;
15828
				$bw = $cw * $cols;
15829
				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15830
			} else {
15831
				// correct vertical distortion
15832
				$ch = $cw * $mh / $mw;
15833
				$bh = $ch * $rows;
15834
				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15835
			}
15836
		}
15837
		// fit the barcode on available space
15838
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15839
		// set alignment
15840
		$this->img_rb_y = $y + $h;
15841
		// set alignment
15842
		if ($this->rtl) {
15843
			if ($style['position'] == 'L') {
15844
				$xpos = $this->lMargin;
15845
			} elseif ($style['position'] == 'C') {
15846
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15847
			} elseif ($style['position'] == 'R') {
15848
				$xpos = $this->w - $this->rMargin - $w;
15849
			} else {
15850
				$xpos = $x - $w;
15851
			}
15852
			$this->img_rb_x = $xpos;
15853
		} else {
15854
			if ($style['position'] == 'L') {
15855
				$xpos = $this->lMargin;
15856
			} elseif ($style['position'] == 'C') {
15857
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15858
			} elseif ($style['position'] == 'R') {
15859
				$xpos = $this->w - $this->rMargin - $w;
15860
			} else {
15861
				$xpos = $x;
15862
			}
15863
			$this->img_rb_x = $xpos + $w;
15864
		}
15865
		$xstart = $xpos + ($style['hpadding'] * $cw);
15866
		$ystart = $y + ($style['vpadding'] * $ch);
15867
		// barcode is always printed in LTR direction
15868
		$tempRTL = $this->rtl;
15869
		$this->rtl = false;
15870
		// print background color
15871
		if ($style['bgcolor']) {
15872
			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15873
		} elseif ($style['border']) {
15874
			$this->Rect($xpos, $y, $w, $h, 'D');
15875
		}
15876
		// set foreground color
15877
		$this->setDrawColorArray($style['fgcolor']);
15878
		// print barcode cells
15879
		// for each row
15880
		for ($r = 0; $r < $rows; ++$r) {
15881
			$xr = $xstart;
15882
			// for each column
15883
			for ($c = 0; $c < $cols; ++$c) {
15884
				if ($arrcode['bcode'][$r][$c] == 1) {
15885
					// draw a single barcode cell
15886
					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15887
				}
15888
				$xr += $cw;
15889
			}
15890
			$ystart += $ch;
15891
		}
15892
		// restore original direction
15893
		$this->rtl = $tempRTL;
15894
		// restore previous settings
15895
		$this->setGraphicVars($gvars);
15896
		// set pointer to align the next text/objects
15897
		switch($align) {
15898
			case 'T':{
15899
				$this->y = $y;
15900
				$this->x = $this->img_rb_x;
15901
				break;
15902
			}
15903
			case 'M':{
15904
				$this->y = $y + round($h/2);
15905
				$this->x = $this->img_rb_x;
15906
				break;
15907
			}
15908
			case 'B':{
15909
				$this->y = $this->img_rb_y;
15910
				$this->x = $this->img_rb_x;
15911
				break;
15912
			}
15913
			case 'N':{
15914
				$this->setY($this->img_rb_y);
15915
				break;
15916
			}
15917
			default:{
15918
				break;
15919
			}
15920
		}
15921
		$this->endlinex = $this->img_rb_x;
15922
	}
15923
15924
	/**
15925
	 * Returns an array containing current margins:
15926
	 * <ul>
15927
			<li>$ret['left'] = left margin</li>
15928
			<li>$ret['right'] = right margin</li>
15929
			<li>$ret['top'] = top margin</li>
15930
			<li>$ret['bottom'] = bottom margin</li>
15931
			<li>$ret['header'] = header margin</li>
15932
			<li>$ret['footer'] = footer margin</li>
15933
			<li>$ret['cell'] = cell padding array</li>
15934
			<li>$ret['padding_left'] = cell left padding</li>
15935
			<li>$ret['padding_top'] = cell top padding</li>
15936
			<li>$ret['padding_right'] = cell right padding</li>
15937
			<li>$ret['padding_bottom'] = cell bottom padding</li>
15938
	 * </ul>
15939
	 * @return array containing all margins measures
15940
	 * @public
15941
	 * @since 3.2.000 (2008-06-23)
15942
	 */
15943
	public function getMargins() {
15944
		$ret = array(
15945
			'left' => $this->lMargin,
15946
			'right' => $this->rMargin,
15947
			'top' => $this->tMargin,
15948
			'bottom' => $this->bMargin,
15949
			'header' => $this->header_margin,
15950
			'footer' => $this->footer_margin,
15951
			'cell' => $this->cell_padding,
15952
			'padding_left' => $this->cell_padding['L'],
15953
			'padding_top' => $this->cell_padding['T'],
15954
			'padding_right' => $this->cell_padding['R'],
15955
			'padding_bottom' => $this->cell_padding['B']
15956
		);
15957
		return $ret;
15958
	}
15959
15960
	/**
15961
	 * Returns an array containing original margins:
15962
	 * <ul>
15963
			<li>$ret['left'] = left margin</li>
15964
			<li>$ret['right'] = right margin</li>
15965
	 * </ul>
15966
	 * @return array containing all margins measures
15967
	 * @public
15968
	 * @since 4.0.012 (2008-07-24)
15969
	 */
15970
	public function getOriginalMargins() {
15971
		$ret = array(
15972
			'left' => $this->original_lMargin,
15973
			'right' => $this->original_rMargin
15974
		);
15975
		return $ret;
15976
	}
15977
15978
	/**
15979
	 * Returns the current font size.
15980
	 * @return float current font size
15981
	 * @public
15982
	 * @since 3.2.000 (2008-06-23)
15983
	 */
15984
	public function getFontSize() {
15985
		return $this->FontSize;
15986
	}
15987
15988
	/**
15989
	 * Returns the current font size in points unit.
15990
	 * @return int current font size in points unit
15991
	 * @public
15992
	 * @since 3.2.000 (2008-06-23)
15993
	 */
15994
	public function getFontSizePt() {
15995
		return $this->FontSizePt;
15996
	}
15997
15998
	/**
15999
	 * Returns the current font family name.
16000
	 * @return string current font family name
16001
	 * @public
16002
	 * @since 4.3.008 (2008-12-05)
16003
	 */
16004
	public function getFontFamily() {
16005
		return $this->FontFamily;
16006
	}
16007
16008
	/**
16009
	 * Returns the current font style.
16010
	 * @return string current font style
16011
	 * @public
16012
	 * @since 4.3.008 (2008-12-05)
16013
	 */
16014
	public function getFontStyle() {
16015
		return $this->FontStyle;
16016
	}
16017
16018
	/**
16019
	 * Cleanup HTML code (requires HTML Tidy library).
16020
	 * @param string $html htmlcode to fix
16021
	 * @param string $default_css CSS commands to add
16022
	 * @param array|null $tagvs parameters for setHtmlVSpace method
16023
	 * @param array|null $tidy_options options for tidy_parse_string function
16024
	 * @return string XHTML code cleaned up
16025
	 * @author Nicola Asuni
16026
	 * @public
16027
	 * @since 5.9.017 (2010-11-16)
16028
	 * @see setHtmlVSpace()
16029
	 */
16030
	public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) {
16031
		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
16032
	}
16033
16034
	/**
16035
	 * Returns the border width from CSS property
16036
	 * @param string $width border width
16037
	 * @return int with in user units
16038
	 * @protected
16039
	 * @since 5.7.000 (2010-08-02)
16040
	 */
16041
	protected function getCSSBorderWidth($width) {
16042
		if ($width == 'thin') {
16043
			$width = (2 / $this->k);
16044
		} elseif ($width == 'medium') {
16045
			$width = (4 / $this->k);
16046
		} elseif ($width == 'thick') {
16047
			$width = (6 / $this->k);
16048
		} else {
16049
			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
16050
		}
16051
		return $width;
16052
	}
16053
16054
	/**
16055
	 * Returns the border dash style from CSS property
16056
	 * @param string $style border style to convert
16057
	 * @return int sash style (return -1 in case of none or hidden border)
16058
	 * @protected
16059
	 * @since 5.7.000 (2010-08-02)
16060
	 */
16061
	protected function getCSSBorderDashStyle($style) {
16062
		switch (strtolower($style)) {
16063
			case 'none':
16064
			case 'hidden': {
16065
				$dash = -1;
16066
				break;
16067
			}
16068
			case 'dotted': {
16069
				$dash = 1;
16070
				break;
16071
			}
16072
			case 'dashed': {
16073
				$dash = 3;
16074
				break;
16075
			}
16076
			case 'double':
16077
			case 'groove':
16078
			case 'ridge':
16079
			case 'inset':
16080
			case 'outset':
16081
			case 'solid':
16082
			default: {
16083
				$dash = 0;
16084
				break;
16085
			}
16086
		}
16087
		return $dash;
16088
	}
16089
16090
	/**
16091
	 * Returns the border style array from CSS border properties
16092
	 * @param string $cssborder border properties
16093
	 * @return array containing border properties
16094
	 * @protected
16095
	 * @since 5.7.000 (2010-08-02)
16096
	 */
16097
	protected function getCSSBorderStyle($cssborder) {
16098
		$bprop = preg_split('/[\s]+/', trim($cssborder));
16099
		$count = count($bprop);
16100
		if ($count > 0 && $bprop[$count - 1] === '!important') {
16101
			unset($bprop[$count - 1]);
16102
			--$count;
16103
		}
16104
16105
		$border = array(); // value to be returned
16106
		switch ($count) {
16107
			case 2: {
16108
				$width = 'medium';
16109
				$style = $bprop[0];
16110
				$color = $bprop[1];
16111
				break;
16112
			}
16113
			case 1: {
16114
				$width = 'medium';
16115
				$style = $bprop[0];
16116
				$color = 'black';
16117
				break;
16118
			}
16119
			case 0: {
16120
				$width = 'medium';
16121
				$style = 'solid';
16122
				$color = 'black';
16123
				break;
16124
			}
16125
			default: {
16126
				$width = $bprop[0];
16127
				$style = $bprop[1];
16128
				$color = $bprop[2];
16129
				break;
16130
			}
16131
		}
16132
		if ($style == 'none') {
16133
			return array();
16134
		}
16135
		$border['cap'] = 'square';
16136
		$border['join'] = 'miter';
16137
		$border['dash'] = $this->getCSSBorderDashStyle($style);
16138
		if ($border['dash'] < 0) {
16139
			return array();
16140
		}
16141
		$border['width'] = $this->getCSSBorderWidth($width);
16142
		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16143
		return $border;
16144
	}
16145
16146
	/**
16147
	 * Get the internal Cell padding from CSS attribute.
16148
	 * @param string $csspadding padding properties
16149
	 * @param float $width width of the containing element
16150
	 * @return array of cell paddings
16151
	 * @public
16152
	 * @since 5.9.000 (2010-10-04)
16153
	 */
16154
	public function getCSSPadding($csspadding, $width=0) {
16155
		$padding = preg_split('/[\s]+/', trim($csspadding));
16156
		$cell_padding = array(); // value to be returned
16157
		switch (count($padding)) {
16158
			case 4: {
16159
				$cell_padding['T'] = $padding[0];
16160
				$cell_padding['R'] = $padding[1];
16161
				$cell_padding['B'] = $padding[2];
16162
				$cell_padding['L'] = $padding[3];
16163
				break;
16164
			}
16165
			case 3: {
16166
				$cell_padding['T'] = $padding[0];
16167
				$cell_padding['R'] = $padding[1];
16168
				$cell_padding['B'] = $padding[2];
16169
				$cell_padding['L'] = $padding[1];
16170
				break;
16171
			}
16172
			case 2: {
16173
				$cell_padding['T'] = $padding[0];
16174
				$cell_padding['R'] = $padding[1];
16175
				$cell_padding['B'] = $padding[0];
16176
				$cell_padding['L'] = $padding[1];
16177
				break;
16178
			}
16179
			case 1: {
16180
				$cell_padding['T'] = $padding[0];
16181
				$cell_padding['R'] = $padding[0];
16182
				$cell_padding['B'] = $padding[0];
16183
				$cell_padding['L'] = $padding[0];
16184
				break;
16185
			}
16186
			default: {
16187
				return $this->cell_padding;
16188
			}
16189
		}
16190
		if ($width == 0) {
16191
			$width = $this->w - $this->lMargin - $this->rMargin;
16192
		}
16193
		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16194
		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16195
		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16196
		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16197
		return $cell_padding;
16198
	}
16199
16200
	/**
16201
	 * Get the internal Cell margin from CSS attribute.
16202
	 * @param string $cssmargin margin properties
16203
	 * @param float $width width of the containing element
16204
	 * @return array of cell margins
16205
	 * @public
16206
	 * @since 5.9.000 (2010-10-04)
16207
	 */
16208
	public function getCSSMargin($cssmargin, $width=0) {
16209
		$margin = preg_split('/[\s]+/', trim($cssmargin));
16210
		$cell_margin = array(); // value to be returned
16211
		switch (count($margin)) {
16212
			case 4: {
16213
				$cell_margin['T'] = $margin[0];
16214
				$cell_margin['R'] = $margin[1];
16215
				$cell_margin['B'] = $margin[2];
16216
				$cell_margin['L'] = $margin[3];
16217
				break;
16218
			}
16219
			case 3: {
16220
				$cell_margin['T'] = $margin[0];
16221
				$cell_margin['R'] = $margin[1];
16222
				$cell_margin['B'] = $margin[2];
16223
				$cell_margin['L'] = $margin[1];
16224
				break;
16225
			}
16226
			case 2: {
16227
				$cell_margin['T'] = $margin[0];
16228
				$cell_margin['R'] = $margin[1];
16229
				$cell_margin['B'] = $margin[0];
16230
				$cell_margin['L'] = $margin[1];
16231
				break;
16232
			}
16233
			case 1: {
16234
				$cell_margin['T'] = $margin[0];
16235
				$cell_margin['R'] = $margin[0];
16236
				$cell_margin['B'] = $margin[0];
16237
				$cell_margin['L'] = $margin[0];
16238
				break;
16239
			}
16240
			default: {
16241
				return $this->cell_margin;
16242
			}
16243
		}
16244
		if ($width == 0) {
16245
			$width = $this->w - $this->lMargin - $this->rMargin;
16246
		}
16247
		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16248
		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16249
		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16250
		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16251
		return $cell_margin;
16252
	}
16253
16254
	/**
16255
	 * Get the border-spacing from CSS attribute.
16256
	 * @param string $cssbspace border-spacing CSS properties
16257
	 * @param float $width width of the containing element
16258
	 * @return array of border spacings
16259
	 * @public
16260
	 * @since 5.9.010 (2010-10-27)
16261
	 */
16262
	public function getCSSBorderMargin($cssbspace, $width=0) {
16263
		$space = preg_split('/[\s]+/', trim($cssbspace));
16264
		$border_spacing = array(); // value to be returned
16265
		switch (count($space)) {
16266
			case 2: {
16267
				$border_spacing['H'] = $space[0];
16268
				$border_spacing['V'] = $space[1];
16269
				break;
16270
			}
16271
			case 1: {
16272
				$border_spacing['H'] = $space[0];
16273
				$border_spacing['V'] = $space[0];
16274
				break;
16275
			}
16276
			default: {
16277
				return array('H' => 0, 'V' => 0);
16278
			}
16279
		}
16280
		if ($width == 0) {
16281
			$width = $this->w - $this->lMargin - $this->rMargin;
16282
		}
16283
		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16284
		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16285
		return $border_spacing;
16286
	}
16287
16288
	/**
16289
	 * Returns the letter-spacing value from CSS value
16290
	 * @param string $spacing letter-spacing value
16291
	 * @param float $parent font spacing (tracking) value of the parent element
16292
	 * @return float quantity to increases or decreases the space between characters in a text.
16293
	 * @protected
16294
	 * @since 5.9.000 (2010-10-02)
16295
	 */
16296
	protected function getCSSFontSpacing($spacing, $parent=0) {
16297
		$val = 0; // value to be returned
16298
		$spacing = trim($spacing);
16299
		switch ($spacing) {
16300
			case 'normal': {
16301
				$val = 0;
16302
				break;
16303
			}
16304
			case 'inherit': {
16305
				if ($parent == 'normal') {
16306
					$val = 0;
16307
				} else {
16308
					$val = $parent;
16309
				}
16310
				break;
16311
			}
16312
			default: {
16313
				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16314
			}
16315
		}
16316
		return $val;
16317
	}
16318
16319
	/**
16320
	 * Returns the percentage of font stretching from CSS value
16321
	 * @param string $stretch stretch mode
16322
	 * @param float $parent stretch value of the parent element
16323
	 * @return float font stretching percentage
16324
	 * @protected
16325
	 * @since 5.9.000 (2010-10-02)
16326
	 */
16327
	protected function getCSSFontStretching($stretch, $parent=100) {
16328
		$val = 100; // value to be returned
16329
		$stretch = trim($stretch);
16330
		switch ($stretch) {
16331
			case 'ultra-condensed': {
16332
				$val = 40;
16333
				break;
16334
			}
16335
			case 'extra-condensed': {
16336
				$val = 55;
16337
				break;
16338
			}
16339
			case 'condensed': {
16340
				$val = 70;
16341
				break;
16342
			}
16343
			case 'semi-condensed': {
16344
				$val = 85;
16345
				break;
16346
			}
16347
			case 'normal': {
16348
				$val = 100;
16349
				break;
16350
			}
16351
			case 'semi-expanded': {
16352
				$val = 115;
16353
				break;
16354
			}
16355
			case 'expanded': {
16356
				$val = 130;
16357
				break;
16358
			}
16359
			case 'extra-expanded': {
16360
				$val = 145;
16361
				break;
16362
			}
16363
			case 'ultra-expanded': {
16364
				$val = 160;
16365
				break;
16366
			}
16367
			case 'wider': {
16368
				$val = ($parent + 10);
16369
				break;
16370
			}
16371
			case 'narrower': {
16372
				$val = ($parent - 10);
16373
				break;
16374
			}
16375
			case 'inherit': {
16376
				if ($parent == 'normal') {
16377
					$val = 100;
16378
				} else {
16379
					$val = $parent;
16380
				}
16381
				break;
16382
			}
16383
			default: {
16384
				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16385
			}
16386
		}
16387
		return $val;
16388
	}
16389
16390
	/**
16391
	 * Convert HTML string containing font size value to points
16392
	 * @param string $val String containing font size value and unit.
16393
	 * @param float $refsize Reference font size in points.
16394
	 * @param float $parent_size Parent font size in points.
16395
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16396
	 * @return float value in points
16397
	 * @public
16398
	 */
16399
	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16400
		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16401
		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16402
		switch ($val) {
16403
			case 'xx-small': {
16404
				$size = ($refsize - 4);
16405
				break;
16406
			}
16407
			case 'x-small': {
16408
				$size = ($refsize - 3);
16409
				break;
16410
			}
16411
			case 'small': {
16412
				$size = ($refsize - 2);
16413
				break;
16414
			}
16415
			case 'medium': {
16416
				$size = $refsize;
16417
				break;
16418
			}
16419
			case 'large': {
16420
				$size = ($refsize + 2);
16421
				break;
16422
			}
16423
			case 'x-large': {
16424
				$size = ($refsize + 4);
16425
				break;
16426
			}
16427
			case 'xx-large': {
16428
				$size = ($refsize + 6);
16429
				break;
16430
			}
16431
			case 'smaller': {
16432
				$size = ($parent_size - 3);
16433
				break;
16434
			}
16435
			case 'larger': {
16436
				$size = ($parent_size + 3);
16437
				break;
16438
			}
16439
			default: {
16440
				$parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16441
				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16442
			}
16443
		}
16444
		return $size;
16445
	}
16446
16447
	/**
16448
	 * Returns the HTML DOM array.
16449
	 * @param string $html html code
16450
	 * @return array
16451
	 * @protected
16452
	 * @since 3.2.000 (2008-06-20)
16453
	 */
16454
	protected function getHtmlDomArray($html) {
16455
		// set inheritable properties fot the first void element
16456
		// 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
16457
		$dom = array(
16458
			array(
16459
				'tag' => false,
16460
				'block' => false,
16461
				'value' => '',
16462
				'parent' => 0,
16463
				'hide' => false,
16464
				'fontname' => $this->FontFamily,
16465
				'fontstyle' => $this->FontStyle,
16466
				'fontsize' => $this->FontSizePt,
16467
				'font-stretch' => $this->font_stretching,
16468
				'letter-spacing' => $this->font_spacing,
16469
				'stroke' => $this->textstrokewidth,
16470
				'fill' => (($this->textrendermode % 2) == 0),
16471
				'clip' => ($this->textrendermode > 3),
16472
				'line-height' => $this->cell_height_ratio,
16473
				'bgcolor' => false,
16474
				'fgcolor' => $this->fgcolor, // color
16475
				'strokecolor' => $this->strokecolor,
16476
				'align' => '',
16477
				'listtype' => '',
16478
				'text-indent' => 0,
16479
				'text-transform' => '',
16480
				'border' => array(),
16481
				'dir' => $this->rtl?'rtl':'ltr',
16482
				'width' => 0,
16483
				'height' => 0,
16484
				'x' => 0,
16485
				'y' => 0,
16486
				'w' => 0,
16487
				'h' => 0,
16488
				'l' => 0,
16489
				't' => 0,
16490
				'r' => 0,
16491
				'b' => 0,
16492
				'padding' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16493
				'margin' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16494
				'border-spacing' => array('H' => 0, 'V' => 0),
16495
				'border-collapse' => 'separate',
16496
			)
16497
		);
16498
16499
		if($html === '' || $html === null) {
16500
			return $dom;
16501
		}
16502
		// array of CSS styles ( selector => properties).
16503
		$css = array();
16504
		// get CSS array defined at previous call
16505
		$matches = array();
16506
		if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16507
			if (isset($matches[1][0])) {
16508
				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16509
			}
16510
			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16511
		}
16512
		// extract external CSS files
16513
		$matches = array();
16514
		if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16515
			foreach ($matches[1] as $key => $link) {
16516
				$type = array();
16517
				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16518
					$type = array();
16519
					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16520
					// get 'all' and 'print' media, other media types are discarded
16521
					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16522
					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16523
						$type = array();
16524
						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16525
							// read CSS data file
16526
                            $cssdata = $this->getCachedFileContents(trim($type[1]));
16527
							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16528
								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16529
							}
16530
						}
16531
					}
16532
				}
16533
			}
16534
		}
16535
		// extract style tags
16536
		$matches = array();
16537
		if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16538
			foreach ($matches[1] as $key => $media) {
16539
				$type = array();
16540
				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16541
				// get 'all' and 'print' media, other media types are discarded
16542
				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16543
				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16544
					$cssdata = $matches[2][$key];
16545
					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16546
				}
16547
			}
16548
		}
16549
		// create a special tag to contain the CSS array (used for table content)
16550
		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16551
		// remove head and style blocks
16552
		$html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16553
		$html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16554
		// define block tags
16555
		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16556
		// define self-closing tags
16557
		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16558
		// remove all unsupported tags (the line below lists all supported tags)
16559
		$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>');
16560
		//replace some blank characters
16561
		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16562
		$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);
16563
		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16564
		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16565
		$html = strtr($html, $repTable);
16566
		$offset = 0;
16567
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16568
			$html_a = substr($html, 0, $offset);
16569
			$html_b = substr($html, $offset, ($pos - $offset + 6));
16570
			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16571
				// preserve newlines on <pre> tag
16572
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16573
			}
16574
			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16575
				// preserve spaces on <pre> tag
16576
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16577
			}
16578
			$html = $html_a.$html_b.substr($html, $pos + 6);
16579
			$offset = strlen($html_a.$html_b);
16580
		}
16581
		$offset = 0;
16582
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16583
			$html_a = substr($html, 0, $offset);
16584
			$html_b = substr($html, $offset, ($pos - $offset + 11));
16585
			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16586
				// preserve newlines on <textarea> tag
16587
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16588
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16589
			}
16590
			$html = $html_a.$html_b.substr($html, $pos + 11);
16591
			$offset = strlen($html_a.$html_b);
16592
		}
16593
		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16594
		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16595
		$offset = 0;
16596
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16597
			$html_a = substr($html, 0, $offset);
16598
			$html_b = substr($html, $offset, ($pos - $offset + 9));
16599
			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16600
				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16601
				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16602
			}
16603
			$html = $html_a.$html_b.substr($html, $pos + 9);
16604
			$offset = strlen($html_a.$html_b);
16605
		}
16606
		if (preg_match("'</select'si", $html)) {
16607
			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16608
			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16609
		}
16610
		$html = str_replace("\n", ' ', $html);
16611
		// restore textarea newlines
16612
		$html = str_replace('<TBR>', "\n", $html);
16613
		// remove extra spaces from code
16614
		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16615
		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16616
		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16617
		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16618
		$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);
16619
		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16620
		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16621
		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16622
		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16623
		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16624
		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16625
		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16626
		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16627
		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16628
		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16629
		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16630
		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16631
		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16632
		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16633
		// trim string
16634
		$html = $this->stringTrim($html);
16635
		// fix br tag after li
16636
		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16637
		// fix first image tag alignment
16638
		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16639
		// pattern for generic tag
16640
		$tagpattern = '/(<[^>]+>)/';
16641
		// explodes the string
16642
		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16643
		// count elements
16644
		$maxel = count($a);
16645
		$elkey = 0;
16646
		$thead = false; // true when we are inside the THEAD tag
16647
		$key = 1;
16648
		$level = array();
16649
		array_push($level, 0); // root
16650
		while ($elkey < $maxel) {
16651
			$dom[$key] = array();
16652
			$element = $a[$elkey];
16653
			$dom[$key]['elkey'] = $elkey;
16654
			if (preg_match($tagpattern, $element)) {
16655
				// html tag
16656
				$element = substr($element, 1, -1);
16657
				// get tag name
16658
				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16659
				$tagname = strtolower($tag[1]);
16660
				// check if we are inside a table header
16661
				if ($tagname == 'thead') {
16662
					if ($element[0] == '/') {
16663
						$thead = false;
16664
					} else {
16665
						$thead = true;
16666
					}
16667
					++$elkey;
16668
					continue;
16669
				}
16670
				$dom[$key]['tag'] = true;
16671
				$dom[$key]['value'] = $tagname;
16672
				if (in_array($dom[$key]['value'], $blocktags)) {
16673
					$dom[$key]['block'] = true;
16674
				} else {
16675
					$dom[$key]['block'] = false;
16676
				}
16677
				if ($element[0] == '/') {
16678
					// *** closing html tag
16679
					$dom[$key]['opening'] = false;
16680
					$dom[$key]['parent'] = end($level);
16681
					array_pop($level);
16682
					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16683
					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16684
					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16685
					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16686
					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16687
					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16688
					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16689
					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16690
					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16691
					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16692
					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16693
					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16694
					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16695
					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16696
					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16697
					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16698
					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16699
						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16700
					}
16701
					// set the number of columns in table tag
16702
					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16703
						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16704
					}
16705
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16706
						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16707
						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16708
							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16709
						}
16710
						$key = $i;
16711
						// mark nested tables
16712
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16713
						// remove thead sections from nested tables
16714
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16715
						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16716
					}
16717
					// store header rows on a new table
16718
					if (
16719
						($dom[$key]['value'] === 'tr')
16720
						&& !empty($dom[($dom[$key]['parent'])]['thead'])
16721
						&& ($dom[($dom[$key]['parent'])]['thead'] === true)
16722
					) {
16723
						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16724
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16725
						}
16726
						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16727
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16728
						}
16729
						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16730
							$dom[($dom[$key]['parent'])]['attribute'] = array();
16731
						}
16732
						// header elements must be always contained in a single page
16733
						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16734
					}
16735
					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16736
						// remove the nobr attributes from the table header
16737
						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16738
						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16739
					}
16740
				} else {
16741
					// *** opening or self-closing html tag
16742
					$dom[$key]['opening'] = true;
16743
					$dom[$key]['parent'] = end($level);
16744
					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16745
						// self-closing tag
16746
						$dom[$key]['self'] = true;
16747
					} else {
16748
						// opening tag
16749
						array_push($level, $key);
16750
						$dom[$key]['self'] = false;
16751
					}
16752
					// copy some values from parent
16753
					$parentkey = 0;
16754
					if ($key > 0) {
16755
						$parentkey = $dom[$key]['parent'];
16756
						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16757
						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16758
						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16759
						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16760
						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16761
						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16762
						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16763
						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16764
						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16765
						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16766
						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16767
						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16768
						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16769
						$dom[$key]['align'] = $dom[$parentkey]['align'];
16770
						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16771
						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16772
						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16773
						$dom[$key]['border'] = array();
16774
						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16775
					}
16776
					// get attributes
16777
					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16778
					$dom[$key]['attribute'] = array(); // reset attribute array
16779
                    foreach($attr_array[1] as $id => $name) {
16780
                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16781
                    }
16782
					if (!empty($css)) {
16783
						// merge CSS style to current style
16784
						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16785
						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16786
					}
16787
					// split style attributes
16788
					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16789
						// get style attributes
16790
						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16791
						$dom[$key]['style'] = array(); // reset style attribute array
16792
                        foreach($style_array[1] as $id => $name) {
16793
                            // in case of duplicate attribute the last replace the previous
16794
                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16795
                        }
16796
						// --- get some style attributes ---
16797
						// text direction
16798
						if (isset($dom[$key]['style']['direction'])) {
16799
							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16800
						}
16801
						// display
16802
						if (isset($dom[$key]['style']['display'])) {
16803
							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16804
						}
16805
						// font family
16806
						if (isset($dom[$key]['style']['font-family'])) {
16807
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16808
						}
16809
						// list-style-type
16810
						if (isset($dom[$key]['style']['list-style-type'])) {
16811
							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16812
							if ($dom[$key]['listtype'] == 'inherit') {
16813
								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16814
							}
16815
						}
16816
						// text-indent
16817
						if (isset($dom[$key]['style']['text-indent'])) {
16818
							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16819
							if ($dom[$key]['text-indent'] == 'inherit') {
16820
								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16821
							}
16822
						}
16823
						// text-transform
16824
						if (isset($dom[$key]['style']['text-transform'])) {
16825
							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16826
						}
16827
						// font size
16828
						if (isset($dom[$key]['style']['font-size'])) {
16829
							$fsize = trim($dom[$key]['style']['font-size']);
16830
							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16831
						}
16832
						// font-stretch
16833
						if (isset($dom[$key]['style']['font-stretch'])) {
16834
							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16835
						}
16836
						// letter-spacing
16837
						if (isset($dom[$key]['style']['letter-spacing'])) {
16838
							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16839
						}
16840
						// line-height (internally is the cell height ratio)
16841
						if (isset($dom[$key]['style']['line-height'])) {
16842
							$lineheight = trim($dom[$key]['style']['line-height']);
16843
							switch ($lineheight) {
16844
								// A normal line height. This is default
16845
								case 'normal': {
16846
									$dom[$key]['line-height'] = $dom[0]['line-height'];
16847
									break;
16848
								}
16849
								case 'inherit': {
16850
									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16851
								}
16852
								default: {
16853
									if (is_numeric($lineheight)) {
16854
										// convert to percentage of font height
16855
										$lineheight = ($lineheight * 100).'%';
16856
									}
16857
									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16858
									if (substr($lineheight, -1) !== '%') {
16859
										if ($dom[$key]['fontsize'] <= 0) {
16860
											$dom[$key]['line-height'] = 1;
16861
										} else {
16862
											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16863
										}
16864
									}
16865
								}
16866
							}
16867
						}
16868
						// font style
16869
						if (isset($dom[$key]['style']['font-weight'])) {
16870
							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16871
								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16872
									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16873
								}
16874
							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16875
								$dom[$key]['fontstyle'] .= 'B';
16876
							}
16877
						}
16878
						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16879
							$dom[$key]['fontstyle'] .= 'I';
16880
						}
16881
						// font color
16882
						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16883
							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16884
						} elseif ($dom[$key]['value'] == 'a') {
16885
							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16886
						}
16887
						// background color
16888
						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16889
							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16890
						}
16891
						// text-decoration
16892
						if (isset($dom[$key]['style']['text-decoration'])) {
16893
							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16894
							foreach ($decors as $dec) {
16895
								$dec = trim($dec);
16896
								if (!TCPDF_STATIC::empty_string($dec)) {
16897
									if ($dec[0] == 'u') {
16898
										// underline
16899
										$dom[$key]['fontstyle'] .= 'U';
16900
									} elseif ($dec[0] == 'l') {
16901
										// line-through
16902
										$dom[$key]['fontstyle'] .= 'D';
16903
									} elseif ($dec[0] == 'o') {
16904
										// overline
16905
										$dom[$key]['fontstyle'] .= 'O';
16906
									}
16907
								}
16908
							}
16909
						} elseif ($dom[$key]['value'] == 'a') {
16910
							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16911
						}
16912
						// check for width attribute
16913
						if (isset($dom[$key]['style']['width'])) {
16914
							$dom[$key]['width'] = $dom[$key]['style']['width'];
16915
						}
16916
						// check for height attribute
16917
						if (isset($dom[$key]['style']['height'])) {
16918
							$dom[$key]['height'] = $dom[$key]['style']['height'];
16919
						}
16920
						// check for text alignment
16921
						if (isset($dom[$key]['style']['text-align'])) {
16922
							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16923
						}
16924
						// check for CSS border properties
16925
						if (isset($dom[$key]['style']['border'])) {
16926
							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16927
							if (!empty($borderstyle)) {
16928
								$dom[$key]['border']['LTRB'] = $borderstyle;
16929
							}
16930
						}
16931
						if (isset($dom[$key]['style']['border-color'])) {
16932
							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16933
							if (isset($brd_colors[3])) {
16934
								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16935
							}
16936
							if (isset($brd_colors[1])) {
16937
								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16938
							}
16939
							if (isset($brd_colors[0])) {
16940
								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16941
							}
16942
							if (isset($brd_colors[2])) {
16943
								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16944
							}
16945
						}
16946
						if (isset($dom[$key]['style']['border-width'])) {
16947
							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16948
							if (isset($brd_widths[3])) {
16949
								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16950
							}
16951
							if (isset($brd_widths[1])) {
16952
								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16953
							}
16954
							if (isset($brd_widths[0])) {
16955
								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16956
							}
16957
							if (isset($brd_widths[2])) {
16958
								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16959
							}
16960
						}
16961
						if (isset($dom[$key]['style']['border-style'])) {
16962
							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16963
							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16964
								$dom[$key]['border']['L']['cap'] = 'square';
16965
								$dom[$key]['border']['L']['join'] = 'miter';
16966
								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16967
								if ($dom[$key]['border']['L']['dash'] < 0) {
16968
									$dom[$key]['border']['L'] = array();
16969
								}
16970
							}
16971
							if (isset($brd_styles[1])) {
16972
								$dom[$key]['border']['R']['cap'] = 'square';
16973
								$dom[$key]['border']['R']['join'] = 'miter';
16974
								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16975
								if ($dom[$key]['border']['R']['dash'] < 0) {
16976
									$dom[$key]['border']['R'] = array();
16977
								}
16978
							}
16979
							if (isset($brd_styles[0])) {
16980
								$dom[$key]['border']['T']['cap'] = 'square';
16981
								$dom[$key]['border']['T']['join'] = 'miter';
16982
								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16983
								if ($dom[$key]['border']['T']['dash'] < 0) {
16984
									$dom[$key]['border']['T'] = array();
16985
								}
16986
							}
16987
							if (isset($brd_styles[2])) {
16988
								$dom[$key]['border']['B']['cap'] = 'square';
16989
								$dom[$key]['border']['B']['join'] = 'miter';
16990
								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16991
								if ($dom[$key]['border']['B']['dash'] < 0) {
16992
									$dom[$key]['border']['B'] = array();
16993
								}
16994
							}
16995
						}
16996
						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16997
						foreach ($cellside as $bsk => $bsv) {
16998
							if (isset($dom[$key]['style']['border-'.$bsv])) {
16999
								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
17000
								if (!empty($borderstyle)) {
17001
									$dom[$key]['border'][$bsk] = $borderstyle;
17002
								}
17003
							}
17004
							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
17005
								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
17006
							}
17007
							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
17008
								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
17009
							}
17010
							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
17011
								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
17012
								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
17013
									$dom[$key]['border'][$bsk] = array();
17014
								}
17015
							}
17016
						}
17017
						// check for CSS padding properties
17018
						if (isset($dom[$key]['style']['padding'])) {
17019
							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
17020
						} else {
17021
							$dom[$key]['padding'] = $this->cell_padding;
17022
						}
17023
						foreach ($cellside as $psk => $psv) {
17024
							if (isset($dom[$key]['style']['padding-'.$psv])) {
17025
								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
17026
							}
17027
						}
17028
						// check for CSS margin properties
17029
						if (isset($dom[$key]['style']['margin'])) {
17030
							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
17031
						} else {
17032
							$dom[$key]['margin'] = $this->cell_margin;
17033
						}
17034
						foreach ($cellside as $psk => $psv) {
17035
							if (isset($dom[$key]['style']['margin-'.$psv])) {
17036
								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
17037
							}
17038
						}
17039
						// check for CSS border-spacing properties
17040
						if (isset($dom[$key]['style']['border-spacing'])) {
17041
							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
17042
						}
17043
						// page-break-inside
17044
						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
17045
							$dom[$key]['attribute']['nobr'] = 'true';
17046
						}
17047
						// page-break-before
17048
						if (isset($dom[$key]['style']['page-break-before'])) {
17049
							if ($dom[$key]['style']['page-break-before'] == 'always') {
17050
								$dom[$key]['attribute']['pagebreak'] = 'true';
17051
							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
17052
								$dom[$key]['attribute']['pagebreak'] = 'left';
17053
							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
17054
								$dom[$key]['attribute']['pagebreak'] = 'right';
17055
							}
17056
						}
17057
						// page-break-after
17058
						if (isset($dom[$key]['style']['page-break-after'])) {
17059
							if ($dom[$key]['style']['page-break-after'] == 'always') {
17060
								$dom[$key]['attribute']['pagebreakafter'] = 'true';
17061
							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
17062
								$dom[$key]['attribute']['pagebreakafter'] = 'left';
17063
							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
17064
								$dom[$key]['attribute']['pagebreakafter'] = 'right';
17065
							}
17066
						}
17067
					}
17068
					if (isset($dom[$key]['attribute']['display'])) {
17069
						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
17070
					}
17071
					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
17072
						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
17073
						if (!empty($borderstyle)) {
17074
							$dom[$key]['border']['LTRB'] = $borderstyle;
17075
						}
17076
					}
17077
					// check for font tag
17078
					if ($dom[$key]['value'] == 'font') {
17079
						// font family
17080
						if (isset($dom[$key]['attribute']['face'])) {
17081
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17082
						}
17083
						// font size
17084
						if (isset($dom[$key]['attribute']['size'])) {
17085
							if ($key > 0) {
17086
								if ($dom[$key]['attribute']['size'][0] == '+') {
17087
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
17088
								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
17089
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
17090
								} else {
17091
									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17092
								}
17093
							} else {
17094
								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17095
							}
17096
						}
17097
					}
17098
					// force natural alignment for lists
17099
					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
17100
						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
17101
						if ($this->rtl) {
17102
							$dom[$key]['align'] = 'R';
17103
						} else {
17104
							$dom[$key]['align'] = 'L';
17105
						}
17106
					}
17107
					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17108
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17109
							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17110
						}
17111
					}
17112
					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17113
						$dom[$key]['fontstyle'] .= 'B';
17114
					}
17115
					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17116
						$dom[$key]['fontstyle'] .= 'I';
17117
					}
17118
					if ($dom[$key]['value'] == 'u') {
17119
						$dom[$key]['fontstyle'] .= 'U';
17120
					}
17121
					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17122
						$dom[$key]['fontstyle'] .= 'D';
17123
					}
17124
					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17125
						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17126
					}
17127
					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17128
						$dom[$key]['fontname'] = $this->default_monospaced_font;
17129
					}
17130
					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)) {
17131
						// headings h1, h2, h3, h4, h5, h6
17132
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17133
							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17134
							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17135
						}
17136
						if (!isset($dom[$key]['style']['font-weight'])) {
17137
							$dom[$key]['fontstyle'] .= 'B';
17138
						}
17139
					}
17140
					if (($dom[$key]['value'] == 'table')) {
17141
						$dom[$key]['rows'] = 0; // number of rows
17142
						$dom[$key]['trids'] = array(); // IDs of TR elements
17143
						$dom[$key]['thead'] = ''; // table header rows
17144
					}
17145
					if (($dom[$key]['value'] == 'tr')) {
17146
						$dom[$key]['cols'] = 0;
17147
						if ($thead) {
17148
							$dom[$key]['thead'] = true;
17149
							// rows on thead block are printed as a separate table
17150
						} else {
17151
							$dom[$key]['thead'] = false;
17152
							$parent = $dom[$key]['parent'];
17153
17154
							if (!isset($dom[$parent]['rows'])) {
17155
								$dom[$parent]['rows'] = 0;
17156
							}
17157
							// store the number of rows on table element
17158
							++$dom[$parent]['rows'];
17159
17160
							if (!isset($dom[$parent]['trids'])) {
17161
								$dom[$parent]['trids'] = array();
17162
							}
17163
17164
							// store the TR elements IDs on table element
17165
							array_push($dom[$parent]['trids'], $key);
17166
						}
17167
					}
17168
					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17169
						if (isset($dom[$key]['attribute']['colspan'])) {
17170
							$colspan = intval($dom[$key]['attribute']['colspan']);
17171
						} else {
17172
							$colspan = 1;
17173
						}
17174
						$dom[$key]['attribute']['colspan'] = $colspan;
17175
						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
17176
					}
17177
					// text direction
17178
					if (isset($dom[$key]['attribute']['dir'])) {
17179
						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17180
					}
17181
					// set foreground color attribute
17182
					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17183
						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17184
					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17185
						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17186
					}
17187
					// set background color attribute
17188
					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17189
						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17190
					}
17191
					// set stroke color attribute
17192
					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17193
						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17194
					}
17195
					// check for width attribute
17196
					if (isset($dom[$key]['attribute']['width'])) {
17197
						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
17198
					}
17199
					// check for height attribute
17200
					if (isset($dom[$key]['attribute']['height'])) {
17201
						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
17202
					}
17203
					// check for text alignment
17204
					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17205
						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17206
					}
17207
					// check for text rendering mode (the following attributes do not exist in HTML)
17208
					if (isset($dom[$key]['attribute']['stroke'])) {
17209
						// font stroke width
17210
						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17211
					}
17212
					if (isset($dom[$key]['attribute']['fill'])) {
17213
						// font fill
17214
						if ($dom[$key]['attribute']['fill'] == 'true') {
17215
							$dom[$key]['fill'] = true;
17216
						} else {
17217
							$dom[$key]['fill'] = false;
17218
						}
17219
					}
17220
					if (isset($dom[$key]['attribute']['clip'])) {
17221
						// clipping mode
17222
						if ($dom[$key]['attribute']['clip'] == 'true') {
17223
							$dom[$key]['clip'] = true;
17224
						} else {
17225
							$dom[$key]['clip'] = false;
17226
						}
17227
					}
17228
				} // end opening tag
17229
			} else {
17230
				// text
17231
				$dom[$key]['tag'] = false;
17232
				$dom[$key]['block'] = false;
17233
				$dom[$key]['parent'] = end($level);
17234
				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17235
				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17236
					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17237
					if (function_exists('mb_convert_case')) {
17238
						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17239
						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17240
							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17241
						}
17242
					} elseif (!$this->isunicode) {
17243
						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17244
							case 'capitalize': {
17245
								$element = ucwords(strtolower($element));
17246
								break;
17247
							}
17248
							case 'uppercase': {
17249
								$element = strtoupper($element);
17250
								break;
17251
							}
17252
							case 'lowercase': {
17253
								$element = strtolower($element);
17254
								break;
17255
							}
17256
						}
17257
					}
17258
					$element = preg_replace("/&NBSP;/i", "&nbsp;", $element);
17259
				}
17260
				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17261
			}
17262
			++$elkey;
17263
			++$key;
17264
		}
17265
		return $dom;
17266
	}
17267
17268
	/**
17269
	 * Returns the string used to find spaces
17270
	 * @return string
17271
	 * @protected
17272
	 * @author Nicola Asuni
17273
	 * @since 4.8.024 (2010-01-15)
17274
	 */
17275
	protected function getSpaceString() {
17276
		$spacestr = chr(32);
17277
		if ($this->isUnicodeFont()) {
17278
			$spacestr = chr(0).chr(32);
17279
		}
17280
		return $spacestr;
17281
	}
17282
17283
	/**
17284
	 * Calculates the hash value of the given data.
17285
	 *
17286
	 * @param string $data The data to be hashed.
17287
	 * @return string The hashed value of the data.
17288
	 */
17289
	protected function hashTCPDFtag($data) {
17290
		return hash_hmac('sha256', $data, $this->hash_key, false);
17291
	}
17292
17293
	/**
17294
	 * Serialize data to be used with TCPDF tag in HTML code.
17295
	 * @param string $method TCPDF method name
17296
	 * @param array $params Method parameters
17297
	 * @return string Serialized data
17298
	 * @public static
17299
	 */
17300
	public function serializeTCPDFtag($method, $params=array()) {
17301
		$data = array('m' => $method, 'p' => $params);
17302
		$encoded = urlencode(json_encode($data));
17303
		$hash = $this->hashTCPDFtag($encoded);
17304
		return strlen($hash).'+'.$hash.'+'.$encoded;
17305
	}
17306
17307
	/**
17308
	 * Unserialize data to be used with TCPDF tag in HTML code.
17309
	 * @param string $data serialized data
17310
	 * @return array containing unserialized data
17311
	 * @protected static
17312
	 */
17313
	protected function unserializeTCPDFtag($data) {
17314
		$hpos = strpos($data, '+');
17315
		$hlen = intval(substr($data, 0, $hpos));
17316
		$hash = substr($data, $hpos + 1, $hlen);
17317
		$encoded = substr($data, $hpos + 2 + $hlen);
17318
		if (!hash_equals( $this->hashTCPDFtag($encoded), $hash)) {
17319
			$this->Error('Invalid parameters');
17320
		}
17321
		return json_decode(urldecode($encoded), true);
17322
	}
17323
17324
	/**
17325
	 * Check if a TCPDF tag is allowed
17326
	 * @param string $method TCPDF method name
17327
	 * @return boolean
17328
	 * @protected
17329
	 */
17330
	protected function allowedTCPDFtag($method) {
17331
		if (defined('K_ALLOWED_TCPDF_TAGS')) {
17332
			return (strpos(K_ALLOWED_TCPDF_TAGS, '|'.$method.'|') !== false);
17333
		}
17334
		return false;
17335
	}
17336
17337
	/**
17338
	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17339
	 * 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 />
17340
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17341
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17342
	 * 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
17343
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17344
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17345
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
17346
	 * @param float|null $x upper-left corner X coordinate
17347
	 * @param float|null $y upper-left corner Y coordinate
17348
	 * @param string $html html text to print. Default value: empty string.
17349
	 * @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)))
17350
	 * @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> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17351
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17352
	 * @param boolean $reseth if true reset the last cell height (default true).
17353
	 * @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>
17354
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17355
	 * @see Multicell(), writeHTML()
17356
	 * @public
17357
	 */
17358
	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17359
		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17360
	}
17361
17362
	/**
17363
	 * Allows to preserve some HTML formatting (limited support).<br />
17364
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17365
	 * 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
17366
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17367
	 * @param string $html text to display
17368
	 * @param boolean $ln if true add a new line after text (default = true)
17369
	 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17370
	 * @param boolean $reseth if true reset the last cell height (default false).
17371
	 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17372
	 * @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>
17373
	 * @public
17374
	 */
17375
	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17376
		$gvars = $this->getGraphicVars();
17377
		// store current values
17378
		$prev_cell_margin = $this->cell_margin;
17379
		$prev_cell_padding = $this->cell_padding;
17380
		$prevPage = $this->page;
17381
		$prevlMargin = $this->lMargin;
17382
		$prevrMargin = $this->rMargin;
17383
		$curfontname = $this->FontFamily;
17384
		$curfontstyle = $this->FontStyle;
17385
		$curfontsize = $this->FontSizePt;
17386
		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17387
		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17388
		$curfontstretcing = $this->font_stretching;
17389
		$curfonttracking = $this->font_spacing;
17390
		$this->newline = true;
17391
		$newline = true;
17392
		$startlinepage = $this->page;
17393
		$minstartliney = $this->y;
17394
		$maxbottomliney = 0;
17395
		$startlinex = $this->x;
17396
		$startliney = $this->y;
17397
		$yshift = 0;
17398
		$loop = 0;
17399
		$curpos = 0;
17400
		$this_method_vars = array();
17401
		$undo = false;
17402
		$fontaligned = false;
17403
		$reverse_dir = false; // true when the text direction is reversed
17404
		$this->premode = false;
17405
		if ($this->inxobj) {
17406
			// we are inside an XObject template
17407
			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17408
		} elseif (isset($this->PageAnnots[$this->page])) {
17409
			$pask = count($this->PageAnnots[$this->page]);
17410
		} else {
17411
			$pask = 0;
17412
		}
17413
		if ($this->inxobj) {
17414
			// we are inside an XObject template
17415
			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17416
		} elseif (!$this->InFooter) {
17417
			if (isset($this->footerlen[$this->page])) {
17418
				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17419
			} else {
17420
				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17421
			}
17422
			$startlinepos = $this->footerpos[$this->page];
17423
		} else {
17424
			// we are inside the footer
17425
			$startlinepos = $this->pagelen[$this->page];
17426
		}
17427
		$lalign = $align;
17428
		$plalign = $align;
17429
		if ($this->rtl) {
17430
			$w = $this->x - $this->lMargin;
17431
		} else {
17432
			$w = $this->w - $this->rMargin - $this->x;
17433
		}
17434
		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17435
		if ($cell) {
17436
			if ($this->rtl) {
17437
				$this->x -= $this->cell_padding['R'];
17438
				$this->lMargin += $this->cell_padding['L'];
17439
			} else {
17440
				$this->x += $this->cell_padding['L'];
17441
				$this->rMargin += $this->cell_padding['R'];
17442
			}
17443
		}
17444
		if ($this->customlistindent >= 0) {
17445
			$this->listindent = $this->customlistindent;
17446
		} else {
17447
			$this->listindent = $this->GetStringWidth('000000');
17448
		}
17449
		$this->listindentlevel = 0;
17450
		// save previous states
17451
		$prev_cell_height_ratio = $this->cell_height_ratio;
17452
		$prev_listnum = $this->listnum;
17453
		$prev_listordered = $this->listordered;
17454
		$prev_listcount = $this->listcount;
17455
		$prev_lispacer = $this->lispacer;
17456
		$this->listnum = 0;
17457
		$this->listordered = array();
17458
		$this->listcount = array();
17459
		$this->lispacer = '';
17460
		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17461
			// reset row height
17462
			$this->resetLastH();
17463
		}
17464
		$dom = $this->getHtmlDomArray($html);
17465
		$maxel = count($dom);
17466
		$key = 0;
17467
		while ($key < $maxel) {
17468
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17469
				// store the node key
17470
				$hidden_node_key = $key;
17471
				if ($dom[$key]['self']) {
17472
					// skip just this self-closing tag
17473
					++$key;
17474
				} else {
17475
					// skip this and all children tags
17476
					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17477
						// skip hidden objects
17478
						++$key;
17479
					}
17480
					++$key;
17481
				}
17482
			}
17483
			if ($key == $maxel) break;
17484
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND !empty($dom[$key]['attribute']['id'])) {
17485
				$this->setDestination($dom[$key]['attribute']['id']);
17486
			}
17487
			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17488
				// check for pagebreak
17489
				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17490
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17491
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17492
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17493
				}
17494
				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17495
					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17496
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17497
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17498
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17499
				}
17500
			}
17501
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17502
				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17503
					$dom[$key]['attribute']['nobr'] = false;
17504
				} else {
17505
					// store current object
17506
					$this->startTransaction();
17507
					// save this method vars
17508
					$this_method_vars['html'] = $html;
17509
					$this_method_vars['ln'] = $ln;
17510
					$this_method_vars['fill'] = $fill;
17511
					$this_method_vars['reseth'] = $reseth;
17512
					$this_method_vars['cell'] = $cell;
17513
					$this_method_vars['align'] = $align;
17514
					$this_method_vars['gvars'] = $gvars;
17515
					$this_method_vars['prevPage'] = $prevPage;
17516
					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17517
					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17518
					$this_method_vars['prevlMargin'] = $prevlMargin;
17519
					$this_method_vars['prevrMargin'] = $prevrMargin;
17520
					$this_method_vars['curfontname'] = $curfontname;
17521
					$this_method_vars['curfontstyle'] = $curfontstyle;
17522
					$this_method_vars['curfontsize'] = $curfontsize;
17523
					$this_method_vars['curfontascent'] = $curfontascent;
17524
					$this_method_vars['curfontdescent'] = $curfontdescent;
17525
					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17526
					$this_method_vars['curfonttracking'] = $curfonttracking;
17527
					$this_method_vars['minstartliney'] = $minstartliney;
17528
					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17529
					$this_method_vars['yshift'] = $yshift;
17530
					$this_method_vars['startlinepage'] = $startlinepage;
17531
					$this_method_vars['startlinepos'] = $startlinepos;
17532
					$this_method_vars['startlinex'] = $startlinex;
17533
					$this_method_vars['startliney'] = $startliney;
17534
					$this_method_vars['newline'] = $newline;
17535
					$this_method_vars['loop'] = $loop;
17536
					$this_method_vars['curpos'] = $curpos;
17537
					$this_method_vars['pask'] = $pask;
17538
					$this_method_vars['lalign'] = $lalign;
17539
					$this_method_vars['plalign'] = $plalign;
17540
					$this_method_vars['w'] = $w;
17541
					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17542
					$this_method_vars['prev_listnum'] = $prev_listnum;
17543
					$this_method_vars['prev_listordered'] = $prev_listordered;
17544
					$this_method_vars['prev_listcount'] = $prev_listcount;
17545
					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17546
					$this_method_vars['fontaligned'] = $fontaligned;
17547
					$this_method_vars['key'] = $key;
17548
					$this_method_vars['dom'] = $dom;
17549
				}
17550
			}
17551
			// print THEAD block
17552
			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17553
				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17554
					$this->inthead = true;
17555
					// print table header (thead)
17556
					$this->writeHTML($this->thead, false, false, false, false, '');
17557
					// check if we are on a new page or on a new column
17558
					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17559
						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17560
						// restore previous object
17561
						$this->rollbackTransaction(true);
17562
						// restore previous values
17563
						foreach ($this_method_vars as $vkey => $vval) {
17564
							$$vkey = $vval;
17565
						}
17566
						// disable table header
17567
						$tmp_thead = $this->thead;
17568
						$this->thead = '';
17569
						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17570
						$pre_y = $this->y;
17571
						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17572
							// fix for multicolumn mode
17573
							$startliney = $this->y;
17574
						}
17575
						$this->start_transaction_page = $this->page;
17576
						$this->start_transaction_y = $this->y;
17577
						// restore table header
17578
						$this->thead = $tmp_thead;
17579
						// fix table border properties
17580
						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17581
							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17582
						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17583
							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17584
						} else {
17585
							$tmp_cellspacing = 0;
17586
						}
17587
						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17588
						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17589
						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17590
						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17591
						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17592
						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17593
						// print table header (thead)
17594
						$this->writeHTML($this->thead, false, false, false, false, '');
17595
					}
17596
				}
17597
				// move $key index forward to skip THEAD block
17598
				while ( ($key < $maxel) AND (!(
17599
					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17600
					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17601
					++$key;
17602
				}
17603
			}
17604
			if ($dom[$key]['tag'] OR ($key == 0)) {
17605
				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17606
					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17607
				}
17608
				// vertically align image in line
17609
				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17610
					// get image height
17611
					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17612
					$autolinebreak = false;
17613
					if (!empty($dom[$key]['width'])) {
17614
						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17615
						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17616
							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17617
							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17618
							// add automatic line break
17619
							$autolinebreak = true;
17620
							$this->Ln('', $cell);
17621
							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17622
								// go back to evaluate this line break
17623
								--$key;
17624
							}
17625
						}
17626
					}
17627
					if (!$autolinebreak) {
17628
						if ($this->inPageBody()) {
17629
							$pre_y = $this->y;
17630
							// check for page break
17631
							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17632
								// fix for multicolumn mode
17633
								$startliney = $this->y;
17634
							}
17635
						}
17636
						if ($this->page > $startlinepage) {
17637
							// fix line splitted over two pages
17638
							if (isset($this->footerlen[$startlinepage])) {
17639
								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17640
							}
17641
							// line to be moved one page forward
17642
							$pagebuff = $this->getPageBuffer($startlinepage);
17643
							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17644
							$tstart = substr($pagebuff, 0, $startlinepos);
17645
							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17646
							// remove line from previous page
17647
							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17648
							$pagebuff = $this->getPageBuffer($this->page);
17649
							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17650
							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17651
							// add line start to current page
17652
							$yshift = ($minstartliney - $this->y);
17653
							if ($fontaligned) {
17654
								$yshift += ($curfontsize / $this->k);
17655
							}
17656
							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17657
							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17658
							// shift the annotations and links
17659
							if (isset($this->PageAnnots[$this->page])) {
17660
								$next_pask = count($this->PageAnnots[$this->page]);
17661
							} else {
17662
								$next_pask = 0;
17663
							}
17664
							if (isset($this->PageAnnots[$startlinepage])) {
17665
								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17666
									if ($pak >= $pask) {
17667
										$this->PageAnnots[$this->page][] = $pac;
17668
										unset($this->PageAnnots[$startlinepage][$pak]);
17669
										$npak = count($this->PageAnnots[$this->page]) - 1;
17670
										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17671
									}
17672
								}
17673
							}
17674
							$pask = $next_pask;
17675
							$startlinepos = $this->cntmrk[$this->page];
17676
							$startlinepage = $this->page;
17677
							$startliney = $this->y;
17678
							$this->newline = false;
17679
						}
17680
						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17681
						$minstartliney = min($this->y, $minstartliney);
17682
						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17683
					}
17684
				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17685
					// account for different font size
17686
					$pfontname = $curfontname;
17687
					$pfontstyle = $curfontstyle;
17688
					$pfontsize = $curfontsize;
17689
					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17690
					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17691
					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17692
					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17693
					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17694
					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17695
						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17696
						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17697
						if (($key < ($maxel - 1)) AND (
17698
								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17699
								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17700
								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17701
								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17702
								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17703
							)) {
17704
							if ($this->page > $startlinepage) {
17705
								// fix lines splitted over two pages
17706
								if (isset($this->footerlen[$startlinepage])) {
17707
									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17708
								}
17709
								// line to be moved one page forward
17710
								$pagebuff = $this->getPageBuffer($startlinepage);
17711
								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17712
								$tstart = substr($pagebuff, 0, $startlinepos);
17713
								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17714
								// remove line start from previous page
17715
								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17716
								$pagebuff = $this->getPageBuffer($this->page);
17717
								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17718
								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17719
								// add line start to current page
17720
								$yshift = ($minstartliney - $this->y);
17721
								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17722
								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17723
								// shift the annotations and links
17724
								if (isset($this->PageAnnots[$this->page])) {
17725
									$next_pask = count($this->PageAnnots[$this->page]);
17726
								} else {
17727
									$next_pask = 0;
17728
								}
17729
								if (isset($this->PageAnnots[$startlinepage])) {
17730
									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17731
										if ($pak >= $pask) {
17732
											$this->PageAnnots[$this->page][] = $pac;
17733
											unset($this->PageAnnots[$startlinepage][$pak]);
17734
											$npak = count($this->PageAnnots[$this->page]) - 1;
17735
											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17736
										}
17737
									}
17738
								}
17739
								$pask = $next_pask;
17740
								$startlinepos = $this->cntmrk[$this->page];
17741
								$startlinepage = $this->page;
17742
								$startliney = $this->y;
17743
							}
17744
							if (!isset($dom[$key]['line-height'])) {
17745
								$dom[$key]['line-height'] = $this->cell_height_ratio;
17746
							}
17747
							if (!$dom[$key]['block']) {
17748
								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']))) {
17749
									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17750
								}
17751
								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17752
									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17753
									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)))) {
17754
										$minstartliney = min($this->y, $line_align_data[1]);
17755
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17756
									} else {
17757
										$minstartliney = min($this->y, $minstartliney);
17758
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17759
									}
17760
									$line_align_data = $current_line_align_data;
17761
								}
17762
							}
17763
							$this->cell_height_ratio = $dom[$key]['line-height'];
17764
							$fontaligned = true;
17765
						}
17766
						$this->setFont($fontname, $fontstyle, $fontsize);
17767
						// reset row height
17768
						$this->resetLastH();
17769
						$curfontname = $fontname;
17770
						$curfontstyle = $fontstyle;
17771
						$curfontsize = $fontsize;
17772
						$curfontascent = $fontascent;
17773
						$curfontdescent = $fontdescent;
17774
					}
17775
				}
17776
				// set text rendering mode
17777
				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17778
				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17779
				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17780
				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17781
				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17782
					$this->setFontStretching($dom[$key]['font-stretch']);
17783
				}
17784
				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17785
					$this->setFontSpacing($dom[$key]['letter-spacing']);
17786
				}
17787
				if (($plalign == 'J') AND $dom[$key]['block']) {
17788
					$plalign = '';
17789
				}
17790
				// get current position on page buffer
17791
				$curpos = $this->pagelen[$startlinepage];
17792
				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17793
					$this->setFillColorArray($dom[$key]['bgcolor']);
17794
					$wfill = true;
17795
				} else {
17796
					$wfill = $fill | false;
17797
				}
17798
				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17799
					$this->setTextColorArray($dom[$key]['fgcolor']);
17800
				}
17801
				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17802
					$this->setDrawColorArray($dom[$key]['strokecolor']);
17803
				}
17804
				if (isset($dom[$key]['align'])) {
17805
					$lalign = $dom[$key]['align'];
17806
				}
17807
				if (TCPDF_STATIC::empty_string($lalign)) {
17808
					$lalign = $align;
17809
				}
17810
			}
17811
			// align lines
17812
			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17813
				$newline = true;
17814
				$fontaligned = false;
17815
				// we are at the beginning of a new line
17816
				if (isset($startlinex)) {
17817
					$yshift = ($minstartliney - $startliney);
17818
					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17819
						$yshift = 0;
17820
					}
17821
					$t_x = 0;
17822
					// the last line must be shifted to be aligned as requested
17823
					$linew = abs($this->endlinex - $startlinex);
17824
					if ($this->inxobj) {
17825
						// we are inside an XObject template
17826
						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17827
						if (isset($opentagpos)) {
17828
							$midpos = $opentagpos;
17829
						} else {
17830
							$midpos = 0;
17831
						}
17832
						if ($midpos > 0) {
17833
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17834
							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17835
						} else {
17836
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17837
							$pend = '';
17838
						}
17839
					} else {
17840
						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17841
						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17842
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17843
							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17844
						} elseif (isset($opentagpos)) {
17845
							$midpos = $opentagpos;
17846
						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17847
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17848
							$midpos = $this->footerpos[$startlinepage];
17849
						} else {
17850
							$midpos = 0;
17851
						}
17852
						if ($midpos > 0) {
17853
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17854
							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17855
						} else {
17856
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17857
							$pend = '';
17858
						}
17859
					}
17860
					if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17861
						// calculate shifting amount
17862
						$tw = $w;
17863
						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17864
							$tw += $this->cell_padding['R'];
17865
						}
17866
						if ($this->lMargin != $prevlMargin) {
17867
							$tw += ($prevlMargin - $this->lMargin);
17868
						}
17869
						if ($this->rMargin != $prevrMargin) {
17870
							$tw += ($prevrMargin - $this->rMargin);
17871
						}
17872
						$one_space_width = $this->GetStringWidth(chr(32));
17873
						$no = 0; // number of spaces on a line contained on a single block
17874
						if ($this->isRTLTextDir()) { // RTL
17875
							// remove left space if exist
17876
							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17877
							if ($pos1 > 0) {
17878
								$pos1 = intval($pos1);
17879
								if ($this->isUnicodeFont()) {
17880
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17881
									$spacelen = 2;
17882
								} else {
17883
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17884
									$spacelen = 1;
17885
								}
17886
								if ($pos1 == $pos2) {
17887
									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17888
									if (substr($pmid, $pos1, 4) == '[()]') {
17889
										$linew -= $one_space_width;
17890
									} elseif ($pos1 == strpos($pmid, '[(')) {
17891
										$no = 1;
17892
									}
17893
								}
17894
							}
17895
						} else { // LTR
17896
							// remove right space if exist
17897
							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17898
							if ($pos1 > 0) {
17899
								$pos1 = intval($pos1);
17900
								if ($this->isUnicodeFont()) {
17901
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17902
									$spacelen = 2;
17903
								} else {
17904
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17905
									$spacelen = 1;
17906
								}
17907
								if ($pos1 == $pos2) {
17908
									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17909
									$linew -= $one_space_width;
17910
								}
17911
							}
17912
						}
17913
						$mdiff = ($tw - $linew);
17914
						if ($plalign == 'C') {
17915
							if ($this->rtl) {
17916
								$t_x = -($mdiff / 2);
17917
							} else {
17918
								$t_x = ($mdiff / 2);
17919
							}
17920
						} elseif ($plalign == 'R') {
17921
							// right alignment on LTR document
17922
							$t_x = $mdiff;
17923
						} elseif ($plalign == 'L') {
17924
							// left alignment on RTL document
17925
							$t_x = -$mdiff;
17926
						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17927
							// Justification
17928
							if ($this->isRTLTextDir()) {
17929
								// align text on the left
17930
								$t_x = -$mdiff;
17931
							}
17932
							$ns = 0; // number of spaces
17933
							$pmidtemp = $pmid;
17934
							// escape special characters
17935
							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17936
							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17937
							// search spaces
17938
							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17939
								$spacestr = $this->getSpaceString();
17940
								$maxkk = count($lnstring[1]) - 1;
17941
								for ($kk=0; $kk <= $maxkk; ++$kk) {
17942
									// restore special characters
17943
									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17944
									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17945
									// store number of spaces on the strings
17946
									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17947
									// count total spaces on line
17948
									$ns += $lnstring[2][$kk];
17949
									$lnstring[3][$kk] = $ns;
17950
								}
17951
								if ($ns == 0) {
17952
									$ns = 1;
17953
								}
17954
								// calculate additional space to add to each existing space
17955
								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17956
								if ($this->FontSize <= 0) {
17957
									$this->FontSize = 1;
17958
								}
17959
								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17960
								if ($this->font_spacing != 0) {
17961
									// fixed spacing mode
17962
									$osw = -1000 * $this->font_spacing / $this->FontSize;
17963
									$spacewidthu += $osw;
17964
								}
17965
								$nsmax = $ns;
17966
								$ns = 0;
17967
								reset($lnstring);
17968
								$offset = 0;
17969
								$strcount = 0;
17970
								$prev_epsposbeg = 0;
17971
								$textpos = 0;
17972
								if ($this->isRTLTextDir()) {
17973
									$textpos = $this->wPt;
17974
								}
17975
								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17976
									// check if we are inside a string section '[( ... )]'
17977
									$stroffset = strpos($pmid, '[(', $offset);
17978
									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17979
										// set offset to the end of string section
17980
										$offset = strpos($pmid, ')]', $stroffset);
17981
										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17982
											$offset = strpos($pmid, ')]', ($offset + 1));
17983
										}
17984
										if ($offset === false) {
17985
											$this->Error('HTML Justification: malformed PDF code.');
17986
										}
17987
										continue;
17988
									}
17989
									if ($this->isRTLTextDir()) {
17990
										$spacew = ($spacewidth * ($nsmax - $ns));
17991
									} else {
17992
										$spacew = ($spacewidth * $ns);
17993
									}
17994
									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17995
									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17996
									if ($epsposend !== null) {
17997
										$epsposend += strlen($this->epsmarker.'Q');
17998
										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17999
										if ($epsposbeg === null) {
18000
											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
18001
											$prev_epsposbeg = $epsposbeg;
18002
										}
18003
										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
18004
											// shift EPS images
18005
											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
18006
											$pmid_b = substr($pmid, 0, $epsposbeg);
18007
											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
18008
											$pmid_e = substr($pmid, $epsposend);
18009
											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
18010
											$offset = $epsposend;
18011
											continue;
18012
										}
18013
									}
18014
									$currentxpos = 0;
18015
									// shift blocks of code
18016
									switch ($strpiece[2][0]) {
18017
										case 'Td':
18018
										case 'cm':
18019
										case 'm':
18020
										case 'l': {
18021
											// get current X position
18022
											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
18023
											if (!isset($xmatches[1])) {
18024
												break;
18025
											}
18026
											$currentxpos = $xmatches[1];
18027
											$textpos = $currentxpos;
18028
											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
18029
												$ns = $lnstring[3][$strcount];
18030
												if ($this->isRTLTextDir()) {
18031
													$spacew = ($spacewidth * ($nsmax - $ns));
18032
												}
18033
												++$strcount;
18034
											}
18035
											// justify block
18036
											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
18037
												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
18038
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18039
												unset($pmatch, $newpmid);
18040
											}
18041
											break;
18042
										}
18043
										case 're': {
18044
											// justify block
18045
											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
18046
												$this->lispacer = '';
18047
												break;
18048
											}
18049
											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
18050
											if (!isset($xmatches[1])) {
18051
												break;
18052
											}
18053
											$currentxpos = $xmatches[1];
18054
											$x_diff = 0;
18055
											$w_diff = 0;
18056
											if ($this->isRTLTextDir()) { // RTL
18057
												if ($currentxpos < $textpos) {
18058
													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
18059
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
18060
												} else {
18061
													if ($strcount > 0) {
18062
														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
18063
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18064
													}
18065
												}
18066
											} else { // LTR
18067
												if ($currentxpos > $textpos) {
18068
													if ($strcount > 0) {
18069
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
18070
													}
18071
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
18072
												} else {
18073
													if ($strcount > 1) {
18074
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
18075
													}
18076
													if ($strcount > 0) {
18077
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18078
													}
18079
												}
18080
											}
18081
											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
18082
												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
18083
												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
18084
												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
18085
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18086
												unset($pmatch, $newpmid, $newx, $neww);
18087
											}
18088
											break;
18089
										}
18090
										case 'c': {
18091
											// get current X position
18092
											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);
18093
											if (!isset($xmatches[1])) {
18094
												break;
18095
											}
18096
											$currentxpos = $xmatches[1];
18097
											// justify block
18098
											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) {
18099
												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
18100
												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
18101
												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
18102
												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
18103
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18104
												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
18105
											}
18106
											break;
18107
										}
18108
									}
18109
									// shift the annotations and links
18110
									$cxpos = ($currentxpos / $this->k);
18111
									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
18112
									if ($this->inxobj) {
18113
										// we are inside an XObject template
18114
										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18115
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18116
												if ($cxpos > $lmpos) {
18117
													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
18118
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18119
												} else {
18120
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18121
												}
18122
												break;
18123
											}
18124
										}
18125
									} elseif (isset($this->PageAnnots[$this->page])) {
18126
										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18127
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18128
												if ($cxpos > $lmpos) {
18129
													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
18130
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18131
												} else {
18132
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18133
												}
18134
												break;
18135
											}
18136
										}
18137
									}
18138
								} // end of while
18139
								// remove markers
18140
								$pmid = str_replace('x*#!#*x', '', $pmid);
18141
								if ($this->isUnicodeFont()) {
18142
									// multibyte characters
18143
									$spacew = $spacewidthu;
18144
									if ($this->font_stretching != 100) {
18145
										// word spacing is affected by stretching
18146
										$spacew /= ($this->font_stretching / 100);
18147
									}
18148
									// escape special characters
18149
									$pos = 0;
18150
									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18151
									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18152
									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18153
										foreach($pamatch[0] as $pk => $pmatch) {
18154
											$replace = $pamatch[1][$pk];
18155
											$replace = str_replace('#!#OP#!#', '(', $replace);
18156
											$replace = str_replace('#!#CP#!#', ')', $replace);
18157
											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18158
											$pos = strpos($pmid, $pmatch, $pos);
18159
											if ($pos !== FALSE) {
18160
												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18161
											}
18162
											++$pos;
18163
										}
18164
										unset($pamatch);
18165
									}
18166
									if ($this->inxobj) {
18167
										// we are inside an XObject template
18168
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18169
									} else {
18170
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18171
									}
18172
									$endlinepos = strlen($pstart."\n".$pmid."\n");
18173
								} else {
18174
									// non-unicode (single-byte characters)
18175
									if ($this->font_stretching != 100) {
18176
										// word spacing (Tw) is affected by stretching
18177
										$spacewidth /= ($this->font_stretching / 100);
18178
									}
18179
									$rs = sprintf('%F Tw', $spacewidth);
18180
									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18181
									if ($this->inxobj) {
18182
										// we are inside an XObject template
18183
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18184
									} else {
18185
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18186
									}
18187
									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18188
								}
18189
							}
18190
						} // end of J
18191
					} // end if $startlinex
18192
					if (($t_x != 0) OR ($yshift < 0)) {
18193
						// shift the line
18194
						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18195
						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18196
						$endlinepos = strlen($pstart);
18197
						if ($this->inxobj) {
18198
							// we are inside an XObject template
18199
							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18200
							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18201
								if ($pak >= $pask) {
18202
									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18203
									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18204
								}
18205
							}
18206
						} else {
18207
							$this->setPageBuffer($startlinepage, $pstart.$pend);
18208
							// shift the annotations and links
18209
							if (isset($this->PageAnnots[$this->page])) {
18210
								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18211
									if ($pak >= $pask) {
18212
										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18213
										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18214
									}
18215
								}
18216
							}
18217
						}
18218
						$this->y -= $yshift;
18219
					}
18220
				}
18221
				$pbrk = $this->checkPageBreak($this->lasth);
18222
				$this->newline = false;
18223
				$startlinex = $this->x;
18224
				$startliney = $this->y;
18225
				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18226
					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18227
				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18228
					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
18229
				} else {
18230
					$minstartliney = $startliney;
18231
					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18232
				}
18233
				$startlinepage = $this->page;
18234
				if (isset($endlinepos) AND (!$pbrk)) {
18235
					$startlinepos = $endlinepos;
18236
				} else {
18237
					if ($this->inxobj) {
18238
						// we are inside an XObject template
18239
						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18240
					} elseif (!$this->InFooter) {
18241
						if (isset($this->footerlen[$this->page])) {
18242
							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18243
						} else {
18244
							$this->footerpos[$this->page] = $this->pagelen[$this->page];
18245
						}
18246
						$startlinepos = $this->footerpos[$this->page];
18247
					} else {
18248
						$startlinepos = $this->pagelen[$this->page];
18249
					}
18250
				}
18251
				unset($endlinepos);
18252
				$plalign = $lalign;
18253
				if (isset($this->PageAnnots[$this->page])) {
18254
					$pask = count($this->PageAnnots[$this->page]);
18255
				} else {
18256
					$pask = 0;
18257
				}
18258
				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18259
					AND (isset($this->emptypagemrk[$this->page]))
18260
					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18261
					$this->setFont($fontname, $fontstyle, $fontsize);
18262
					if ($wfill) {
18263
						$this->setFillColorArray($this->bgcolor);
18264
					}
18265
				}
18266
			} // end newline
18267
			if (isset($opentagpos)) {
18268
				unset($opentagpos);
18269
			}
18270
			if ($dom[$key]['tag']) {
18271
				if ($dom[$key]['opening']) {
18272
					// get text indentation (if any)
18273
					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18274
						$this->textindent = $dom[$key]['text-indent'];
18275
						$this->newline = true;
18276
					}
18277
					// table
18278
					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18279
						// available page width
18280
						if ($this->rtl) {
18281
							$wtmp = $this->x - $this->lMargin;
18282
						} else {
18283
							$wtmp = $this->w - $this->rMargin - $this->x;
18284
						}
18285
						// get cell spacing
18286
						if (isset($dom[$key]['attribute']['cellspacing'])) {
18287
							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18288
							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18289
						} elseif (isset($dom[$key]['border-spacing'])) {
18290
							$cellspacing = $dom[$key]['border-spacing'];
18291
						} else {
18292
							$cellspacing = array('H' => 0, 'V' => 0);
18293
						}
18294
						// table width
18295
						if (isset($dom[$key]['width'])) {
18296
							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18297
						} else {
18298
							$table_width = $wtmp;
18299
						}
18300
						$table_width -= (2 * $cellspacing['H']);
18301
						if (!$this->inthead) {
18302
							$this->y += $cellspacing['V'];
18303
						}
18304
						if ($this->rtl) {
18305
							$cellspacingx = -$cellspacing['H'];
18306
						} else {
18307
							$cellspacingx = $cellspacing['H'];
18308
						}
18309
						// total table width without cellspaces
18310
						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18311
						// minimum column width
18312
						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18313
						// array of custom column widths
18314
						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18315
					}
18316
					// table row
18317
					if ($dom[$key]['value'] == 'tr') {
18318
						// reset column counter
18319
						$colid = 0;
18320
					}
18321
					// table cell
18322
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18323
						$trid = $dom[$key]['parent'];
18324
						$table_el = $dom[$trid]['parent'];
18325
						if (!isset($dom[$table_el]['cols'])) {
18326
							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18327
						}
18328
						// store border info
18329
						$tdborder = 0;
18330
						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18331
							$tdborder = $dom[$key]['border'];
18332
						}
18333
						$colspan = intval($dom[$key]['attribute']['colspan']);
18334
						if ($colspan <= 0) {
18335
							$colspan = 1;
18336
						}
18337
						$old_cell_padding = $this->cell_padding;
18338
						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18339
							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18340
							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18341
						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18342
							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18343
						} else {
18344
							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18345
						}
18346
						$this->cell_padding = $current_cell_padding;
18347
						if (isset($dom[$key]['height'])) {
18348
							// minimum cell height
18349
							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18350
						} else {
18351
							$cellh = 0;
18352
						}
18353
						if (isset($dom[$key]['content'])) {
18354
							$cell_content = $dom[$key]['content'];
18355
						} else {
18356
							$cell_content = '&nbsp;';
18357
						}
18358
						$tagtype = $dom[$key]['value'];
18359
						$parentid = $key;
18360
						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18361
							// move $key index forward
18362
							++$key;
18363
						}
18364
						if (!isset($dom[$trid]['startpage'])) {
18365
							$dom[$trid]['startpage'] = $this->page;
18366
						} else {
18367
							$this->setPage($dom[$trid]['startpage']);
18368
						}
18369
						if (!isset($dom[$trid]['startcolumn'])) {
18370
							$dom[$trid]['startcolumn'] = $this->current_column;
18371
						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18372
							$tmpx = $this->x;
18373
							$this->selectColumn($dom[$trid]['startcolumn']);
18374
							$this->x = $tmpx;
18375
						}
18376
						if (!isset($dom[$trid]['starty'])) {
18377
							$dom[$trid]['starty'] = $this->y;
18378
						} else {
18379
							$this->y = $dom[$trid]['starty'];
18380
						}
18381
						if (!isset($dom[$trid]['startx'])) {
18382
							$dom[$trid]['startx'] = $this->x;
18383
							$this->x += $cellspacingx;
18384
						} else {
18385
							$this->x += ($cellspacingx / 2);
18386
						}
18387
						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18388
							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18389
						} else {
18390
							$rowspan = 1;
18391
						}
18392
						// skip row-spanned cells started on the previous rows
18393
						if (isset($dom[$table_el]['rowspans'])) {
18394
							$rsk = 0;
18395
							$rskmax = count($dom[$table_el]['rowspans']);
18396
							while ($rsk < $rskmax) {
18397
								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18398
								$rsstartx = $trwsp['startx'];
18399
								$rsendx = $trwsp['endx'];
18400
								// account for margin changes
18401
								if ($trwsp['startpage'] < $this->page) {
18402
									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18403
										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18404
										$rsstartx -= $dl;
18405
										$rsendx -= $dl;
18406
									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18407
										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18408
										$rsstartx += $dl;
18409
										$rsendx += $dl;
18410
									}
18411
								}
18412
								if (($trwsp['rowspan'] > 0)
18413
									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18414
									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18415
									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18416
									// set the starting X position of the current cell
18417
									$this->x = $rsendx + $cellspacingx;
18418
									// increment column indicator
18419
									$colid += $trwsp['colspan'];
18420
									if (($trwsp['rowspan'] == 1)
18421
										AND (isset($dom[$trid]['endy']))
18422
										AND (isset($dom[$trid]['endpage']))
18423
										AND (isset($dom[$trid]['endcolumn']))
18424
										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18425
										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18426
										// set ending Y position for row
18427
										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18428
										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18429
									}
18430
									$rsk = 0;
18431
								} else {
18432
									++$rsk;
18433
								}
18434
							}
18435
						}
18436
						if (isset($dom[$parentid]['width'])) {
18437
							// user specified width
18438
							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18439
							$tmpcw = ($cellw / $colspan);
18440
							for ($i = 0; $i < $colspan; ++$i) {
18441
								$table_colwidths[($colid + $i)] = $tmpcw;
18442
							}
18443
						} else {
18444
							// inherit column width
18445
							$cellw = 0;
18446
							for ($i = 0; $i < $colspan; ++$i) {
18447
								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18448
							}
18449
						}
18450
						$cellw += (($colspan - 1) * $cellspacing['H']);
18451
						// increment column indicator
18452
						$colid += $colspan;
18453
						// add rowspan information to table element
18454
						if ($rowspan > 1) {
18455
							$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));
18456
						}
18457
						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18458
						if ($rowspan > 1) {
18459
							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18460
						}
18461
						// push background colors
18462
						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18463
							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18464
						}
18465
						// store border info
18466
						if (!empty($tdborder)) {
18467
							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18468
						}
18469
						$prevLastH = $this->lasth;
18470
						// store some info for multicolumn mode
18471
						if ($this->rtl) {
18472
							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18473
						} else {
18474
							$this->colxshift['x'] = $this->x - $this->lMargin;
18475
						}
18476
						$this->colxshift['s'] = $cellspacing;
18477
						$this->colxshift['p'] = $current_cell_padding;
18478
						// ****** write the cell content ******
18479
						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18480
						// restore some values
18481
						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18482
						$this->lasth = $prevLastH;
18483
						$this->cell_padding = $old_cell_padding;
18484
						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18485
						// update the end of row position
18486
						if ($rowspan <= 1) {
18487
							if (isset($dom[$trid]['endy'])) {
18488
								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18489
									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18490
								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18491
									$dom[$trid]['endy'] = $this->y;
18492
								}
18493
							} else {
18494
								$dom[$trid]['endy'] = $this->y;
18495
							}
18496
							if (isset($dom[$trid]['endpage'])) {
18497
								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18498
							} else {
18499
								$dom[$trid]['endpage'] = $this->page;
18500
							}
18501
							if (isset($dom[$trid]['endcolumn'])) {
18502
								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18503
							} else {
18504
								$dom[$trid]['endcolumn'] = $this->current_column;
18505
							}
18506
						} else {
18507
							// account for row-spanned cells
18508
							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18509
							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18510
							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18511
							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18512
						}
18513
						if (isset($dom[$table_el]['rowspans'])) {
18514
							// update endy and endpage on rowspanned cells
18515
							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18516
								if ($trwsp['rowspan'] > 0) {
18517
									if (isset($dom[$trid]['endpage'])) {
18518
										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18519
											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18520
										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18521
											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18522
											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18523
											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18524
										} else {
18525
											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18526
										}
18527
									}
18528
								}
18529
							}
18530
						}
18531
						$this->x += ($cellspacingx / 2);
18532
					} else {
18533
						// opening tag (or self-closing tag)
18534
						if (!isset($opentagpos)) {
18535
							if ($this->inxobj) {
18536
								// we are inside an XObject template
18537
								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18538
							} elseif (!$this->InFooter) {
18539
								if (isset($this->footerlen[$this->page])) {
18540
									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18541
								} else {
18542
									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18543
								}
18544
								$opentagpos = $this->footerpos[$this->page];
18545
							}
18546
						}
18547
						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18548
					}
18549
				} else { // closing tag
18550
					$prev_numpages = $this->numpages;
18551
					$old_bordermrk = $this->bordermrk[$this->page];
18552
					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18553
					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18554
						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18555
					}
18556
					if ($prev_numpages > $this->numpages) {
18557
						$startlinepage = $this->page;
18558
					}
18559
				}
18560
			} elseif (strlen($dom[$key]['value']) > 0) {
18561
				// print list-item
18562
				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18563
					$this->setFont($pfontname, $pfontstyle, $pfontsize);
18564
					$this->resetLastH();
18565
					$minstartliney = $this->y;
18566
					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18567
					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18568
						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18569
					}
18570
					$this->setFont($curfontname, $curfontstyle, $curfontsize);
18571
					$this->resetLastH();
18572
					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18573
						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18574
						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18575
						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18576
						$minstartliney = min($this->y, $minstartliney);
18577
						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18578
					}
18579
				}
18580
				// text
18581
				$this->htmlvspace = 0;
18582
				$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']);
18583
				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18584
					// reverse spaces order
18585
					$lsp = ''; // left spaces
18586
					$rsp = ''; // right spaces
18587
					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18588
						$lsp = $matches[1];
18589
					}
18590
					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18591
						$rsp = $matches[1];
18592
					}
18593
					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18594
				}
18595
				if ($newline) {
18596
					if (!$this->premode) {
18597
						$prelen = strlen($dom[$key]['value']);
18598
						if ($this->isRTLTextDir() AND !$isRTLString) {
18599
							// right trim except non-breaking space
18600
							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18601
						} else {
18602
							// left trim except non-breaking space
18603
							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18604
						}
18605
						$postlen = strlen($dom[$key]['value']);
18606
						if (($postlen == 0) AND ($prelen > 0)) {
18607
							$dom[$key]['trimmed_space'] = true;
18608
						}
18609
					}
18610
					$newline = false;
18611
					$firstblock = true;
18612
				} else {
18613
					$firstblock = false;
18614
					// replace empty multiple spaces string with a single space
18615
					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18616
				}
18617
				$strrest = '';
18618
				if ($this->rtl) {
18619
					$this->x -= $this->textindent;
18620
				} else {
18621
					$this->x += $this->textindent;
18622
				}
18623
				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18624
					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18625
					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18626
						// HTML <a> Link
18627
						$hrefcolor = '';
18628
						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18629
							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18630
						}
18631
						$hrefstyle = -1;
18632
						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18633
							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18634
						}
18635
						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18636
					} else {
18637
						$wadj = 0; // space to leave for block continuity
18638
						if ($this->rtl) {
18639
							$cwa = ($this->x - $this->lMargin);
18640
						} else {
18641
							$cwa = ($this->w - $this->rMargin - $this->x);
18642
						}
18643
						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18644
							// check the next text blocks for continuity
18645
							$nkey = ($key + 1);
18646
							$write_block = true;
18647
							$same_textdir = true;
18648
							$tmp_fontname = $this->FontFamily;
18649
							$tmp_fontstyle = $this->FontStyle;
18650
							$tmp_fontsize = $this->FontSizePt;
18651
							while ($write_block AND isset($dom[$nkey])) {
18652
								if ($dom[$nkey]['tag']) {
18653
									if ($dom[$nkey]['block']) {
18654
										// end of block
18655
										$write_block = false;
18656
									}
18657
									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18658
									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18659
									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18660
									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18661
								} else {
18662
									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18663
									if (isset($nextstr[0]) AND $same_textdir) {
18664
										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18665
										if (isset($nextstr[1])) {
18666
											$write_block = false;
18667
										}
18668
									}
18669
								}
18670
								++$nkey;
18671
							}
18672
						}
18673
						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18674
							$wadj = 0;
18675
							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18676
							$numblks = count($nextstr);
18677
							if ($numblks > 1) {
18678
								// try to split on blank spaces
18679
								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18680
							} else {
18681
								// set the entire block on new line
18682
								$wadj = $this->GetStringWidth($nextstr[0]);
18683
							}
18684
						}
18685
						// check for reversed text direction
18686
						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18687
							// LTR text on RTL direction or RTL text on LTR direction
18688
							$reverse_dir = true;
18689
							$this->rtl = !$this->rtl;
18690
							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18691
							if ($this->rtl) {
18692
								$this->x += $revshift;
18693
							} else {
18694
								$this->x -= $revshift;
18695
							}
18696
							$xws = $this->x;
18697
						}
18698
						// ****** write only until the end of the line and get the rest ******
18699
						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18700
						// restore default direction
18701
						if ($reverse_dir AND ($wadj == 0)) {
18702
							$this->x = $xws; // @phpstan-ignore-line
18703
							$this->rtl = !$this->rtl;
18704
							$reverse_dir = false;
18705
						}
18706
					}
18707
				}
18708
				$this->textindent = 0;
18709
				if (strlen($strrest) > 0) {
18710
					// store the remaining string on the previous $key position
18711
					$this->newline = true;
18712
					if ($strrest == $dom[$key]['value']) {
18713
						// used to avoid infinite loop
18714
						++$loop;
18715
					} else {
18716
						$loop = 0;
18717
					}
18718
					$dom[$key]['value'] = $strrest;
18719
					if ($cell) {
18720
						if ($this->rtl) {
18721
							$this->x -= $this->cell_padding['R'];
18722
						} else {
18723
							$this->x += $this->cell_padding['L'];
18724
						}
18725
					}
18726
					if ($loop < 3) {
18727
						--$key;
18728
					}
18729
				} else {
18730
					$loop = 0;
18731
					// add the positive font spacing of the last character (if any)
18732
					 if ($this->font_spacing > 0) {
18733
					 	if ($this->rtl) {
18734
							$this->x -= $this->font_spacing;
18735
						} else {
18736
							$this->x += $this->font_spacing;
18737
						}
18738
					}
18739
				}
18740
			}
18741
			++$key;
18742
			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')) {
18743
				// check if we are on a new page or on a new column
18744
				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18745
					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18746
					// restore previous object
18747
					$this->rollbackTransaction(true);
18748
					// restore previous values
18749
					foreach ($this_method_vars as $vkey => $vval) {
18750
						$$vkey = $vval;
18751
					}
18752
					if (!empty($dom[$key]['thead'])) {
18753
						$this->inthead = true;
18754
					}
18755
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18756
					$pre_y = $this->y;
18757
					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18758
						$startliney = $this->y;
18759
					}
18760
					$undo = true; // avoid infinite loop
18761
				} else {
18762
					$undo = false;
18763
				}
18764
			}
18765
		} // end for each $key
18766
		// align the last line
18767
		if (isset($startlinex)) {
18768
			$yshift = ($minstartliney - $startliney);
18769
			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18770
				$yshift = 0;
18771
			}
18772
			$t_x = 0;
18773
			// the last line must be shifted to be aligned as requested
18774
			$linew = abs($this->endlinex - $startlinex);
18775
			if ($this->inxobj) {
18776
				// we are inside an XObject template
18777
				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18778
				if (isset($opentagpos)) {
18779
					$midpos = $opentagpos;
18780
				} else {
18781
					$midpos = 0;
18782
				}
18783
				if ($midpos > 0) {
18784
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18785
					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18786
				} else {
18787
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18788
					$pend = '';
18789
				}
18790
			} else {
18791
				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18792
				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18793
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18794
					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18795
				} elseif (isset($opentagpos)) {
18796
					$midpos = $opentagpos;
18797
				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18798
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18799
					$midpos = $this->footerpos[$startlinepage];
18800
				} else {
18801
					$midpos = 0;
18802
				}
18803
				if ($midpos > 0) {
18804
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18805
					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18806
				} else {
18807
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18808
					$pend = '';
18809
				}
18810
			}
18811
			if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18812
				// calculate shifting amount
18813
				$tw = $w;
18814
				if ($this->lMargin != $prevlMargin) {
18815
					$tw += ($prevlMargin - $this->lMargin);
18816
				}
18817
				if ($this->rMargin != $prevrMargin) {
18818
					$tw += ($prevrMargin - $this->rMargin);
18819
				}
18820
				$one_space_width = $this->GetStringWidth(chr(32));
18821
				$no = 0; // number of spaces on a line contained on a single block
18822
				if ($this->isRTLTextDir()) { // RTL
18823
					// remove left space if exist
18824
					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18825
					if ($pos1 > 0) {
18826
						$pos1 = intval($pos1);
18827
						if ($this->isUnicodeFont()) {
18828
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18829
							$spacelen = 2;
18830
						} else {
18831
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18832
							$spacelen = 1;
18833
						}
18834
						if ($pos1 == $pos2) {
18835
							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18836
							if (substr($pmid, $pos1, 4) == '[()]') {
18837
								$linew -= $one_space_width;
18838
							} elseif ($pos1 == strpos($pmid, '[(')) {
18839
								$no = 1;
18840
							}
18841
						}
18842
					}
18843
				} else { // LTR
18844
					// remove right space if exist
18845
					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18846
					if ($pos1 > 0) {
18847
						$pos1 = intval($pos1);
18848
						if ($this->isUnicodeFont()) {
18849
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18850
							$spacelen = 2;
18851
						} else {
18852
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18853
							$spacelen = 1;
18854
						}
18855
						if ($pos1 == $pos2) {
18856
							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18857
							$linew -= $one_space_width;
18858
						}
18859
					}
18860
				}
18861
				$mdiff = ($tw - $linew);
18862
				if ($plalign == 'C') {
18863
					if ($this->rtl) {
18864
						$t_x = -($mdiff / 2);
18865
					} else {
18866
						$t_x = ($mdiff / 2);
18867
					}
18868
				} elseif ($plalign == 'R') {
18869
					// right alignment on LTR document
18870
					$t_x = $mdiff;
18871
				} elseif ($plalign == 'L') {
18872
					// left alignment on RTL document
18873
					$t_x = -$mdiff;
18874
				}
18875
			} // end if startlinex
18876
			if (($t_x != 0) OR ($yshift < 0)) {
18877
				// shift the line
18878
				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18879
				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18880
				$endlinepos = strlen($pstart);
18881
				if ($this->inxobj) {
18882
					// we are inside an XObject template
18883
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18884
					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18885
						if ($pak >= $pask) {
18886
							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18887
							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18888
						}
18889
					}
18890
				} else {
18891
					$this->setPageBuffer($startlinepage, $pstart.$pend);
18892
					// shift the annotations and links
18893
					if (isset($this->PageAnnots[$this->page])) {
18894
						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18895
							if ($pak >= $pask) {
18896
								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18897
								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18898
							}
18899
						}
18900
					}
18901
				}
18902
				$this->y -= $yshift;
18903
				$yshift = 0;
18904
			}
18905
		}
18906
		// restore previous values
18907
		$this->setGraphicVars($gvars);
18908
		if ($this->num_columns > 1) {
18909
			$this->selectColumn();
18910
		} elseif ($this->page > $prevPage) {
18911
			$this->lMargin = $this->pagedim[$this->page]['olm'];
18912
			$this->rMargin = $this->pagedim[$this->page]['orm'];
18913
		}
18914
		// restore previous list state
18915
		$this->cell_height_ratio = $prev_cell_height_ratio;
18916
		$this->listnum = $prev_listnum;
18917
		$this->listordered = $prev_listordered;
18918
		$this->listcount = $prev_listcount;
18919
		$this->lispacer = $prev_lispacer;
18920
		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18921
			$this->Ln($this->lasth);
18922
			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18923
				$this->y = $maxbottomliney;
18924
			}
18925
		}
18926
		unset($dom);
18927
	}
18928
18929
	/**
18930
	 * Check if the path is relative.
18931
	 * @param string $path path to check
18932
	 * @return boolean true if the path is relative
18933
	 * @protected
18934
	 * @since 6.9.1
18935
	 */
18936
	protected function isRelativePath($path) {
18937
		return (strpos(str_ireplace('%2E', '.', $this->unhtmlentities($path)), '..') !== false);
18938
	}
18939
18940
	/**
18941
	 * Check if it contains a non-allowed external protocol.
18942
	 * @param string $path path to check
18943
	 * @return boolean true if the protocol is not allowed.
18944
	 * @protected
18945
	 * @since 6.9.3
18946
	 */
18947
	protected function hasExtForbiddenProtocol($path) {
18948
		return ((strpos($path, '://') !== false)
18949
			&& (preg_match('|^https?://|', $path) !== 1));
18950
	}
18951
18952
	/**
18953
	 * Process opening tags.
18954
	 * @param array $dom html dom array
18955
	 * @param int $key current element id
18956
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18957
	 * @return array $dom
18958
	 * @protected
18959
	 */
18960
	protected function openHTMLTagHandler($dom, $key, $cell) {
18961
		$tag = $dom[$key];
18962
		$parent = $dom[($dom[$key]['parent'])];
18963
		$firsttag = ($key == 1);
18964
		// check for text direction attribute
18965
		if (isset($tag['dir'])) {
18966
			$this->setTempRTL($tag['dir']);
18967
		} else {
18968
			$this->tmprtl = false;
18969
		}
18970
		if ($tag['block']) {
18971
			$hbz = 0; // distance from y to line bottom
18972
			$hb = 0; // vertical space between block tags
18973
			// calculate vertical space for block tags
18974
			if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18975
				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18976
			} elseif (isset($tag['fontsize'])) {
18977
				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18978
			} else {
18979
				$cur_h = $this->getCellHeight($this->FontSize);
18980
			}
18981
			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18982
				$on = $this->tagvspaces[$tag['value']][0]['n'];
18983
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18984
				$on = 0.6;
18985
			} else {
18986
				$on = 1;
18987
			}
18988
			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18989
				$hb = 0;
18990
			} else {
18991
				$hb = ($on * $cur_h);
18992
			}
18993
			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18994
				if (isset($parent['fontsize'])) {
18995
					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18996
				} else {
18997
					$hbz = $this->getCellHeight($this->FontSize);
18998
				}
18999
			}
19000
			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
19001
				// fix vertical space after table
19002
				$hbz = 0;
19003
			}
19004
			// closing vertical space
19005
			$hbc = 0;
19006
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19007
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19008
			} elseif (isset($parent['fontsize'])) {
19009
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19010
			} else {
19011
				$pre_h = $this->getCellHeight($this->FontSize);
19012
			}
19013
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19014
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19015
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19016
				$cn = 0.6;
19017
			} else {
19018
				$cn = 1;
19019
			}
19020
			if (isset($this->tagvspaces[$tag['value']][1])) {
19021
				$hbc = ($cn * $pre_h);
19022
			}
19023
		}
19024
		// Opening tag
19025
		switch($tag['value']) {
19026
			case 'table': {
19027
				$cp = 0;
19028
				$cs = 0;
19029
				$dom[$key]['rowspans'] = array();
19030
				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
19031
					$this->htmlvspace = 0;
19032
					// set table header
19033
					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
19034
						// set table header
19035
						$this->thead = $dom[$key]['thead'];
19036
						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
19037
							$this->theadMargins = array();
19038
							$this->theadMargins['cell_padding'] = $this->cell_padding;
19039
							$this->theadMargins['lmargin'] = $this->lMargin;
19040
							$this->theadMargins['rmargin'] = $this->rMargin;
19041
							$this->theadMargins['page'] = $this->page;
19042
							$this->theadMargins['cell'] = $cell;
19043
							$this->theadMargins['gvars'] = $this->getGraphicVars();
19044
						}
19045
					}
19046
				}
19047
				// store current margins and page
19048
				$dom[$key]['old_cell_padding'] = $this->cell_padding;
19049
				if (isset($tag['attribute']['cellpadding'])) {
19050
					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
19051
					$this->setCellPadding($pad);
19052
				} elseif (isset($tag['padding'])) {
19053
					$this->cell_padding = $tag['padding'];
19054
				}
19055
				if (isset($tag['attribute']['cellspacing'])) {
19056
					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19057
				} elseif (isset($tag['border-spacing'])) {
19058
					$cs = $tag['border-spacing']['V'];
19059
				}
19060
				$prev_y = $this->y;
19061
				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
19062
					$this->inthead = true;
19063
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
19064
					$this->checkPageBreak($this->PageBreakTrigger + 1);
19065
				}
19066
				break;
19067
			}
19068
			case 'tr': {
19069
				// array of columns positions
19070
				$dom[$key]['cellpos'] = array();
19071
				break;
19072
			}
19073
			case 'hr': {
19074
				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
19075
					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
19076
				} else {
19077
					$hrHeight = $this->GetLineWidth();
19078
				}
19079
				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
19080
				$x = $this->GetX();
19081
				$y = $this->GetY();
19082
				$wtmp = $this->w - $this->lMargin - $this->rMargin;
19083
				if ($cell) {
19084
					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
19085
				}
19086
				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
19087
					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
19088
				} else {
19089
					$hrWidth = $wtmp;
19090
				}
19091
				$prevlinewidth = $this->GetLineWidth();
19092
				$this->setLineWidth($hrHeight);
19093
19094
				$lineStyle = array();
19095
				if (isset($tag['fgcolor'])) {
19096
					$lineStyle['color'] = $tag['fgcolor'];
19097
				}
19098
19099
				if (isset($tag['fgcolor'])) {
19100
					$lineStyle['color'] = $tag['fgcolor'];
19101
				}
19102
19103
				if (isset($tag['style']['cap'])) {
19104
					$lineStyle['cap'] = $tag['style']['cap'];
19105
				}
19106
19107
				if (isset($tag['style']['join'])) {
19108
					$lineStyle['join'] = $tag['style']['join'];
19109
				}
19110
19111
				if (isset($tag['style']['dash'])) {
19112
					$lineStyle['dash'] = $tag['style']['dash'];
19113
				}
19114
19115
				if (isset($tag['style']['phase'])) {
19116
					$lineStyle['phase'] = $tag['style']['phase'];
19117
				}
19118
19119
				$lineStyle = array_filter($lineStyle);
19120
19121
				$this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
19122
				$this->setLineWidth($prevlinewidth);
19123
				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
19124
				break;
19125
			}
19126
			case 'a': {
19127
				if (array_key_exists('href', $tag['attribute'])) {
19128
					$this->HREF['url'] = $tag['attribute']['href'];
19129
				}
19130
				break;
19131
			}
19132
			case 'img': {
19133
				if (empty($tag['attribute']['src'])) {
19134
					break;
19135
				}
19136
				$imgsrc = $tag['attribute']['src'];
19137
				if ($imgsrc[0] === '@') {
19138
					// data stream
19139
					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
19140
					$type = preg_match('/<svg\s+[^>]*[^>]*>.*<\/svg>/is', $imgsrc) ? 'svg' : '';
19141
				} else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19142
					$imgsrc = '@'.base64_decode($reg[2]);
19143
					$type = $reg[1];
19144
				} elseif ($this->isRelativePath($imgsrc)) {
19145
					// accessing parent folders is not allowed
19146
					break;
19147
				} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
19148
					// get image type from a local file path
19149
					$imgsrc = substr($imgsrc, 7);
19150
					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
19151
				} elseif ($this->hasExtForbiddenProtocol($imgsrc)) {
19152
					break;
19153
				} else {
19154
					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19155
						// fix image path
19156
						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
19157
						if (($findroot === false) OR ($findroot > 1)) {
19158
							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
19159
								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
19160
							} else {
19161
								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
19162
							}
19163
						}
19164
						$imgsrc = urldecode($imgsrc);
19165
						$testscrtype = @parse_url($imgsrc);
19166
						if (empty($testscrtype['query'])) {
19167
							// convert URL to server path
19168
							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
19169
						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
19170
							// convert URL to server path
19171
							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
19172
						}
19173
					}
19174
					// get image type
19175
					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
19176
				}
19177
				if (!isset($tag['width'])) {
19178
					$tag['width'] = 0;
19179
				}
19180
				if (!isset($tag['height'])) {
19181
					$tag['height'] = 0;
19182
				}
19183
				//if (!isset($tag['attribute']['align'])) {
19184
					// the only alignment supported is "bottom"
19185
					// further development is required for other modes.
19186
					$tag['attribute']['align'] = 'bottom';
19187
				//}
19188
				switch($tag['attribute']['align']) {
19189
					case 'top': {
19190
						$align = 'T';
19191
						break;
19192
					}
19193
					case 'middle': {
19194
						$align = 'M';
19195
						break;
19196
					}
19197
					case 'bottom': {
19198
						$align = 'B';
19199
						break;
19200
					}
19201
					default: {
19202
						$align = 'B';
19203
						break;
19204
					}
19205
				}
19206
				$prevy = $this->y;
19207
				$xpos = $this->x;
19208
				$imglink = '';
19209
				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19210
					$imglink = $this->HREF['url'];
19211
					if ($imglink[0] == '#' AND isset($imglink[1]) AND is_numeric($imglink[1])) {
19212
						// convert url to internal link
19213
						$lnkdata = explode(',', $imglink);
19214
						if (isset($lnkdata[0])) {
19215
							$page = intval(substr($lnkdata[0], 1));
19216
							if (empty($page) OR ($page <= 0)) {
19217
								$page = $this->page;
19218
							}
19219
							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19220
								$lnky = floatval($lnkdata[1]);
19221
							} else {
19222
								$lnky = 0;
19223
							}
19224
							$imglink = $this->AddLink();
19225
							$this->setLink($imglink, $lnky, $page);
19226
						}
19227
					}
19228
				}
19229
				$border = 0;
19230
				if (isset($tag['border']) AND !empty($tag['border'])) {
19231
					// currently only support 1 (frame) or a combination of 'LTRB'
19232
					$border = $tag['border'];
19233
				}
19234
				$iw = '';
19235
				if (isset($tag['width'])) {
19236
					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19237
				}
19238
				$ih = '';
19239
				if (isset($tag['height'])) {
19240
					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19241
				}
19242
				if (($type == 'eps') OR ($type == 'ai')) {
19243
					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19244
				} elseif ($type == 'svg') {
19245
					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19246
				} else {
19247
					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19248
				}
19249
				switch($align) {
19250
					case 'T': {
19251
						$this->y = $prevy;
19252
						break;
19253
					}
19254
					case 'M': {
19255
						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19256
						break;
19257
					}
19258
					case 'B': {
19259
						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19260
						break;
19261
					}
19262
				}
19263
				break;
19264
			}
19265
			case 'dl': {
19266
				++$this->listnum;
19267
				if ($this->listnum == 1) {
19268
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19269
				} else {
19270
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19271
				}
19272
				break;
19273
			}
19274
			case 'dt': {
19275
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19276
				break;
19277
			}
19278
			case 'dd': {
19279
				if ($this->rtl) {
19280
					$this->rMargin += $this->listindent;
19281
				} else {
19282
					$this->lMargin += $this->listindent;
19283
				}
19284
				++$this->listindentlevel;
19285
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19286
				break;
19287
			}
19288
			case 'ul':
19289
			case 'ol': {
19290
				++$this->listnum;
19291
				if ($tag['value'] == 'ol') {
19292
					$this->listordered[$this->listnum] = true;
19293
				} else {
19294
					$this->listordered[$this->listnum] = false;
19295
				}
19296
				if (isset($tag['attribute']['start'])) {
19297
					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19298
				} else {
19299
					$this->listcount[$this->listnum] = 0;
19300
				}
19301
				if ($this->rtl) {
19302
					$this->rMargin += $this->listindent;
19303
					$this->x -= $this->listindent;
19304
				} else {
19305
					$this->lMargin += $this->listindent;
19306
					$this->x += $this->listindent;
19307
				}
19308
				++$this->listindentlevel;
19309
				if ($this->listnum == 1) {
19310
					if ($key > 1) {
19311
						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19312
					}
19313
				} else {
19314
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19315
				}
19316
				break;
19317
			}
19318
			case 'li': {
19319
				if ($key > 2) {
19320
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19321
				}
19322
				if ($this->listordered[$this->listnum]) {
19323
					// ordered item
19324
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19325
						$this->lispacer = $parent['attribute']['type'];
19326
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19327
						$this->lispacer = $parent['listtype'];
19328
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19329
						$this->lispacer = $this->lisymbol;
19330
					} else {
19331
						$this->lispacer = '#';
19332
					}
19333
					++$this->listcount[$this->listnum];
19334
					if (isset($tag['attribute']['value'])) {
19335
						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19336
					}
19337
				} else {
19338
					// unordered item
19339
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19340
						$this->lispacer = $parent['attribute']['type'];
19341
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19342
						$this->lispacer = $parent['listtype'];
19343
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19344
						$this->lispacer = $this->lisymbol;
19345
					} else {
19346
						$this->lispacer = '!';
19347
					}
19348
				}
19349
				break;
19350
			}
19351
			case 'blockquote': {
19352
				if ($this->rtl) {
19353
					$this->rMargin += $this->listindent;
19354
				} else {
19355
					$this->lMargin += $this->listindent;
19356
				}
19357
				++$this->listindentlevel;
19358
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19359
				break;
19360
			}
19361
			case 'br': {
19362
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19363
				break;
19364
			}
19365
			case 'div': {
19366
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19367
				break;
19368
			}
19369
			case 'p': {
19370
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19371
				break;
19372
			}
19373
			case 'pre': {
19374
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19375
				$this->premode = true;
19376
				break;
19377
			}
19378
			case 'sup': {
19379
				$this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19380
				break;
19381
			}
19382
			case 'sub': {
19383
				$this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19384
				break;
19385
			}
19386
			case 'h1':
19387
			case 'h2':
19388
			case 'h3':
19389
			case 'h4':
19390
			case 'h5':
19391
			case 'h6': {
19392
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19393
				break;
19394
			}
19395
			// Form fields (since 4.8.000 - 2009-09-07)
19396
			case 'form': {
19397
				if (isset($tag['attribute']['action'])) {
19398
					$this->form_action = $tag['attribute']['action'];
19399
				} else {
19400
					$this->Error('Please explicitly set action attribute path!');
19401
				}
19402
				if (isset($tag['attribute']['enctype'])) {
19403
					$this->form_enctype = $tag['attribute']['enctype'];
19404
				} else {
19405
					$this->form_enctype = 'application/x-www-form-urlencoded';
19406
				}
19407
				if (isset($tag['attribute']['method'])) {
19408
					$this->form_mode = $tag['attribute']['method'];
19409
				} else {
19410
					$this->form_mode = 'post';
19411
				}
19412
				break;
19413
			}
19414
			case 'input': {
19415
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19416
					$name = $tag['attribute']['name'];
19417
				} else {
19418
					break;
19419
				}
19420
				$prop = array();
19421
				$opt = array();
19422
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19423
					$prop['readonly'] = true;
19424
				}
19425
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19426
					$value = $tag['attribute']['value'];
19427
				}
19428
				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19429
					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19430
				}
19431
				$h = $this->getCellHeight($this->FontSize);
19432
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19433
					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19434
				} else {
19435
					$w = $h;
19436
				}
19437
				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19438
					$checked = true;
19439
				} else {
19440
					$checked = false;
19441
				}
19442
				if (isset($tag['align'])) {
19443
					switch ($tag['align']) {
19444
						case 'C': {
19445
							$opt['q'] = 1;
19446
							break;
19447
						}
19448
						case 'R': {
19449
							$opt['q'] = 2;
19450
							break;
19451
						}
19452
						case 'L':
19453
						default: {
19454
							break;
19455
						}
19456
					}
19457
				}
19458
				switch ($tag['attribute']['type']) {
19459
					case 'text': {
19460
						if (isset($value)) {
19461
							$opt['v'] = $value;
19462
						}
19463
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19464
						break;
19465
					}
19466
					case 'password': {
19467
						if (isset($value)) {
19468
							$opt['v'] = $value;
19469
						}
19470
						$prop['password'] = 'true';
19471
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19472
						break;
19473
					}
19474
					case 'checkbox': {
19475
						if (!isset($value)) {
19476
							break;
19477
						}
19478
						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19479
						break;
19480
					}
19481
					case 'radio': {
19482
						if (!isset($value)) {
19483
							break;
19484
						}
19485
						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19486
						break;
19487
					}
19488
					case 'submit': {
19489
						if (!isset($value)) {
19490
							$value = 'submit';
19491
						}
19492
						$w = $this->GetStringWidth($value) * 1.5;
19493
						$h *= 1.6;
19494
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19495
						$action = array();
19496
						$action['S'] = 'SubmitForm';
19497
						$action['F'] = $this->form_action;
19498
						if ($this->form_enctype != 'FDF') {
19499
							$action['Flags'] = array('ExportFormat');
19500
						}
19501
						if ($this->form_mode == 'get') {
19502
							$action['Flags'] = array('GetMethod');
19503
						}
19504
						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19505
						break;
19506
					}
19507
					case 'reset': {
19508
						if (!isset($value)) {
19509
							$value = 'reset';
19510
						}
19511
						$w = $this->GetStringWidth($value) * 1.5;
19512
						$h *= 1.6;
19513
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19514
						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19515
						break;
19516
					}
19517
					case 'file': {
19518
						$prop['fileSelect'] = 'true';
19519
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19520
						if (!isset($value)) {
19521
							$value = '*';
19522
						}
19523
						$w = $this->GetStringWidth($value) * 2;
19524
						$h *= 1.2;
19525
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19526
						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19527
						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19528
						break;
19529
					}
19530
					case 'hidden': {
19531
						if (isset($value)) {
19532
							$opt['v'] = $value;
19533
						}
19534
						$opt['f'] = array('invisible', 'hidden');
19535
						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19536
						break;
19537
					}
19538
					case 'image': {
19539
						// THIS TYPE MUST BE FIXED
19540
						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19541
							$img = $tag['attribute']['src'];
19542
						} else {
19543
							break;
19544
						}
19545
						$value = 'img';
19546
						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19547
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19548
							$jsaction = $tag['attribute']['onclick'];
19549
						} else {
19550
							$jsaction = '';
19551
						}
19552
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19553
						break;
19554
					}
19555
					case 'button': {
19556
						if (!isset($value)) {
19557
							$value = ' ';
19558
						}
19559
						$w = $this->GetStringWidth($value) * 1.5;
19560
						$h *= 1.6;
19561
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19562
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19563
							$jsaction = $tag['attribute']['onclick'];
19564
						} else {
19565
							$jsaction = '';
19566
						}
19567
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19568
						break;
19569
					}
19570
				}
19571
				break;
19572
			}
19573
			case 'textarea': {
19574
				$prop = array();
19575
				$opt = array();
19576
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19577
					$prop['readonly'] = true;
19578
				}
19579
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19580
					$name = $tag['attribute']['name'];
19581
				} else {
19582
					break;
19583
				}
19584
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19585
					$opt['v'] = $tag['attribute']['value'];
19586
				}
19587
				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19588
					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19589
				} else {
19590
					$w = 40;
19591
				}
19592
				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19593
					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19594
				} else {
19595
					$h = 10;
19596
				}
19597
				$prop['multiline'] = 'true';
19598
				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19599
				break;
19600
			}
19601
			case 'select': {
19602
				$h = $this->getCellHeight($this->FontSize);
19603
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19604
					$h *= ($tag['attribute']['size'] + 1);
19605
				}
19606
				$prop = array();
19607
				$opt = array();
19608
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19609
					$name = $tag['attribute']['name'];
19610
				} else {
19611
					break;
19612
				}
19613
				$w = 0;
19614
				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19615
					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19616
					$values = array();
19617
					foreach ($options as $val) {
19618
						if (strpos($val, '#!TaB!#') !== false) {
19619
							$opts = explode('#!TaB!#', $val);
19620
							$values[] = $opts;
19621
							$w = max($w, $this->GetStringWidth($opts[1]));
19622
						} else {
19623
							$values[] = $val;
19624
							$w = max($w, $this->GetStringWidth($val));
19625
						}
19626
					}
19627
				} else {
19628
					break;
19629
				}
19630
				$w *= 2;
19631
				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19632
					$prop['multipleSelection'] = 'true';
19633
					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19634
				} else {
19635
					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19636
				}
19637
				break;
19638
			}
19639
			case 'tcpdf': {
19640
				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19641
					// Special tag used to call TCPDF methods
19642
					// This tag is disabled by default by the K_TCPDF_CALLS_IN_HTML constant on TCPDF configuration file.
19643
					// Please use this feature only if you are in control of the HTML content and you are sure that it does not contain any harmful code.
19644
					if (!empty($tag['attribute']['data'])) {
19645
						$tcpdf_tag_data = $this->unserializeTCPDFtag($tag['attribute']['data']);
19646
						if ($this->allowedTCPDFtag($tcpdf_tag_data['m'])) {
19647
							call_user_func_array(array($this, $tcpdf_tag_data['m']), $tcpdf_tag_data['p']);
19648
						}
19649
						$this->newline = true;
19650
					}
19651
				}
19652
				break;
19653
			}
19654
			default: {
19655
				break;
19656
			}
19657
		}
19658
		// define tags that support borders and background colors
19659
		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19660
		if (in_array($tag['value'], $bordertags)) {
19661
			// set border
19662
			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19663
		}
19664
		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19665
			$pba = $dom[$key]['attribute']['pagebreakafter'];
19666
			// check for pagebreak
19667
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19668
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19669
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19670
			}
19671
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19672
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19673
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19674
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19675
			}
19676
		}
19677
		return $dom;
19678
	}
19679
19680
	/**
19681
	 * Process closing tags.
19682
	 * @param array $dom html dom array
19683
	 * @param int $key current element id
19684
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19685
	 * @param int $maxbottomliney maximum y value of current line
19686
	 * @return array $dom
19687
	 * @protected
19688
	 */
19689
	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19690
		$tag = $dom[$key];
19691
		$parent = $dom[($dom[$key]['parent'])];
19692
		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19693
		$in_table_head = false;
19694
		// maximum x position (used to draw borders)
19695
		if ($this->rtl) {
19696
			$xmax = $this->w;
19697
		} else {
19698
			$xmax = 0;
19699
		}
19700
		if ($tag['block']) {
19701
			$hbz = 0; // distance from y to line bottom
19702
			$hb = 0; // vertical space between block tags
19703
			// calculate vertical space for block tags
19704
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19705
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19706
			} elseif (isset($parent['fontsize'])) {
19707
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19708
			} else {
19709
				$pre_h = $this->getCellHeight($this->FontSize);
19710
			}
19711
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19712
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19713
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19714
				$cn = 0.6;
19715
			} else {
19716
				$cn = 1;
19717
			}
19718
			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19719
				$hb = 0;
19720
			} else {
19721
				$hb = ($cn * $pre_h);
19722
			}
19723
			if ($maxbottomliney > $this->PageBreakTrigger) {
19724
				$hbz = $this->getCellHeight($this->FontSize);
19725
			} elseif ($this->y < $maxbottomliney) {
19726
				$hbz = ($maxbottomliney - $this->y);
19727
			}
19728
		}
19729
		// Closing tag
19730
		switch($tag['value']) {
19731
			case 'tr': {
19732
				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19733
				if (!isset($parent['endy'])) {
19734
					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19735
					$parent['endy'] = $this->y;
19736
				}
19737
				if (!isset($parent['endpage'])) {
19738
					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19739
					$parent['endpage'] = $this->page;
19740
				}
19741
				if (!isset($parent['endcolumn'])) {
19742
					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19743
					$parent['endcolumn'] = $this->current_column;
19744
				}
19745
				// update row-spanned cells
19746
				if (isset($dom[$table_el]['rowspans'])) {
19747
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19748
						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19749
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19750
							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19751
								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19752
							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19753
								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19754
								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19755
								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19756
							}
19757
						}
19758
					}
19759
					// report new endy and endpage to the rowspanned cells
19760
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19761
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19762
							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19763
							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19764
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19765
							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19766
							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19767
							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19768
						}
19769
					}
19770
					// update remaining rowspanned cells
19771
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19772
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19773
							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19774
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19775
							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19776
						}
19777
					}
19778
				}
19779
				$prev_page = $this->page;
19780
				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19781
				if ($this->num_columns > 1) {
19782
					if (($prev_page < $this->page)
19783
						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19784
							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19785
						// page jump
19786
						$this->selectColumn(0);
19787
						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19788
						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19789
					} else {
19790
						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19791
						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19792
					}
19793
				} else {
19794
					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19795
				}
19796
				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19797
					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19798
				} elseif (isset($dom[$table_el]['border-spacing'])) {
19799
					$this->y += $dom[$table_el]['border-spacing']['V'];
19800
				}
19801
				$this->Ln(0, $cell);
19802
				if ($this->current_column == $parent['startcolumn']) {
19803
					$this->x = $parent['startx'];
19804
				}
19805
				// account for booklet mode
19806
				if ($this->page > $parent['startpage']) {
19807
					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19808
						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19809
					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19810
						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19811
					}
19812
				}
19813
				break;
19814
			}
19815
			case 'tablehead':
19816
				// closing tag used for the thead part
19817
				$in_table_head = true;
19818
				$this->inthead = false;
19819
			case 'table': {
19820
				$table_el = $parent;
19821
				// set default border
19822
				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19823
					// set default border
19824
					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19825
				} else {
19826
					$border = 0;
19827
				}
19828
				$default_border = $border;
19829
				// fix bottom line alignment of last line before page break
19830
				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19831
					// update row-spanned cells
19832
					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19833
						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19834
							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19835
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19836
							}
19837
							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19838
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19839
							}
19840
						}
19841
					}
19842
					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19843
						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19844
						$dom[$prevtrkey]['endy'] = $pgendy;
19845
						// update row-spanned cells
19846
						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19847
							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19848
								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19849
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19850
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19851
								}
19852
							}
19853
						}
19854
					}
19855
					$prevtrkey = $trkey;
19856
					$table_el = $dom[($dom[$key]['parent'])];
19857
				}
19858
				// for each row
19859
				if (!empty($table_el['trids'])) {
19860
					unset($xmax);
19861
				}
19862
				foreach ($table_el['trids'] as $j => $trkey) {
19863
					$parent = $dom[$trkey];
19864
					if (!isset($xmax)) {
19865
						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19866
					}
19867
					// for each cell on the row
19868
					foreach ($parent['cellpos'] as $k => $cellpos) {
19869
						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19870
							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19871
							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19872
							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19873
							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19874
							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19875
							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19876
							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19877
						} else {
19878
							$endy = $parent['endy'];
19879
							$startpage = $parent['startpage'];
19880
							$endpage = $parent['endpage'];
19881
							$startcolumn = $parent['startcolumn'];
19882
							$endcolumn = $parent['endcolumn'];
19883
						}
19884
						if ($this->num_columns == 0) {
19885
							$this->num_columns = 1;
19886
						}
19887
						if (isset($cellpos['border'])) {
19888
							$border = $cellpos['border'];
19889
						}
19890
						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19891
							$this->setFillColorArray($cellpos['bgcolor']);
19892
							$fill = true;
19893
						} else {
19894
							$fill = false;
19895
						}
19896
						$x = $cellpos['startx'];
19897
						$y = $parent['starty'];
19898
						$starty = $y;
19899
						$w = abs($cellpos['endx'] - $cellpos['startx']);
19900
						// get border modes
19901
						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19902
						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19903
						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19904
						// design borders around HTML cells.
19905
						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19906
							$ccode = '';
19907
							$this->setPage($page);
19908
							if ($this->num_columns < 2) {
19909
								// single-column mode
19910
								$this->x = $x;
19911
								$this->y = $this->tMargin;
19912
							}
19913
							// account for margin changes
19914
							if ($page > $startpage) {
19915
								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19916
									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19917
								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19918
									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19919
								}
19920
							}
19921
							if ($startpage == $endpage) { // single page
19922
								$deltacol = 0;
19923
								$deltath = 0;
19924
								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19925
									$this->selectColumn($column);
19926
									if ($startcolumn == $endcolumn) { // single column
19927
										$cborder = $border;
19928
										$h = $endy - $parent['starty'];
19929
										$this->y = $y;
19930
										$this->x = $x;
19931
									} elseif ($column == $startcolumn) { // first column
19932
										$cborder = $border_start;
19933
										$this->y = $starty;
19934
										$this->x = $x;
19935
										$h = $this->h - $this->y - $this->bMargin;
19936
										if ($this->rtl) {
19937
											$deltacol = $this->x + $this->rMargin - $this->w;
19938
										} else {
19939
											$deltacol = $this->x - $this->lMargin;
19940
										}
19941
									} elseif ($column == $endcolumn) { // end column
19942
										$cborder = $border_end;
19943
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19944
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19945
										}
19946
										$this->x += $deltacol;
19947
										$h = $endy - $this->y;
19948
									} else { // middle column
19949
										$cborder = $border_middle;
19950
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19951
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19952
										}
19953
										$this->x += $deltacol;
19954
										$h = $this->h - $this->y - $this->bMargin;
19955
									}
19956
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19957
								} // end for each column
19958
							} elseif ($page == $startpage) { // first page
19959
								$deltacol = 0;
19960
								$deltath = 0;
19961
								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19962
									$this->selectColumn($column);
19963
									if ($column == $startcolumn) { // first column
19964
										$cborder = $border_start;
19965
										$this->y = $starty;
19966
										$this->x = $x;
19967
										$h = $this->h - $this->y - $this->bMargin;
19968
										if ($this->rtl) {
19969
											$deltacol = $this->x + $this->rMargin - $this->w;
19970
										} else {
19971
											$deltacol = $this->x - $this->lMargin;
19972
										}
19973
									} else { // middle column
19974
										$cborder = $border_middle;
19975
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19976
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19977
										}
19978
										$this->x += $deltacol;
19979
										$h = $this->h - $this->y - $this->bMargin;
19980
									}
19981
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19982
								} // end for each column
19983
							} elseif ($page == $endpage) { // last page
19984
								$deltacol = 0;
19985
								$deltath = 0;
19986
								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19987
									$this->selectColumn($column);
19988
									if ($column == $endcolumn) { // end column
19989
										$cborder = $border_end;
19990
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19991
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19992
										}
19993
										$this->x += $deltacol;
19994
										$h = $endy - $this->y;
19995
									} else { // middle column
19996
										$cborder = $border_middle;
19997
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19998
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19999
										}
20000
										$this->x += $deltacol;
20001
										$h = $this->h - $this->y - $this->bMargin;
20002
									}
20003
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20004
								} // end for each column
20005
							} else { // middle page
20006
								$deltacol = 0;
20007
								$deltath = 0;
20008
								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20009
									$this->selectColumn($column);
20010
									$cborder = $border_middle;
20011
									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
20012
										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
20013
									}
20014
									$this->x += $deltacol;
20015
									$h = $this->h - $this->y - $this->bMargin;
20016
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20017
								} // end for each column
20018
							}
20019
							if (!empty($cborder) OR !empty($fill)) {
20020
								$offsetlen = strlen($ccode);
20021
								// draw border and fill
20022
								if ($this->inxobj) {
20023
									// we are inside an XObject template
20024
									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20025
										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20026
										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20027
										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20028
									} else {
20029
										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20030
										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20031
									}
20032
									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20033
									$pstart = substr($pagebuff, 0, $pagemark);
20034
									$pend = substr($pagebuff, $pagemark);
20035
									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20036
								} else {
20037
									// draw border and fill
20038
									if (end($this->transfmrk[$this->page]) !== false) {
20039
										$pagemarkkey = key($this->transfmrk[$this->page]);
20040
										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20041
									} elseif ($this->InFooter) {
20042
										$pagemark = $this->footerpos[$this->page];
20043
									} else {
20044
										$pagemark = $this->intmrk[$this->page];
20045
									}
20046
									$pagebuff = $this->getPageBuffer($this->page);
20047
									$pstart = substr($pagebuff, 0, $pagemark);
20048
									$pend = substr($pagebuff, $pagemark);
20049
									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20050
								}
20051
							}
20052
						} // end for each page
20053
						// restore default border
20054
						$border = $default_border;
20055
					} // end for each cell on the row
20056
					if (isset($table_el['attribute']['cellspacing'])) {
20057
						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
20058
					} elseif (isset($table_el['border-spacing'])) {
20059
						$this->y += $table_el['border-spacing']['V'];
20060
					}
20061
					$this->Ln(0, $cell);
20062
					$this->x = $parent['startx'];
20063
					if ($endpage > $startpage) {
20064
						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
20065
							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
20066
						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
20067
							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
20068
						}
20069
					}
20070
				}
20071
				if (!$in_table_head) { // we are not inside a thead section
20072
					$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
20073
					// reset row height
20074
					$this->resetLastH();
20075
					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
20076
						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
20077
						if (($plendiff > 0) AND ($plendiff < 60)) {
20078
							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
20079
							if (substr($pagediff, 0, 5) == 'BT /F') {
20080
								// the difference is only a font setting
20081
								$plendiff = 0;
20082
							}
20083
						}
20084
						if ($plendiff == 0) {
20085
							// remove last blank page
20086
							$this->deletePage($this->numpages);
20087
						}
20088
					}
20089
					if (isset($this->theadMargins['top'])) {
20090
						// restore top margin
20091
						$this->tMargin = $this->theadMargins['top'];
20092
					}
20093
					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
20094
						// reset main table header
20095
						$this->thead = '';
20096
						$this->theadMargins = array();
20097
						$this->pagedim[$this->page]['tm'] = $this->tMargin;
20098
					}
20099
				}
20100
				$parent = $table_el;
20101
				break;
20102
			}
20103
			case 'a': {
20104
				$this->HREF = array();
20105
				break;
20106
			}
20107
			case 'sup': {
20108
				$this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
20109
				break;
20110
			}
20111
			case 'sub': {
20112
				$this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
20113
				break;
20114
			}
20115
			case 'div': {
20116
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20117
				break;
20118
			}
20119
			case 'blockquote': {
20120
				if ($this->rtl) {
20121
					$this->rMargin -= $this->listindent;
20122
				} else {
20123
					$this->lMargin -= $this->listindent;
20124
				}
20125
				--$this->listindentlevel;
20126
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20127
				break;
20128
			}
20129
			case 'p': {
20130
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20131
				break;
20132
			}
20133
			case 'pre': {
20134
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20135
				$this->premode = false;
20136
				break;
20137
			}
20138
			case 'dl': {
20139
				--$this->listnum;
20140
				if ($this->listnum <= 0) {
20141
					$this->listnum = 0;
20142
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20143
				} else {
20144
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20145
				}
20146
				$this->resetLastH();
20147
				break;
20148
			}
20149
			case 'dt': {
20150
				$this->lispacer = '';
20151
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20152
				break;
20153
			}
20154
			case 'dd': {
20155
				$this->lispacer = '';
20156
				if ($this->rtl) {
20157
					$this->rMargin -= $this->listindent;
20158
				} else {
20159
					$this->lMargin -= $this->listindent;
20160
				}
20161
				--$this->listindentlevel;
20162
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20163
				break;
20164
			}
20165
			case 'ul':
20166
			case 'ol': {
20167
				--$this->listnum;
20168
				$this->lispacer = '';
20169
				if ($this->rtl) {
20170
					$this->rMargin -= $this->listindent;
20171
				} else {
20172
					$this->lMargin -= $this->listindent;
20173
				}
20174
				--$this->listindentlevel;
20175
				if ($this->listnum <= 0) {
20176
					$this->listnum = 0;
20177
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20178
				} else {
20179
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20180
				}
20181
				$this->resetLastH();
20182
				break;
20183
			}
20184
			case 'li': {
20185
				$this->lispacer = '';
20186
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20187
				break;
20188
			}
20189
			case 'h1':
20190
			case 'h2':
20191
			case 'h3':
20192
			case 'h4':
20193
			case 'h5':
20194
			case 'h6': {
20195
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20196
				break;
20197
			}
20198
			// Form fields (since 4.8.000 - 2009-09-07)
20199
			case 'form': {
20200
				$this->form_action = '';
20201
				$this->form_enctype = 'application/x-www-form-urlencoded';
20202
				break;
20203
			}
20204
			default : {
20205
				break;
20206
			}
20207
		}
20208
		// draw border and background (if any)
20209
		$this->drawHTMLTagBorder($parent, $xmax);
20210
		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20211
			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20212
			// check for pagebreak
20213
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20214
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20215
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20216
			}
20217
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20218
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20219
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20220
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20221
			}
20222
		}
20223
		$this->tmprtl = false;
20224
		return $dom;
20225
	}
20226
20227
	/**
20228
	 * Add vertical spaces if needed.
20229
	 * @param string $hbz Distance between current y and line bottom.
20230
	 * @param string $hb The height of the break.
20231
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20232
	 * @param boolean $firsttag set to true when the tag is the first.
20233
	 * @param boolean $lasttag set to true when the tag is the last.
20234
	 * @protected
20235
	 */
20236
	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20237
		if ($firsttag) {
20238
			$this->Ln(0, $cell);
20239
			$this->htmlvspace = 0;
20240
			return;
20241
		}
20242
		if ($lasttag) {
20243
			$this->Ln($hbz, $cell);
20244
			$this->htmlvspace = 0;
20245
			return;
20246
		}
20247
		if ($hb < $this->htmlvspace) {
20248
			$hd = 0;
20249
		} else {
20250
			$hd = $hb - $this->htmlvspace;
20251
			$this->htmlvspace = $hb;
20252
		}
20253
		$this->Ln(($hbz + $hd), $cell);
20254
	}
20255
20256
	/**
20257
	 * Return the starting coordinates to draw an html border
20258
	 * @return array containing top-left border coordinates
20259
	 * @protected
20260
	 * @since 5.7.000 (2010-08-03)
20261
	 */
20262
	protected function getBorderStartPosition() {
20263
		if ($this->rtl) {
20264
			$xmax = $this->lMargin;
20265
		} else {
20266
			$xmax = $this->w - $this->rMargin;
20267
		}
20268
		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20269
	}
20270
20271
	/**
20272
	 * Draw an HTML block border and fill
20273
	 * @param array $tag array of tag properties.
20274
	 * @param int $xmax end X coordinate for border.
20275
	 * @protected
20276
	 * @since 5.7.000 (2010-08-03)
20277
	 */
20278
	protected function drawHTMLTagBorder($tag, $xmax) {
20279
		if (!isset($tag['borderposition'])) {
20280
			// nothing to draw
20281
			return;
20282
		}
20283
		$prev_x = $this->x;
20284
		$prev_y = $this->y;
20285
		$prev_lasth = $this->lasth;
20286
		$border = 0;
20287
		$fill = false;
20288
		$this->lasth = 0;
20289
		if (isset($tag['border']) AND !empty($tag['border'])) {
20290
			// get border style
20291
			$border = $tag['border'];
20292
			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20293
				// border for table header
20294
				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20295
			}
20296
		}
20297
		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20298
			// get background color
20299
			$old_bgcolor = $this->bgcolor;
20300
			$this->setFillColorArray($tag['bgcolor']);
20301
			$fill = true;
20302
		}
20303
		if (!$border AND !$fill) {
20304
			// nothing to draw
20305
			return;
20306
		}
20307
		if (isset($tag['attribute']['cellspacing'])) {
20308
			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20309
			$cellspacing = array('H' => $clsp, 'V' => $clsp);
20310
		} elseif (isset($tag['border-spacing'])) {
20311
			$cellspacing = $tag['border-spacing'];
20312
		} else {
20313
			$cellspacing = array('H' => 0, 'V' => 0);
20314
		}
20315
		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20316
			// draw the border externally respect the sqare edge.
20317
			$border['mode'] = 'ext';
20318
		}
20319
		if ($this->rtl) {
20320
			if ($xmax >= $tag['borderposition']['x']) {
20321
				$xmax = $tag['borderposition']['xmax'];
20322
			}
20323
			$w = ($tag['borderposition']['x'] - $xmax);
20324
		} else {
20325
			if ($xmax <= $tag['borderposition']['x']) {
20326
				$xmax = $tag['borderposition']['xmax'];
20327
			}
20328
			$w = ($xmax - $tag['borderposition']['x']);
20329
		}
20330
		if ($w <= 0) {
20331
			return;
20332
		}
20333
		$w += $cellspacing['H'];
20334
		$startpage = $tag['borderposition']['page'];
20335
		$startcolumn = $tag['borderposition']['column'];
20336
		$x = $tag['borderposition']['x'];
20337
		$y = $tag['borderposition']['y'];
20338
		$endpage = $this->page;
20339
		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20340
		$currentY = $this->y;
20341
		$this->x = $x;
20342
		// get latest column
20343
		$endcolumn = $this->current_column;
20344
		if ($this->num_columns == 0) {
20345
			$this->num_columns = 1;
20346
		}
20347
		// get border modes
20348
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20349
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20350
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20351
		// temporary disable page regions
20352
		$temp_page_regions = $this->page_regions;
20353
		$this->page_regions = array();
20354
		// design borders around HTML cells.
20355
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20356
			$ccode = '';
20357
			$this->setPage($page);
20358
			if ($this->num_columns < 2) {
20359
				// single-column mode
20360
				$this->x = $x;
20361
				$this->y = $this->tMargin;
20362
			}
20363
			// account for margin changes
20364
			if ($page > $startpage) {
20365
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20366
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20367
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20368
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20369
				}
20370
			}
20371
			if ($startpage == $endpage) {
20372
				// single page
20373
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20374
					$this->selectColumn($column);
20375
					if ($startcolumn == $endcolumn) { // single column
20376
						$cborder = $border;
20377
						$h = ($currentY - $y) + $cellspacing['V'];
20378
						$this->y = $starty;
20379
					} elseif ($column == $startcolumn) { // first column
20380
						$cborder = $border_start;
20381
						$this->y = $starty;
20382
						$h = $this->h - $this->y - $this->bMargin;
20383
					} elseif ($column == $endcolumn) { // end column
20384
						$cborder = $border_end;
20385
						$h = $currentY - $this->y;
20386
					} else { // middle column
20387
						$cborder = $border_middle;
20388
						$h = $this->h - $this->y - $this->bMargin;
20389
					}
20390
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20391
				} // end for each column
20392
			} elseif ($page == $startpage) { // first page
20393
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20394
					$this->selectColumn($column);
20395
					if ($column == $startcolumn) { // first column
20396
						$cborder = $border_start;
20397
						$this->y = $starty;
20398
						$h = $this->h - $this->y - $this->bMargin;
20399
					} else { // middle column
20400
						$cborder = $border_middle;
20401
						$h = $this->h - $this->y - $this->bMargin;
20402
					}
20403
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20404
				} // end for each column
20405
			} elseif ($page == $endpage) { // last page
20406
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20407
					$this->selectColumn($column);
20408
					if ($column == $endcolumn) {
20409
						// end column
20410
						$cborder = $border_end;
20411
						$h = $currentY - $this->y;
20412
					} else {
20413
						// middle column
20414
						$cborder = $border_middle;
20415
						$h = $this->h - $this->y - $this->bMargin;
20416
					}
20417
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20418
				} // end for each column
20419
			} else { // middle page
20420
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20421
					$this->selectColumn($column);
20422
					$cborder = $border_middle;
20423
					$h = $this->h - $this->y - $this->bMargin;
20424
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20425
				} // end for each column
20426
			}
20427
			if ($cborder OR $fill) {
20428
				$offsetlen = strlen($ccode);
20429
				// draw border and fill
20430
				if ($this->inxobj) {
20431
					// we are inside an XObject template
20432
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20433
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20434
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20435
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20436
					} else {
20437
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20438
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20439
					}
20440
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20441
					$pstart = substr($pagebuff, 0, $pagemark);
20442
					$pend = substr($pagebuff, $pagemark);
20443
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20444
				} else {
20445
					if (end($this->transfmrk[$this->page]) !== false) {
20446
						$pagemarkkey = key($this->transfmrk[$this->page]);
20447
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20448
					} elseif ($this->InFooter) {
20449
						$pagemark = $this->footerpos[$this->page];
20450
					} else {
20451
						$pagemark = $this->intmrk[$this->page];
20452
					}
20453
					$pagebuff = $this->getPageBuffer($this->page);
20454
					$pstart = substr($pagebuff, 0, $pagemark);
20455
					$pend = substr($pagebuff, $pagemark);
20456
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20457
					$this->bordermrk[$this->page] += $offsetlen;
20458
					$this->cntmrk[$this->page] += $offsetlen;
20459
				}
20460
			}
20461
		} // end for each page
20462
		// restore page regions
20463
		$this->page_regions = $temp_page_regions;
20464
		if (isset($old_bgcolor)) {
20465
			// restore background color
20466
			$this->setFillColorArray($old_bgcolor);
20467
		}
20468
		// restore pointer position
20469
		$this->x = $prev_x;
20470
		$this->y = $prev_y;
20471
		$this->lasth = $prev_lasth;
20472
	}
20473
20474
	/**
20475
	 * Set the default bullet to be used as LI bullet symbol
20476
	 * @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')
20477
	 * @public
20478
	 * @since 4.0.028 (2008-09-26)
20479
	 */
20480
	public function setLIsymbol($symbol='!') {
20481
		// check for custom image symbol
20482
		if (substr($symbol, 0, 4) == 'img|') {
20483
			$this->lisymbol = $symbol;
20484
			return;
20485
		}
20486
		$symbol = strtolower($symbol);
20487
		$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');
20488
		if (in_array($symbol, $valid_symbols)) {
20489
			$this->lisymbol = $symbol;
20490
		} else {
20491
			$this->lisymbol = '';
20492
		}
20493
	}
20494
20495
	/**
20496
	 * Set the booklet mode for double-sided pages.
20497
	 * @param boolean $booklet true set the booklet mode on, false otherwise.
20498
	 * @param float $inner Inner page margin.
20499
	 * @param float $outer Outer page margin.
20500
	 * @public
20501
	 * @since 4.2.000 (2008-10-29)
20502
	 */
20503
	public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20504
		$this->booklet = $booklet;
20505
		if ($inner >= 0) {
20506
			$this->lMargin = $inner;
20507
		}
20508
		if ($outer >= 0) {
20509
			$this->rMargin = $outer;
20510
		}
20511
	}
20512
20513
	/**
20514
	 * Swap the left and right margins.
20515
	 * @param boolean $reverse if true swap left and right margins.
20516
	 * @protected
20517
	 * @since 4.2.000 (2008-10-29)
20518
	 */
20519
	protected function swapMargins($reverse=true) {
20520
		if ($reverse) {
20521
			// swap left and right margins
20522
			$mtemp = $this->original_lMargin;
20523
			$this->original_lMargin = $this->original_rMargin;
20524
			$this->original_rMargin = $mtemp;
20525
			$deltam = $this->original_lMargin - $this->original_rMargin;
20526
			$this->lMargin += $deltam;
20527
			$this->rMargin -= $deltam;
20528
		}
20529
	}
20530
20531
	/**
20532
	 * Set the vertical spaces for HTML tags.
20533
	 * The array must have the following structure (example):
20534
	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20535
	 * The first array level contains the tag names,
20536
	 * the second level contains 0 for opening tags or 1 for closing tags,
20537
	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20538
	 * If the h parameter is not specified, default values are used.
20539
	 * @param array $tagvs array of tags and relative vertical spaces.
20540
	 * @public
20541
	 * @since 4.2.001 (2008-10-30)
20542
	 */
20543
	public function setHtmlVSpace($tagvs) {
20544
		$this->tagvspaces = $tagvs;
20545
	}
20546
20547
	/**
20548
	 * Set custom width for list indentation.
20549
	 * @param float $width width of the indentation. Use negative value to disable it.
20550
	 * @public
20551
	 * @since 4.2.007 (2008-11-12)
20552
	 */
20553
	public function setListIndentWidth($width) {
20554
		return $this->customlistindent = floatval($width);
20555
	}
20556
20557
	/**
20558
	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20559
	 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20560
	 * @public
20561
	 * @since 4.2.010 (2008-11-14)
20562
	 */
20563
	public function setOpenCell($isopen) {
20564
		$this->opencell = $isopen;
20565
	}
20566
20567
	/**
20568
	 * Set the color and font style for HTML links.
20569
	 * @param array $color RGB array of colors
20570
	 * @param string $fontstyle additional font styles to add
20571
	 * @public
20572
	 * @since 4.4.003 (2008-12-09)
20573
	 */
20574
	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20575
		$this->htmlLinkColorArray = $color;
20576
		$this->htmlLinkFontStyle = $fontstyle;
20577
	}
20578
20579
	/**
20580
	 * Convert HTML string containing value and unit of measure to user's units or points.
20581
	 * @param string $htmlval String containing values and unit.
20582
	 * @param string $refsize Reference value in points.
20583
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20584
	 * @param boolean $points If true returns points, otherwise returns value in user's units.
20585
	 * @return float value in user's unit or point if $points=true
20586
	 * @public
20587
	 * @since 4.4.004 (2008-12-10)
20588
	 */
20589
	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20590
		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20591
		$retval = 0;
20592
		$value = 0;
20593
		$unit = 'px';
20594
		if ($points) {
20595
			$k = 1;
20596
		} else {
20597
			$k = $this->k;
20598
		}
20599
		if (in_array($defaultunit, $supportedunits)) {
20600
			$unit = $defaultunit;
20601
		}
20602
		if (is_numeric($htmlval)) {
20603
			$value = floatval($htmlval);
20604
		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20605
			$value = floatval($mnum[1]);
20606
			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20607
				if (in_array($munit[1], $supportedunits)) {
20608
					$unit = $munit[1];
20609
				}
20610
			}
20611
		}
20612
		switch ($unit) {
20613
			// percentage
20614
			case '%': {
20615
				$retval = (($value * $refsize) / 100);
20616
				break;
20617
			}
20618
			// relative-size
20619
			case 'em': {
20620
				$retval = ($value * $refsize);
20621
				break;
20622
			}
20623
			// height of lower case 'x' (about half the font-size)
20624
			case 'ex': {
20625
				$retval = ($value * ($refsize / 2));
20626
				break;
20627
			}
20628
			// absolute-size
20629
			case 'in': {
20630
				$retval = (($value * $this->dpi) / $k);
20631
				break;
20632
			}
20633
			// centimeters
20634
			case 'cm': {
20635
				$retval = (($value / 2.54 * $this->dpi) / $k);
20636
				break;
20637
			}
20638
			// millimeters
20639
			case 'mm': {
20640
				$retval = (($value / 25.4 * $this->dpi) / $k);
20641
				break;
20642
			}
20643
			// one pica is 12 points
20644
			case 'pc': {
20645
				$retval = (($value * 12) / $k);
20646
				break;
20647
			}
20648
			// points
20649
			case 'pt': {
20650
				$retval = ($value / $k);
20651
				break;
20652
			}
20653
			// pixels
20654
			case 'px': {
20655
				$retval = $this->pixelsToUnits($value);
20656
				if ($points) {
20657
					$retval *= $this->k;
20658
				}
20659
				break;
20660
			}
20661
		}
20662
		return $retval;
20663
	}
20664
20665
	/**
20666
	 * Output an HTML list bullet or ordered item symbol
20667
	 * @param int $listdepth list nesting level
20668
	 * @param string $listtype type of list
20669
	 * @param float $size current font size
20670
	 * @protected
20671
	 * @since 4.4.004 (2008-12-10)
20672
	 */
20673
	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20674
		if ($this->state != 2) {
20675
			return;
20676
		}
20677
		$size /= $this->k;
20678
		$fill = '';
20679
		$bgcolor = $this->bgcolor;
20680
		$color = $this->fgcolor;
20681
		$strokecolor = $this->strokecolor;
20682
		$width = 0;
20683
		$textitem = '';
20684
		$tmpx = $this->x;
20685
		$lspace = $this->GetStringWidth('  ');
20686
		if ($listtype == '^') {
20687
			// special symbol used for avoid justification of rect bullet
20688
			$this->lispacer = '';
20689
			return;
20690
		} elseif ($listtype == '!') {
20691
			// set default list type for unordered list
20692
			$deftypes = array('disc', 'circle', 'square');
20693
			$listtype = $deftypes[($listdepth - 1) % 3];
20694
		} elseif ($listtype == '#') {
20695
			// set default list type for ordered list
20696
			$listtype = 'decimal';
20697
		} elseif (substr($listtype, 0, 4) == 'img|') {
20698
			// custom image type ('img|type|width|height|image.ext')
20699
			$img = explode('|', $listtype);
20700
			$listtype = 'img';
20701
		}
20702
		switch ($listtype) {
20703
			// unordered types
20704
			case 'none': {
20705
				break;
20706
			}
20707
			case 'disc': {
20708
				$r = $size / 6;
20709
				$lspace += (2 * $r);
20710
				if ($this->rtl) {
20711
					$this->x += $lspace;
20712
				} else {
20713
					$this->x -= $lspace;
20714
				}
20715
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20716
				break;
20717
			}
20718
			case 'circle': {
20719
				$r = $size / 6;
20720
				$lspace += (2 * $r);
20721
				if ($this->rtl) {
20722
					$this->x += $lspace;
20723
				} else {
20724
					$this->x -= $lspace;
20725
				}
20726
				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20727
				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20728
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20729
				$this->_out($prev_line_style); // restore line settings
20730
				break;
20731
			}
20732
			case 'square': {
20733
				$l = $size / 3;
20734
				$lspace += $l;
20735
				if ($this->rtl) {;
20736
					$this->x += $lspace;
20737
				} else {
20738
					$this->x -= $lspace;
20739
				}
20740
				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20741
				break;
20742
			}
20743
			case 'img': {
20744
				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20745
				$lspace += $img[2];
20746
				if ($this->rtl) {;
20747
					$this->x += $lspace;
20748
				} else {
20749
					$this->x -= $lspace;
20750
				}
20751
				$imgtype = strtolower($img[1]);
20752
				$prev_y = $this->y;
20753
				switch ($imgtype) {
20754
					case 'svg': {
20755
						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20756
						break;
20757
					}
20758
					case 'ai':
20759
					case 'eps': {
20760
						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20761
						break;
20762
					}
20763
					default: {
20764
						$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);
20765
						break;
20766
					}
20767
				}
20768
				$this->y = $prev_y;
20769
				break;
20770
			}
20771
			// ordered types
20772
			// $this->listcount[$this->listnum];
20773
			// $textitem
20774
			case '1':
20775
			case 'decimal': {
20776
				$textitem = $this->listcount[$this->listnum];
20777
				break;
20778
			}
20779
			case 'decimal-leading-zero': {
20780
				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20781
				break;
20782
			}
20783
			case 'i':
20784
			case 'lower-roman': {
20785
				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20786
				break;
20787
			}
20788
			case 'I':
20789
			case 'upper-roman': {
20790
				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20791
				break;
20792
			}
20793
			case 'a':
20794
			case 'lower-alpha':
20795
			case 'lower-latin': {
20796
				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20797
				break;
20798
			}
20799
			case 'A':
20800
			case 'upper-alpha':
20801
			case 'upper-latin': {
20802
				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20803
				break;
20804
			}
20805
			case 'lower-greek': {
20806
				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20807
				break;
20808
			}
20809
			/*
20810
			// Types to be implemented (special handling)
20811
			case 'hebrew': {
20812
				break;
20813
			}
20814
			case 'armenian': {
20815
				break;
20816
			}
20817
			case 'georgian': {
20818
				break;
20819
			}
20820
			case 'cjk-ideographic': {
20821
				break;
20822
			}
20823
			case 'hiragana': {
20824
				break;
20825
			}
20826
			case 'katakana': {
20827
				break;
20828
			}
20829
			case 'hiragana-iroha': {
20830
				break;
20831
			}
20832
			case 'katakana-iroha': {
20833
				break;
20834
			}
20835
			*/
20836
			default: {
20837
				$textitem = $this->listcount[$this->listnum];
20838
			}
20839
		}
20840
		if (!TCPDF_STATIC::empty_string($textitem)) {
20841
			// Check whether we need a new page or new column
20842
			$prev_y = $this->y;
20843
			$h = $this->getCellHeight($this->FontSize);
20844
			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20845
				$tmpx = $this->x;
20846
			}
20847
			// print ordered item
20848
			if ($this->rtl) {
20849
				$textitem = '.'.$textitem;
20850
			} else {
20851
				$textitem = $textitem.'.';
20852
			}
20853
			$lspace += $this->GetStringWidth($textitem);
20854
			if ($this->rtl) {
20855
				$this->x += $lspace;
20856
			} else {
20857
				$this->x -= $lspace;
20858
			}
20859
			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20860
		}
20861
		$this->x = $tmpx;
20862
		$this->lispacer = '^';
20863
		// restore colors
20864
		$this->setFillColorArray($bgcolor);
20865
		$this->setDrawColorArray($strokecolor);
20866
		$this->settextColorArray($color);
20867
	}
20868
20869
	/**
20870
	 * Returns current graphic variables as array.
20871
	 * @return array of graphic variables
20872
	 * @protected
20873
	 * @since 4.2.010 (2008-11-14)
20874
	 */
20875
	protected function getGraphicVars() {
20876
		$grapvars = array(
20877
			'FontFamily' => $this->FontFamily,
20878
			'FontStyle' => $this->FontStyle,
20879
			'FontSizePt' => $this->FontSizePt,
20880
			'rMargin' => $this->rMargin,
20881
			'lMargin' => $this->lMargin,
20882
			'cell_padding' => $this->cell_padding,
20883
			'cell_margin' => $this->cell_margin,
20884
			'LineWidth' => $this->LineWidth,
20885
			'linestyleWidth' => $this->linestyleWidth,
20886
			'linestyleCap' => $this->linestyleCap,
20887
			'linestyleJoin' => $this->linestyleJoin,
20888
			'linestyleDash' => $this->linestyleDash,
20889
			'textrendermode' => $this->textrendermode,
20890
			'textstrokewidth' => $this->textstrokewidth,
20891
			'DrawColor' => $this->DrawColor,
20892
			'FillColor' => $this->FillColor,
20893
			'TextColor' => $this->TextColor,
20894
			'ColorFlag' => $this->ColorFlag,
20895
			'bgcolor' => $this->bgcolor,
20896
			'fgcolor' => $this->fgcolor,
20897
			'htmlvspace' => $this->htmlvspace,
20898
			'listindent' => $this->listindent,
20899
			'listindentlevel' => $this->listindentlevel,
20900
			'listnum' => $this->listnum,
20901
			'listordered' => $this->listordered,
20902
			'listcount' => $this->listcount,
20903
			'lispacer' => $this->lispacer,
20904
			'cell_height_ratio' => $this->cell_height_ratio,
20905
			'font_stretching' => $this->font_stretching,
20906
			'font_spacing' => $this->font_spacing,
20907
			'alpha' => $this->alpha,
20908
			// extended
20909
			'lasth' => $this->lasth,
20910
			'tMargin' => $this->tMargin,
20911
			'bMargin' => $this->bMargin,
20912
			'AutoPageBreak' => $this->AutoPageBreak,
20913
			'PageBreakTrigger' => $this->PageBreakTrigger,
20914
			'x' => $this->x,
20915
			'y' => $this->y,
20916
			'w' => $this->w,
20917
			'h' => $this->h,
20918
			'wPt' => $this->wPt,
20919
			'hPt' => $this->hPt,
20920
			'fwPt' => $this->fwPt,
20921
			'fhPt' => $this->fhPt,
20922
			'page' => $this->page,
20923
			'current_column' => $this->current_column,
20924
			'num_columns' => $this->num_columns
20925
			);
20926
		return $grapvars;
20927
	}
20928
20929
	/**
20930
	 * Set graphic variables.
20931
	 * @param array $gvars array of graphic variablesto restore
20932
	 * @param boolean $extended if true restore extended graphic variables
20933
	 * @protected
20934
	 * @since 4.2.010 (2008-11-14)
20935
	 */
20936
	protected function setGraphicVars($gvars, $extended=false) {
20937
		if ($this->state != 2) {
20938
			 return;
20939
		}
20940
		$this->FontFamily = $gvars['FontFamily'];
20941
		$this->FontStyle = $gvars['FontStyle'];
20942
		$this->FontSizePt = $gvars['FontSizePt'];
20943
		$this->rMargin = $gvars['rMargin'];
20944
		$this->lMargin = $gvars['lMargin'];
20945
		$this->cell_padding = $gvars['cell_padding'];
20946
		$this->cell_margin = $gvars['cell_margin'];
20947
		$this->LineWidth = $gvars['LineWidth'];
20948
		$this->linestyleWidth = $gvars['linestyleWidth'];
20949
		$this->linestyleCap = $gvars['linestyleCap'];
20950
		$this->linestyleJoin = $gvars['linestyleJoin'];
20951
		$this->linestyleDash = $gvars['linestyleDash'];
20952
		$this->textrendermode = $gvars['textrendermode'];
20953
		$this->textstrokewidth = $gvars['textstrokewidth'];
20954
		$this->DrawColor = $gvars['DrawColor'];
20955
		$this->FillColor = $gvars['FillColor'];
20956
		$this->TextColor = $gvars['TextColor'];
20957
		$this->ColorFlag = $gvars['ColorFlag'];
20958
		$this->bgcolor = $gvars['bgcolor'];
20959
		$this->fgcolor = $gvars['fgcolor'];
20960
		$this->htmlvspace = $gvars['htmlvspace'];
20961
		$this->listindent = $gvars['listindent'];
20962
		$this->listindentlevel = $gvars['listindentlevel'];
20963
		$this->listnum = $gvars['listnum'];
20964
		$this->listordered = $gvars['listordered'];
20965
		$this->listcount = $gvars['listcount'];
20966
		$this->lispacer = $gvars['lispacer'];
20967
		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20968
		$this->font_stretching = $gvars['font_stretching'];
20969
		$this->font_spacing = $gvars['font_spacing'];
20970
		$this->alpha = $gvars['alpha'];
20971
		if ($extended) {
20972
			// restore extended values
20973
			$this->lasth = $gvars['lasth'];
20974
			$this->tMargin = $gvars['tMargin'];
20975
			$this->bMargin = $gvars['bMargin'];
20976
			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20977
			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20978
			$this->x = $gvars['x'];
20979
			$this->y = $gvars['y'];
20980
			$this->w = $gvars['w'];
20981
			$this->h = $gvars['h'];
20982
			$this->wPt = $gvars['wPt'];
20983
			$this->hPt = $gvars['hPt'];
20984
			$this->fwPt = $gvars['fwPt'];
20985
			$this->fhPt = $gvars['fhPt'];
20986
			$this->page = $gvars['page'];
20987
			$this->current_column = $gvars['current_column'];
20988
			$this->num_columns = $gvars['num_columns'];
20989
		}
20990
		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20991
		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20992
			$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20993
		}
20994
	}
20995
20996
	/**
20997
	 * Outputs the "save graphics state" operator 'q'
20998
	 * @protected
20999
	 */
21000
	protected function _outSaveGraphicsState() {
21001
		$this->_out('q');
21002
	}
21003
21004
	/**
21005
	 * Outputs the "restore graphics state" operator 'Q'
21006
	 * @protected
21007
	 */
21008
	protected function _outRestoreGraphicsState() {
21009
		$this->_out('Q');
21010
	}
21011
21012
	/**
21013
	 * Set buffer content (always append data).
21014
	 * @param string $data data
21015
	 * @protected
21016
	 * @since 4.5.000 (2009-01-02)
21017
	 */
21018
	protected function setBuffer($data) {
21019
		$this->bufferlen += strlen($data);
21020
		$this->buffer .= $data;
21021
	}
21022
21023
	/**
21024
	 * Replace the buffer content
21025
	 * @param string $data data
21026
	 * @protected
21027
	 * @since 5.5.000 (2010-06-22)
21028
	 */
21029
	protected function replaceBuffer($data) {
21030
		$this->bufferlen = strlen($data);
21031
		$this->buffer = $data;
21032
	}
21033
21034
	/**
21035
	 * Get buffer content.
21036
	 * @return string buffer content
21037
	 * @protected
21038
	 * @since 4.5.000 (2009-01-02)
21039
	 */
21040
	protected function getBuffer() {
21041
		return $this->buffer;
21042
	}
21043
21044
	/**
21045
	 * Set page buffer content.
21046
	 * @param int $page page number
21047
	 * @param string $data page data
21048
	 * @param boolean $append if true append data, false replace.
21049
	 * @protected
21050
	 * @since 4.5.000 (2008-12-31)
21051
	 */
21052
	protected function setPageBuffer($page, $data, $append=false) {
21053
		if ($append) {
21054
			$this->pages[$page] .= $data;
21055
		} else {
21056
			$this->pages[$page] = $data;
21057
		}
21058
		if ($append AND isset($this->pagelen[$page])) {
21059
			$this->pagelen[$page] += strlen($data);
21060
		} else {
21061
			$this->pagelen[$page] = strlen($data);
21062
		}
21063
	}
21064
21065
	/**
21066
	 * Get page buffer content.
21067
	 * @param int $page page number
21068
	 * @return string page buffer content or false in case of error
21069
	 * @protected
21070
	 * @since 4.5.000 (2008-12-31)
21071
	 */
21072
	protected function getPageBuffer($page) {
21073
		if (isset($this->pages[$page])) {
21074
			return $this->pages[$page];
21075
		}
21076
		return false;
21077
	}
21078
21079
	/**
21080
	 * Set image buffer content.
21081
	 * @param string $image image key
21082
	 * @param array $data image data
21083
	 * @return int image index number
21084
	 * @protected
21085
	 * @since 4.5.000 (2008-12-31)
21086
	 */
21087
	protected function setImageBuffer($image, $data) {
21088
		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
21089
			$this->imagekeys[$this->numimages] = $image;
21090
			$data['i'] = $this->numimages;
21091
			++$this->numimages;
21092
		}
21093
		$this->images[$image] = $data;
21094
		return $data['i'];
21095
	}
21096
21097
	/**
21098
	 * Set image buffer content for a specified sub-key.
21099
	 * @param string $image image key
21100
	 * @param string $key image sub-key
21101
	 * @param array $data image data
21102
	 * @protected
21103
	 * @since 4.5.000 (2008-12-31)
21104
	 */
21105
	protected function setImageSubBuffer($image, $key, $data) {
21106
		if (!isset($this->images[$image])) {
21107
			$this->setImageBuffer($image, array());
21108
		}
21109
		$this->images[$image][$key] = $data;
21110
	}
21111
21112
	/**
21113
	 * Get image buffer content.
21114
	 * @param string $image image key
21115
	 * @return string|false image buffer content or false in case of error
21116
	 * @protected
21117
	 * @since 4.5.000 (2008-12-31)
21118
	 */
21119
	protected function getImageBuffer($image) {
21120
		if (isset($this->images[$image])) {
21121
			return $this->images[$image];
21122
		}
21123
		return false;
21124
	}
21125
21126
	/**
21127
	 * Set font buffer content.
21128
	 * @param string $font font key
21129
	 * @param array $data font data
21130
	 * @protected
21131
	 * @since 4.5.000 (2009-01-02)
21132
	 */
21133
	protected function setFontBuffer($font, $data) {
21134
		$this->fonts[$font] = $data;
21135
		if (!in_array($font, $this->fontkeys)) {
21136
			$this->fontkeys[] = $font;
21137
			// store object ID for current font
21138
			++$this->n;
21139
			$this->font_obj_ids[$font] = $this->n;
21140
			$this->setFontSubBuffer($font, 'n', $this->n);
21141
		}
21142
	}
21143
21144
	/**
21145
	 * Set font buffer content.
21146
	 * @param string $font font key
21147
	 * @param string $key font sub-key
21148
	 * @param mixed $data font data
21149
	 * @protected
21150
	 * @since 4.5.000 (2009-01-02)
21151
	 */
21152
	protected function setFontSubBuffer($font, $key, $data) {
21153
		if (!isset($this->fonts[$font])) {
21154
			$this->setFontBuffer($font, array());
21155
		}
21156
		$this->fonts[$font][$key] = $data;
21157
	}
21158
21159
	/**
21160
	 * Get font buffer content.
21161
	 * @param string $font font key
21162
	 * @return string|false font buffer content or false in case of error
21163
	 * @protected
21164
	 * @since 4.5.000 (2009-01-02)
21165
	 */
21166
	protected function getFontBuffer($font) {
21167
		if (isset($this->fonts[$font])) {
21168
			return $this->fonts[$font];
21169
		}
21170
		return false;
21171
	}
21172
21173
	/**
21174
	 * Move a page to a previous position.
21175
	 * @param int $frompage number of the source page
21176
	 * @param int $topage number of the destination page (must be less than $frompage)
21177
	 * @return bool true in case of success, false in case of error.
21178
	 * @public
21179
	 * @since 4.5.000 (2009-01-02)
21180
	 */
21181
	public function movePage($frompage, $topage) {
21182
		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21183
			return false;
21184
		}
21185
		if ($frompage == $this->page) {
21186
			// close the page before moving it
21187
			$this->endPage();
21188
		}
21189
		// move all page-related states
21190
		$tmppage = $this->getPageBuffer($frompage);
21191
		$tmppagedim = $this->pagedim[$frompage];
21192
		$tmppagelen = $this->pagelen[$frompage];
21193
		$tmpintmrk = $this->intmrk[$frompage];
21194
		$tmpbordermrk = $this->bordermrk[$frompage];
21195
		$tmpcntmrk = $this->cntmrk[$frompage];
21196
		$tmppageobjects = $this->pageobjects[$frompage];
21197
		if (isset($this->footerpos[$frompage])) {
21198
			$tmpfooterpos = $this->footerpos[$frompage];
21199
		}
21200
		if (isset($this->footerlen[$frompage])) {
21201
			$tmpfooterlen = $this->footerlen[$frompage];
21202
		}
21203
		if (isset($this->transfmrk[$frompage])) {
21204
			$tmptransfmrk = $this->transfmrk[$frompage];
21205
		}
21206
		if (isset($this->PageAnnots[$frompage])) {
21207
			$tmpannots = $this->PageAnnots[$frompage];
21208
		}
21209
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21210
			for ($i = $frompage; $i > $topage; --$i) {
21211
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21212
					--$this->pagegroups[$this->newpagegroup[$i]];
21213
					break;
21214
				}
21215
			}
21216
			for ($i = $topage; $i > 0; --$i) {
21217
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21218
					++$this->pagegroups[$this->newpagegroup[$i]];
21219
					break;
21220
				}
21221
			}
21222
		}
21223
		for ($i = $frompage; $i > $topage; --$i) {
21224
			$j = $i - 1;
21225
			// shift pages down
21226
			$this->setPageBuffer($i, $this->getPageBuffer($j));
21227
			$this->pagedim[$i] = $this->pagedim[$j];
21228
			$this->pagelen[$i] = $this->pagelen[$j];
21229
			$this->intmrk[$i] = $this->intmrk[$j];
21230
			$this->bordermrk[$i] = $this->bordermrk[$j];
21231
			$this->cntmrk[$i] = $this->cntmrk[$j];
21232
			$this->pageobjects[$i] = $this->pageobjects[$j];
21233
			if (isset($this->footerpos[$j])) {
21234
				$this->footerpos[$i] = $this->footerpos[$j];
21235
			} elseif (isset($this->footerpos[$i])) {
21236
				unset($this->footerpos[$i]);
21237
			}
21238
			if (isset($this->footerlen[$j])) {
21239
				$this->footerlen[$i] = $this->footerlen[$j];
21240
			} elseif (isset($this->footerlen[$i])) {
21241
				unset($this->footerlen[$i]);
21242
			}
21243
			if (isset($this->transfmrk[$j])) {
21244
				$this->transfmrk[$i] = $this->transfmrk[$j];
21245
			} elseif (isset($this->transfmrk[$i])) {
21246
				unset($this->transfmrk[$i]);
21247
			}
21248
			if (isset($this->PageAnnots[$j])) {
21249
				$this->PageAnnots[$i] = $this->PageAnnots[$j];
21250
			} elseif (isset($this->PageAnnots[$i])) {
21251
				unset($this->PageAnnots[$i]);
21252
			}
21253
			if (isset($this->newpagegroup[$j])) {
21254
				$this->newpagegroup[$i] = $this->newpagegroup[$j];
21255
				unset($this->newpagegroup[$j]);
21256
			}
21257
			if ($this->currpagegroup == $j) {
21258
				$this->currpagegroup = $i;
21259
			}
21260
		}
21261
		$this->setPageBuffer($topage, $tmppage);
21262
		$this->pagedim[$topage] = $tmppagedim;
21263
		$this->pagelen[$topage] = $tmppagelen;
21264
		$this->intmrk[$topage] = $tmpintmrk;
21265
		$this->bordermrk[$topage] = $tmpbordermrk;
21266
		$this->cntmrk[$topage] = $tmpcntmrk;
21267
		$this->pageobjects[$topage] = $tmppageobjects;
21268
		if (isset($tmpfooterpos)) {
21269
			$this->footerpos[$topage] = $tmpfooterpos;
21270
		} elseif (isset($this->footerpos[$topage])) {
21271
			unset($this->footerpos[$topage]);
21272
		}
21273
		if (isset($tmpfooterlen)) {
21274
			$this->footerlen[$topage] = $tmpfooterlen;
21275
		} elseif (isset($this->footerlen[$topage])) {
21276
			unset($this->footerlen[$topage]);
21277
		}
21278
		if (isset($tmptransfmrk)) {
21279
			$this->transfmrk[$topage] = $tmptransfmrk;
21280
		} elseif (isset($this->transfmrk[$topage])) {
21281
			unset($this->transfmrk[$topage]);
21282
		}
21283
		if (isset($tmpannots)) {
21284
			$this->PageAnnots[$topage] = $tmpannots;
21285
		} elseif (isset($this->PageAnnots[$topage])) {
21286
			unset($this->PageAnnots[$topage]);
21287
		}
21288
		// adjust outlines
21289
		$tmpoutlines = $this->outlines;
21290
		foreach ($tmpoutlines as $key => $outline) {
21291
			if (!$outline['f']) {
21292
				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21293
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
21294
				} elseif ($outline['p'] == $frompage) {
21295
					$this->outlines[$key]['p'] = $topage;
21296
				}
21297
			}
21298
		}
21299
		// adjust dests
21300
		$tmpdests = $this->dests;
21301
		foreach ($tmpdests as $key => $dest) {
21302
			if (!$dest['f']) {
21303
				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21304
					$this->dests[$key]['p'] = ($dest['p'] + 1);
21305
				} elseif ($dest['p'] == $frompage) {
21306
					$this->dests[$key]['p'] = $topage;
21307
				}
21308
			}
21309
		}
21310
		// adjust links
21311
		$tmplinks = $this->links;
21312
		foreach ($tmplinks as $key => $link) {
21313
			if (!$link['f']) {
21314
				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21315
					$this->links[$key]['p'] = ($link['p'] + 1);
21316
				} elseif ($link['p'] == $frompage) {
21317
					$this->links[$key]['p'] = $topage;
21318
				}
21319
			}
21320
		}
21321
		// adjust javascript
21322
		$jfrompage = $frompage;
21323
		$jtopage = $topage;
21324
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21325
			foreach($pamatch[0] as $pk => $pmatch) {
21326
				$pagenum = intval($pamatch[3][$pk]) + 1;
21327
				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21328
					$newpage = ($pagenum + 1);
21329
				} elseif ($pagenum == $jfrompage) {
21330
					$newpage = $jtopage;
21331
				} else {
21332
					$newpage = $pagenum;
21333
				}
21334
				--$newpage;
21335
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21336
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21337
			}
21338
			unset($pamatch);
21339
		}
21340
		// return to last page
21341
		$this->lastPage(true);
21342
		return true;
21343
	}
21344
21345
	/**
21346
	 * Remove the specified page.
21347
	 * @param int $page page to remove
21348
	 * @return bool true in case of success, false in case of error.
21349
	 * @public
21350
	 * @since 4.6.004 (2009-04-23)
21351
	 */
21352
	public function deletePage($page) {
21353
		if (($page < 1) OR ($page > $this->numpages)) {
21354
			return false;
21355
		}
21356
		// delete current page
21357
		unset($this->pages[$page]);
21358
		unset($this->pagedim[$page]);
21359
		unset($this->pagelen[$page]);
21360
		unset($this->intmrk[$page]);
21361
		unset($this->bordermrk[$page]);
21362
		unset($this->cntmrk[$page]);
21363
		foreach ($this->pageobjects[$page] as $oid) {
21364
			if (isset($this->offsets[$oid])){
21365
				unset($this->offsets[$oid]);
21366
			}
21367
		}
21368
		unset($this->pageobjects[$page]);
21369
		if (isset($this->footerpos[$page])) {
21370
			unset($this->footerpos[$page]);
21371
		}
21372
		if (isset($this->footerlen[$page])) {
21373
			unset($this->footerlen[$page]);
21374
		}
21375
		if (isset($this->transfmrk[$page])) {
21376
			unset($this->transfmrk[$page]);
21377
		}
21378
		if (isset($this->PageAnnots[$page])) {
21379
			unset($this->PageAnnots[$page]);
21380
		}
21381
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21382
			for ($i = $page; $i > 0; --$i) {
21383
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21384
					--$this->pagegroups[$this->newpagegroup[$i]];
21385
					break;
21386
				}
21387
			}
21388
		}
21389
		if (isset($this->pageopen[$page])) {
21390
			unset($this->pageopen[$page]);
21391
		}
21392
		if ($page < $this->numpages) {
21393
			// update remaining pages
21394
			for ($i = $page; $i < $this->numpages; ++$i) {
21395
				$j = $i + 1;
21396
				// shift pages
21397
				$this->setPageBuffer($i, $this->getPageBuffer($j));
21398
				$this->pagedim[$i] = $this->pagedim[$j];
21399
				$this->pagelen[$i] = $this->pagelen[$j];
21400
				$this->intmrk[$i] = $this->intmrk[$j];
21401
				$this->bordermrk[$i] = $this->bordermrk[$j];
21402
				$this->cntmrk[$i] = $this->cntmrk[$j];
21403
				$this->pageobjects[$i] = $this->pageobjects[$j];
21404
				if (isset($this->footerpos[$j])) {
21405
					$this->footerpos[$i] = $this->footerpos[$j];
21406
				} elseif (isset($this->footerpos[$i])) {
21407
					unset($this->footerpos[$i]);
21408
				}
21409
				if (isset($this->footerlen[$j])) {
21410
					$this->footerlen[$i] = $this->footerlen[$j];
21411
				} elseif (isset($this->footerlen[$i])) {
21412
					unset($this->footerlen[$i]);
21413
				}
21414
				if (isset($this->transfmrk[$j])) {
21415
					$this->transfmrk[$i] = $this->transfmrk[$j];
21416
				} elseif (isset($this->transfmrk[$i])) {
21417
					unset($this->transfmrk[$i]);
21418
				}
21419
				if (isset($this->PageAnnots[$j])) {
21420
					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21421
				} elseif (isset($this->PageAnnots[$i])) {
21422
					unset($this->PageAnnots[$i]);
21423
				}
21424
				if (isset($this->newpagegroup[$j])) {
21425
					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21426
					unset($this->newpagegroup[$j]);
21427
				}
21428
				if ($this->currpagegroup == $j) {
21429
					$this->currpagegroup = $i;
21430
				}
21431
				if (isset($this->pageopen[$j])) {
21432
					$this->pageopen[$i] = $this->pageopen[$j];
21433
				} elseif (isset($this->pageopen[$i])) {
21434
					unset($this->pageopen[$i]);
21435
				}
21436
			}
21437
			// remove last page
21438
			unset($this->pages[$this->numpages]);
21439
			unset($this->pagedim[$this->numpages]);
21440
			unset($this->pagelen[$this->numpages]);
21441
			unset($this->intmrk[$this->numpages]);
21442
			unset($this->bordermrk[$this->numpages]);
21443
			unset($this->cntmrk[$this->numpages]);
21444
			foreach ($this->pageobjects[$this->numpages] as $oid) {
21445
				if (isset($this->offsets[$oid])){
21446
					unset($this->offsets[$oid]);
21447
				}
21448
			}
21449
			unset($this->pageobjects[$this->numpages]);
21450
			if (isset($this->footerpos[$this->numpages])) {
21451
				unset($this->footerpos[$this->numpages]);
21452
			}
21453
			if (isset($this->footerlen[$this->numpages])) {
21454
				unset($this->footerlen[$this->numpages]);
21455
			}
21456
			if (isset($this->transfmrk[$this->numpages])) {
21457
				unset($this->transfmrk[$this->numpages]);
21458
			}
21459
			if (isset($this->PageAnnots[$this->numpages])) {
21460
				unset($this->PageAnnots[$this->numpages]);
21461
			}
21462
			if (isset($this->newpagegroup[$this->numpages])) {
21463
				unset($this->newpagegroup[$this->numpages]);
21464
			}
21465
			if ($this->currpagegroup == $this->numpages) {
21466
				$this->currpagegroup = ($this->numpages - 1);
21467
			}
21468
			if (isset($this->pagegroups[$this->numpages])) {
21469
				unset($this->pagegroups[$this->numpages]);
21470
			}
21471
			if (isset($this->pageopen[$this->numpages])) {
21472
				unset($this->pageopen[$this->numpages]);
21473
			}
21474
		}
21475
		--$this->numpages;
21476
		$this->page = $this->numpages;
21477
		// adjust outlines
21478
		$tmpoutlines = $this->outlines;
21479
		foreach ($tmpoutlines as $key => $outline) {
21480
			if (!$outline['f']) {
21481
				if ($outline['p'] > $page) {
21482
					$this->outlines[$key]['p'] = $outline['p'] - 1;
21483
				} elseif ($outline['p'] == $page) {
21484
					unset($this->outlines[$key]);
21485
				}
21486
			}
21487
		}
21488
		// adjust dests
21489
		$tmpdests = $this->dests;
21490
		foreach ($tmpdests as $key => $dest) {
21491
			if (!$dest['f']) {
21492
				if ($dest['p'] > $page) {
21493
					$this->dests[$key]['p'] = $dest['p'] - 1;
21494
				} elseif ($dest['p'] == $page) {
21495
					unset($this->dests[$key]);
21496
				}
21497
			}
21498
		}
21499
		// adjust links
21500
		$tmplinks = $this->links;
21501
		foreach ($tmplinks as $key => $link) {
21502
			if (!$link['f']) {
21503
				if ($link['p'] > $page) {
21504
					$this->links[$key]['p'] = $link['p'] - 1;
21505
				} elseif ($link['p'] == $page) {
21506
					unset($this->links[$key]);
21507
				}
21508
			}
21509
		}
21510
		// adjust javascript
21511
		$jpage = $page;
21512
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21513
			foreach($pamatch[0] as $pk => $pmatch) {
21514
				$pagenum = intval($pamatch[3][$pk]) + 1;
21515
				if ($pagenum >= $jpage) {
21516
					$newpage = ($pagenum - 1);
21517
				} elseif ($pagenum == $jpage) {
21518
					$newpage = 1;
21519
				} else {
21520
					$newpage = $pagenum;
21521
				}
21522
				--$newpage;
21523
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21524
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21525
			}
21526
			unset($pamatch);
21527
		}
21528
		// return to last page
21529
		if ($this->numpages > 0) {
21530
			$this->lastPage(true);
21531
		}
21532
		return true;
21533
	}
21534
21535
	/**
21536
	 * Clone the specified page to a new page.
21537
	 * @param int $page number of page to copy (0 = current page)
21538
	 * @return bool true in case of success, false in case of error.
21539
	 * @public
21540
	 * @since 4.9.015 (2010-04-20)
21541
	 */
21542
	public function copyPage($page=0) {
21543
		if ($page == 0) {
21544
			// default value
21545
			$page = $this->page;
21546
		}
21547
		if (($page < 1) OR ($page > $this->numpages)) {
21548
			return false;
21549
		}
21550
		// close the last page
21551
		$this->endPage();
21552
		// copy all page-related states
21553
		++$this->numpages;
21554
		$this->page = $this->numpages;
21555
		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21556
		$this->pagedim[$this->page] = $this->pagedim[$page];
21557
		$this->pagelen[$this->page] = $this->pagelen[$page];
21558
		$this->intmrk[$this->page] = $this->intmrk[$page];
21559
		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21560
		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21561
		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21562
		$this->pageopen[$this->page] = false;
21563
		if (isset($this->footerpos[$page])) {
21564
			$this->footerpos[$this->page] = $this->footerpos[$page];
21565
		}
21566
		if (isset($this->footerlen[$page])) {
21567
			$this->footerlen[$this->page] = $this->footerlen[$page];
21568
		}
21569
		if (isset($this->transfmrk[$page])) {
21570
			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21571
		}
21572
		if (isset($this->PageAnnots[$page])) {
21573
			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21574
		}
21575
		if (isset($this->newpagegroup[$page])) {
21576
			// start a new group
21577
			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21578
			$this->currpagegroup = $this->newpagegroup[$this->page];
21579
			$this->pagegroups[$this->currpagegroup] = 1;
21580
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21581
			++$this->pagegroups[$this->currpagegroup];
21582
		}
21583
		// copy outlines
21584
		$tmpoutlines = $this->outlines;
21585
		foreach ($tmpoutlines as $key => $outline) {
21586
			if ($outline['p'] == $page) {
21587
				$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']);
21588
			}
21589
		}
21590
		// copy links
21591
		$tmplinks = $this->links;
21592
		foreach ($tmplinks as $key => $link) {
21593
			if ($link['p'] == $page) {
21594
				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21595
			}
21596
		}
21597
		// return to last page
21598
		$this->lastPage(true);
21599
		return true;
21600
	}
21601
21602
	/**
21603
	 * Output a Table of Content Index (TOC).
21604
	 * This method must be called after all Bookmarks were set.
21605
	 * Before calling this method you have to open the page using the addTOCPage() method.
21606
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21607
	 * You can override this method to achieve different styles.
21608
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21609
	 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21610
	 * @param string $filler string used to fill the space between text and page number.
21611
	 * @param string $toc_name name to use for TOC bookmark.
21612
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21613
	 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21614
	 * @public
21615
	 * @author Nicola Asuni
21616
	 * @since 4.5.000 (2009-01-02)
21617
	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21618
	 */
21619
	public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21620
		$fontsize = $this->FontSizePt;
21621
		$fontfamily = $this->FontFamily;
21622
		$fontstyle = $this->FontStyle;
21623
		$w = $this->w - $this->lMargin - $this->rMargin;
21624
		$spacer = $this->GetStringWidth(chr(32)) * 4;
21625
		$lmargin = $this->lMargin;
21626
		$rmargin = $this->rMargin;
21627
		$x_start = $this->GetX();
21628
		$page_first = $this->page;
21629
		$current_page = $this->page;
21630
		$page_fill_start = false;
21631
		$page_fill_end = false;
21632
		$current_column = $this->current_column;
21633
		if (TCPDF_STATIC::empty_string($numbersfont)) {
21634
			$numbersfont = $this->default_monospaced_font;
21635
		}
21636
		if (TCPDF_STATIC::empty_string($filler)) {
21637
			$filler = ' ';
21638
		}
21639
		if (TCPDF_STATIC::empty_string($page)) {
21640
			$gap = ' ';
21641
		} else {
21642
			$gap = '';
21643
			if ($page < 1) {
21644
				$page = 1;
21645
			}
21646
		}
21647
		$this->setFont($numbersfont, $fontstyle, $fontsize);
21648
		$numwidth = $this->GetStringWidth('00000');
21649
		$maxpage = 0; //used for pages on attached documents
21650
		foreach ($this->outlines as $key => $outline) {
21651
			// check for extra pages (used for attachments)
21652
			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21653
				$outline['p'] += ($this->page - $page_first);
21654
			}
21655
			if ($this->rtl) {
21656
				$aligntext = 'R';
21657
				$alignnum = 'L';
21658
			} else {
21659
				$aligntext = 'L';
21660
				$alignnum = 'R';
21661
			}
21662
			if ($outline['l'] == 0) {
21663
				$this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21664
			} else {
21665
				$this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21666
			}
21667
			$this->setTextColorArray($outline['c']);
21668
			// check for page break
21669
			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21670
			// set margins and X position
21671
			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21672
				$this->lMargin = $lmargin;
21673
				$this->rMargin = $rmargin;
21674
			} else {
21675
				if ($this->current_column != $current_column) {
21676
					if ($this->rtl) {
21677
						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21678
					} else {
21679
						$x_start = $this->columns[$this->current_column]['x'];
21680
					}
21681
				}
21682
				$lmargin = $this->lMargin;
21683
				$rmargin = $this->rMargin;
21684
				$current_page = $this->page;
21685
				$current_column = $this->current_column;
21686
			}
21687
			$this->setX($x_start);
21688
			$indent = ($spacer * $outline['l']);
21689
			if ($this->rtl) {
21690
				$this->x -= $indent;
21691
				$this->rMargin = $this->w - $this->x;
21692
			} else {
21693
				$this->x += $indent;
21694
				$this->lMargin = $this->x;
21695
			}
21696
			$link = $this->AddLink();
21697
			$this->setLink($link, $outline['y'], $outline['p']);
21698
			// write the text
21699
			if ($this->rtl) {
21700
				$txt = ' '.$outline['t'];
21701
			} else {
21702
				$txt = $outline['t'].' ';
21703
			}
21704
			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21705
			if ($this->rtl) {
21706
				$tw = $this->x - $this->lMargin;
21707
			} else {
21708
				$tw = $this->w - $this->rMargin - $this->x;
21709
			}
21710
			$this->setFont($numbersfont, $fontstyle, $fontsize);
21711
			if (TCPDF_STATIC::empty_string($page)) {
21712
				$pagenum = $outline['p'];
21713
			} else {
21714
				// placemark to be replaced with the correct number
21715
				$pagenum = '{#'.($outline['p']).'}';
21716
				if ($this->isUnicodeFont()) {
21717
					$pagenum = '{'.$pagenum.'}';
21718
				}
21719
				$maxpage = max($maxpage, $outline['p']);
21720
			}
21721
			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21722
			$wfiller = $this->GetStringWidth($filler);
21723
			if ($wfiller > 0) {
21724
				$numfills = floor($fw / $wfiller);
21725
			} else {
21726
				$numfills = 0;
21727
			}
21728
			if ($numfills > 0) {
21729
				$rowfill = str_repeat($filler, $numfills);
21730
			} else {
21731
				$rowfill = '';
21732
			}
21733
			if ($this->rtl) {
21734
				$pagenum = $pagenum.$gap.$rowfill;
21735
			} else {
21736
				$pagenum = $rowfill.$gap.$pagenum;
21737
			}
21738
			// write the number
21739
			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21740
		}
21741
		$page_last = $this->getPage();
21742
		$numpages = ($page_last - $page_first + 1);
21743
		// account for booklet mode
21744
		if ($this->booklet) {
21745
			// check if a blank page is required before TOC
21746
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21747
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21748
			if ($page_fill_start) {
21749
				// add a page at the end (to be moved before TOC)
21750
				$this->addPage();
21751
				++$page_last;
21752
				++$numpages;
21753
			}
21754
			if ($page_fill_end) {
21755
				// add a page at the end
21756
				$this->addPage();
21757
				++$page_last;
21758
				++$numpages;
21759
			}
21760
		}
21761
		$maxpage = max($maxpage, $page_last);
21762
		if (!TCPDF_STATIC::empty_string($page)) {
21763
			for ($p = $page_first; $p <= $page_last; ++$p) {
21764
				// get page data
21765
				$temppage = $this->getPageBuffer($p);
21766
				for ($n = 1; $n <= $maxpage; ++$n) {
21767
					// update page numbers
21768
					$a = '{#'.$n.'}';
21769
					// get page number aliases
21770
					$pnalias = $this->getInternalPageNumberAliases($a);
21771
					// calculate replacement number
21772
					if (($n >= $page) AND ($n <= $this->numpages)) {
21773
						$np = $n + $numpages;
21774
					} else {
21775
						$np = $n;
21776
					}
21777
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21778
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21779
					// replace aliases with numbers
21780
					foreach ($pnalias['u'] as $u) {
21781
						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21782
						if ($this->rtl) {
21783
							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21784
						} else {
21785
							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21786
						}
21787
						$temppage = str_replace($u, $nr, $temppage);
21788
					}
21789
					foreach ($pnalias['a'] as $a) {
21790
						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21791
						if ($this->rtl) {
21792
							$nr = $na.' '.$sfill;
21793
						} else {
21794
							$nr = $sfill.' '.$na;
21795
						}
21796
						$temppage = str_replace($a, $nr, $temppage);
21797
					}
21798
				}
21799
				// save changes
21800
				$this->setPageBuffer($p, $temppage);
21801
			}
21802
			// move pages
21803
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21804
			if ($page_fill_start) {
21805
				$this->movePage($page_last, $page_first);
21806
			}
21807
			for ($i = 0; $i < $numpages; ++$i) {
21808
				$this->movePage($page_last, $page);
21809
			}
21810
		}
21811
	}
21812
21813
	/**
21814
	 * Output a Table Of Content Index (TOC) using HTML templates.
21815
	 * This method must be called after all Bookmarks were set.
21816
	 * Before calling this method you have to open the page using the addTOCPage() method.
21817
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21818
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21819
	 * @param string $toc_name name to use for TOC bookmark.
21820
	 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21821
	 * @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)
21822
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21823
	 * @param array $color RGB color array for title (values from 0 to 255).
21824
	 * @public
21825
	 * @author Nicola Asuni
21826
	 * @since 5.0.001 (2010-05-06)
21827
	 * @see addTOCPage(), endTOCPage(), addTOC()
21828
	 */
21829
	public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21830
		$filler = ' ';
21831
		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21832
		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21833
		// set new style for link
21834
		$this->htmlLinkColorArray = array();
21835
		$this->htmlLinkFontStyle = '';
21836
		$page_first = $this->getPage();
21837
		$page_fill_start = false;
21838
		$page_fill_end = false;
21839
		// get the font type used for numbers in each template
21840
		$current_font = $this->FontFamily;
21841
		foreach ($templates as $level => $html) {
21842
			$dom = $this->getHtmlDomArray($html);
21843
			foreach ($dom as $key => $value) {
21844
				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21845
					$this->setFont($dom[($key - 1)]['fontname']);
21846
					$templates['F'.$level] = $this->isUnicodeFont();
21847
				}
21848
			}
21849
		}
21850
		$this->setFont($current_font);
21851
		$maxpage = 0; //used for pages on attached documents
21852
		foreach ($this->outlines as $key => $outline) {
21853
			// get HTML template
21854
			$row = $templates[$outline['l']];
21855
			if (TCPDF_STATIC::empty_string($page)) {
21856
				$pagenum = $outline['p'];
21857
			} else {
21858
				// placemark to be replaced with the correct number
21859
				$pagenum = '{#'.($outline['p']).'}';
21860
				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21861
					$pagenum = '{'.$pagenum.'}';
21862
				}
21863
				$maxpage = max($maxpage, $outline['p']);
21864
			}
21865
			// replace templates with current values
21866
			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21867
			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21868
			// add link to page
21869
			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21870
			// write bookmark entry
21871
			$this->writeHTML($row, false, false, true, false, '');
21872
		}
21873
		// restore link styles
21874
		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21875
		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21876
		// move TOC page and replace numbers
21877
		$page_last = $this->getPage();
21878
		$numpages = ($page_last - $page_first + 1);
21879
		// account for booklet mode
21880
		if ($this->booklet) {
21881
			// check if a blank page is required before TOC
21882
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21883
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21884
			if ($page_fill_start) {
21885
				// add a page at the end (to be moved before TOC)
21886
				$this->addPage();
21887
				++$page_last;
21888
				++$numpages;
21889
			}
21890
			if ($page_fill_end) {
21891
				// add a page at the end
21892
				$this->addPage();
21893
				++$page_last;
21894
				++$numpages;
21895
			}
21896
		}
21897
		$maxpage = max($maxpage, $page_last);
21898
		if (!TCPDF_STATIC::empty_string($page)) {
21899
			for ($p = $page_first; $p <= $page_last; ++$p) {
21900
				// get page data
21901
				$temppage = $this->getPageBuffer($p);
21902
				for ($n = 1; $n <= $maxpage; ++$n) {
21903
					// update page numbers
21904
					$a = '{#'.$n.'}';
21905
					// get page number aliases
21906
					$pnalias = $this->getInternalPageNumberAliases($a);
21907
					// calculate replacement number
21908
					if ($n >= $page) {
21909
						$np = $n + $numpages;
21910
					} else {
21911
						$np = $n;
21912
					}
21913
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21914
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21915
					// replace aliases with numbers
21916
					foreach ($pnalias['u'] as $u) {
21917
						if ($correct_align) {
21918
							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21919
							if ($this->rtl) {
21920
								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21921
							} else {
21922
								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21923
							}
21924
						} else {
21925
							$nr = $nu;
21926
						}
21927
						$temppage = str_replace($u, $nr, $temppage);
21928
					}
21929
					foreach ($pnalias['a'] as $a) {
21930
						if ($correct_align) {
21931
							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21932
							if ($this->rtl) {
21933
								$nr = $na.' '.$sfill;
21934
							} else {
21935
								$nr = $sfill.' '.$na;
21936
							}
21937
						} else {
21938
							$nr = $na;
21939
						}
21940
						$temppage = str_replace($a, $nr, $temppage);
21941
					}
21942
				}
21943
				// save changes
21944
				$this->setPageBuffer($p, $temppage);
21945
			}
21946
			// move pages
21947
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21948
			if ($page_fill_start) {
21949
				$this->movePage($page_last, $page_first);
21950
			}
21951
			for ($i = 0; $i < $numpages; ++$i) {
21952
				$this->movePage($page_last, $page);
21953
			}
21954
		}
21955
	}
21956
21957
	/**
21958
	 * Stores a copy of the current TCPDF object used for undo operation.
21959
	 * @public
21960
	 * @since 4.5.029 (2009-03-19)
21961
	 */
21962
	public function startTransaction() {
21963
		if (isset($this->objcopy)) {
21964
			// remove previous copy
21965
			$this->commitTransaction();
21966
		}
21967
		// record current page number and Y position
21968
		$this->start_transaction_page = $this->page;
21969
		$this->start_transaction_y = $this->y;
21970
		// clone current object
21971
		$this->objcopy = TCPDF_STATIC::objclone($this);
21972
	}
21973
21974
	/**
21975
	 * Delete the copy of the current TCPDF object used for undo operation.
21976
	 * @public
21977
	 * @since 4.5.029 (2009-03-19)
21978
	 */
21979
	public function commitTransaction() {
21980
		if (isset($this->objcopy)) {
21981
			$this->objcopy->_destroy(true, true);
21982
			/* The unique file_id should not be used during cleanup again */
21983
			$this->objcopy->file_id = NULL;
21984
			unset($this->objcopy);
21985
		}
21986
	}
21987
21988
	/**
21989
	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21990
	 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21991
	 * @return TCPDF object.
21992
	 * @public
21993
	 * @since 4.5.029 (2009-03-19)
21994
	 */
21995
	public function rollbackTransaction($self=false) {
21996
		if (!isset($this->objcopy)) {
21997
			return $this;
21998
		}
21999
		$file_id = $this->file_id;
22000
		$objcopy = $this->objcopy;
22001
		$this->_destroy(true, true);
22002
		if ($self) {
22003
			$objvars = get_object_vars($objcopy);
22004
			foreach ($objvars as $key => $value) {
22005
				$this->$key = $value;
22006
			}
22007
			$objcopy->_destroy(true, true);
22008
			unset($objcopy);
22009
			return $this;
22010
		}
22011
		$this->file_id = $file_id;
22012
		return $objcopy;
22013
	}
22014
22015
	// --- MULTI COLUMNS METHODS -----------------------
22016
22017
	/**
22018
	 * Set multiple columns of the same size
22019
	 * @param int $numcols number of columns (set to zero to disable columns mode)
22020
	 * @param int $width column width
22021
	 * @param int|null $y column starting Y position (leave empty for current Y position)
22022
	 * @public
22023
	 * @since 4.9.001 (2010-03-28)
22024
	 */
22025
	public function setEqualColumns($numcols=0, $width=0, $y=null) {
22026
		$this->columns = array();
22027
		if ($numcols < 2) {
22028
			$numcols = 0;
22029
			$this->columns = array();
22030
		} else {
22031
			// maximum column width
22032
			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
22033
			if (($width == 0) OR ($width > $maxwidth)) {
22034
				$width = $maxwidth;
22035
			}
22036
			if (TCPDF_STATIC::empty_string($y)) {
22037
				$y = $this->y;
22038
			}
22039
			// space between columns
22040
			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
22041
			// fill the columns array (with, space, starting Y position)
22042
			for ($i = 0; $i < $numcols; ++$i) {
22043
				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
22044
			}
22045
		}
22046
		$this->num_columns = $numcols;
22047
		$this->current_column = 0;
22048
		$this->column_start_page = $this->page;
22049
		$this->selectColumn(0);
22050
	}
22051
22052
	/**
22053
	 * Remove columns and reset page margins.
22054
	 * @public
22055
	 * @since 5.9.072 (2011-04-26)
22056
	 */
22057
	public function resetColumns() {
22058
		$this->lMargin = $this->original_lMargin;
22059
		$this->rMargin = $this->original_rMargin;
22060
		$this->setEqualColumns();
22061
	}
22062
22063
	/**
22064
	 * Set columns array.
22065
	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
22066
	 * @param array $columns
22067
	 * @public
22068
	 * @since 4.9.001 (2010-03-28)
22069
	 */
22070
	public function setColumnsArray($columns) {
22071
		$this->columns = $columns;
22072
		$this->num_columns = count($columns);
22073
		$this->current_column = 0;
22074
		$this->column_start_page = $this->page;
22075
		$this->selectColumn(0);
22076
	}
22077
22078
	/**
22079
	 * Set position at a given column
22080
	 * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
22081
	 * @public
22082
	 * @since 4.9.001 (2010-03-28)
22083
	 */
22084
	public function selectColumn($col=null) {
22085
		if (TCPDF_STATIC::empty_string($col)) {
22086
			$col = $this->current_column;
22087
		} elseif ($col >= $this->num_columns) {
22088
			$col = 0;
22089
		}
22090
		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
22091
		$enable_thead = false;
22092
		if ($this->num_columns > 1) {
22093
			if ($col != $this->current_column) {
22094
				// move Y pointer at the top of the column
22095
				if ($this->column_start_page == $this->page) {
22096
					$this->y = $this->columns[$col]['y'];
22097
				} else {
22098
					$this->y = $this->tMargin;
22099
				}
22100
				// Avoid to write table headers more than once
22101
				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
22102
					$enable_thead = true;
22103
					$this->maxselcol['page'] = $this->page;
22104
					$this->maxselcol['column'] = $col;
22105
				}
22106
			}
22107
			$xshift = $this->colxshift;
22108
			// set X position of the current column by case
22109
			$listindent = ($this->listindentlevel * $this->listindent);
22110
			// calculate column X position
22111
			$colpos = 0;
22112
			for ($i = 0; $i < $col; ++$i) {
22113
				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
22114
			}
22115
			if ($this->rtl) {
22116
				$x = $this->w - $this->original_rMargin - $colpos;
22117
				$this->rMargin = ($this->w - $x + $listindent);
22118
				$this->lMargin = ($x - $this->columns[$col]['w']);
22119
				$this->x = $x - $listindent;
22120
			} else {
22121
				$x = $this->original_lMargin + $colpos;
22122
				$this->lMargin = ($x + $listindent);
22123
				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
22124
				$this->x = $x + $listindent;
22125
			}
22126
			$this->columns[$col]['x'] = $x;
22127
		}
22128
		$this->current_column = $col;
22129
		// fix for HTML mode
22130
		$this->newline = true;
22131
		// print HTML table header (if any)
22132
		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
22133
			if ($enable_thead) {
22134
				// print table header
22135
				$this->writeHTML($this->thead, false, false, false, false, '');
22136
				$this->y += $xshift['s']['V'];
22137
				// store end of header position
22138
				if (!isset($this->columns[$col]['th'])) {
22139
					$this->columns[$col]['th'] = array();
22140
				}
22141
				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22142
				$this->lasth = 0;
22143
			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22144
				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22145
			}
22146
		}
22147
		// account for an html table cell over multiple columns
22148
		if ($this->rtl) {
22149
			$this->rMargin += $xshift['x'];
22150
			$this->x -= ($xshift['x'] + $xshift['p']['R']);
22151
		} else {
22152
			$this->lMargin += $xshift['x'];
22153
			$this->x += $xshift['x'] + $xshift['p']['L'];
22154
		}
22155
	}
22156
22157
	/**
22158
	 * Return the current column number
22159
	 * @return int current column number
22160
	 * @public
22161
	 * @since 5.5.011 (2010-07-08)
22162
	 */
22163
	public function getColumn() {
22164
		return $this->current_column;
22165
	}
22166
22167
	/**
22168
	 * Return the current number of columns.
22169
	 * @return int number of columns
22170
	 * @public
22171
	 * @since 5.8.018 (2010-08-25)
22172
	 */
22173
	public function getNumberOfColumns() {
22174
		return $this->num_columns;
22175
	}
22176
22177
	/**
22178
	 * Set Text rendering mode.
22179
	 * @param int $stroke outline size in user units (0 = disable).
22180
	 * @param boolean $fill if true fills the text (default).
22181
	 * @param boolean $clip if true activate clipping mode
22182
	 * @public
22183
	 * @since 4.9.008 (2009-04-02)
22184
	 */
22185
	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22186
		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22187
		// convert text rendering parameters
22188
		if ($stroke < 0 || !is_numeric($stroke)) {
22189
			$stroke = 0;
22190
		}
22191
		if ($fill === true) {
22192
			if ($stroke > 0) {
22193
				if ($clip === true) {
22194
					// Fill, then stroke text and add to path for clipping
22195
					$textrendermode = 6;
22196
				} else {
22197
					// Fill, then stroke text
22198
					$textrendermode = 2;
22199
				}
22200
				$textstrokewidth = $stroke;
22201
			} else {
22202
				if ($clip === true) {
22203
					// Fill text and add to path for clipping
22204
					$textrendermode = 4;
22205
				} else {
22206
					// Fill text
22207
					$textrendermode = 0;
22208
				}
22209
			}
22210
		} else {
22211
			if ($stroke > 0) {
22212
				if ($clip === true) {
22213
					// Stroke text and add to path for clipping
22214
					$textrendermode = 5;
22215
				} else {
22216
					// Stroke text
22217
					$textrendermode = 1;
22218
				}
22219
				$textstrokewidth = $stroke;
22220
			} else {
22221
				if ($clip === true) {
22222
					// Add text to path for clipping
22223
					$textrendermode = 7;
22224
				} else {
22225
					// Neither fill nor stroke text (invisible)
22226
					$textrendermode = 3;
22227
				}
22228
			}
22229
		}
22230
		$this->textrendermode = $textrendermode;
22231
		$this->textstrokewidth = $stroke;
22232
	}
22233
22234
	/**
22235
	 * Set parameters for drop shadow effect for text.
22236
	 * @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.
22237
	 * @since 5.9.174 (2012-07-25)
22238
	 * @public
22239
	*/
22240
	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22241
		if (isset($params['enabled'])) {
22242
			$this->txtshadow['enabled'] = $params['enabled']?true:false;
22243
		} else {
22244
			$this->txtshadow['enabled'] = false;
22245
		}
22246
		if (isset($params['depth_w'])) {
22247
			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
22248
		} else {
22249
			$this->txtshadow['depth_w'] = 0;
22250
		}
22251
		if (isset($params['depth_h'])) {
22252
			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
22253
		} else {
22254
			$this->txtshadow['depth_h'] = 0;
22255
		}
22256
		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22257
			$this->txtshadow['color'] = $params['color'];
22258
		} else {
22259
			$this->txtshadow['color'] = $this->strokecolor;
22260
		}
22261
		if (isset($params['opacity'])) {
22262
			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22263
		} else {
22264
			$this->txtshadow['opacity'] = 1;
22265
		}
22266
		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'))) {
22267
			$this->txtshadow['blend_mode'] = $params['blend_mode'];
22268
		} else {
22269
			$this->txtshadow['blend_mode'] = 'Normal';
22270
		}
22271
		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22272
			$this->txtshadow['enabled'] = false;
22273
		}
22274
	}
22275
22276
	/**
22277
	 * Return the text shadow parameters array.
22278
	 * @return array array of parameters.
22279
	 * @since 5.9.174 (2012-07-25)
22280
	 * @public
22281
	 */
22282
	public function getTextShadow() {
22283
		return $this->txtshadow;
22284
	}
22285
22286
	/**
22287
	 * Returns an array of chars containing soft hyphens.
22288
	 * @param array $word array of chars
22289
	 * @param array $patterns Array of hypenation patterns.
22290
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22291
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22292
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22293
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22294
	 * @param int $charmax Maximum length of broken piece of word.
22295
	 * @return array text with soft hyphens
22296
	 * @author Nicola Asuni
22297
	 * @since 4.9.012 (2010-04-12)
22298
	 * @protected
22299
	 */
22300
	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22301
		$hyphenword = array(); // hyphens positions
22302
		$numchars = count($word);
22303
		if ($numchars <= $charmin) {
22304
			return $word;
22305
		}
22306
		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22307
		// some words will be returned as-is
22308
		$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})(\]?)$/';
22309
		if (preg_match($pattern, $word_string) > 0) {
22310
			// email
22311
			return $word;
22312
		}
22313
		$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})(\]?)$/';
22314
		if (preg_match($pattern, $word_string) > 0) {
22315
			// URL
22316
			return $word;
22317
		}
22318
		if (isset($dictionary[$word_string])) {
22319
			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22320
		}
22321
		// surround word with '_' characters
22322
		$tmpword = array_merge(array(46), $word, array(46));
22323
		$tmpnumchars = $numchars + 2;
22324
		$maxpos = $tmpnumchars - 1;
22325
		for ($pos = 0; $pos < $maxpos; ++$pos) {
22326
			$imax = min(($tmpnumchars - $pos), $charmax);
22327
			for ($i = 1; $i <= $imax; ++$i) {
22328
				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22329
				if (isset($patterns[$subword])) {
22330
					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22331
					$pattern_length = count($pattern);
22332
					$digits = 1;
22333
					for ($j = 0; $j < $pattern_length; ++$j) {
22334
						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22335
						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22336
							if ($j == 0) {
22337
								$zero = $pos - 1;
22338
							} else {
22339
								$zero = $pos + $j - $digits;
22340
							}
22341
							// get hyphenation level
22342
							$level = ($pattern[$j] - 48);
22343
							// if two levels from two different patterns match at the same point, the higher one is selected.
22344
							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22345
								$hyphenword[$zero] = $level;
22346
							}
22347
							++$digits;
22348
						}
22349
					}
22350
				}
22351
			}
22352
		}
22353
		$inserted = 0;
22354
		$maxpos = $numchars - $rightmin;
22355
		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22356
			// only odd levels indicate allowed hyphenation points
22357
			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22358
				// 173 = soft hyphen character
22359
				array_splice($word, $i + $inserted, 0, 173);
22360
				++$inserted;
22361
			}
22362
		}
22363
		return $word;
22364
	}
22365
22366
	/**
22367
	 * Returns text with soft hyphens.
22368
	 * @param string $text text to process
22369
	 * @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/
22370
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22371
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22372
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22373
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22374
	 * @param int $charmax Maximum length of broken piece of word.
22375
	 * @return string text with soft hyphens
22376
	 * @author Nicola Asuni
22377
	 * @since 4.9.012 (2010-04-12)
22378
	 * @public
22379
	 */
22380
	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22381
		$text = $this->unhtmlentities($text);
22382
		$word = array(); // last word
22383
		$txtarr = array(); // text to be returned
22384
		$intag = false; // true if we are inside an HTML tag
22385
		$skip = false; // true to skip hyphenation
22386
		if (!is_array($patterns)) {
22387
			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22388
		}
22389
		// get array of characters
22390
		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22391
		// for each char
22392
		foreach ($unichars as $char) {
22393
			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22394
				// letter character
22395
				$word[] = $char;
22396
			} else {
22397
				// other type of character
22398
				if (!TCPDF_STATIC::empty_string($word)) {
22399
					// hypenate the word
22400
					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22401
					$word = array();
22402
				}
22403
				$txtarr[] = $char;
22404
				if (chr($char) == '<') {
22405
					// we are inside an HTML tag
22406
					$intag = true;
22407
				} elseif ($intag AND (chr($char) == '>')) {
22408
					// end of HTML tag
22409
					$intag = false;
22410
					// check for style tag
22411
					$expected = array(115, 116, 121, 108, 101); // = 'style'
22412
					$current = array_slice($txtarr, -6, 5); // last 5 chars
22413
					$compare = array_diff($expected, $current);
22414
					if (empty($compare)) {
22415
						// check if it is a closing tag
22416
						$expected = array(47); // = '/'
22417
						$current = array_slice($txtarr, -7, 1);
22418
						$compare = array_diff($expected, $current);
22419
						if (empty($compare)) {
22420
							// closing style tag
22421
							$skip = false;
22422
						} else {
22423
							// opening style tag
22424
							$skip = true;
22425
						}
22426
					}
22427
				}
22428
			}
22429
		}
22430
		if (!TCPDF_STATIC::empty_string($word)) {
22431
			// hypenate the word
22432
			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22433
		}
22434
		// convert char array to string and return
22435
		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22436
	}
22437
22438
	/**
22439
	 * Enable/disable rasterization of vector images using ImageMagick library.
22440
	 * @param boolean $mode if true enable rasterization, false otherwise.
22441
	 * @public
22442
	 * @since 5.0.000 (2010-04-27)
22443
	 */
22444
	public function setRasterizeVectorImages($mode) {
22445
		$this->rasterize_vector_images = $mode;
22446
	}
22447
22448
	/**
22449
	 * Enable or disable default option for font subsetting.
22450
	 * @param boolean $enable if true enable font subsetting by default.
22451
	 * @author Nicola Asuni
22452
	 * @public
22453
	 * @since 5.3.002 (2010-06-07)
22454
	 */
22455
	public function setFontSubsetting($enable=true) {
22456
		if ($this->pdfa_mode) {
22457
			$this->font_subsetting = false;
22458
		} else {
22459
			$this->font_subsetting = $enable ? true : false;
22460
		}
22461
	}
22462
22463
	/**
22464
	 * Return the default option for font subsetting.
22465
	 * @return bool default font subsetting state.
22466
	 * @author Nicola Asuni
22467
	 * @public
22468
	 * @since 5.3.002 (2010-06-07)
22469
	 */
22470
	public function getFontSubsetting() {
22471
		return $this->font_subsetting;
22472
	}
22473
22474
	/**
22475
	 * Left trim the input string
22476
	 * @param string $str string to trim
22477
	 * @param string $replace string that replace spaces.
22478
	 * @return string left trimmed string
22479
	 * @author Nicola Asuni
22480
	 * @public
22481
	 * @since 5.8.000 (2010-08-11)
22482
	 */
22483
	public function stringLeftTrim($str, $replace='') {
22484
		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22485
	}
22486
22487
	/**
22488
	 * Right trim the input string
22489
	 * @param string $str string to trim
22490
	 * @param string $replace string that replace spaces.
22491
	 * @return string right trimmed string
22492
	 * @author Nicola Asuni
22493
	 * @public
22494
	 * @since 5.8.000 (2010-08-11)
22495
	 */
22496
	public function stringRightTrim($str, $replace='') {
22497
		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22498
	}
22499
22500
	/**
22501
	 * Trim the input string
22502
	 * @param string $str string to trim
22503
	 * @param string $replace string that replace spaces.
22504
	 * @return string trimmed string
22505
	 * @author Nicola Asuni
22506
	 * @public
22507
	 * @since 5.8.000 (2010-08-11)
22508
	 */
22509
	public function stringTrim($str, $replace='') {
22510
		$str = $this->stringLeftTrim($str, $replace);
22511
		$str = $this->stringRightTrim($str, $replace);
22512
		return $str;
22513
	}
22514
22515
	/**
22516
	 * Return true if the current font is unicode type.
22517
	 * @return bool true for unicode font, false otherwise.
22518
	 * @author Nicola Asuni
22519
	 * @public
22520
	 * @since 5.8.002 (2010-08-14)
22521
	 */
22522
	public function isUnicodeFont() {
22523
		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22524
	}
22525
22526
	/**
22527
	 * Return normalized font name
22528
	 * @param string $fontfamily property string containing font family names
22529
	 * @return string normalized font name
22530
	 * @author Nicola Asuni
22531
	 * @public
22532
	 * @since 5.8.004 (2010-08-17)
22533
	 */
22534
	public function getFontFamilyName($fontfamily) {
22535
		// remove spaces and symbols
22536
		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22537
		// extract all font names
22538
		$fontslist = preg_split('/[,]/', $fontfamily);
22539
		// find first valid font name
22540
		foreach ($fontslist as $font) {
22541
			// replace font variations
22542
			$font = preg_replace('/regular$/', '', $font);
22543
			$font = preg_replace('/italic$/', 'I', $font);
22544
			$font = preg_replace('/oblique$/', 'I', $font);
22545
			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22546
			// replace common family names and core fonts
22547
			$pattern = array();
22548
			$replacement = array();
22549
			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22550
			$replacement[] = 'times';
22551
			$pattern[] = '/^sansserif/';
22552
			$replacement[] = 'helvetica';
22553
			$pattern[] = '/^monospace/';
22554
			$replacement[] = 'courier';
22555
			$font = preg_replace($pattern, $replacement, $font);
22556
			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22557
				return $font;
22558
			}
22559
		}
22560
		// return current font as default
22561
		return $this->CurrentFont['fontkey'];
22562
	}
22563
22564
	/**
22565
	 * Start a new XObject Template.
22566
	 * 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).
22567
	 * 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.
22568
	 * Note: X,Y coordinates will be reset to 0,0.
22569
	 * @param int $w Template width in user units (empty string or zero = page width less margins).
22570
	 * @param int $h Template height in user units (empty string or zero = page height less margins).
22571
	 * @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).
22572
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22573
	 * @author Nicola Asuni
22574
	 * @public
22575
	 * @since 5.8.017 (2010-08-24)
22576
	 * @see endTemplate(), printTemplate()
22577
	 */
22578
	public function startTemplate($w=0, $h=0, $group=false) {
22579
		if ($this->inxobj) {
22580
			// we are already inside an XObject template
22581
			return false;
22582
		}
22583
		$this->inxobj = true;
22584
		++$this->n;
22585
		// XObject ID
22586
		$this->xobjid = 'XT'.$this->n;
22587
		// object ID
22588
		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22589
		// store current graphic state
22590
		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22591
		// initialize data
22592
		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22593
		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22594
		$this->xobjects[$this->xobjid]['outdata'] = '';
22595
		$this->xobjects[$this->xobjid]['xobjects'] = array();
22596
		$this->xobjects[$this->xobjid]['images'] = array();
22597
		$this->xobjects[$this->xobjid]['fonts'] = array();
22598
		$this->xobjects[$this->xobjid]['annotations'] = array();
22599
		$this->xobjects[$this->xobjid]['extgstates'] = array();
22600
		$this->xobjects[$this->xobjid]['gradients'] = array();
22601
		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22602
		// set new environment
22603
		$this->num_columns = 1;
22604
		$this->current_column = 0;
22605
		$this->setAutoPageBreak(false);
22606
		if (($w === '') OR ($w <= 0)) {
22607
			$w = $this->w - $this->lMargin - $this->rMargin;
22608
		}
22609
		if (($h === '') OR ($h <= 0)) {
22610
			$h = $this->h - $this->tMargin - $this->bMargin;
22611
		}
22612
		$this->xobjects[$this->xobjid]['x'] = 0;
22613
		$this->xobjects[$this->xobjid]['y'] = 0;
22614
		$this->xobjects[$this->xobjid]['w'] = $w;
22615
		$this->xobjects[$this->xobjid]['h'] = $h;
22616
		$this->w = $w;
22617
		$this->h = $h;
22618
		$this->wPt = $this->w * $this->k;
22619
		$this->hPt = $this->h * $this->k;
22620
		$this->fwPt = $this->wPt;
22621
		$this->fhPt = $this->hPt;
22622
		$this->x = 0;
22623
		$this->y = 0;
22624
		$this->lMargin = 0;
22625
		$this->rMargin = 0;
22626
		$this->tMargin = 0;
22627
		$this->bMargin = 0;
22628
		// set group mode
22629
		$this->xobjects[$this->xobjid]['group'] = $group;
22630
		return $this->xobjid;
22631
	}
22632
22633
	/**
22634
	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22635
	 * 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).
22636
	 * 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.
22637
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22638
	 * @author Nicola Asuni
22639
	 * @public
22640
	 * @since 5.8.017 (2010-08-24)
22641
	 * @see startTemplate(), printTemplate()
22642
	 */
22643
	public function endTemplate() {
22644
		if (!$this->inxobj) {
22645
			// we are not inside a template
22646
			return false;
22647
		}
22648
		$this->inxobj = false;
22649
		// restore previous graphic state
22650
		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22651
		return $this->xobjid;
22652
	}
22653
22654
	/**
22655
	 * Print an XObject Template.
22656
	 * You can print an XObject Template inside the currently opened Template.
22657
	 * 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).
22658
	 * 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.
22659
	 * @param string $id The ID of XObject Template to print.
22660
	 * @param float|null $x X position in user units (empty string = current x position)
22661
	 * @param float|null $y Y position in user units (empty string = current y position)
22662
	 * @param float $w Width in user units (zero = remaining page width)
22663
	 * @param float $h Height in user units (zero = remaining page height)
22664
	 * @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>
22665
	 * @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>
22666
	 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22667
	 * @author Nicola Asuni
22668
	 * @public
22669
	 * @since 5.8.017 (2010-08-24)
22670
	 * @see startTemplate(), endTemplate()
22671
	 */
22672
	public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22673
		if ($this->state != 2) {
22674
			 return;
22675
		}
22676
		if (!isset($this->xobjects[$id])) {
22677
			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22678
		}
22679
		if ($this->inxobj) {
22680
			if ($id == $this->xobjid) {
22681
				// close current template
22682
				$this->endTemplate();
22683
			} else {
22684
				// use the template as resource for the template currently opened
22685
				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22686
			}
22687
		}
22688
		// set default values
22689
		if (TCPDF_STATIC::empty_string($x)) {
22690
			$x = $this->x;
22691
		}
22692
		if (TCPDF_STATIC::empty_string($y)) {
22693
			$y = $this->y;
22694
		}
22695
		// check page for no-write regions and adapt page margins if necessary
22696
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22697
		$ow = $this->xobjects[$id]['w'];
22698
		if ($ow <= 0) {
22699
			$ow = 1;
22700
		}
22701
		$oh = $this->xobjects[$id]['h'];
22702
		if ($oh <= 0) {
22703
			$oh = 1;
22704
		}
22705
		// calculate template width and height on document
22706
		if (($w <= 0) AND ($h <= 0)) {
22707
			$w = $ow;
22708
			$h = $oh;
22709
		} elseif ($w <= 0) {
22710
			$w = $h * $ow / $oh;
22711
		} elseif ($h <= 0) {
22712
			$h = $w * $oh / $ow;
22713
		}
22714
		// fit the template on available space
22715
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22716
		// set page alignment
22717
		$rb_y = $y + $h;
22718
		// set alignment
22719
		if ($this->rtl) {
22720
			if ($palign == 'L') {
22721
				$xt = $this->lMargin;
22722
			} elseif ($palign == 'C') {
22723
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22724
			} elseif ($palign == 'R') {
22725
				$xt = $this->w - $this->rMargin - $w;
22726
			} else {
22727
				$xt = $x - $w;
22728
			}
22729
			$rb_x = $xt;
22730
		} else {
22731
			if ($palign == 'L') {
22732
				$xt = $this->lMargin;
22733
			} elseif ($palign == 'C') {
22734
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22735
			} elseif ($palign == 'R') {
22736
				$xt = $this->w - $this->rMargin - $w;
22737
			} else {
22738
				$xt = $x;
22739
			}
22740
			$rb_x = $xt + $w;
22741
		}
22742
		// print XObject Template + Transformation matrix
22743
		$this->StartTransform();
22744
		// translate and scale
22745
		$sx = ($w / $ow);
22746
		$sy = ($h / $oh);
22747
		$tm = array();
22748
		$tm[0] = $sx;
22749
		$tm[1] = 0;
22750
		$tm[2] = 0;
22751
		$tm[3] = $sy;
22752
		$tm[4] = $xt * $this->k;
22753
		$tm[5] = ($this->h - $h - $y) * $this->k;
22754
		$this->Transform($tm);
22755
		// set object
22756
		$this->_out('/'.$id.' Do');
22757
		$this->StopTransform();
22758
		// add annotations
22759
		if (!empty($this->xobjects[$id]['annotations'])) {
22760
			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22761
				// transform original coordinates
22762
				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22763
				$ax = ($coordlt[4] / $this->k);
22764
				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22765
				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22766
				$aw = ($coordrb[4] / $this->k) - $ax;
22767
				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22768
				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22769
			}
22770
		}
22771
		// set pointer to align the next text/objects
22772
		switch($align) {
22773
			case 'T': {
22774
				$this->y = $y;
22775
				$this->x = $rb_x;
22776
				break;
22777
			}
22778
			case 'M': {
22779
				$this->y = $y + round($h/2);
22780
				$this->x = $rb_x;
22781
				break;
22782
			}
22783
			case 'B': {
22784
				$this->y = $rb_y;
22785
				$this->x = $rb_x;
22786
				break;
22787
			}
22788
			case 'N': {
22789
				$this->setY($rb_y);
22790
				break;
22791
			}
22792
			default:{
22793
				break;
22794
			}
22795
		}
22796
	}
22797
22798
	/**
22799
	 * Set the percentage of character stretching.
22800
	 * @param int $perc percentage of stretching (100 = no stretching)
22801
	 * @author Nicola Asuni
22802
	 * @public
22803
	 * @since 5.9.000 (2010-09-29)
22804
	 */
22805
	public function setFontStretching($perc=100) {
22806
		$this->font_stretching = $perc;
22807
	}
22808
22809
	/**
22810
	 * Get the percentage of character stretching.
22811
	 * @return float stretching value
22812
	 * @author Nicola Asuni
22813
	 * @public
22814
	 * @since 5.9.000 (2010-09-29)
22815
	 */
22816
	public function getFontStretching() {
22817
		return $this->font_stretching;
22818
	}
22819
22820
	/**
22821
	 * Set the amount to increase or decrease the space between characters in a text.
22822
	 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22823
	 * @author Nicola Asuni
22824
	 * @public
22825
	 * @since 5.9.000 (2010-09-29)
22826
	 */
22827
	public function setFontSpacing($spacing=0) {
22828
		$this->font_spacing = $spacing;
22829
	}
22830
22831
	/**
22832
	 * Get the amount to increase or decrease the space between characters in a text.
22833
	 * @return int font spacing (tracking) value
22834
	 * @author Nicola Asuni
22835
	 * @public
22836
	 * @since 5.9.000 (2010-09-29)
22837
	 */
22838
	public function getFontSpacing() {
22839
		return $this->font_spacing;
22840
	}
22841
22842
	/**
22843
	 * Return an array of no-write page regions
22844
	 * @return array of no-write page regions
22845
	 * @author Nicola Asuni
22846
	 * @public
22847
	 * @since 5.9.003 (2010-10-13)
22848
	 * @see setPageRegions(), addPageRegion()
22849
	 */
22850
	public function getPageRegions() {
22851
		return $this->page_regions;
22852
	}
22853
22854
	/**
22855
	 * Set no-write regions on page.
22856
	 * 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.
22857
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22858
	 * You can set multiple regions for the same page.
22859
	 * @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.
22860
	 * @author Nicola Asuni
22861
	 * @public
22862
	 * @since 5.9.003 (2010-10-13)
22863
	 * @see addPageRegion(), getPageRegions()
22864
	 */
22865
	public function setPageRegions($regions=array()) {
22866
		// empty current regions array
22867
		$this->page_regions = array();
22868
		// add regions
22869
		foreach ($regions as $data) {
22870
			$this->addPageRegion($data);
22871
		}
22872
	}
22873
22874
	/**
22875
	 * Add a single no-write region on selected page.
22876
	 * 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.
22877
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22878
	 * You can set multiple regions for the same page.
22879
	 * @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).
22880
	 * @author Nicola Asuni
22881
	 * @public
22882
	 * @since 5.9.003 (2010-10-13)
22883
	 * @see setPageRegions(), getPageRegions()
22884
	 */
22885
	public function addPageRegion($region) {
22886
		if (!isset($region['page']) OR empty($region['page'])) {
22887
			$region['page'] = $this->page;
22888
		}
22889
		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22890
			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22891
			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22892
			$this->page_regions[] = $region;
22893
		}
22894
	}
22895
22896
	/**
22897
	 * Remove a single no-write region.
22898
	 * @param int $key region key
22899
	 * @author Nicola Asuni
22900
	 * @public
22901
	 * @since 5.9.003 (2010-10-13)
22902
	 * @see setPageRegions(), getPageRegions()
22903
	 */
22904
	public function removePageRegion($key) {
22905
		if (isset($this->page_regions[$key])) {
22906
			unset($this->page_regions[$key]);
22907
		}
22908
	}
22909
22910
	/**
22911
	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22912
	 * 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.
22913
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22914
	 * @param float $h height of the text/image/object to print in user units
22915
	 * @param float $x current X coordinate in user units
22916
	 * @param float $y current Y coordinate in user units
22917
	 * @return float[] array($x, $y)
22918
	 * @author Nicola Asuni
22919
	 * @protected
22920
	 * @since 5.9.003 (2010-10-13)
22921
	 */
22922
	protected function checkPageRegions($h, $x, $y) {
22923
		// set default values
22924
		if ($x === '') {
22925
			$x = $this->x;
22926
		}
22927
		if ($y === '') {
22928
			$y = $this->y;
22929
		}
22930
		if (!$this->check_page_regions OR empty($this->page_regions)) {
22931
			// no page regions defined
22932
			return array($x, $y);
22933
		}
22934
		if (empty($h)) {
22935
			$h = $this->getCellHeight($this->FontSize);
22936
		}
22937
		// check for page break
22938
		if ($this->checkPageBreak($h, $y)) {
22939
			// the content will be printed on a new page
22940
			$x = $this->x;
22941
			$y = $this->y;
22942
		}
22943
		if ($this->num_columns > 1) {
22944
			if ($this->rtl) {
22945
				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22946
			} else {
22947
				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22948
			}
22949
		} else {
22950
			if ($this->rtl) {
22951
				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22952
			} else {
22953
				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22954
			}
22955
		}
22956
		// adjust coordinates and page margins
22957
		foreach ($this->page_regions as $regid => $regdata) {
22958
			if ($regdata['page'] == $this->page) {
22959
				// check region boundaries
22960
				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22961
					// Y is inside the region
22962
					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22963
					$yt = max($y, $regdata['yt']);
22964
					$yb = min(($yt + $h), $regdata['yb']);
22965
					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22966
					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22967
					if ($regdata['side'] == 'L') { // left side
22968
						$new_margin = max($xt, $xb);
22969
						if ($this->lMargin < $new_margin) {
22970
							if ($this->rtl) {
22971
								// adjust left page margin
22972
								$this->lMargin = max(0, $new_margin);
22973
							}
22974
							if ($x < $new_margin) {
22975
								// adjust x position
22976
								$x = $new_margin;
22977
								if ($new_margin > ($this->w - $this->rMargin)) {
22978
									// adjust y position
22979
									$y = $regdata['yb'] - $h;
22980
								}
22981
							}
22982
						}
22983
					} elseif ($regdata['side'] == 'R') { // right side
22984
						$new_margin = min($xt, $xb);
22985
						if (($this->w - $this->rMargin) > $new_margin) {
22986
							if (!$this->rtl) {
22987
								// adjust right page margin
22988
								$this->rMargin = max(0, ($this->w - $new_margin));
22989
							}
22990
							if ($x > $new_margin) {
22991
								// adjust x position
22992
								$x = $new_margin;
22993
								if ($new_margin > $this->lMargin) {
22994
									// adjust y position
22995
									$y = $regdata['yb'] - $h;
22996
								}
22997
							}
22998
						}
22999
					}
23000
				}
23001
			}
23002
		}
23003
		return array($x, $y);
23004
	}
23005
23006
	// --- SVG METHODS ---------------------------------------------------------
23007
23008
	/**
23009
	 * Embedd a Scalable Vector Graphics (SVG) image.
23010
	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
23011
	 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
23012
	 * @param float|null $x Abscissa of the upper-left corner.
23013
	 * @param float|null $y Ordinate of the upper-left corner.
23014
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
23015
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
23016
	 * @param mixed $link URL or identifier returned by AddLink().
23017
	 * @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.
23018
	 * @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>
23019
	 * @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)))
23020
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
23021
	 * @author Nicola Asuni
23022
	 * @since 5.0.000 (2010-05-02)
23023
	 * @public
23024
	 */
23025
	public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
23026
		if ($this->state != 2) {
23027
			 return;
23028
		}
23029
		// reset SVG vars
23030
		$this->svggradients = array();
23031
		$this->svggradientid = 0;
23032
		$this->svgdefsmode = false;
23033
		$this->svgdefs = array();
23034
		$this->svgclipmode = false;
23035
		$this->svgclippaths = array();
23036
		$this->svgcliptm = array();
23037
		$this->svgclipid = 0;
23038
		$this->svgtext = '';
23039
		$this->svgtextmode = array();
23040
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
23041
			// convert SVG to raster image using GD or ImageMagick libraries
23042
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23043
		}
23044
		if ($file[0] === '@') { // image from string
23045
			$this->svgdir = '';
23046
			$svgdata = substr($file, 1);
23047
		} else { // SVG file
23048
			$this->svgdir = dirname($file);
23049
            $svgdata = $this->getCachedFileContents($file);
23050
		}
23051
		if ($svgdata === FALSE) {
23052
			$this->Error('SVG file not found: '.$file);
23053
		}
23054
		if (TCPDF_STATIC::empty_string($x)) {
23055
			$x = $this->x;
23056
		}
23057
		if (TCPDF_STATIC::empty_string($y)) {
23058
			$y = $this->y;
23059
		}
23060
		// check page for no-write regions and adapt page margins if necessary
23061
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
23062
		$k = $this->k;
23063
		$ox = 0;
23064
		$oy = 0;
23065
		$ow = $w;
23066
		$oh = $h;
23067
		$aspect_ratio_align = 'xMidYMid';
23068
		$aspect_ratio_ms = 'meet';
23069
		$regs = array();
23070
		// get original image width and height
23071
		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
23072
		if (isset($regs[1]) AND !empty($regs[1])) {
23073
			$tmp = array();
23074
			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23075
				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
23076
			}
23077
			$tmp = array();
23078
			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23079
				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
23080
			}
23081
			$tmp = array();
23082
			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23083
				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23084
			}
23085
			$tmp = array();
23086
			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23087
				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23088
			}
23089
			$tmp = array();
23090
			$view_box = array();
23091
			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
23092
				if (count($tmp) == 5) {
23093
					array_shift($tmp);
23094
					foreach ($tmp as $key => $val) {
23095
						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
23096
					}
23097
					$ox = $view_box[0];
23098
					$oy = $view_box[1];
23099
				}
23100
				// get aspect ratio
23101
				$tmp = array();
23102
				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23103
					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
23104
					switch (count($aspect_ratio)) {
23105
						case 3: {
23106
							$aspect_ratio_align = $aspect_ratio[1];
23107
							$aspect_ratio_ms = $aspect_ratio[2];
23108
							break;
23109
						}
23110
						case 2: {
23111
							$aspect_ratio_align = $aspect_ratio[0];
23112
							$aspect_ratio_ms = $aspect_ratio[1];
23113
							break;
23114
						}
23115
						case 1: {
23116
							$aspect_ratio_align = $aspect_ratio[0];
23117
							$aspect_ratio_ms = 'meet';
23118
							break;
23119
						}
23120
					}
23121
				}
23122
			}
23123
		}
23124
		if ($ow <= 0) {
23125
			$ow = 1;
23126
		}
23127
		if ($oh <= 0) {
23128
			$oh = 1;
23129
		}
23130
		// calculate image width and height on document
23131
		if (($w <= 0) AND ($h <= 0)) {
23132
			// convert image size to document unit
23133
			$w = $ow;
23134
			$h = $oh;
23135
		} elseif ($w <= 0) {
23136
			$w = $h * $ow / $oh;
23137
		} elseif ($h <= 0) {
23138
			$h = $w * $oh / $ow;
23139
		}
23140
		// fit the image on available space
23141
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23142
		if ($this->rasterize_vector_images) {
23143
			// convert SVG to raster image using GD or ImageMagick libraries
23144
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23145
		}
23146
		// set alignment
23147
		$this->img_rb_y = $y + $h;
23148
		// set alignment
23149
		if ($this->rtl) {
23150
			if ($palign == 'L') {
23151
				$ximg = $this->lMargin;
23152
			} elseif ($palign == 'C') {
23153
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23154
			} elseif ($palign == 'R') {
23155
				$ximg = $this->w - $this->rMargin - $w;
23156
			} else {
23157
				$ximg = $x - $w;
23158
			}
23159
			$this->img_rb_x = $ximg;
23160
		} else {
23161
			if ($palign == 'L') {
23162
				$ximg = $this->lMargin;
23163
			} elseif ($palign == 'C') {
23164
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23165
			} elseif ($palign == 'R') {
23166
				$ximg = $this->w - $this->rMargin - $w;
23167
			} else {
23168
				$ximg = $x;
23169
			}
23170
			$this->img_rb_x = $ximg + $w;
23171
		}
23172
		// store current graphic vars
23173
		$gvars = $this->getGraphicVars();
23174
		// store SVG position and scale factors
23175
		$svgoffset_x = ($ximg - $ox) * $this->k;
23176
		$svgoffset_y = -($y - $oy) * $this->k;
23177
		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23178
			$ow = $view_box[2];
23179
			$oh = $view_box[3];
23180
		} else {
23181
			if ($ow <= 0) {
23182
				$ow = $w;
23183
			}
23184
			if ($oh <= 0) {
23185
				$oh = $h;
23186
			}
23187
		}
23188
		$svgscale_x = $w / $ow;
23189
		$svgscale_y = $h / $oh;
23190
		// scaling and alignment
23191
		if ($aspect_ratio_align != 'none') {
23192
			// store current scaling values
23193
			$svgscale_old_x = $svgscale_x;
23194
			$svgscale_old_y = $svgscale_y;
23195
			// force uniform scaling
23196
			if ($aspect_ratio_ms == 'slice') {
23197
				// the entire viewport is covered by the viewBox
23198
				if ($svgscale_x > $svgscale_y) {
23199
					$svgscale_y = $svgscale_x;
23200
				} elseif ($svgscale_x < $svgscale_y) {
23201
					$svgscale_x = $svgscale_y;
23202
				}
23203
			} else { // meet
23204
				// the entire viewBox is visible within the viewport
23205
				if ($svgscale_x < $svgscale_y) {
23206
					$svgscale_y = $svgscale_x;
23207
				} elseif ($svgscale_x > $svgscale_y) {
23208
					$svgscale_x = $svgscale_y;
23209
				}
23210
			}
23211
			// correct X alignment
23212
			switch (substr($aspect_ratio_align, 1, 3)) {
23213
				case 'Min': {
23214
					// do nothing
23215
					break;
23216
				}
23217
				case 'Max': {
23218
					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23219
					break;
23220
				}
23221
				default:
23222
				case 'Mid': {
23223
					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23224
					break;
23225
				}
23226
			}
23227
			// correct Y alignment
23228
			switch (substr($aspect_ratio_align, 5)) {
23229
				case 'Min': {
23230
					// do nothing
23231
					break;
23232
				}
23233
				case 'Max': {
23234
					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23235
					break;
23236
				}
23237
				default:
23238
				case 'Mid': {
23239
					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23240
					break;
23241
				}
23242
			}
23243
		}
23244
		// store current page break mode
23245
		$page_break_mode = $this->AutoPageBreak;
23246
		$page_break_margin = $this->getBreakMargin();
23247
		$cell_padding = $this->cell_padding;
23248
		$this->setCellPadding(0);
23249
		$this->setAutoPageBreak(false);
23250
		// save the current graphic state
23251
		$this->_out('q'.$this->epsmarker);
23252
		// set initial clipping mask
23253
		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23254
		// scale and translate
23255
		$e = $ox * $this->k * (1 - $svgscale_x);
23256
		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23257
		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23258
		// creates a new XML parser to be used by the other XML functions
23259
		$parser = xml_parser_create('UTF-8');
23260
		// disable case-folding for this XML parser
23261
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23262
		// sets the element handler functions for the XML parser
23263
		xml_set_element_handler($parser, [$this, 'startSVGElementHandler'], [$this, 'endSVGElementHandler']);
23264
		// sets the character data handler function for the XML parser
23265
		xml_set_character_data_handler($parser, [$this, 'segSVGContentHandler']);
23266
		// start parsing an XML document
23267
		if (!xml_parse($parser, $svgdata)) {
23268
			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23269
			$this->Error($error_message);
23270
		}
23271
		// free this XML parser
23272
		xml_parser_free($parser);
23273
23274
		// >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23275
		unset($parser);
23276
23277
		// restore previous graphic state
23278
		$this->_out($this->epsmarker.'Q');
23279
		// restore graphic vars
23280
		$this->setGraphicVars($gvars);
23281
		$this->lasth = $gvars['lasth'];
23282
		if (!empty($border)) {
23283
			$bx = $this->x;
23284
			$by = $this->y;
23285
			$this->x = $ximg;
23286
			if ($this->rtl) {
23287
				$this->x += $w;
23288
			}
23289
			$this->y = $y;
23290
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23291
			$this->x = $bx;
23292
			$this->y = $by;
23293
		}
23294
		if ($link) {
23295
			$this->Link($ximg, $y, $w, $h, $link, 0);
23296
		}
23297
		// set pointer to align the next text/objects
23298
		switch($align) {
23299
			case 'T':{
23300
				$this->y = $y;
23301
				$this->x = $this->img_rb_x;
23302
				break;
23303
			}
23304
			case 'M':{
23305
				$this->y = $y + round($h/2);
23306
				$this->x = $this->img_rb_x;
23307
				break;
23308
			}
23309
			case 'B':{
23310
				$this->y = $this->img_rb_y;
23311
				$this->x = $this->img_rb_x;
23312
				break;
23313
			}
23314
			case 'N':{
23315
				$this->setY($this->img_rb_y);
23316
				break;
23317
			}
23318
			default:{
23319
				// restore pointer to starting position
23320
				$this->x = $gvars['x'];
23321
				$this->y = $gvars['y'];
23322
				$this->page = $gvars['page'];
23323
				$this->current_column = $gvars['current_column'];
23324
				$this->tMargin = $gvars['tMargin'];
23325
				$this->bMargin = $gvars['bMargin'];
23326
				$this->w = $gvars['w'];
23327
				$this->h = $gvars['h'];
23328
				$this->wPt = $gvars['wPt'];
23329
				$this->hPt = $gvars['hPt'];
23330
				$this->fwPt = $gvars['fwPt'];
23331
				$this->fhPt = $gvars['fhPt'];
23332
				break;
23333
			}
23334
		}
23335
		$this->endlinex = $this->img_rb_x;
23336
		// restore page break
23337
		$this->setAutoPageBreak($page_break_mode, $page_break_margin);
23338
		$this->cell_padding = $cell_padding;
23339
	}
23340
23341
	/**
23342
	 * Convert SVG transformation matrix to PDF.
23343
	 * @param array $tm original SVG transformation matrix
23344
	 * @return array transformation matrix
23345
	 * @protected
23346
	 * @since 5.0.000 (2010-05-02)
23347
	 */
23348
	protected function convertSVGtMatrix($tm) {
23349
		$a = $tm[0];
23350
		$b = -$tm[1];
23351
		$c = -$tm[2];
23352
		$d = $tm[3];
23353
		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23354
		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23355
		$x = 0;
23356
		$y = $this->h * $this->k;
23357
		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23358
		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23359
		return array($a, $b, $c, $d, $e, $f);
23360
	}
23361
23362
	/**
23363
	 * Apply SVG graphic transformation matrix.
23364
	 * @param array $tm original SVG transformation matrix
23365
	 * @protected
23366
	 * @since 5.0.000 (2010-05-02)
23367
	 */
23368
	protected function SVGTransform($tm) {
23369
		$this->Transform($this->convertSVGtMatrix($tm));
23370
	}
23371
23372
	/**
23373
	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23374
	 * @param array $svgstyle array of SVG styles to apply
23375
	 * @param array $prevsvgstyle array of previous SVG style
23376
	 * @param int $x X origin of the bounding box
23377
	 * @param int $y Y origin of the bounding box
23378
	 * @param int $w width of the bounding box
23379
	 * @param int $h height of the bounding box
23380
	 * @param string $clip_function clip function
23381
	 * @param array $clip_params array of parameters for clipping function
23382
	 * @return string style
23383
	 * @author Nicola Asuni
23384
	 * @since 5.0.000 (2010-05-02)
23385
	 * @protected
23386
	 */
23387
	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23388
		if ($this->state != 2) {
23389
			 return;
23390
		}
23391
		$objstyle = '';
23392
		$minlen = (0.01 / $this->k); // minimum acceptable length
23393
		if (!isset($svgstyle['opacity'])) {
23394
			return $objstyle;
23395
		}
23396
		// clip-path
23397
		$regs = array();
23398
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23399
			$clip_path = $this->svgclippaths[$regs[1]];
23400
			foreach ($clip_path as $cp) {
23401
				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23402
			}
23403
		}
23404
		// opacity
23405
		if ($svgstyle['opacity'] != 1) {
23406
			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23407
		}
23408
		// color
23409
		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23410
		$this->setFillColorArray($fill_color);
23411
		// text color
23412
		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23413
		$this->setTextColorArray($text_color);
23414
		// clip
23415
		if (preg_match('/rect\(([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)\)/si', $svgstyle['clip'], $regs)) {
23416
			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23417
			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23418
			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23419
			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23420
			$cx = $x + $left;
23421
			$cy = $y + $top;
23422
			$cw = $w - $left - $right;
23423
			$ch = $h - $top - $bottom;
23424
			if ($svgstyle['clip-rule'] == 'evenodd') {
23425
				$clip_rule = 'CNZ';
23426
			} else {
23427
				$clip_rule = 'CEO';
23428
			}
23429
			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23430
		}
23431
		// fill
23432
		$regs = array();
23433
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23434
			// gradient
23435
			$gradient = $this->svggradients[$regs[1]];
23436
			if (isset($gradient['xref'])) {
23437
				// reference to another gradient definition
23438
				$newgradient = $this->svggradients[$gradient['xref']];
23439
				$newgradient['coords'] = $gradient['coords'];
23440
				$newgradient['mode'] = $gradient['mode'];
23441
				$newgradient['type'] = $gradient['type'];
23442
				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23443
				if (isset($gradient['gradientTransform'])) {
23444
					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23445
				}
23446
				$gradient = $newgradient;
23447
			}
23448
			//save current Graphic State
23449
			$this->_outSaveGraphicsState();
23450
			//set clipping area
23451
			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23452
				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23453
				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23454
					list($x, $y, $w, $h) = $bbox;
23455
				}
23456
			}
23457
			if ($gradient['mode'] == 'measure') {
23458
				if (!isset($gradient['coords'][4])) {
23459
					$gradient['coords'][4] = 0.5;
23460
				}
23461
				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23462
					$gtm = $gradient['gradientTransform'];
23463
					// apply transformation matrix
23464
					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23465
					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23466
					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23467
					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23468
					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23469
					$gradient['coords'][0] = $xa;
23470
					$gradient['coords'][1] = $ya;
23471
					$gradient['coords'][2] = $xb;
23472
					$gradient['coords'][3] = $yb;
23473
					$gradient['coords'][4] = $r;
23474
				}
23475
				// convert SVG coordinates to user units
23476
				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23477
				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23478
				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23479
				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23480
				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23481
				if ($w <= $minlen) {
23482
					$w = $minlen;
23483
				}
23484
				if ($h <= $minlen) {
23485
					$h = $minlen;
23486
				}
23487
				// shift units
23488
				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23489
					// convert to SVG coordinate system
23490
					$gradient['coords'][0] += $x;
23491
					$gradient['coords'][1] += $y;
23492
					$gradient['coords'][2] += $x;
23493
					$gradient['coords'][3] += $y;
23494
				}
23495
				// calculate percentages
23496
				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23497
				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23498
				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23499
				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23500
				$gradient['coords'][4] /= $w;
23501
			} elseif ($gradient['mode'] == 'percentage') {
23502
				foreach($gradient['coords'] as $key => $val) {
23503
					$gradient['coords'][$key] = (intval($val) / 100);
23504
					if ($val < 0) {
23505
						$gradient['coords'][$key] = 0;
23506
					} elseif ($val > 1) {
23507
						$gradient['coords'][$key] = 1;
23508
					}
23509
				}
23510
			}
23511
			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23512
				// single color (no shading)
23513
				$gradient['coords'][0] = 1;
23514
				$gradient['coords'][1] = 0;
23515
				$gradient['coords'][2] = 0.999;
23516
				$gradient['coords'][3] = 0;
23517
			}
23518
			// swap Y coordinates
23519
			$tmp = $gradient['coords'][1];
23520
			$gradient['coords'][1] = $gradient['coords'][3];
23521
			$gradient['coords'][3] = $tmp;
23522
			// set transformation map for gradient
23523
			$cy = ($this->h - $y);
23524
			if ($gradient['type'] == 3) {
23525
				// circular gradient
23526
				$cy -= ($gradient['coords'][1] * ($w + $h));
23527
				$h = $w = max($w, $h);
23528
			} else {
23529
				$cy -= $h;
23530
			}
23531
			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23532
			if ((is_array($gradient['stops']) || $gradient['stops'] instanceof Countable) && count($gradient['stops']) > 1) {
23533
				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops']);
23534
			}
23535
		} elseif ($svgstyle['fill'] != 'none') {
23536
			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23537
			if ($svgstyle['fill-opacity'] != 1) {
23538
				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23539
			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['fill'], $rgba_matches)) {
23540
				$this->setAlpha($this->alpha['CA'], 'Normal', $rgba_matches[1], false);
23541
			}
23542
			$this->setFillColorArray($fill_color);
23543
			if ($svgstyle['fill-rule'] == 'evenodd') {
23544
				$objstyle .= 'F*';
23545
			} else {
23546
				$objstyle .= 'F';
23547
			}
23548
		}
23549
		// stroke
23550
		if ($svgstyle['stroke'] != 'none') {
23551
			if ($svgstyle['stroke-opacity'] != 1) {
23552
				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23553
			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23554
				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23555
			}
23556
			$stroke_style = array(
23557
				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23558
				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23559
				'cap' => $svgstyle['stroke-linecap'],
23560
				'join' => $svgstyle['stroke-linejoin']
23561
				);
23562
			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23563
				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23564
			}
23565
			$this->setLineStyle($stroke_style);
23566
			$objstyle .= 'D';
23567
		}
23568
		// font
23569
		$regs = array();
23570
		if (!empty($svgstyle['font'])) {
23571
			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23572
				$font_family = $this->getFontFamilyName($regs[1]);
23573
			} else {
23574
				$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23575
			}
23576
			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23577
				$font_size = trim($regs[1]);
23578
			} else {
23579
				$font_size = $svgstyle['font-size'];
23580
			}
23581
			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23582
				$font_style = trim($regs[1]);
23583
			} else {
23584
				$font_style = $svgstyle['font-style'];
23585
			}
23586
			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23587
				$font_weight = trim($regs[1]);
23588
			} else {
23589
				$font_weight = $svgstyle['font-weight'];
23590
			}
23591
			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23592
				$font_stretch = trim($regs[1]);
23593
			} else {
23594
				$font_stretch = $svgstyle['font-stretch'];
23595
			}
23596
			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23597
				$font_spacing = trim($regs[1]);
23598
			} else {
23599
				$font_spacing = $svgstyle['letter-spacing'];
23600
			}
23601
		} else {
23602
			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23603
			$font_size = $svgstyle['font-size'];
23604
			$font_style = $svgstyle['font-style'];
23605
			$font_weight = $svgstyle['font-weight'];
23606
			$font_stretch = $svgstyle['font-stretch'];
23607
			$font_spacing = $svgstyle['letter-spacing'];
23608
		}
23609
		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23610
		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23611
		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23612
		switch ($font_style) {
23613
			case 'italic': {
23614
				$font_style = 'I';
23615
				break;
23616
			}
23617
			case 'oblique': {
23618
				$font_style = 'I';
23619
				break;
23620
			}
23621
			default:
23622
			case 'normal': {
23623
				$font_style = '';
23624
				break;
23625
			}
23626
		}
23627
		switch ($font_weight) {
23628
			case 'bold':
23629
			case 'bolder': {
23630
				$font_style .= 'B';
23631
				break;
23632
			}
23633
			case 'normal': {
23634
				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23635
					$font_family = substr($font_family, 0, -2).'I';
23636
				} elseif (substr($font_family, -1) == 'B') {
23637
					$font_family = substr($font_family, 0, -1);
23638
				}
23639
				break;
23640
			}
23641
		}
23642
		switch ($svgstyle['text-decoration']) {
23643
			case 'underline': {
23644
				$font_style .= 'U';
23645
				break;
23646
			}
23647
			case 'overline': {
23648
				$font_style .= 'O';
23649
				break;
23650
			}
23651
			case 'line-through': {
23652
				$font_style .= 'D';
23653
				break;
23654
			}
23655
			default:
23656
			case 'none': {
23657
				break;
23658
			}
23659
		}
23660
		$this->setFont($font_family, $font_style, $font_size);
23661
		$this->setFontStretching($font_stretch);
23662
		$this->setFontSpacing($font_spacing);
23663
		return $objstyle;
23664
	}
23665
23666
	/**
23667
	 * Draws an SVG path
23668
	 * @param string $d attribute d of the path SVG element
23669
	 * @param string $style Style of rendering. Possible values are:
23670
	 * <ul>
23671
	 *	 <li>D or empty string: Draw (default).</li>
23672
	 *	 <li>F: Fill.</li>
23673
	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23674
	 *	 <li>DF or FD: Draw and fill.</li>
23675
	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23676
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23677
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23678
	 * </ul>
23679
	 * @return array of container box measures (x, y, w, h)
23680
	 * @author Nicola Asuni
23681
	 * @since 5.0.000 (2010-05-02)
23682
	 * @protected
23683
	 */
23684
	protected function SVGPath($d, $style='') {
23685
		if ($this->state != 2) {
23686
			return;
23687
		}
23688
		// set fill/stroke style
23689
		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23690
		if (empty($op)) {
23691
			return;
23692
		}
23693
		$paths = array();
23694
		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23695
		$d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23696
		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23697
		$x = 0;
23698
		$y = 0;
23699
		$x1 = 0;
23700
		$y1 = 0;
23701
		$x2 = 0;
23702
		$y2 = 0;
23703
		$xmin = 2147483647;
23704
		$xmax = 0;
23705
		$ymin = 2147483647;
23706
		$ymax = 0;
23707
		$xinitial = 0;
23708
		$yinitial = 0;
23709
		$relcoord = false;
23710
		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23711
		$firstcmd = true; // used to print first point
23712
		// draw curve pieces
23713
		foreach ($paths as $key => $val) {
23714
			// get curve type
23715
			$cmd = trim($val[1]);
23716
			if (strtolower($cmd) == $cmd) {
23717
				// use relative coordinated instead of absolute
23718
				$relcoord = true;
23719
				$xoffset = $x;
23720
				$yoffset = $y;
23721
			} else {
23722
				$relcoord = false;
23723
				$xoffset = 0;
23724
				$yoffset = 0;
23725
			}
23726
			$params = array();
23727
			if (isset($val[2])) {
23728
				// get curve parameters
23729
				preg_match_all('/-?\d*\.?\d+/', trim($val[2]), $matches);
23730
				$rawparams = $matches[0];
23731
				$params = array();
23732
				foreach ($rawparams as $ck => $cp) {
23733
					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23734
					if (abs($params[$ck]) < $minlen) {
23735
						// approximate little values to zero
23736
						$params[$ck] = 0;
23737
					}
23738
				}
23739
			}
23740
			// store current origin point
23741
			$x0 = $x;
23742
			$y0 = $y;
23743
			switch (strtoupper($cmd)) {
23744
				case 'M': { // moveto
23745
					foreach ($params as $ck => $cp) {
23746
						if (($ck % 2) == 0) {
23747
							$x = $cp + $xoffset;
23748
						} else {
23749
							$y = $cp + $yoffset;
23750
							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23751
								if ($ck == 1) {
23752
									$this->_outPoint($x, $y);
23753
									$firstcmd = false;
23754
									$xinitial = $x;
23755
									$yinitial = $y;
23756
								} else {
23757
									$this->_outLine($x, $y);
23758
								}
23759
								$x0 = $x;
23760
								$y0 = $y;
23761
							}
23762
							$xmin = min($xmin, $x);
23763
							$ymin = min($ymin, $y);
23764
							$xmax = max($xmax, $x);
23765
							$ymax = max($ymax, $y);
23766
							if ($relcoord) {
23767
								$xoffset = $x;
23768
								$yoffset = $y;
23769
							}
23770
						}
23771
					}
23772
					break;
23773
				}
23774
				case 'L': { // lineto
23775
					foreach ($params as $ck => $cp) {
23776
						if (($ck % 2) == 0) {
23777
							$x = $cp + $xoffset;
23778
						} else {
23779
							$y = $cp + $yoffset;
23780
							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23781
								$this->_outLine($x, $y);
23782
								$x0 = $x;
23783
								$y0 = $y;
23784
							}
23785
							$xmin = min($xmin, $x);
23786
							$ymin = min($ymin, $y);
23787
							$xmax = max($xmax, $x);
23788
							$ymax = max($ymax, $y);
23789
							if ($relcoord) {
23790
								$xoffset = $x;
23791
								$yoffset = $y;
23792
							}
23793
						}
23794
					}
23795
					break;
23796
				}
23797
				case 'H': { // horizontal lineto
23798
					foreach ($params as $ck => $cp) {
23799
						$x = $cp + $xoffset;
23800
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23801
							$this->_outLine($x, $y);
23802
							$x0 = $x;
23803
							$y0 = $y;
23804
						}
23805
						$xmin = min($xmin, $x);
23806
						$xmax = max($xmax, $x);
23807
						if ($relcoord) {
23808
							$xoffset = $x;
23809
						}
23810
					}
23811
					break;
23812
				}
23813
				case 'V': { // vertical lineto
23814
					foreach ($params as $ck => $cp) {
23815
						$y = $cp + $yoffset;
23816
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23817
							$this->_outLine($x, $y);
23818
							$x0 = $x;
23819
							$y0 = $y;
23820
						}
23821
						$ymin = min($ymin, $y);
23822
						$ymax = max($ymax, $y);
23823
						if ($relcoord) {
23824
							$yoffset = $y;
23825
						}
23826
					}
23827
					break;
23828
				}
23829
				case 'C': { // curveto
23830
					foreach ($params as $ck => $cp) {
23831
						$params[$ck] = $cp;
23832
						if ((($ck + 1) % 6) == 0) {
23833
							$x1 = $params[($ck - 5)] + $xoffset;
23834
							$y1 = $params[($ck - 4)] + $yoffset;
23835
							$x2 = $params[($ck - 3)] + $xoffset;
23836
							$y2 = $params[($ck - 2)] + $yoffset;
23837
							$x = $params[($ck - 1)] + $xoffset;
23838
							$y = $params[($ck)] + $yoffset;
23839
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23840
							$xmin = min($xmin, $x, $x1, $x2);
23841
							$ymin = min($ymin, $y, $y1, $y2);
23842
							$xmax = max($xmax, $x, $x1, $x2);
23843
							$ymax = max($ymax, $y, $y1, $y2);
23844
							if ($relcoord) {
23845
								$xoffset = $x;
23846
								$yoffset = $y;
23847
							}
23848
						}
23849
					}
23850
					break;
23851
				}
23852
				case 'S': { // shorthand/smooth curveto
23853
					foreach ($params as $ck => $cp) {
23854
						$params[$ck] = $cp;
23855
						if ((($ck + 1) % 4) == 0) {
23856
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23857
								$x1 = (2 * $x) - $x2;
23858
								$y1 = (2 * $y) - $y2;
23859
							} else {
23860
								$x1 = $x;
23861
								$y1 = $y;
23862
							}
23863
							$x2 = $params[($ck - 3)] + $xoffset;
23864
							$y2 = $params[($ck - 2)] + $yoffset;
23865
							$x = $params[($ck - 1)] + $xoffset;
23866
							$y = $params[($ck)] + $yoffset;
23867
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23868
							$xmin = min($xmin, $x, $x1, $x2);
23869
							$ymin = min($ymin, $y, $y1, $y2);
23870
							$xmax = max($xmax, $x, $x1, $x2);
23871
							$ymax = max($ymax, $y, $y1, $y2);
23872
							if ($relcoord) {
23873
								$xoffset = $x;
23874
								$yoffset = $y;
23875
							}
23876
						}
23877
					}
23878
					break;
23879
				}
23880
				case 'Q': { // quadratic Bezier curveto
23881
					foreach ($params as $ck => $cp) {
23882
						$params[$ck] = $cp;
23883
						if ((($ck + 1) % 4) == 0) {
23884
							// convert quadratic points to cubic points
23885
							$x1 = $params[($ck - 3)] + $xoffset;
23886
							$y1 = $params[($ck - 2)] + $yoffset;
23887
							$xa = ($x + (2 * $x1)) / 3;
23888
							$ya = ($y + (2 * $y1)) / 3;
23889
							$x = $params[($ck - 1)] + $xoffset;
23890
							$y = $params[($ck)] + $yoffset;
23891
							$xb = ($x + (2 * $x1)) / 3;
23892
							$yb = ($y + (2 * $y1)) / 3;
23893
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23894
							$xmin = min($xmin, $x, $xa, $xb);
23895
							$ymin = min($ymin, $y, $ya, $yb);
23896
							$xmax = max($xmax, $x, $xa, $xb);
23897
							$ymax = max($ymax, $y, $ya, $yb);
23898
							if ($relcoord) {
23899
								$xoffset = $x;
23900
								$yoffset = $y;
23901
							}
23902
						}
23903
					}
23904
					break;
23905
				}
23906
				case 'T': { // shorthand/smooth quadratic Bezier curveto
23907
					foreach ($params as $ck => $cp) {
23908
						$params[$ck] = $cp;
23909
						if (($ck % 2) != 0) {
23910
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23911
								$x1 = (2 * $x) - $x1;
23912
								$y1 = (2 * $y) - $y1;
23913
							} else {
23914
								$x1 = $x;
23915
								$y1 = $y;
23916
							}
23917
							// convert quadratic points to cubic points
23918
							$xa = ($x + (2 * $x1)) / 3;
23919
							$ya = ($y + (2 * $y1)) / 3;
23920
							$x = $params[($ck - 1)] + $xoffset;
23921
							$y = $params[($ck)] + $yoffset;
23922
							$xb = ($x + (2 * $x1)) / 3;
23923
							$yb = ($y + (2 * $y1)) / 3;
23924
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23925
							$xmin = min($xmin, $x, $xa, $xb);
23926
							$ymin = min($ymin, $y, $ya, $yb);
23927
							$xmax = max($xmax, $x, $xa, $xb);
23928
							$ymax = max($ymax, $y, $ya, $yb);
23929
							if ($relcoord) {
23930
								$xoffset = $x;
23931
								$yoffset = $y;
23932
							}
23933
						}
23934
					}
23935
					break;
23936
				}
23937
				case 'A': { // elliptical arc
23938
					foreach ($params as $ck => $cp) {
23939
						$params[$ck] = $cp;
23940
						if ((($ck + 1) % 7) == 0) {
23941
							$x0 = $x;
23942
							$y0 = $y;
23943
							$rx = max(abs($params[($ck - 6)]), .000000001);
23944
							$ry = max(abs($params[($ck - 5)]), .000000001);
23945
							$ang = -$rawparams[($ck - 4)];
23946
							$angle = deg2rad($ang);
23947
							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23948
							$fs = $rawparams[($ck - 2)]; // sweep-flag
23949
							$x = $params[($ck - 1)] + $xoffset;
23950
							$y = $params[$ck] + $yoffset;
23951
							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23952
								// endpoints are almost identical
23953
								$xmin = min($xmin, $x);
23954
								$ymin = min($ymin, $y);
23955
								$xmax = max($xmax, $x);
23956
								$ymax = max($ymax, $y);
23957
							} else {
23958
								$cos_ang = cos($angle);
23959
								$sin_ang = sin($angle);
23960
								$a = (($x0 - $x) / 2);
23961
								$b = (($y0 - $y) / 2);
23962
								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23963
								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23964
								$rx2 = $rx * $rx;
23965
								$ry2 = $ry * $ry;
23966
								$xa2 = $xa * $xa;
23967
								$ya2 = $ya * $ya;
23968
								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23969
								if ($delta > 1) {
23970
									$rx *= sqrt($delta);
23971
									$ry *= sqrt($delta);
23972
									$rx2 = $rx * $rx;
23973
									$ry2 = $ry * $ry;
23974
								}
23975
								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23976
								if ($numerator < 0) {
23977
									$root = 0;
23978
								} else {
23979
									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23980
								}
23981
								if ($fa == $fs){
23982
									$root *= -1;
23983
								}
23984
								$cax = $root * (($rx * $ya) / $ry);
23985
								$cay = -$root * (($ry * $xa) / $rx);
23986
								// coordinates of ellipse center
23987
								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23988
								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23989
								// get angles
23990
								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23991
								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23992
								if (($fs == 0) AND ($dang > 0)) {
23993
									$dang -= (2 * M_PI);
23994
								} elseif (($fs == 1) AND ($dang < 0)) {
23995
									$dang += (2 * M_PI);
23996
								}
23997
								$angf = $angs - $dang;
23998
								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23999
									// reverse angles
24000
									$tmp = $angs;
24001
									$angs = $angf;
24002
									$angf = $tmp;
24003
								}
24004
								$angs = round(rad2deg($angs), 6);
24005
								$angf = round(rad2deg($angf), 6);
24006
								// covent angles to positive values
24007
								if (($angs < 0) AND ($angf < 0)) {
24008
									$angs += 360;
24009
									$angf += 360;
24010
								}
24011
								$pie = false;
24012
								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
24013
									$pie = true;
24014
								}
24015
								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
24016
								$xmin = min($xmin, $x, $axmin);
24017
								$ymin = min($ymin, $y, $aymin);
24018
								$xmax = max($xmax, $x, $axmax);
24019
								$ymax = max($ymax, $y, $aymax);
24020
							}
24021
							if ($relcoord) {
24022
								$xoffset = $x;
24023
								$yoffset = $y;
24024
							}
24025
						}
24026
					}
24027
					break;
24028
				}
24029
				case 'Z': {
24030
					$this->_out('h');
24031
					$x = $x0 = $xinitial;
24032
					$y = $y0 = $yinitial;
24033
					break;
24034
				}
24035
			}
24036
			$firstcmd = false;
24037
		} // end foreach
24038
		$this->_out($op);
24039
		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
24040
	}
24041
24042
	/**
24043
	 * Return the tag name without the namespace
24044
	 * @param string $name Tag name
24045
	 * @protected
24046
	 */
24047
	protected function removeTagNamespace($name) {
24048
		if(strpos($name, ':') !== false) {
24049
			$parts = explode(':', $name);
24050
			return $parts[(sizeof($parts) - 1)];
24051
		}
24052
		return $name;
24053
	}
24054
24055
	/**
24056
	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
24057
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24058
	 * @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.
24059
	 * @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.
24060
	 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
24061
	 * @author Nicola Asuni
24062
	 * @since 5.0.000 (2010-05-02)
24063
	 * @protected
24064
	 */
24065
	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
24066
		$name = $this->removeTagNamespace($name);
24067
		// check if we are in clip mode
24068
		if ($this->svgclipmode) {
24069
			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
24070
			return;
24071
		}
24072
		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
24073
			if (isset($attribs['id'])) {
24074
				$attribs['child_elements'] = array();
24075
				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
24076
				return;
24077
			}
24078
			if (end($this->svgdefs) !== FALSE) {
24079
				$last_svgdefs_id = key($this->svgdefs);
24080
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24081
					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
24082
					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
24083
					return;
24084
				}
24085
			}
24086
			return;
24087
		}
24088
		$clipping = false;
24089
		if ($parser == 'clip-path') {
24090
			// set clipping mode
24091
			$clipping = true;
24092
		}
24093
		// get styling properties
24094
		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
24095
		$svgstyle = $this->svgstyles[0]; // set default style
24096
		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
24097
			// default fill attribute for clipping
24098
			$attribs['fill'] = 'none';
24099
		}
24100
		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
24101
			// fix style for regular expression
24102
			$attribs['style'] = ';'.$attribs['style'];
24103
		}
24104
		foreach ($prev_svgstyle as $key => $val) {
24105
			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
24106
				// inherit previous value
24107
				$svgstyle[$key] = $val;
24108
			}
24109
			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
24110
				// specific attribute settings
24111
				if ($attribs[$key] == 'inherit') {
24112
					$svgstyle[$key] = $val;
24113
				} else {
24114
					$svgstyle[$key] = $attribs[$key];
24115
				}
24116
			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
24117
				// CSS style syntax
24118
				$attrval = array();
24119
				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
24120
					if ($attrval[1] == 'inherit') {
24121
						$svgstyle[$key] = $val;
24122
					} else {
24123
						$svgstyle[$key] = $attrval[1];
24124
					}
24125
				}
24126
			}
24127
		}
24128
		// transformation matrix
24129
		if (!empty($ctm)) {
24130
			$tm = $ctm;
24131
		} else {
24132
			$tm = array(1,0,0,1,0,0);
24133
		}
24134
		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
24135
			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
24136
		}
24137
		$svgstyle['transfmatrix'] = $tm;
24138
		$invisible = false;
24139
		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
24140
			// the current graphics element is invisible (nothing is painted)
24141
			$invisible = true;
24142
		}
24143
		// process tag
24144
		switch($name) {
24145
			case 'defs': {
24146
				$this->svgdefsmode = true;
24147
				break;
24148
			}
24149
			// clipPath
24150
			case 'clipPath': {
24151
				if ($invisible) {
24152
					break;
24153
				}
24154
				$this->svgclipmode = true;
24155
				if (!isset($attribs['id'])) {
24156
					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
24157
				}
24158
				$this->svgclipid = $attribs['id'];
24159
				$this->svgclippaths[$this->svgclipid] = array();
24160
				$this->svgcliptm[$this->svgclipid] = $tm;
24161
				break;
24162
			}
24163
			case 'svg': {
24164
				// start of SVG object
24165
				if(++$this->svg_tag_depth <= 1) {
24166
					break;
24167
				}
24168
				// inner SVG
24169
				array_push($this->svgstyles, $svgstyle);
24170
				$this->StartTransform();
24171
				$svgX = (isset($attribs['x'])?$attribs['x']:0);
24172
				$svgY = (isset($attribs['y'])?$attribs['y']:0);
24173
				$svgW = (isset($attribs['width'])?$attribs['width']:0);
24174
				$svgH = (isset($attribs['height'])?$attribs['height']:0);
24175
				// set x, y position using transform matrix
24176
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24177
				$this->SVGTransform($tm);
24178
				// set clipping for width and height
24179
				$x = 0;
24180
				$y = 0;
24181
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
24182
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
24183
				// draw clipping rect
24184
				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24185
				// parse viewbox, calculate extra transformation matrix
24186
				if (isset($attribs['viewBox'])) {
24187
					$tmp = array();
24188
					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24189
					$tmp = $tmp[0];
24190
					if (sizeof($tmp) == 4) {
24191
						$vx = $tmp[0];
24192
						$vy = $tmp[1];
24193
						$vw = $tmp[2];
24194
						$vh = $tmp[3];
24195
						// get aspect ratio
24196
						$tmp = array();
24197
						$aspectX = 'xMid';
24198
						$aspectY = 'YMid';
24199
						$fit = 'meet';
24200
						if (isset($attribs['preserveAspectRatio'])) {
24201
							if($attribs['preserveAspectRatio'] == 'none') {
24202
								$fit = 'none';
24203
							} else {
24204
								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24205
								$tmp = $tmp[0];
24206
								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24207
									$aspectX = substr($tmp[0], 0, 4);
24208
									$aspectY = substr($tmp[0], 4, 4);
24209
									$fit = $tmp[1];
24210
								}
24211
							}
24212
						}
24213
						$wr = ($svgW / $vw);
24214
						$hr = ($svgH / $vh);
24215
						$ax = $ay = 0;
24216
						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24217
							if ($aspectX == 'xMax') {
24218
								$ax = (($vw * ($wr / $hr)) - $vw);
24219
							}
24220
							if ($aspectX == 'xMid') {
24221
								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24222
							}
24223
							$wr = $hr;
24224
						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24225
							if ($aspectY == 'YMax') {
24226
								$ay = (($vh * ($hr / $wr)) - $vh);
24227
							}
24228
							if ($aspectY == 'YMid') {
24229
								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24230
							}
24231
							$hr = $wr;
24232
						}
24233
						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24234
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24235
						$this->SVGTransform($tm);
24236
					}
24237
				}
24238
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24239
				break;
24240
			}
24241
			case 'g': {
24242
				// group together related graphics elements
24243
				array_push($this->svgstyles, $svgstyle);
24244
				$this->StartTransform();
24245
				$x = (isset($attribs['x'])?$attribs['x']:0);
24246
				$y = (isset($attribs['y'])?$attribs['y']:0);
24247
				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24248
				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24249
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24250
				$this->SVGTransform($tm);
24251
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24252
				break;
24253
			}
24254
			case 'linearGradient': {
24255
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24256
					break;
24257
				}
24258
				if (!isset($attribs['id'])) {
24259
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24260
				}
24261
				$this->svggradientid = $attribs['id'];
24262
				$this->svggradients[$this->svggradientid] = array();
24263
				$this->svggradients[$this->svggradientid]['type'] = 2;
24264
				$this->svggradients[$this->svggradientid]['stops'] = array();
24265
				if (isset($attribs['gradientUnits'])) {
24266
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24267
				} else {
24268
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24269
				}
24270
				//$attribs['spreadMethod']
24271
				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24272
					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24273
						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24274
						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24275
						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24276
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24277
				} else {
24278
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24279
				}
24280
				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24281
				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24282
				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24283
				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24284
				if (isset($attribs['gradientTransform'])) {
24285
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24286
				}
24287
				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24288
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24289
					// gradient is defined on another place
24290
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24291
				}
24292
				break;
24293
			}
24294
			case 'radialGradient': {
24295
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24296
					break;
24297
				}
24298
				if (!isset($attribs['id'])) {
24299
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24300
				}
24301
				$this->svggradientid = $attribs['id'];
24302
				$this->svggradients[$this->svggradientid] = array();
24303
				$this->svggradients[$this->svggradientid]['type'] = 3;
24304
				$this->svggradients[$this->svggradientid]['stops'] = array();
24305
				if (isset($attribs['gradientUnits'])) {
24306
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24307
				} else {
24308
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24309
				}
24310
				//$attribs['spreadMethod']
24311
				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24312
					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24313
					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24314
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24315
				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24316
					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24317
				} else {
24318
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24319
				}
24320
				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24321
				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24322
				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24323
				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24324
				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24325
				if (isset($attribs['gradientTransform'])) {
24326
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24327
				}
24328
				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24329
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24330
					// gradient is defined on another place
24331
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24332
				}
24333
				break;
24334
			}
24335
			case 'stop': {
24336
				// gradient stops
24337
				if (substr($attribs['offset'], -1) == '%') {
24338
					$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24339
				} else {
24340
					$offset = floatval($attribs['offset']);
24341
					if ($offset > 1) {
24342
						$offset /= 100;
24343
					}
24344
				}
24345
				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24346
				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24347
				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24348
				break;
24349
			}
24350
			// paths
24351
			case 'path': {
24352
				if ($invisible) {
24353
					break;
24354
				}
24355
				if (isset($attribs['d'])) {
24356
					$d = trim($attribs['d']);
24357
					if (!empty($d)) {
24358
						$x = (isset($attribs['x'])?$attribs['x']:0);
24359
						$y = (isset($attribs['y'])?$attribs['y']:0);
24360
						$w = (isset($attribs['width'])?$attribs['width']:1);
24361
						$h = (isset($attribs['height'])?$attribs['height']:1);
24362
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24363
						if ($clipping) {
24364
							$this->SVGTransform($tm);
24365
							$this->SVGPath($d, 'CNZ');
24366
						} else {
24367
							$this->StartTransform();
24368
							$this->SVGTransform($tm);
24369
							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24370
							if (!empty($obstyle)) {
24371
								$this->SVGPath($d, $obstyle);
24372
							}
24373
							$this->StopTransform();
24374
						}
24375
					}
24376
				}
24377
				break;
24378
			}
24379
			// shapes
24380
			case 'rect': {
24381
				if ($invisible) {
24382
					break;
24383
				}
24384
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24385
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24386
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24387
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24388
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24389
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24390
				if ($clipping) {
24391
					$this->SVGTransform($tm);
24392
					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24393
				} else {
24394
					$this->StartTransform();
24395
					$this->SVGTransform($tm);
24396
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24397
					if (!empty($obstyle)) {
24398
						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24399
					}
24400
					$this->StopTransform();
24401
				}
24402
				break;
24403
			}
24404
			case 'circle': {
24405
				if ($invisible) {
24406
					break;
24407
				}
24408
				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24409
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24410
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24411
				$x = ($cx - $r);
24412
				$y = ($cy - $r);
24413
				$w = (2 * $r);
24414
				$h = $w;
24415
				if ($clipping) {
24416
					$this->SVGTransform($tm);
24417
					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24418
				} else {
24419
					$this->StartTransform();
24420
					$this->SVGTransform($tm);
24421
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24422
					if (!empty($obstyle)) {
24423
						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24424
					}
24425
					$this->StopTransform();
24426
				}
24427
				break;
24428
			}
24429
			case 'ellipse': {
24430
				if ($invisible) {
24431
					break;
24432
				}
24433
				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24434
				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24435
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24436
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24437
				$x = ($cx - $rx);
24438
				$y = ($cy - $ry);
24439
				$w = (2 * $rx);
24440
				$h = (2 * $ry);
24441
				if ($clipping) {
24442
					$this->SVGTransform($tm);
24443
					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24444
				} else {
24445
					$this->StartTransform();
24446
					$this->SVGTransform($tm);
24447
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24448
					if (!empty($obstyle)) {
24449
						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24450
					}
24451
					$this->StopTransform();
24452
				}
24453
				break;
24454
			}
24455
			case 'line': {
24456
				if ($invisible) {
24457
					break;
24458
				}
24459
				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24460
				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24461
				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24462
				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24463
				$x = $x1;
24464
				$y = $y1;
24465
				$w = abs($x2 - $x1);
24466
				$h = abs($y2 - $y1);
24467
				if (!$clipping) {
24468
					$this->StartTransform();
24469
					$this->SVGTransform($tm);
24470
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24471
					$this->Line($x1, $y1, $x2, $y2);
24472
					$this->StopTransform();
24473
				}
24474
				break;
24475
			}
24476
			case 'polyline':
24477
			case 'polygon': {
24478
				if ($invisible) {
24479
					break;
24480
				}
24481
				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24482
				$points = trim($points);
24483
				// note that point may use a complex syntax not covered here
24484
				$points = preg_split('/[\,\s]+/si', $points);
24485
				if (count($points) < 4) {
24486
					break;
24487
				}
24488
				$p = array();
24489
				$xmin = 2147483647;
24490
				$xmax = 0;
24491
				$ymin = 2147483647;
24492
				$ymax = 0;
24493
				foreach ($points as $key => $val) {
24494
					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24495
					if (($key % 2) == 0) {
24496
						// X coordinate
24497
						$xmin = min($xmin, $p[$key]);
24498
						$xmax = max($xmax, $p[$key]);
24499
					} else {
24500
						// Y coordinate
24501
						$ymin = min($ymin, $p[$key]);
24502
						$ymax = max($ymax, $p[$key]);
24503
					}
24504
				}
24505
				$x = $xmin;
24506
				$y = $ymin;
24507
				$w = ($xmax - $xmin);
24508
				$h = ($ymax - $ymin);
24509
				if ($name == 'polyline') {
24510
					$this->StartTransform();
24511
					$this->SVGTransform($tm);
24512
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24513
					if (!empty($obstyle)) {
24514
						$this->PolyLine($p, $obstyle, array(), array());
24515
					}
24516
					$this->StopTransform();
24517
				} else { // polygon
24518
					if ($clipping) {
24519
						$this->SVGTransform($tm);
24520
						$this->Polygon($p, 'CNZ', array(), array(), true);
24521
					} else {
24522
						$this->StartTransform();
24523
						$this->SVGTransform($tm);
24524
						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24525
						if (!empty($obstyle)) {
24526
							$this->Polygon($p, $obstyle, array(), array(), true);
24527
						}
24528
						$this->StopTransform();
24529
					}
24530
				}
24531
				break;
24532
			}
24533
			// image
24534
			case 'image': {
24535
				if ($invisible) {
24536
					break;
24537
				}
24538
				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24539
					break;
24540
				}
24541
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24542
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24543
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24544
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24545
				$img = $attribs['xlink:href'];
24546
				if (!$clipping) {
24547
					$this->StartTransform();
24548
					$this->SVGTransform($tm);
24549
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24550
					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24551
						// embedded image encoded as base64
24552
						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24553
					} else {
24554
						// fix image path
24555
						if ($this->isRelativePath($img) || $this->hasExtForbiddenProtocol($img)) {
24556
							break;
24557
						}
24558
						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24559
							// replace relative path with full server path
24560
							$img = $this->svgdir.'/'.$img;
24561
						}
24562
						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24563
							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24564
							if (($findroot === false) OR ($findroot > 1)) {
24565
								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24566
									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24567
								} else {
24568
									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24569
								}
24570
							}
24571
						}
24572
						$img = urldecode($img);
24573
						$testscrtype = @parse_url($img);
24574
						if (empty($testscrtype['query'])) {
24575
							// convert URL to server path
24576
							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24577
						} elseif (preg_match('|^https?://|', $img) !== 1) {
24578
							// convert server path to URL
24579
							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24580
						}
24581
					}
24582
					// get image type
24583
					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24584
					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24585
						$this->ImageEps($img, $x, $y, $w, $h);
24586
					} elseif ($imgtype == 'svg') {
24587
						// store SVG vars
24588
						$svggradients = $this->svggradients;
24589
						$svggradientid = $this->svggradientid;
24590
						$svgdefsmode = $this->svgdefsmode;
24591
						$svgdefs = $this->svgdefs;
24592
						$svgclipmode = $this->svgclipmode;
24593
						$svgclippaths = $this->svgclippaths;
24594
						$svgcliptm = $this->svgcliptm;
24595
						$svgclipid = $this->svgclipid;
24596
						$svgtext = $this->svgtext;
24597
						$svgtextmode = $this->svgtextmode;
24598
						$this->ImageSVG($img, $x, $y, $w, $h);
24599
						// restore SVG vars
24600
						$this->svggradients = $svggradients;
24601
						$this->svggradientid = $svggradientid;
24602
						$this->svgdefsmode = $svgdefsmode;
24603
						$this->svgdefs = $svgdefs;
24604
						$this->svgclipmode = $svgclipmode;
24605
						$this->svgclippaths = $svgclippaths;
24606
						$this->svgcliptm = $svgcliptm;
24607
						$this->svgclipid = $svgclipid;
24608
						$this->svgtext = $svgtext;
24609
						$this->svgtextmode = $svgtextmode;
24610
					} else {
24611
						$this->Image($img, $x, $y, $w, $h);
24612
					}
24613
					$this->StopTransform();
24614
				}
24615
				break;
24616
			}
24617
			// text
24618
			case 'text':
24619
			case 'tspan': {
24620
				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24621
					// @TODO: unsupported feature
24622
				}
24623
				// only basic support - advanced features must be implemented
24624
				$this->svgtextmode['invisible'] = $invisible;
24625
				if ($invisible) {
24626
					break;
24627
				}
24628
				array_push($this->svgstyles, $svgstyle);
24629
				if (isset($attribs['x'])) {
24630
					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24631
				} elseif ($name == 'tspan') {
24632
					$x = $this->x;
24633
				} else {
24634
					$x = 0;
24635
				}
24636
				if (isset($attribs['dx'])) {
24637
					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24638
				}
24639
				if (isset($attribs['y'])) {
24640
					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24641
				} elseif ($name == 'tspan') {
24642
					$y = $this->y;
24643
				} else {
24644
					$y = 0;
24645
				}
24646
				if (isset($attribs['dy'])) {
24647
					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24648
				}
24649
				$svgstyle['text-color'] = $svgstyle['fill'];
24650
				$this->svgtext = '';
24651
				if (isset($svgstyle['text-anchor'])) {
24652
					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24653
				} else {
24654
					$this->svgtextmode['text-anchor'] = 'start';
24655
				}
24656
				if (isset($svgstyle['direction'])) {
24657
					if ($svgstyle['direction'] == 'rtl') {
24658
						$this->svgtextmode['rtl'] = true;
24659
					} else {
24660
						$this->svgtextmode['rtl'] = false;
24661
					}
24662
				} else {
24663
					$this->svgtextmode['rtl'] = false;
24664
				}
24665
				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24666
					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24667
				} else {
24668
					$this->svgtextmode['stroke'] = false;
24669
				}
24670
				$this->StartTransform();
24671
				$this->SVGTransform($tm);
24672
				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24673
				$this->x = $x;
24674
				$this->y = $y;
24675
				break;
24676
			}
24677
			// use
24678
			case 'use': {
24679
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24680
					$svgdefid = substr($attribs['xlink:href'], 1);
24681
					if (isset($this->svgdefs[$svgdefid])) {
24682
						$use = $this->svgdefs[$svgdefid];
24683
						if (isset($attribs['xlink:href'])) {
24684
							unset($attribs['xlink:href']);
24685
						}
24686
						if (isset($attribs['id'])) {
24687
							unset($attribs['id']);
24688
						}
24689
						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24690
							$attribs['x'] += $use['attribs']['x'];
24691
						}
24692
						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24693
							$attribs['y'] += $use['attribs']['y'];
24694
						}
24695
						if (empty($attribs['style'])) {
24696
							$attribs['style'] = '';
24697
						}
24698
						if (!empty($use['attribs']['style'])) {
24699
							// merge styles
24700
							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24701
						}
24702
						$attribs = array_merge($use['attribs'], $attribs);
24703
						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24704
						return;
24705
					}
24706
				}
24707
				break;
24708
			}
24709
			default: {
24710
				break;
24711
			}
24712
		} // end of switch
24713
		// process child elements
24714
		if (!empty($attribs['child_elements'])) {
24715
			$child_elements = $attribs['child_elements'];
24716
			unset($attribs['child_elements']);
24717
			foreach($child_elements as $child_element) {
24718
				if (empty($child_element['attribs']['closing_tag'])) {
24719
					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24720
				} else {
24721
					if (isset($child_element['attribs']['content'])) {
24722
						$this->svgtext = $child_element['attribs']['content'];
24723
					}
24724
					$this->endSVGElementHandler('child-tag', $child_element['name']);
24725
				}
24726
			}
24727
		}
24728
	}
24729
24730
	/**
24731
	 * Sets the closing SVG element handler function for the XML parser.
24732
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24733
	 * @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.
24734
	 * @author Nicola Asuni
24735
	 * @since 5.0.000 (2010-05-02)
24736
	 * @protected
24737
	 */
24738
	protected function endSVGElementHandler($parser, $name) {
24739
		$name = $this->removeTagNamespace($name);
24740
		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24741
			if (end($this->svgdefs) !== FALSE) {
24742
				$last_svgdefs_id = key($this->svgdefs);
24743
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24744
					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24745
						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24746
							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24747
							return;
24748
						}
24749
					}
24750
					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24751
						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24752
						return;
24753
					}
24754
				}
24755
			}
24756
			return;
24757
		}
24758
		switch($name) {
24759
			case 'defs': {
24760
				$this->svgdefsmode = false;
24761
				break;
24762
			}
24763
			// clipPath
24764
			case 'clipPath': {
24765
				$this->svgclipmode = false;
24766
				break;
24767
			}
24768
			case 'svg': {
24769
				if (--$this->svg_tag_depth <= 0) {
24770
					break;
24771
				}
24772
			}
24773
			case 'g': {
24774
				// ungroup: remove last style from array
24775
				array_pop($this->svgstyles);
24776
				$this->StopTransform();
24777
				break;
24778
			}
24779
			case 'text':
24780
			case 'tspan': {
24781
				if ($this->svgtextmode['invisible']) {
24782
					// This implementation must be fixed to following the rule:
24783
					// 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.
24784
					break;
24785
				}
24786
				// print text
24787
				$text = $this->svgtext;
24788
				//$text = $this->stringTrim($text);
24789
				$textlen = $this->GetStringWidth($text);
24790
				if ($this->svgtextmode['text-anchor'] != 'start') {
24791
					// check if string is RTL text
24792
					if ($this->svgtextmode['text-anchor'] == 'end') {
24793
						if ($this->svgtextmode['rtl']) {
24794
							$this->x += $textlen;
24795
						} else {
24796
							$this->x -= $textlen;
24797
						}
24798
					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24799
						if ($this->svgtextmode['rtl']) {
24800
							$this->x += ($textlen / 2);
24801
						} else {
24802
							$this->x -= ($textlen / 2);
24803
						}
24804
					}
24805
				}
24806
				$textrendermode = $this->textrendermode;
24807
				$textstrokewidth = $this->textstrokewidth;
24808
				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24809
				if ($name == 'text') {
24810
					// store current coordinates
24811
					$tmpx = $this->x;
24812
					$tmpy = $this->y;
24813
				}
24814
				// print the text
24815
				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24816
				if ($name == 'text') {
24817
					// restore coordinates
24818
					$this->x = $tmpx;
24819
					$this->y = $tmpy;
24820
				}
24821
				// restore previous rendering mode
24822
				$this->textrendermode = $textrendermode;
24823
				$this->textstrokewidth = $textstrokewidth;
24824
				$this->svgtext = '';
24825
				$this->StopTransform();
24826
				if (!$this->svgdefsmode) {
24827
					array_pop($this->svgstyles);
24828
				}
24829
				break;
24830
			}
24831
			default: {
24832
				break;
24833
			}
24834
		}
24835
	}
24836
24837
	/**
24838
	 * Sets the character data handler function for the XML parser.
24839
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24840
	 * @param string $data The second parameter, data, contains the character data as a string.
24841
	 * @author Nicola Asuni
24842
	 * @since 5.0.000 (2010-05-02)
24843
	 * @protected
24844
	 */
24845
	protected function segSVGContentHandler($parser, $data) {
24846
		$this->svgtext .= $data;
24847
	}
24848
24849
	// --- END SVG METHODS -----------------------------------------------------
24850
24851
    /**
24852
     * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24853
     * @param string $file
24854
     * @return string
24855
     */
24856
    protected function getCachedFileContents($file)
24857
    {
24858
        if (!isset($this->fileContentCache[$file])) {
24859
            $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24860
        }
24861
        return $this->fileContentCache[$file];
24862
    }
24863
24864
    /**
24865
     * Avoid multiple calls to an external server to see if a file exists
24866
     * @param string $file
24867
     * @return bool
24868
     */
24869
    protected function fileExists($file)
24870
    {
24871
        if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24872
            return true;
24873
        }
24874
24875
        return TCPDF_STATIC::file_exists($file);
24876
    }
24877
24878
	/**
24879
	 * Wrapper for unlink with disabled protocols.
24880
	 * @param string $file
24881
	 * @return bool
24882
	 */
24883
	protected function _unlink($file)
24884
	{
24885
		if ((strpos($file, '://') !== false) && ((substr($file, 0, 7) !== 'file://') || (!$this->allowLocalFiles))) {
24886
			// forbidden protocol
24887
			return false;
24888
		}
24889
		return @unlink($file);
24890
	}
24891
24892
} // END OF TCPDF CLASS
24893
24894
//============================================================+
24895
// END OF FILE
24896
//============================================================+
24897