Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
//============================================================+
3
// File name   : tcpdf.php
1441 ariadna 4
// Version     : 6.8.2
1 efrain 5
// Begin       : 2002-08-03
1441 ariadna 6
// Last Update : 2024-12-23
1 efrain 7
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9
// -------------------------------------------------------------------
1441 ariadna 10
// Copyright (C) 2002-2025 Nicola Asuni - Tecnick.com LTD
1 efrain 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
1441 ariadna 107
 * @version 6.8.2
1 efrain 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.
1441 ariadna 131
 * @version 6.8.2
1 efrain 132
 * @author Nicola Asuni - info@tecnick.com
133
 * @IgnoreAnnotation("protected")
134
 * @IgnoreAnnotation("public")
135
 * @IgnoreAnnotation("pre")
136
 */
137
class TCPDF {
138
 
139
	// Protected properties
140
 
141
	/**
142
	 * Current page number.
143
	 * @protected
144
	 */
145
	protected $page;
146
 
147
	/**
148
	 * Current object number.
149
	 * @protected
150
	 */
151
	protected $n;
152
 
153
	/**
154
	 * Array of object offsets.
155
	 * @protected
156
	 */
157
	protected $offsets = array();
158
 
159
	/**
160
	 * Array of object IDs for each page.
161
	 * @protected
162
	 */
163
	protected $pageobjects = array();
164
 
165
	/**
166
	 * Buffer holding in-memory PDF.
167
	 * @protected
168
	 */
169
	protected $buffer;
170
 
171
	/**
172
	 * Array containing pages.
173
	 * @protected
174
	 */
175
	protected $pages = array();
176
 
177
	/**
178
	 * Current document state.
179
	 * @protected
180
	 */
181
	protected $state;
182
 
183
	/**
184
	 * Compression flag.
185
	 * @protected
186
	 */
187
	protected $compress;
188
 
189
	/**
190
	 * Current page orientation (P = Portrait, L = Landscape).
191
	 * @protected
192
	 */
193
	protected $CurOrientation;
194
 
195
	/**
196
	 * Page dimensions.
197
	 * @protected
198
	 */
199
	protected $pagedim = array();
200
 
201
	/**
202
	 * Scale factor (number of points in user unit).
203
	 * @protected
204
	 */
205
	protected $k;
206
 
207
	/**
208
	 * Width of page format in points.
209
	 * @protected
210
	 */
211
	protected $fwPt;
212
 
213
	/**
214
	 * Height of page format in points.
215
	 * @protected
216
	 */
217
	protected $fhPt;
218
 
219
	/**
220
	 * Current width of page in points.
221
	 * @protected
222
	 */
223
	protected $wPt;
224
 
225
	/**
226
	 * Current height of page in points.
227
	 * @protected
228
	 */
229
	protected $hPt;
230
 
231
	/**
232
	 * Current width of page in user unit.
233
	 * @protected
234
	 */
235
	protected $w;
236
 
237
	/**
238
	 * Current height of page in user unit.
239
	 * @protected
240
	 */
241
	protected $h;
242
 
243
	/**
244
	 * Left margin.
245
	 * @protected
246
	 */
247
	protected $lMargin;
248
 
249
	/**
250
	 * Right margin.
251
	 * @protected
252
	 */
253
	protected $rMargin;
254
 
255
	/**
256
	 * Cell left margin (used by regions).
257
	 * @protected
258
	 */
259
	protected $clMargin;
260
 
261
	/**
262
	 * Cell right margin (used by regions).
263
	 * @protected
264
	 */
265
	protected $crMargin;
266
 
267
	/**
268
	 * Top margin.
269
	 * @protected
270
	 */
271
	protected $tMargin;
272
 
273
	/**
274
	 * Page break margin.
275
	 * @protected
276
	 */
277
	protected $bMargin;
278
 
279
	/**
280
	 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281
	 * @since 5.9.000 (2010-10-03)
282
	 * @protected
283
	 */
284
	protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
285
 
286
	/**
287
	 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
288
	 * @since 5.9.000 (2010-10-04)
289
	 * @protected
290
	 */
291
	protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
292
 
293
	/**
294
	 * Current horizontal position in user unit for cell positioning.
295
	 * @protected
296
	 */
297
	protected $x;
298
 
299
	/**
300
	 * Current vertical position in user unit for cell positioning.
301
	 * @protected
302
	 */
303
	protected $y;
304
 
305
	/**
306
	 * Height of last cell printed.
307
	 * @protected
308
	 */
309
	protected $lasth;
310
 
311
	/**
312
	 * Line width in user unit.
313
	 * @protected
314
	 */
315
	protected $LineWidth;
316
 
317
	/**
318
	 * Array of standard font names.
319
	 * @protected
320
	 */
321
	protected $CoreFonts;
322
 
323
	/**
324
	 * Array of used fonts.
325
	 * @protected
326
	 */
327
	protected $fonts = array();
328
 
329
	/**
330
	 * Array of font files.
331
	 * @protected
332
	 */
333
	protected $FontFiles = array();
334
 
335
	/**
336
	 * Array of encoding differences.
337
	 * @protected
338
	 */
339
	protected $diffs = array();
340
 
341
	/**
342
	 * Array of used images.
343
	 * @protected
344
	 */
345
	protected $images = array();
346
 
347
	/**
348
	 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
349
	 * @protected
350
	 */
351
	protected $svg_tag_depth = 0;
352
 
353
	/**
354
	 * Array of Annotations in pages.
355
	 * @protected
356
	 */
357
	protected $PageAnnots = array();
358
 
359
	/**
360
	 * Array of internal links.
361
	 * @protected
362
	 */
363
	protected $links = array();
364
 
365
	/**
366
	 * Current font family.
367
	 * @protected
368
	 */
369
	protected $FontFamily;
370
 
371
	/**
372
	 * Current font style.
373
	 * @protected
374
	 */
375
	protected $FontStyle;
376
 
377
	/**
378
	 * Current font ascent (distance between font top and baseline).
379
	 * @protected
380
	 * @since 2.8.000 (2007-03-29)
381
	 */
382
	protected $FontAscent;
383
 
384
	/**
385
	 * Current font descent (distance between font bottom and baseline).
386
	 * @protected
387
	 * @since 2.8.000 (2007-03-29)
388
	 */
389
	protected $FontDescent;
390
 
391
	/**
392
	 * Underlining flag.
393
	 * @protected
394
	 */
395
	protected $underline;
396
 
397
	/**
398
	 * Overlining flag.
399
	 * @protected
400
	 */
401
	protected $overline;
402
 
403
	/**
404
	 * Current font info.
405
	 * @protected
406
	 */
407
	protected $CurrentFont;
408
 
409
	/**
410
	 * Current font size in points.
411
	 * @protected
412
	 */
413
	protected $FontSizePt;
414
 
415
	/**
416
	 * Current font size in user unit.
417
	 * @protected
418
	 */
419
	protected $FontSize;
420
 
421
	/**
422
	 * Commands for drawing color.
423
	 * @protected
424
	 */
425
	protected $DrawColor;
426
 
427
	/**
428
	 * Commands for filling color.
429
	 * @protected
430
	 */
431
	protected $FillColor;
432
 
433
	/**
434
	 * Commands for text color.
435
	 * @protected
436
	 */
437
	protected $TextColor;
438
 
439
	/**
440
	 * Indicates whether fill and text colors are different.
441
	 * @protected
442
	 */
443
	protected $ColorFlag;
444
 
445
	/**
446
	 * Automatic page breaking.
447
	 * @protected
448
	 */
449
	protected $AutoPageBreak;
450
 
451
	/**
452
	 * Threshold used to trigger page breaks.
453
	 * @protected
454
	 */
455
	protected $PageBreakTrigger;
456
 
457
	/**
458
	 * Flag set when processing page header.
459
	 * @protected
460
	 */
461
	protected $InHeader = false;
462
 
463
	/**
464
	 * Flag set when processing page footer.
465
	 * @protected
466
	 */
467
	protected $InFooter = false;
468
 
469
	/**
470
	 * Zoom display mode.
471
	 * @protected
472
	 */
473
	protected $ZoomMode;
474
 
475
	/**
476
	 * Layout display mode.
477
	 * @protected
478
	 */
479
	protected $LayoutMode;
480
 
481
	/**
482
	 * If true set the document information dictionary in Unicode.
483
	 * @protected
484
	 */
485
	protected $docinfounicode = true;
486
 
487
	/**
488
	 * Document title.
489
	 * @protected
490
	 */
491
	protected $title = '';
492
 
493
	/**
494
	 * Document subject.
495
	 * @protected
496
	 */
497
	protected $subject = '';
498
 
499
	/**
500
	 * Document author.
501
	 * @protected
502
	 */
503
	protected $author = '';
504
 
505
	/**
506
	 * Document keywords.
507
	 * @protected
508
	 */
509
	protected $keywords = '';
510
 
511
	/**
512
	 * Document creator.
513
	 * @protected
514
	 */
515
	protected $creator = '';
516
 
517
	/**
518
	 * Starting page number.
519
	 * @protected
520
	 */
521
	protected $starting_page_number = 1;
522
 
523
	/**
524
	 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
525
	 * @since 2002-07-31
526
	 * @author Nicola Asuni
527
	 * @protected
528
	 */
529
	protected $img_rb_x;
530
 
531
	/**
532
	 * The right-bottom corner Y coordinate of last inserted image.
533
	 * @since 2002-07-31
534
	 * @author Nicola Asuni
535
	 * @protected
536
	 */
537
	protected $img_rb_y;
538
 
539
	/**
540
	 * Adjusting factor to convert pixels to user units.
541
	 * @since 2004-06-14
542
	 * @author Nicola Asuni
543
	 * @protected
544
	 */
545
	protected $imgscale = 1;
546
 
547
	/**
548
	 * Boolean flag set to true when the input text is unicode (require unicode fonts).
549
	 * @since 2005-01-02
550
	 * @author Nicola Asuni
551
	 * @protected
552
	 */
553
	protected $isunicode = false;
554
 
555
	/**
556
	 * PDF version.
557
	 * @since 1.5.3
558
	 * @protected
559
	 */
560
	protected $PDFVersion = '1.7';
561
 
562
	/**
563
	 * ID of the stored default header template (-1 = not set).
564
	 * @protected
565
	 */
566
	protected $header_xobjid = false;
567
 
568
	/**
569
	 * If true reset the Header Xobject template at each page
570
	 * @protected
571
	 */
572
	protected $header_xobj_autoreset = false;
573
 
574
	/**
575
	 * Minimum distance between header and top page margin.
576
	 * @protected
577
	 * @var float
578
	 */
579
	protected $header_margin;
580
 
581
	/**
582
	 * Minimum distance between footer and bottom page margin.
583
	 * @protected
584
	 * @var float
585
	 */
586
	protected $footer_margin;
587
 
588
	/**
589
	 * Original left margin value.
590
	 * @protected
591
	 * @since 1.53.0.TC013
592
	 */
593
	protected $original_lMargin;
594
 
595
	/**
596
	 * Original right margin value.
597
	 * @protected
598
	 * @since 1.53.0.TC013
599
	 */
600
	protected $original_rMargin;
601
 
602
	/**
603
	 * Default font used on page header.
604
	 * @protected
605
	 * @var array<int,string|float|null>
606
	 * @phpstan-var array{0: string, 1: string, 2: float|null}
607
	 */
608
	protected $header_font;
609
 
610
	/**
611
	 * Default font used on page footer.
612
	 * @protected
613
	 * @var array<int,string|float|null>
614
	 * @phpstan-var array{0: string, 1: string, 2: float|null}
615
	 */
616
	protected $footer_font;
617
 
618
	/**
619
	 * Language templates.
620
	 * @protected
621
	 */
622
	protected $l;
623
 
624
	/**
625
	 * Barcode to print on page footer (only if set).
626
	 * @protected
627
	 */
628
	protected $barcode = false;
629
 
630
	/**
631
	 * Boolean flag to print/hide page header.
632
	 * @protected
633
	 */
634
	protected $print_header = true;
635
 
636
	/**
637
	 * Boolean flag to print/hide page footer.
638
	 * @protected
639
	 */
640
	protected $print_footer = true;
641
 
642
	/**
643
	 * Header image logo.
644
	 * @protected
645
	 */
646
	protected $header_logo = '';
647
 
648
	/**
649
	 * Width of header image logo in user units.
650
	 * @protected
651
	 */
652
	protected $header_logo_width = 30;
653
 
654
	/**
655
	 * Title to be printed on default page header.
656
	 * @protected
657
	 */
658
	protected $header_title = '';
659
 
660
	/**
661
	 * String to print on page header after title.
662
	 * @protected
663
	 */
664
	protected $header_string = '';
665
 
666
	/**
667
	 * Color for header text (RGB array).
668
	 * @since 5.9.174 (2012-07-25)
669
	 * @protected
670
	 * @var int[]
671
	 * @phpstan-var array{0: int, 1: int, 2: int}
672
	 */
673
	protected $header_text_color = array(0,0,0);
674
 
675
	/**
676
	 * Color for header line (RGB array).
677
	 * @since 5.9.174 (2012-07-25)
678
	 * @protected
679
	 * @var int[]
680
	 * @phpstan-var array{0: int, 1: int, 2: int}
681
	 */
682
	protected $header_line_color = array(0,0,0);
683
 
684
	/**
685
	 * Color for footer text (RGB array).
686
	 * @since 5.9.174 (2012-07-25)
687
	 * @protected
688
	 * @var int[]
689
	 * @phpstan-var array{0: int, 1: int, 2: int}
690
	 */
691
	protected $footer_text_color = array(0,0,0);
692
 
693
	/**
694
	 * Color for footer line (RGB array).
695
	 * @since 5.9.174 (2012-07-25)
696
	 * @protected
697
	 * @var int[]
698
	 * @phpstan-var array{0: int, 1: int, 2: int}
699
	 */
700
	protected $footer_line_color = array(0,0,0);
701
 
702
	/**
703
	 * Text shadow data array.
704
	 * @since 5.9.174 (2012-07-25)
705
	 * @protected
706
	 */
707
	protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
708
 
709
	/**
710
	 * Default number of columns for html table.
711
	 * @protected
712
	 */
713
	protected $default_table_columns = 4;
714
 
715
	// variables for html parser
716
 
717
	/**
718
	 * HTML PARSER: array to store current link and rendering styles.
719
	 * @protected
720
	 */
721
	protected $HREF = array();
722
 
723
	/**
724
	 * List of available fonts on filesystem.
725
	 * @protected
726
	 */
727
	protected $fontlist = array();
728
 
729
	/**
730
	 * Current foreground color.
731
	 * @protected
732
	 */
733
	protected $fgcolor;
734
 
735
	/**
736
	 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
737
	 * @protected
738
	 */
739
	protected $listordered = array();
740
 
741
	/**
742
	 * HTML PARSER: array count list items on nested lists.
743
	 * @protected
744
	 */
745
	protected $listcount = array();
746
 
747
	/**
748
	 * HTML PARSER: current list nesting level.
749
	 * @protected
750
	 */
751
	protected $listnum = 0;
752
 
753
	/**
754
	 * HTML PARSER: indent amount for lists.
755
	 * @protected
756
	 */
757
	protected $listindent = 0;
758
 
759
	/**
760
	 * HTML PARSER: current list indententation level.
761
	 * @protected
762
	 */
763
	protected $listindentlevel = 0;
764
 
765
	/**
766
	 * Current background color.
767
	 * @protected
768
	 */
769
	protected $bgcolor;
770
 
771
	/**
772
	 * Temporary font size in points.
773
	 * @protected
774
	 */
775
	protected $tempfontsize = 10;
776
 
777
	/**
778
	 * Spacer string for LI tags.
779
	 * @protected
780
	 */
781
	protected $lispacer = '';
782
 
783
	/**
784
	 * Default encoding.
785
	 * @protected
786
	 * @since 1.53.0.TC010
787
	 */
788
	protected $encoding = 'UTF-8';
789
 
790
	/**
791
	 * Boolean flag to indicate if the document language is Right-To-Left.
792
	 * @protected
793
	 * @since 2.0.000
794
	 */
795
	protected $rtl = false;
796
 
797
	/**
798
	 * Boolean flag used to force RTL or LTR string direction.
799
	 * @protected
800
	 * @since 2.0.000
801
	 */
802
	protected $tmprtl = false;
803
 
804
	// --- Variables used for document encryption:
805
 
806
	/**
807
	 * IBoolean flag indicating whether document is protected.
808
	 * @protected
809
	 * @since 2.0.000 (2008-01-02)
810
	 */
811
	protected $encrypted;
812
 
813
	/**
814
	 * Array containing encryption settings.
815
	 * @protected
816
	 * @since 5.0.005 (2010-05-11)
817
	 */
818
	protected $encryptdata = array();
819
 
820
	/**
821
	 * Last RC4 key encrypted (cached for optimisation).
822
	 * @protected
823
	 * @since 2.0.000 (2008-01-02)
824
	 */
825
	protected $last_enc_key;
826
 
827
	/**
828
	 * Last RC4 computed key.
829
	 * @protected
830
	 * @since 2.0.000 (2008-01-02)
831
	 */
832
	protected $last_enc_key_c;
833
 
834
	/**
835
	 * File ID (used on document trailer).
836
	 * @protected
837
	 * @since 5.0.005 (2010-05-12)
838
	 */
839
	protected $file_id;
840
 
1441 ariadna 841
	/**
842
	 * Internal secret used to encrypt data.
843
	 * @protected
844
	 * @since 6.7.5 (2024-03-21)
845
	 */
846
	protected $hash_key;
847
 
1 efrain 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
	 * Overprint mode array.
1815
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1816
	 * @protected
1817
	 * @since 5.9.152 (2012-03-23)
1818
	 * @var array<string,bool|int>
1819
	 */
1820
	protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1821
 
1822
	/**
1823
	 * Alpha mode array.
1824
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1825
	 * @protected
1826
	 * @since 5.9.152 (2012-03-23)
1827
	 */
1828
	protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1829
 
1830
	/**
1831
	 * Define the page boundaries boxes to be set on document.
1832
	 * @protected
1833
	 * @since 5.9.152 (2012-03-23)
1834
	 */
1835
	protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1836
 
1837
	/**
1838
	 * If true print TCPDF meta link.
1839
	 * @protected
1840
	 * @since 5.9.152 (2012-03-23)
1841
	 */
1842
	protected $tcpdflink = true;
1843
 
1844
	/**
1845
	 * Cache array for computed GD gamma values.
1846
	 * @protected
1847
	 * @since 5.9.1632 (2012-06-05)
1848
	 */
1849
	protected $gdgammacache = array();
1850
 
1851
    /**
1852
     * Cache array for file content
1853
     * @protected
1854
     * @var array
1855
     * @since 6.3.5 (2020-09-28)
1856
     */
1857
	protected $fileContentCache = array();
1858
 
1859
	/**
1860
	 * Whether to allow local file path in image html tags, when prefixed with file://
1861
	 *
1862
	 * @var bool
1863
	 * @protected
1864
	 * @since 6.4 (2020-07-23)
1865
	 */
1866
	protected $allowLocalFiles = false;
1867
 
1868
	//------------------------------------------------------------
1869
	// METHODS
1870
	//------------------------------------------------------------
1871
 
1872
	/**
1873
	 * This is the class constructor.
1874
	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1875
	 *
1876
	 * @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>
1877
	 * @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.
1878
	 * @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().
1879
	 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1880
	 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1881
	 * @param boolean $diskcache DEPRECATED FEATURE
1882
	 * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1883
	 * @public
1884
	 * @see getPageSizeFromFormat(), setPageFormat()
1885
	 */
1886
	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1887
		// set file ID for trailer
1888
		$serformat = (is_array($format) ? json_encode($format) : $format);
1889
		$this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1441 ariadna 1890
		$this->hash_key = hash_hmac('sha256', TCPDF_STATIC::getRandomSeed($this->file_id), TCPDF_STATIC::getRandomSeed('TCPDF'), false);
1 efrain 1891
		$this->font_obj_ids = array();
1892
		$this->page_obj_id = array();
1893
		$this->form_obj_id = array();
1894
		// set pdf/a mode
1895
		if ($pdfa != false) {
1896
			$this->pdfa_mode = true;
1897
			$this->pdfa_version = $pdfa;  // 1 or 3
1898
		} else
1899
			$this->pdfa_mode = false;
1900
 
1901
		$this->force_srgb = false;
1902
		// set language direction
1903
		$this->rtl = false;
1904
		$this->tmprtl = false;
1905
		// some checks
1906
		$this->_dochecks();
1907
		// initialization of properties
1908
		$this->isunicode = $unicode;
1909
		$this->page = 0;
1910
		$this->transfmrk[0] = array();
1911
		$this->pagedim = array();
1912
		$this->n = 2;
1913
		$this->buffer = '';
1914
		$this->pages = array();
1915
		$this->state = 0;
1916
		$this->fonts = array();
1917
		$this->FontFiles = array();
1918
		$this->diffs = array();
1919
		$this->images = array();
1920
		$this->links = array();
1921
		$this->gradients = array();
1922
		$this->InFooter = false;
1923
		$this->lasth = 0;
1924
		$this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1925
		$this->FontStyle = '';
1926
		$this->FontSizePt = 12;
1927
		$this->underline = false;
1928
		$this->overline = false;
1929
		$this->linethrough = false;
1930
		$this->DrawColor = '0 G';
1931
		$this->FillColor = '0 g';
1932
		$this->TextColor = '0 g';
1933
		$this->ColorFlag = false;
1934
		$this->pdflayers = array();
1935
		// encryption values
1936
		$this->encrypted = false;
1937
		$this->last_enc_key = '';
1938
		// standard Unicode fonts
1939
		$this->CoreFonts = array(
1940
			'courier'=>'Courier',
1941
			'courierB'=>'Courier-Bold',
1942
			'courierI'=>'Courier-Oblique',
1943
			'courierBI'=>'Courier-BoldOblique',
1944
			'helvetica'=>'Helvetica',
1945
			'helveticaB'=>'Helvetica-Bold',
1946
			'helveticaI'=>'Helvetica-Oblique',
1947
			'helveticaBI'=>'Helvetica-BoldOblique',
1948
			'times'=>'Times-Roman',
1949
			'timesB'=>'Times-Bold',
1950
			'timesI'=>'Times-Italic',
1951
			'timesBI'=>'Times-BoldItalic',
1952
			'symbol'=>'Symbol',
1953
			'zapfdingbats'=>'ZapfDingbats'
1954
		);
1955
		// set scale factor
1956
		$this->setPageUnit($unit);
1957
		// set page format and orientation
1958
		$this->setPageFormat($format, $orientation);
1959
		// page margins (1 cm)
1960
		$margin = 28.35 / $this->k;
1961
		$this->setMargins($margin, $margin);
1962
		$this->clMargin = $this->lMargin;
1963
		$this->crMargin = $this->rMargin;
1964
		// internal cell padding
1965
		$cpadding = $margin / 10;
1966
		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1967
		// cell margins
1968
		$this->setCellMargins(0, 0, 0, 0);
1969
		// line width (0.2 mm)
1970
		$this->LineWidth = 0.57 / $this->k;
1971
		$this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1972
		$this->linestyleCap = '0 J';
1973
		$this->linestyleJoin = '0 j';
1974
		$this->linestyleDash = '[] 0 d';
1975
		// automatic page break
1976
		$this->setAutoPageBreak(true, (2 * $margin));
1977
		// full width display mode
1978
		$this->setDisplayMode('fullwidth');
1979
		// compression
1980
		$this->setCompression();
1981
		// set default PDF version number
1982
		$this->setPDFVersion();
1983
		$this->tcpdflink = true;
1984
		$this->encoding = $encoding;
1985
		$this->HREF = array();
1986
		$this->getFontsList();
1987
		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1988
		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1989
		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1990
		$this->extgstates = array();
1991
		$this->setTextShadow();
1992
		// signature
1993
		$this->sign = false;
1994
		$this->tsa_timestamp = false;
1995
		$this->tsa_data = array();
1996
		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1997
		$this->empty_signature_appearance = array();
1998
		// user's rights
1999
		$this->ur['enabled'] = false;
2000
		$this->ur['document'] = '/FullSave';
2001
		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
2002
		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
2003
		$this->ur['signature'] = '/Modify';
2004
		$this->ur['ef'] = '/Create/Delete/Modify/Import';
2005
		$this->ur['formex'] = '';
2006
		// set default JPEG quality
2007
		$this->jpeg_quality = 75;
2008
		// initialize some settings
2009
		TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
2010
		// set default font
2011
		$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2012
		$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2013
		$this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2014
		// check if PCRE Unicode support is enabled
2015
		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2016
			// PCRE unicode support is turned ON
2017
			// \s     : any whitespace character
2018
			// \p{Z}  : any separator
2019
			// \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2020
			// \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2021
			//$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2022
			$this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2023
		} else {
2024
			// PCRE unicode support is turned OFF
2025
			$this->setSpacesRE('/[^\S\xa0]/');
2026
		}
2027
		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2028
		// set document creation and modification timestamp
2029
		$this->doc_creation_timestamp = time();
2030
		$this->doc_modification_timestamp = $this->doc_creation_timestamp;
2031
		// get default graphic vars
2032
		$this->default_graphic_vars = $this->getGraphicVars();
2033
		$this->header_xobj_autoreset = false;
2034
		$this->custom_xmp = '';
2035
		$this->custom_xmp_rdf = '';
2036
	}
2037
 
2038
	/**
2039
	 * Default destructor.
2040
	 * @public
2041
	 * @since 1.53.0.TC016
2042
	 */
2043
	public function __destruct() {
2044
		// cleanup
2045
		$this->_destroy(true);
2046
	}
2047
 
2048
	/**
2049
	 * Set the units of measure for the document.
2050
	 * @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.
2051
	 * @public
2052
	 * @since 3.0.015 (2008-06-06)
2053
	 */
2054
	public function setPageUnit($unit) {
2055
		$unit = strtolower($unit);
2056
		//Set scale factor
2057
		switch ($unit) {
2058
			// points
2059
			case 'px':
2060
			case 'pt': {
2061
				$this->k = 1;
2062
				break;
2063
			}
2064
			// millimeters
2065
			case 'mm': {
2066
				$this->k = $this->dpi / 25.4;
2067
				break;
2068
			}
2069
			// centimeters
2070
			case 'cm': {
2071
				$this->k = $this->dpi / 2.54;
2072
				break;
2073
			}
2074
			// inches
2075
			case 'in': {
2076
				$this->k = $this->dpi;
2077
				break;
2078
			}
2079
			// unsupported unit
2080
			default : {
2081
				$this->Error('Incorrect unit: '.$unit);
2082
				break;
2083
			}
2084
		}
2085
		$this->pdfunit = $unit;
2086
		if (isset($this->CurOrientation)) {
2087
			$this->setPageOrientation($this->CurOrientation);
2088
		}
2089
	}
2090
 
2091
	/**
2092
	 * Change the format of the current page
2093
	 * @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>
2094
	 * <li>['format'] = page format name (one of the above);</li>
2095
	 * <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>
2096
	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2097
	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2098
	 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2099
	 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2100
	 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2101
	 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2102
	 * <li>['CropBox'] : the visible region of default user space:</li>
2103
	 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2104
	 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2105
	 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2106
	 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2107
	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2108
	 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2109
	 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2110
	 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2111
	 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2112
	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2113
	 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2114
	 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2115
	 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2116
	 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2117
	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2118
	 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2119
	 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2120
	 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2121
	 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2122
	 * <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>
2123
	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2124
	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2125
	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2126
	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2127
	 * <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>
2128
	 * <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>
2129
	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2130
	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2131
	 * <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>
2132
	 * <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>
2133
	 * <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>
2134
	 * <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>
2135
	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2136
	 * </ul>
2137
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2138
	 * <li>P or Portrait (default)</li>
2139
	 * <li>L or Landscape</li>
2140
	 * <li>'' (empty string) for automatic orientation</li>
2141
	 * </ul>
2142
	 * @protected
2143
	 * @since 3.0.015 (2008-06-06)
2144
	 * @see getPageSizeFromFormat()
2145
	 */
2146
	protected function setPageFormat($format, $orientation='P') {
2147
		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2148
			// remove inherited values
2149
			unset($this->pagedim[$this->page]);
2150
		}
2151
		if (is_string($format)) {
2152
			// get page measures from format name
2153
			$pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2154
			$this->fwPt = $pf[0];
2155
			$this->fhPt = $pf[1];
2156
		} else {
2157
			// the boundaries of the physical medium on which the page shall be displayed or printed
2158
			if (isset($format['MediaBox'])) {
2159
				$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);
2160
				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2161
				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2162
			} else {
2163
				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2164
					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2165
				} else {
2166
					if (!isset($format['format'])) {
2167
						// default value
2168
						$format['format'] = 'A4';
2169
					}
2170
					$pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2171
				}
2172
				$this->fwPt = $pf[0];
2173
				$this->fhPt = $pf[1];
2174
				$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2175
			}
2176
			// the visible region of default user space
2177
			if (isset($format['CropBox'])) {
2178
				$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);
2179
			}
2180
			// the region to which the contents of the page shall be clipped when output in a production environment
2181
			if (isset($format['BleedBox'])) {
2182
				$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);
2183
			}
2184
			// the intended dimensions of the finished page after trimming
2185
			if (isset($format['TrimBox'])) {
2186
				$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);
2187
			}
2188
			// the page's meaningful content (including potential white space)
2189
			if (isset($format['ArtBox'])) {
2190
				$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);
2191
			}
2192
			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2193
			if (isset($format['BoxColorInfo'])) {
2194
				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2195
			}
2196
			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2197
				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2198
				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2199
			}
2200
			if (isset($format['PZ'])) {
2201
				// The page's preferred zoom (magnification) factor
2202
				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2203
			}
2204
			if (isset($format['trans'])) {
2205
				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2206
				if (isset($format['trans']['Dur'])) {
2207
					// The page's display duration
2208
					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2209
				}
2210
				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2211
				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2212
					// The transition style that shall be used when moving to this page from another during a presentation
2213
					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2214
					$valid_effect = array('Split', 'Blinds');
2215
					$valid_vals = array('H', 'V');
2216
					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2217
						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2218
					}
2219
					$valid_effect = array('Split', 'Box', 'Fly');
2220
					$valid_vals = array('I', 'O');
2221
					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2222
						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2223
					}
2224
					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2225
					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2226
						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2227
							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2228
							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2229
							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2230
						}
2231
					}
2232
					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2233
						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2234
					}
2235
					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2236
						$this->pagedim[$this->page]['trans']['B'] = 'true';
2237
					}
2238
				} else {
2239
					$this->pagedim[$this->page]['trans']['S'] = 'R';
2240
				}
2241
				if (isset($format['trans']['D'])) {
2242
					// The duration of the transition effect, in seconds
2243
					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2244
				} else {
2245
					$this->pagedim[$this->page]['trans']['D'] = 1;
2246
				}
2247
			}
2248
		}
2249
		$this->setPageOrientation($orientation);
2250
	}
2251
 
2252
	/**
2253
	 * Set page orientation.
2254
	 * @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>
2255
	 * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2256
	 * @param float|null $bottommargin bottom margin of the page.
2257
	 * @public
2258
	 * @since 3.0.015 (2008-06-06)
2259
	 */
2260
	public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) {
2261
		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2262
			// the boundaries of the physical medium on which the page shall be displayed or printed
2263
			$this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2264
		}
2265
		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2266
			// the visible region of default user space
2267
			$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);
2268
		}
2269
		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2270
			// the region to which the contents of the page shall be clipped when output in a production environment
2271
			$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);
2272
		}
2273
		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2274
			// the intended dimensions of the finished page after trimming
2275
			$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);
2276
		}
2277
		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2278
			// the page's meaningful content (including potential white space)
2279
			$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);
2280
		}
2281
		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2282
			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2283
			$this->pagedim[$this->page]['Rotate'] = 0;
2284
		}
2285
		if (!isset($this->pagedim[$this->page]['PZ'])) {
2286
			// The page's preferred zoom (magnification) factor
2287
			$this->pagedim[$this->page]['PZ'] = 1;
2288
		}
2289
		if ($this->fwPt > $this->fhPt) {
2290
			// landscape
2291
			$default_orientation = 'L';
2292
		} else {
2293
			// portrait
2294
			$default_orientation = 'P';
2295
		}
2296
		$valid_orientations = array('P', 'L');
2297
		if (empty($orientation)) {
2298
			$orientation = $default_orientation;
2299
		} else {
2300
			$orientation = strtoupper($orientation[0]);
2301
		}
2302
		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2303
			$this->CurOrientation = $orientation;
2304
			$this->wPt = $this->fhPt;
2305
			$this->hPt = $this->fwPt;
2306
		} else {
2307
			$this->CurOrientation = $default_orientation;
2308
			$this->wPt = $this->fwPt;
2309
			$this->hPt = $this->fhPt;
2310
		}
2311
		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2312
			// swap X and Y coordinates (change page orientation)
2313
			$this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2314
		}
2315
		$this->w = ($this->wPt / $this->k);
2316
		$this->h = ($this->hPt / $this->k);
2317
		if (TCPDF_STATIC::empty_string($autopagebreak)) {
2318
			if (isset($this->AutoPageBreak)) {
2319
				$autopagebreak = $this->AutoPageBreak;
2320
			} else {
2321
				$autopagebreak = true;
2322
			}
2323
		}
2324
		if (TCPDF_STATIC::empty_string($bottommargin)) {
2325
			if (isset($this->bMargin)) {
2326
				$bottommargin = $this->bMargin;
2327
			} else {
2328
				// default value = 2 cm
2329
				$bottommargin = 2 * 28.35 / $this->k;
2330
			}
2331
		}
2332
		$this->setAutoPageBreak($autopagebreak, $bottommargin);
2333
		// store page dimensions
2334
		$this->pagedim[$this->page]['w'] = $this->wPt;
2335
		$this->pagedim[$this->page]['h'] = $this->hPt;
2336
		$this->pagedim[$this->page]['wk'] = $this->w;
2337
		$this->pagedim[$this->page]['hk'] = $this->h;
2338
		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2339
		$this->pagedim[$this->page]['bm'] = $bottommargin;
2340
		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2341
		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2342
		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2343
		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2344
		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2345
		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2346
	}
2347
 
2348
	/**
2349
	 * Set regular expression to detect withespaces or word separators.
2350
	 * The pattern delimiter must be the forward-slash character "/".
2351
	 * Some example patterns are:
2352
	 * <pre>
2353
	 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2354
	 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2355
	 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2356
	 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2357
	 *      \s     : any whitespace character
2358
	 *      \p{Z}  : any separator
2359
	 *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2360
	 *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2361
	 * </pre>
2362
	 * @param string $re regular expression (leave empty for default).
2363
	 * @public
2364
	 * @since 4.6.016 (2009-06-15)
2365
	 */
2366
	public function setSpacesRE($re='/[^\S\xa0]/') {
2367
		$this->re_spaces = $re;
2368
		$re_parts = explode('/', $re);
2369
		// get pattern parts
2370
		$this->re_space = array();
2371
		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2372
			$this->re_space['p'] = $re_parts[1];
2373
		} else {
2374
			$this->re_space['p'] = '[\s]';
2375
		}
2376
		// set pattern modifiers
2377
		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2378
			$this->re_space['m'] = $re_parts[2];
2379
		} else {
2380
			$this->re_space['m'] = '';
2381
		}
2382
	}
2383
 
2384
	/**
2385
	 * Enable or disable Right-To-Left language mode
2386
	 * @param boolean $enable if true enable Right-To-Left language mode.
2387
	 * @param boolean $resetx if true reset the X position on direction change.
2388
	 * @public
2389
	 * @since 2.0.000 (2008-01-03)
2390
	 */
2391
	public function setRTL($enable, $resetx=true) {
2392
		$enable = $enable ? true : false;
2393
		$resetx = ($resetx AND ($enable != $this->rtl));
2394
		$this->rtl = $enable;
2395
		$this->tmprtl = false;
2396
		if ($resetx) {
2397
			$this->Ln(0);
2398
		}
2399
	}
2400
 
2401
	/**
2402
	 * Return the RTL status
2403
	 * @return bool
2404
	 * @public
2405
	 * @since 4.0.012 (2008-07-24)
2406
	 */
2407
	public function getRTL() {
2408
		return $this->rtl;
2409
	}
2410
 
2411
	/**
2412
	 * Force temporary RTL language direction
2413
	 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2414
	 * @public
2415
	 * @since 2.1.000 (2008-01-09)
2416
	 */
2417
	public function setTempRTL($mode) {
2418
		$newmode = false;
2419
		switch (strtoupper($mode)) {
2420
			case 'LTR':
2421
			case 'L': {
2422
				if ($this->rtl) {
2423
					$newmode = 'L';
2424
				}
2425
				break;
2426
			}
2427
			case 'RTL':
2428
			case 'R': {
2429
				if (!$this->rtl) {
2430
					$newmode = 'R';
2431
				}
2432
				break;
2433
			}
2434
			case false:
2435
			default: {
2436
				$newmode = false;
2437
				break;
2438
			}
2439
		}
2440
		$this->tmprtl = $newmode;
2441
	}
2442
 
2443
	/**
2444
	 * Return the current temporary RTL status
2445
	 * @return bool
2446
	 * @public
2447
	 * @since 4.8.014 (2009-11-04)
2448
	 */
2449
	public function isRTLTextDir() {
2450
		return ($this->rtl OR ($this->tmprtl == 'R'));
2451
	}
2452
 
2453
	/**
2454
	 * Set the last cell height.
2455
	 * @param float $h cell height.
2456
	 * @author Nicola Asuni
2457
	 * @public
2458
	 * @since 1.53.0.TC034
2459
	 */
2460
	public function setLastH($h) {
2461
		$this->lasth = $h;
2462
	}
2463
 
2464
	/**
2465
	 * Return the cell height
2466
	 * @param int $fontsize Font size in internal units
2467
	 * @param boolean $padding If true add cell padding
2468
	 * @public
2469
	 * @return float
2470
	 */
2471
	public function getCellHeight($fontsize, $padding=TRUE) {
2472
		$height = ($fontsize * $this->cell_height_ratio);
2473
		if ($padding && !empty($this->cell_padding)) {
2474
			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2475
		}
2476
		return round($height, 6);
2477
	}
2478
 
2479
	/**
2480
	 * Reset the last cell height.
2481
	 * @public
2482
	 * @since 5.9.000 (2010-10-03)
2483
	 */
2484
	public function resetLastH() {
2485
		$this->lasth = $this->getCellHeight($this->FontSize);
2486
	}
2487
 
2488
	/**
2489
	 * Get the last cell height.
2490
	 * @return float last cell height
2491
	 * @public
2492
	 * @since 4.0.017 (2008-08-05)
2493
	 */
2494
	public function getLastH() {
2495
		return $this->lasth;
2496
	}
2497
 
2498
	/**
2499
	 * Set the adjusting factor to convert pixels to user units.
2500
	 * @param float $scale adjusting factor to convert pixels to user units.
2501
	 * @author Nicola Asuni
2502
	 * @public
2503
	 * @since 1.5.2
2504
	 */
2505
	public function setImageScale($scale) {
2506
		$this->imgscale = $scale;
2507
	}
2508
 
2509
	/**
2510
	 * Returns the adjusting factor to convert pixels to user units.
2511
	 * @return float adjusting factor to convert pixels to user units.
2512
	 * @author Nicola Asuni
2513
	 * @public
2514
	 * @since 1.5.2
2515
	 */
2516
	public function getImageScale() {
2517
		return $this->imgscale;
2518
	}
2519
 
2520
	/**
2521
	 * Returns an array of page dimensions:
2522
	 * <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>
2523
	 * @param int|null $pagenum page number (empty = current page)
2524
	 * @return array of page dimensions.
2525
	 * @author Nicola Asuni
2526
	 * @public
2527
	 * @since 4.5.027 (2009-03-16)
2528
	 */
2529
	public function getPageDimensions($pagenum=null) {
2530
		if (empty($pagenum)) {
2531
			$pagenum = $this->page;
2532
		}
2533
		return $this->pagedim[$pagenum];
2534
	}
2535
 
2536
	/**
2537
	 * Returns the page width in units.
2538
	 * @param int|null $pagenum page number (empty = current page)
2539
	 * @return int|float page width.
2540
	 * @author Nicola Asuni
2541
	 * @public
2542
	 * @since 1.5.2
2543
	 * @see getPageDimensions()
2544
	 */
2545
	public function getPageWidth($pagenum=null) {
2546
		if (empty($pagenum)) {
2547
			return $this->w;
2548
		}
2549
		return $this->pagedim[$pagenum]['w'];
2550
	}
2551
 
2552
	/**
2553
	 * Returns the page height in units.
2554
	 * @param int|null $pagenum page number (empty = current page)
2555
	 * @return int|float page height.
2556
	 * @author Nicola Asuni
2557
	 * @public
2558
	 * @since 1.5.2
2559
	 * @see getPageDimensions()
2560
	 */
2561
	public function getPageHeight($pagenum=null) {
2562
		if (empty($pagenum)) {
2563
			return $this->h;
2564
		}
2565
		return $this->pagedim[$pagenum]['h'];
2566
	}
2567
 
2568
	/**
2569
	 * Returns the page break margin.
2570
	 * @param int|null $pagenum page number (empty = current page)
2571
	 * @return int|float page break margin.
2572
	 * @author Nicola Asuni
2573
	 * @public
2574
	 * @since 1.5.2
2575
	 * @see getPageDimensions()
2576
	 */
2577
	public function getBreakMargin($pagenum=null) {
2578
		if (empty($pagenum)) {
2579
			return $this->bMargin;
2580
		}
2581
		return $this->pagedim[$pagenum]['bm'];
2582
	}
2583
 
2584
	/**
2585
	 * Returns the scale factor (number of points in user unit).
2586
	 * @return int scale factor.
2587
	 * @author Nicola Asuni
2588
	 * @public
2589
	 * @since 1.5.2
2590
	 */
2591
	public function getScaleFactor() {
2592
		return $this->k;
2593
	}
2594
 
2595
	/**
2596
	 * Defines the left, top and right margins.
2597
	 * @param int|float $left Left margin.
2598
	 * @param int|float $top Top margin.
2599
	 * @param int|float|null $right Right margin. Default value is the left one.
2600
	 * @param boolean $keepmargins if true overwrites the default page margins
2601
	 * @public
2602
	 * @since 1.0
2603
	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2604
	 */
2605
	public function setMargins($left, $top, $right=null, $keepmargins=false) {
2606
		//Set left, top and right margins
2607
		$this->lMargin = $left;
2608
		$this->tMargin = $top;
2609
		if ($right == -1 OR $right === null) {
2610
			$right = $left;
2611
		}
2612
		$this->rMargin = $right;
2613
		if ($keepmargins) {
2614
			// overwrite original values
2615
			$this->original_lMargin = $this->lMargin;
2616
			$this->original_rMargin = $this->rMargin;
2617
		}
2618
	}
2619
 
2620
	/**
2621
	 * 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.
2622
	 * @param int|float $margin The margin.
2623
	 * @public
2624
	 * @since 1.4
2625
	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2626
	 */
2627
	public function setLeftMargin($margin) {
2628
		//Set left margin
2629
		$this->lMargin = $margin;
2630
		if (($this->page > 0) AND ($this->x < $margin)) {
2631
			$this->x = $margin;
2632
		}
2633
	}
2634
 
2635
	/**
2636
	 * Defines the top margin. The method can be called before creating the first page.
2637
	 * @param int|float $margin The margin.
2638
	 * @public
2639
	 * @since 1.5
2640
	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2641
	 */
2642
	public function setTopMargin($margin) {
2643
		//Set top margin
2644
		$this->tMargin = $margin;
2645
		if (($this->page > 0) AND ($this->y < $margin)) {
2646
			$this->y = $margin;
2647
		}
2648
	}
2649
 
2650
	/**
2651
	 * Defines the right margin. The method can be called before creating the first page.
2652
	 * @param int|float $margin The margin.
2653
	 * @public
2654
	 * @since 1.5
2655
	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2656
	 */
2657
	public function setRightMargin($margin) {
2658
		$this->rMargin = $margin;
2659
		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2660
			$this->x = $this->w - $margin;
2661
		}
2662
	}
2663
 
2664
	/**
2665
	 * Set the same internal Cell padding for top, right, bottom, left-
2666
	 * @param int|float $pad internal padding.
2667
	 * @public
2668
	 * @since 2.1.000 (2008-01-09)
2669
	 * @see getCellPaddings(), setCellPaddings()
2670
	 */
2671
	public function setCellPadding($pad) {
2672
		if ($pad >= 0) {
2673
			$this->cell_padding['L'] = $pad;
2674
			$this->cell_padding['T'] = $pad;
2675
			$this->cell_padding['R'] = $pad;
2676
			$this->cell_padding['B'] = $pad;
2677
		}
2678
	}
2679
 
2680
	/**
2681
	 * Set the internal Cell paddings.
2682
	 * @param int|float|null $left left padding
2683
	 * @param int|float|null $top top padding
2684
	 * @param int|float|null $right right padding
2685
	 * @param int|float|null $bottom bottom padding
2686
	 * @public
2687
	 * @since 5.9.000 (2010-10-03)
2688
	 * @see getCellPaddings(), SetCellPadding()
2689
	 */
2690
	public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) {
2691
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2692
			$this->cell_padding['L'] = $left;
2693
		}
2694
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2695
			$this->cell_padding['T'] = $top;
2696
		}
2697
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2698
			$this->cell_padding['R'] = $right;
2699
		}
2700
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2701
			$this->cell_padding['B'] = $bottom;
2702
		}
2703
	}
2704
 
2705
	/**
2706
	 * Get the internal Cell padding array.
2707
	 * @return array of padding values
2708
	 * @public
2709
	 * @since 5.9.000 (2010-10-03)
2710
	 * @see setCellPaddings(), SetCellPadding()
2711
	 */
2712
	public function getCellPaddings() {
2713
		return $this->cell_padding;
2714
	}
2715
 
2716
	/**
2717
	 * Set the internal Cell margins.
2718
	 * @param int|float|null $left left margin
2719
	 * @param int|float|null $top top margin
2720
	 * @param int|float|null $right right margin
2721
	 * @param int|float|null $bottom bottom margin
2722
	 * @public
2723
	 * @since 5.9.000 (2010-10-03)
2724
	 * @see getCellMargins()
2725
	 */
2726
	public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) {
2727
		if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2728
			$this->cell_margin['L'] = $left;
2729
		}
2730
		if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2731
			$this->cell_margin['T'] = $top;
2732
		}
2733
		if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2734
			$this->cell_margin['R'] = $right;
2735
		}
2736
		if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2737
			$this->cell_margin['B'] = $bottom;
2738
		}
2739
	}
2740
 
2741
	/**
2742
	 * Get the internal Cell margin array.
2743
	 * @return array of margin values
2744
	 * @public
2745
	 * @since 5.9.000 (2010-10-03)
2746
	 * @see setCellMargins()
2747
	 */
2748
	public function getCellMargins() {
2749
		return $this->cell_margin;
2750
	}
2751
 
2752
	/**
2753
	 * Adjust the internal Cell padding array to take account of the line width.
2754
	 * @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)))
2755
	 * @return void|array array of adjustments
2756
	 * @public
2757
	 * @since 5.9.000 (2010-10-03)
2758
	 */
2759
	protected function adjustCellPadding($brd=0) {
2760
		if (empty($brd)) {
2761
			return;
2762
		}
2763
		if (is_string($brd)) {
2764
			// convert string to array
2765
			$slen = strlen($brd);
2766
			$newbrd = array();
2767
			for ($i = 0; $i < $slen; ++$i) {
2768
				$newbrd[$brd[$i]] = true;
2769
			}
2770
			$brd = $newbrd;
2771
		} elseif (
2772
			($brd === 1)
2773
			|| ($brd === true)
2774
			|| (is_numeric($brd) && ((int)$brd > 0))
2775
		) {
2776
			$brd = array('LRTB' => true);
2777
		}
2778
		if (!is_array($brd)) {
2779
			return;
2780
		}
2781
		// store current cell padding
2782
		$cp = $this->cell_padding;
2783
		// select border mode
2784
		if (isset($brd['mode'])) {
2785
			$mode = $brd['mode'];
2786
			unset($brd['mode']);
2787
		} else {
2788
			$mode = 'normal';
2789
		}
2790
		// process borders
2791
		foreach ($brd as $border => $style) {
2792
			$line_width = $this->LineWidth;
2793
			if (is_array($style) && isset($style['width'])) {
2794
				// get border width
2795
				$line_width = $style['width'];
2796
			}
2797
			$adj = 0; // line width inside the cell
2798
			switch ($mode) {
2799
				case 'ext': {
2800
					$adj = 0;
2801
					break;
2802
				}
2803
				case 'int': {
2804
					$adj = $line_width;
2805
					break;
2806
				}
2807
				case 'normal':
2808
				default: {
2809
					$adj = ($line_width / 2);
2810
					break;
2811
				}
2812
			}
2813
			// correct internal cell padding if required to avoid overlap between text and lines
2814
			if (
2815
				is_numeric($this->cell_padding['T'])
2816
				&& ($this->cell_padding['T'] < $adj)
2817
				&& (strpos($border, 'T') !== false)
2818
			) {
2819
				$this->cell_padding['T'] = $adj;
2820
			}
2821
			if (
2822
				is_numeric($this->cell_padding['R'])
2823
				&& ($this->cell_padding['R'] < $adj)
2824
				&& (strpos($border, 'R') !== false)
2825
			) {
2826
				$this->cell_padding['R'] = $adj;
2827
			}
2828
			if (
2829
				is_numeric($this->cell_padding['B'])
2830
				&& ($this->cell_padding['B'] < $adj)
2831
				&& (strpos($border, 'B') !== false)
2832
			) {
2833
				$this->cell_padding['B'] = $adj;
2834
			}
2835
			if (
2836
				is_numeric($this->cell_padding['L'])
2837
				&& ($this->cell_padding['L'] < $adj)
2838
				&& (strpos($border, 'L') !== false)
2839
			) {
2840
				$this->cell_padding['L'] = $adj;
2841
			}
2842
 
2843
		}
2844
 
2845
		return array(
2846
			'T' => ($this->cell_padding['T'] - $cp['T']),
2847
			'R' => ($this->cell_padding['R'] - $cp['R']),
2848
			'B' => ($this->cell_padding['B'] - $cp['B']),
2849
			'L' => ($this->cell_padding['L'] - $cp['L']),
2850
		);
2851
	}
2852
 
2853
	/**
2854
	 * 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.
2855
	 * @param boolean $auto Boolean indicating if mode should be on or off.
2856
	 * @param float $margin Distance from the bottom of the page.
2857
	 * @public
2858
	 * @since 1.0
2859
	 * @see Cell(), MultiCell(), AcceptPageBreak()
2860
	 */
2861
	public function setAutoPageBreak($auto, $margin=0) {
2862
		$this->AutoPageBreak = $auto ? true : false;
2863
		$this->bMargin = $margin;
2864
		$this->PageBreakTrigger = $this->h - $margin;
2865
	}
2866
 
2867
	/**
2868
	 * Return the auto-page-break mode (true or false).
2869
	 * @return bool auto-page-break mode
2870
	 * @public
2871
	 * @since 5.9.088
2872
	 */
2873
	public function getAutoPageBreak() {
2874
		return $this->AutoPageBreak;
2875
	}
2876
 
2877
	/**
2878
	 * Defines the way the document is to be displayed by the viewer.
2879
	 * @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>
2880
	 * @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>
2881
	 * @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>
2882
	 * @public
2883
	 * @since 1.2
2884
	 */
2885
	public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2886
		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2887
			$this->ZoomMode = $zoom;
2888
		} else {
2889
			$this->Error('Incorrect zoom display mode: '.$zoom);
2890
		}
2891
		$this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2892
		$this->PageMode = TCPDF_STATIC::getPageMode($mode);
2893
	}
2894
 
2895
	/**
2896
	 * 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.
2897
	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2898
	 * @param boolean $compress Boolean indicating if compression must be enabled.
2899
	 * @public
2900
	 * @since 1.4
2901
	 */
2902
	public function setCompression($compress=true) {
2903
		$this->compress = false;
2904
		if (function_exists('gzcompress')) {
2905
			if ($compress) {
2906
				if ( !$this->pdfa_mode) {
2907
					$this->compress = true;
2908
				}
2909
			}
2910
		}
2911
	}
2912
 
2913
	/**
2914
	 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2915
	 * @param boolean $mode If true force sRGB output intent.
2916
	 * @public
2917
	 * @since 5.9.121 (2011-09-28)
2918
	 */
2919
	public function setSRGBmode($mode=false) {
2920
		$this->force_srgb = $mode ? true : false;
2921
	}
2922
 
2923
	/**
2924
	 * Turn on/off Unicode mode for document information dictionary (meta tags).
2925
	 * This has effect only when unicode mode is set to false.
2926
	 * @param boolean $unicode if true set the meta information in Unicode
2927
	 * @since 5.9.027 (2010-12-01)
2928
	 * @public
2929
	 */
2930
	public function setDocInfoUnicode($unicode=true) {
2931
		$this->docinfounicode = $unicode ? true : false;
2932
	}
2933
 
2934
	/**
2935
	 * Defines the title of the document.
2936
	 * @param string $title The title.
2937
	 * @public
2938
	 * @since 1.2
2939
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2940
	 */
2941
	public function setTitle($title) {
2942
		$this->title = $title;
2943
	}
2944
 
2945
	/**
2946
	 * Defines the subject of the document.
2947
	 * @param string $subject The subject.
2948
	 * @public
2949
	 * @since 1.2
2950
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2951
	 */
2952
	public function setSubject($subject) {
2953
		$this->subject = $subject;
2954
	}
2955
 
2956
	/**
2957
	 * Defines the author of the document.
2958
	 * @param string $author The name of the author.
2959
	 * @public
2960
	 * @since 1.2
2961
	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2962
	 */
2963
	public function setAuthor($author) {
2964
		$this->author = $author;
2965
	}
2966
 
2967
	/**
2968
	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2969
	 * @param string $keywords The list of keywords.
2970
	 * @public
2971
	 * @since 1.2
2972
	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2973
	 */
2974
	public function setKeywords($keywords) {
2975
		$this->keywords = $keywords;
2976
	}
2977
 
2978
	/**
2979
	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2980
	 * @param string $creator The name of the creator.
2981
	 * @public
2982
	 * @since 1.2
2983
	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2984
	 */
2985
	public function setCreator($creator) {
2986
		$this->creator = $creator;
2987
	}
2988
 
2989
	/**
2990
	 * Whether to allow local file path in image html tags, when prefixed with file://
2991
	 *
2992
	 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
2993
	 * @public
2994
	 * @since 6.4
2995
	 */
2996
	public function setAllowLocalFiles($allowLocalFiles) {
2997
		$this->allowLocalFiles = (bool) $allowLocalFiles;
2998
	}
2999
 
3000
 
3001
	/**
3002
	 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
3003
	 * @param string $msg The error message
3004
	 * @public
3005
	 * @since 1.0
3006
	 */
3007
	public function Error($msg) {
3008
		// unset all class variables
3009
		$this->_destroy(true);
1441 ariadna 3010
		$msg = htmlspecialchars(string: $msg, encoding: 'UTF-8');
1 efrain 3011
		if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
3012
			die('<strong>TCPDF ERROR: </strong>'.$msg);
3013
		} else {
3014
			throw new Exception('TCPDF ERROR: '.$msg);
3015
		}
3016
	}
3017
 
3018
	/**
3019
	 * This method begins the generation of the PDF document.
3020
	 * It is not necessary to call it explicitly because AddPage() does it automatically.
3021
	 * Note: no page is created by this method
3022
	 * @public
3023
	 * @since 1.0
3024
	 * @see AddPage(), Close()
3025
	 */
3026
	public function Open() {
3027
		$this->state = 1;
3028
	}
3029
 
3030
	/**
3031
	 * Terminates the PDF document.
3032
	 * It is not necessary to call this method explicitly because Output() does it automatically.
3033
	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3034
	 * @public
3035
	 * @since 1.0
3036
	 * @see Open(), Output()
3037
	 */
3038
	public function Close() {
3039
		if ($this->state == 3) {
3040
			return;
3041
		}
3042
		if ($this->page == 0) {
3043
			$this->AddPage();
3044
		}
3045
		$this->endLayer();
3046
		if ($this->tcpdflink) {
3047
			// save current graphic settings
3048
			$gvars = $this->getGraphicVars();
3049
			$this->setEqualColumns();
3050
			$this->lastpage(true);
3051
			$this->setAutoPageBreak(false);
3052
			$this->x = 0;
3053
			$this->y = $this->h - (1 / $this->k);
3054
			$this->lMargin = 0;
3055
			$this->_outSaveGraphicsState();
3056
			$font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3057
			$this->setFont($font, '', 1);
3058
			$this->setTextRenderingMode(0, false, false);
3059
			$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";
3060
			$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3061
			$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3062
			$this->_outRestoreGraphicsState();
3063
			// restore graphic settings
3064
			$this->setGraphicVars($gvars);
3065
		}
3066
		// close page
3067
		$this->endPage();
3068
		// close document
3069
		$this->_enddoc();
3070
		// unset all class variables (except critical ones)
3071
		$this->_destroy(false);
3072
	}
3073
 
3074
	/**
3075
	 * Move pointer at the specified document page and update page dimensions.
3076
	 * @param int $pnum page number (1 ... numpages)
3077
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3078
	 * @public
3079
	 * @since 2.1.000 (2008-01-07)
3080
	 * @see getPage(), lastpage(), getNumPages()
3081
	 */
3082
	public function setPage($pnum, $resetmargins=false) {
3083
		if (($pnum == $this->page) AND ($this->state == 2)) {
3084
			return;
3085
		}
3086
		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3087
			$this->state = 2;
3088
			// save current graphic settings
3089
			//$gvars = $this->getGraphicVars();
3090
			$oldpage = $this->page;
3091
			$this->page = $pnum;
3092
			$this->wPt = $this->pagedim[$this->page]['w'];
3093
			$this->hPt = $this->pagedim[$this->page]['h'];
3094
			$this->w = $this->pagedim[$this->page]['wk'];
3095
			$this->h = $this->pagedim[$this->page]['hk'];
3096
			$this->tMargin = $this->pagedim[$this->page]['tm'];
3097
			$this->bMargin = $this->pagedim[$this->page]['bm'];
3098
			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3099
			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3100
			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3101
			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3102
			$this->setAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3103
			// restore graphic settings
3104
			//$this->setGraphicVars($gvars);
3105
			if ($resetmargins) {
3106
				$this->lMargin = $this->pagedim[$this->page]['olm'];
3107
				$this->rMargin = $this->pagedim[$this->page]['orm'];
3108
				$this->setY($this->tMargin);
3109
			} else {
3110
				// account for booklet mode
3111
				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3112
					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3113
					$this->lMargin += $deltam;
3114
					$this->rMargin -= $deltam;
3115
				}
3116
			}
3117
		} else {
3118
			$this->Error('Wrong page number on setPage() function: '.$pnum);
3119
		}
3120
	}
3121
 
3122
	/**
3123
	 * Reset pointer to the last document page.
3124
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3125
	 * @public
3126
	 * @since 2.0.000 (2008-01-04)
3127
	 * @see setPage(), getPage(), getNumPages()
3128
	 */
3129
	public function lastPage($resetmargins=false) {
3130
		$this->setPage($this->getNumPages(), $resetmargins);
3131
	}
3132
 
3133
	/**
3134
	 * Get current document page number.
3135
	 * @return int page number
3136
	 * @public
3137
	 * @since 2.1.000 (2008-01-07)
3138
	 * @see setPage(), lastpage(), getNumPages()
3139
	 */
3140
	public function getPage() {
3141
		return $this->page;
3142
	}
3143
 
3144
	/**
3145
	 * Get the total number of insered pages.
3146
	 * @return int number of pages
3147
	 * @public
3148
	 * @since 2.1.000 (2008-01-07)
3149
	 * @see setPage(), getPage(), lastpage()
3150
	 */
3151
	public function getNumPages() {
3152
		return $this->numpages;
3153
	}
3154
 
3155
	/**
3156
	 * Adds a new TOC (Table Of Content) page to the document.
3157
	 * @param string $orientation page orientation.
3158
	 * @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().
3159
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3160
	 * @public
3161
	 * @since 5.0.001 (2010-05-06)
3162
	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3163
	 */
3164
	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3165
		$this->AddPage($orientation, $format, $keepmargins, true);
3166
	}
3167
 
3168
	/**
3169
	 * Terminate the current TOC (Table Of Content) page
3170
	 * @public
3171
	 * @since 5.0.001 (2010-05-06)
3172
	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3173
	 */
3174
	public function endTOCPage() {
3175
		$this->endPage(true);
3176
	}
3177
 
3178
	/**
3179
	 * 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).
3180
	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3181
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3182
	 * @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().
3183
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3184
	 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3185
	 * @public
3186
	 * @since 1.0
3187
	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3188
	 */
3189
	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3190
		if ($this->inxobj) {
3191
			// we are inside an XObject template
3192
			return;
3193
		}
3194
		if (!isset($this->original_lMargin) OR $keepmargins) {
3195
			$this->original_lMargin = $this->lMargin;
3196
		}
3197
		if (!isset($this->original_rMargin) OR $keepmargins) {
3198
			$this->original_rMargin = $this->rMargin;
3199
		}
3200
		// terminate previous page
3201
		$this->endPage();
3202
		// start new page
3203
		$this->startPage($orientation, $format, $tocpage);
3204
	}
3205
 
3206
	/**
3207
	 * Terminate the current page
3208
	 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3209
	 * @public
3210
	 * @since 4.2.010 (2008-11-14)
3211
	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3212
	 */
3213
	public function endPage($tocpage=false) {
3214
		// check if page is already closed
3215
		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3216
			return;
3217
		}
3218
		// print page footer
3219
		$this->setFooter();
3220
		// close page
3221
		$this->_endpage();
3222
		// mark page as closed
3223
		$this->pageopen[$this->page] = false;
3224
		if ($tocpage) {
3225
			$this->tocpage = false;
3226
		}
3227
	}
3228
 
3229
	/**
3230
	 * Starts a new page to the document. The page must be closed using the endPage() function.
3231
	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3232
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3233
	 * @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().
3234
	 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3235
	 * @since 4.2.010 (2008-11-14)
3236
	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3237
	 * @public
3238
	 */
3239
	public function startPage($orientation='', $format='', $tocpage=false) {
3240
		if ($tocpage) {
3241
			$this->tocpage = true;
3242
		}
3243
		// move page numbers of documents to be attached
3244
		if ($this->tocpage) {
3245
			// move reference to unexistent pages (used for page attachments)
3246
			// adjust outlines
3247
			$tmpoutlines = $this->outlines;
3248
			foreach ($tmpoutlines as $key => $outline) {
3249
				if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3250
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
3251
				}
3252
			}
3253
			// adjust dests
3254
			$tmpdests = $this->dests;
3255
			foreach ($tmpdests as $key => $dest) {
3256
				if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3257
					$this->dests[$key]['p'] = ($dest['p'] + 1);
3258
				}
3259
			}
3260
			// adjust links
3261
			$tmplinks = $this->links;
3262
			foreach ($tmplinks as $key => $link) {
3263
				if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3264
					$this->links[$key]['p'] = ($link['p'] + 1);
3265
				}
3266
			}
3267
		}
3268
		if ($this->numpages > $this->page) {
3269
			// this page has been already added
3270
			$this->setPage($this->page + 1);
3271
			$this->setY($this->tMargin);
3272
			return;
3273
		}
3274
		// start a new page
3275
		if ($this->state == 0) {
3276
			$this->Open();
3277
		}
3278
		++$this->numpages;
3279
		$this->swapMargins($this->booklet);
3280
		// save current graphic settings
3281
		$gvars = $this->getGraphicVars();
3282
		// start new page
3283
		$this->_beginpage($orientation, $format);
3284
		// mark page as open
3285
		$this->pageopen[$this->page] = true;
3286
		// restore graphic settings
3287
		$this->setGraphicVars($gvars);
3288
		// mark this point
3289
		$this->setPageMark();
3290
		// print page header
3291
		$this->setHeader();
3292
		// restore graphic settings
3293
		$this->setGraphicVars($gvars);
3294
		// mark this point
3295
		$this->setPageMark();
3296
		// print table header (if any)
3297
		$this->setTableHeader();
3298
		// set mark for empty page check
3299
		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3300
	}
3301
 
3302
	/**
3303
	 * Set start-writing mark on current page stream used to put borders and fills.
3304
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3305
	 * This function must be called after calling Image() function for a background image.
3306
	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3307
	 * @public
3308
	 * @since 4.0.016 (2008-07-30)
3309
	 */
3310
	public function setPageMark() {
3311
		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3312
		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3313
		$this->setContentMark();
3314
	}
3315
 
3316
	/**
3317
	 * Set start-writing mark on selected page.
3318
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3319
	 * @param int $page page number (default is the current page)
3320
	 * @protected
3321
	 * @since 4.6.021 (2009-07-20)
3322
	 */
3323
	protected function setContentMark($page=0) {
3324
		if ($page <= 0) {
3325
			$page = $this->page;
3326
		}
3327
		if (isset($this->footerlen[$page])) {
3328
			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3329
		} else {
3330
			$this->cntmrk[$page] = $this->pagelen[$page];
3331
		}
3332
	}
3333
 
3334
	/**
3335
	 * Set header data.
3336
	 * @param string $ln header image logo
3337
	 * @param int $lw header image logo width in mm
3338
	 * @param string $ht string to print as title on document header
3339
	 * @param string $hs string to print on document header
3340
	 * @param int[] $tc RGB array color for text.
3341
	 * @param int[] $lc RGB array color for line.
3342
	 * @public
3343
	 */
3344
	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3345
		$this->header_logo = $ln;
3346
		$this->header_logo_width = $lw;
3347
		$this->header_title = $ht;
3348
		$this->header_string = $hs;
3349
		$this->header_text_color = $tc;
3350
		$this->header_line_color = $lc;
3351
	}
3352
 
3353
	/**
3354
	 * Set footer data.
3355
	 * @param int[] $tc RGB array color for text.
3356
	 * @param int[] $lc RGB array color for line.
3357
	 * @public
3358
	 */
3359
	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3360
		$this->footer_text_color = $tc;
3361
		$this->footer_line_color = $lc;
3362
	}
3363
 
3364
	/**
3365
	 * Returns header data:
3366
	 * <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>
3367
	 * @return array<string,mixed>
3368
	 * @public
3369
	 * @since 4.0.012 (2008-07-24)
3370
	 */
3371
	public function getHeaderData() {
3372
		$ret = array();
3373
		$ret['logo'] = $this->header_logo;
3374
		$ret['logo_width'] = $this->header_logo_width;
3375
		$ret['title'] = $this->header_title;
3376
		$ret['string'] = $this->header_string;
3377
		$ret['text_color'] = $this->header_text_color;
3378
		$ret['line_color'] = $this->header_line_color;
3379
		return $ret;
3380
	}
3381
 
3382
	/**
3383
	 * Set header margin.
3384
	 * (minimum distance between header and top page margin)
3385
	 * @param float $hm distance in user units
3386
	 * @public
3387
	 */
3388
	public function setHeaderMargin($hm=10) {
3389
		$this->header_margin = $hm;
3390
	}
3391
 
3392
	/**
3393
	 * Returns header margin in user units.
3394
	 * @return float
3395
	 * @since 4.0.012 (2008-07-24)
3396
	 * @public
3397
	 */
3398
	public function getHeaderMargin() {
3399
		return $this->header_margin;
3400
	}
3401
 
3402
	/**
3403
	 * Set footer margin.
3404
	 * (minimum distance between footer and bottom page margin)
3405
	 * @param float $fm distance in user units
3406
	 * @public
3407
	 */
3408
	public function setFooterMargin($fm=10) {
3409
		$this->footer_margin = $fm;
3410
	}
3411
 
3412
	/**
3413
	 * Returns footer margin in user units.
3414
	 * @return float
3415
	 * @since 4.0.012 (2008-07-24)
3416
	 * @public
3417
	 */
3418
	public function getFooterMargin() {
3419
		return $this->footer_margin;
3420
	}
3421
	/**
3422
	 * Set a flag to print page header.
3423
	 * @param boolean $val set to true to print the page header (default), false otherwise.
3424
	 * @public
3425
	 */
3426
	public function setPrintHeader($val=true) {
3427
		$this->print_header = $val ? true : false;
3428
	}
3429
 
3430
	/**
3431
	 * Set a flag to print page footer.
3432
	 * @param boolean $val set to true to print the page footer (default), false otherwise.
3433
	 * @public
3434
	 */
3435
	public function setPrintFooter($val=true) {
3436
		$this->print_footer = $val ? true : false;
3437
	}
3438
 
3439
	/**
3440
	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3441
	 * @return float
3442
	 * @public
3443
	 */
3444
	public function getImageRBX() {
3445
		return $this->img_rb_x;
3446
	}
3447
 
3448
	/**
3449
	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3450
	 * @return float
3451
	 * @public
3452
	 */
3453
	public function getImageRBY() {
3454
		return $this->img_rb_y;
3455
	}
3456
 
3457
	/**
3458
	 * Reset the xobject template used by Header() method.
3459
	 * @public
3460
	 */
3461
	public function resetHeaderTemplate() {
3462
		$this->header_xobjid = false;
3463
	}
3464
 
3465
	/**
3466
	 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3467
	 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3468
	 * @public
3469
	 */
3470
	public function setHeaderTemplateAutoreset($val=true) {
3471
		$this->header_xobj_autoreset = $val ? true : false;
3472
	}
3473
 
3474
	/**
3475
	 * This method is used to render the page header.
3476
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3477
	 * @public
3478
	 */
3479
	public function Header() {
3480
		if ($this->header_xobjid === false) {
3481
			// start a new XObject Template
3482
			$this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3483
			$headerfont = $this->getHeaderFont();
3484
			$headerdata = $this->getHeaderData();
3485
			$this->y = $this->header_margin;
3486
			if ($this->rtl) {
3487
				$this->x = $this->w - $this->original_rMargin;
3488
			} else {
3489
				$this->x = $this->original_lMargin;
3490
			}
3491
			if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3492
				$imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3493
				if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3494
					$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3495
				} elseif ($imgtype == 'svg') {
3496
					$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3497
				} else {
3498
					$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3499
				}
3500
				$imgy = $this->getImageRBY();
3501
			} else {
3502
				$imgy = $this->y;
3503
			}
3504
			$cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3505
			// set starting margin for text data cell
3506
			if ($this->getRTL()) {
3507
				$header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3508
			} else {
3509
				$header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3510
			}
3511
			$cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3512
			$this->setTextColorArray($this->header_text_color);
3513
			// header title
3514
			$this->setFont($headerfont[0], 'B', $headerfont[2] + 1);
3515
			$this->setX($header_x);
3516
			$this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3517
			// header string
3518
			$this->setFont($headerfont[0], $headerfont[1], $headerfont[2]);
3519
			$this->setX($header_x);
3520
			$this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3521
			// print an ending header line
3522
			$this->setLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3523
			$this->setY((2.835 / $this->k) + max($imgy, $this->y));
3524
			if ($this->rtl) {
3525
				$this->setX($this->original_rMargin);
3526
			} else {
3527
				$this->setX($this->original_lMargin);
3528
			}
3529
			$this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3530
			$this->endTemplate();
3531
		}
3532
		// print header template
3533
		$x = 0;
3534
		$dx = 0;
3535
		if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3536
			// adjust margins for booklet mode
3537
			$dx = ($this->original_lMargin - $this->original_rMargin);
3538
		}
3539
		if ($this->rtl) {
3540
			$x = $this->w + $dx;
3541
		} else {
3542
			$x = 0 + $dx;
3543
		}
3544
		$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3545
		if ($this->header_xobj_autoreset) {
3546
			// reset header xobject template at each page
3547
			$this->header_xobjid = false;
3548
		}
3549
	}
3550
 
3551
	/**
3552
	 * This method is used to render the page footer.
3553
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3554
	 * @public
3555
	 */
3556
	public function Footer() {
3557
		$cur_y = $this->y;
3558
		$this->setTextColorArray($this->footer_text_color);
3559
		//set style for cell border
3560
		$line_width = (0.85 / $this->k);
3561
		$this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3562
		//print document barcode
3563
		$barcode = $this->getBarcode();
3564
		if (!empty($barcode)) {
3565
			$this->Ln($line_width);
3566
			$barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3567
			$style = array(
3568
				'position' => $this->rtl?'R':'L',
3569
				'align' => $this->rtl?'R':'L',
3570
				'stretch' => false,
3571
				'fitwidth' => true,
3572
				'cellfitalign' => '',
3573
				'border' => false,
3574
				'padding' => 0,
3575
				'fgcolor' => array(0,0,0),
3576
				'bgcolor' => false,
3577
				'text' => false
3578
			);
3579
			$this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3580
		}
3581
		$w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3582
		if (empty($this->pagegroups)) {
3583
			$pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3584
		} else {
3585
			$pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3586
		}
3587
		$this->setY($cur_y);
3588
		//Print page number
3589
		if ($this->getRTL()) {
3590
			$this->setX($this->original_rMargin);
3591
			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3592
		} else {
3593
			$this->setX($this->original_lMargin);
3594
			$this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3595
		}
3596
	}
3597
 
3598
	/**
3599
	 * This method is used to render the page header.
3600
	 * @protected
3601
	 * @since 4.0.012 (2008-07-24)
3602
	 */
3603
	protected function setHeader() {
3604
		if (!$this->print_header OR ($this->state != 2)) {
3605
			return;
3606
		}
3607
		$this->InHeader = true;
3608
		$this->setGraphicVars($this->default_graphic_vars);
3609
		$temp_thead = $this->thead;
3610
		$temp_theadMargins = $this->theadMargins;
3611
		$lasth = $this->lasth;
3612
		$newline = $this->newline;
3613
		$this->_outSaveGraphicsState();
3614
		$this->rMargin = $this->original_rMargin;
3615
		$this->lMargin = $this->original_lMargin;
3616
		$this->setCellPadding(0);
3617
		//set current position
3618
		if ($this->rtl) {
3619
			$this->setXY($this->original_rMargin, $this->header_margin);
3620
		} else {
3621
			$this->setXY($this->original_lMargin, $this->header_margin);
3622
		}
3623
		$this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3624
		$this->Header();
3625
		//restore position
3626
		if ($this->rtl) {
3627
			$this->setXY($this->original_rMargin, $this->tMargin);
3628
		} else {
3629
			$this->setXY($this->original_lMargin, $this->tMargin);
3630
		}
3631
		$this->_outRestoreGraphicsState();
3632
		$this->lasth = $lasth;
3633
		$this->thead = $temp_thead;
3634
		$this->theadMargins = $temp_theadMargins;
3635
		$this->newline = $newline;
3636
		$this->InHeader = false;
3637
	}
3638
 
3639
	/**
3640
	 * This method is used to render the page footer.
3641
	 * @protected
3642
	 * @since 4.0.012 (2008-07-24)
3643
	 */
3644
	protected function setFooter() {
3645
		if ($this->state != 2) {
3646
			return;
3647
		}
3648
		$this->InFooter = true;
3649
		// save current graphic settings
3650
		$gvars = $this->getGraphicVars();
3651
		// mark this point
3652
		$this->footerpos[$this->page] = $this->pagelen[$this->page];
3653
		$this->_out("\n");
3654
		if ($this->print_footer) {
3655
			$this->setGraphicVars($this->default_graphic_vars);
3656
			$this->current_column = 0;
3657
			$this->num_columns = 1;
3658
			$temp_thead = $this->thead;
3659
			$temp_theadMargins = $this->theadMargins;
3660
			$lasth = $this->lasth;
3661
			$this->_outSaveGraphicsState();
3662
			$this->rMargin = $this->original_rMargin;
3663
			$this->lMargin = $this->original_lMargin;
3664
			$this->setCellPadding(0);
3665
			//set current position
3666
			$footer_y = $this->h - $this->footer_margin;
3667
			if ($this->rtl) {
3668
				$this->setXY($this->original_rMargin, $footer_y);
3669
			} else {
3670
				$this->setXY($this->original_lMargin, $footer_y);
3671
			}
3672
			$this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3673
			$this->Footer();
3674
			//restore position
3675
			if ($this->rtl) {
3676
				$this->setXY($this->original_rMargin, $this->tMargin);
3677
			} else {
3678
				$this->setXY($this->original_lMargin, $this->tMargin);
3679
			}
3680
			$this->_outRestoreGraphicsState();
3681
			$this->lasth = $lasth;
3682
			$this->thead = $temp_thead;
3683
			$this->theadMargins = $temp_theadMargins;
3684
		}
3685
		// restore graphic settings
3686
		$this->setGraphicVars($gvars);
3687
		$this->current_column = $gvars['current_column'];
3688
		$this->num_columns = $gvars['num_columns'];
3689
		// calculate footer length
3690
		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3691
		$this->InFooter = false;
3692
	}
3693
 
3694
	/**
3695
	 * Check if we are on the page body (excluding page header and footer).
3696
	 * @return bool true if we are not in page header nor in page footer, false otherwise.
3697
	 * @protected
3698
	 * @since 5.9.091 (2011-06-15)
3699
	 */
3700
	protected function inPageBody() {
3701
		return (($this->InHeader === false) AND ($this->InFooter === false));
3702
	}
3703
 
3704
	/**
3705
	 * This method is used to render the table header on new page (if any).
3706
	 * @protected
3707
	 * @since 4.5.030 (2009-03-25)
3708
	 */
3709
	protected function setTableHeader() {
3710
		if ($this->num_columns > 1) {
3711
			// multi column mode
3712
			return;
3713
		}
3714
		if (isset($this->theadMargins['top'])) {
3715
			// restore the original top-margin
3716
			$this->tMargin = $this->theadMargins['top'];
3717
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3718
			$this->y = $this->tMargin;
3719
		}
3720
		if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3721
			// set margins
3722
			$prev_lMargin = $this->lMargin;
3723
			$prev_rMargin = $this->rMargin;
3724
			$prev_cell_padding = $this->cell_padding;
3725
			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3726
			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3727
			$this->cell_padding = $this->theadMargins['cell_padding'];
3728
			if ($this->rtl) {
3729
				$this->x = $this->w - $this->rMargin;
3730
			} else {
3731
				$this->x = $this->lMargin;
3732
			}
3733
			// account for special "cell" mode
3734
			if ($this->theadMargins['cell']) {
3735
				if ($this->rtl) {
3736
					$this->x -= $this->cell_padding['R'];
3737
				} else {
3738
					$this->x += $this->cell_padding['L'];
3739
				}
3740
			}
3741
			$gvars = $this->getGraphicVars();
3742
			if (!empty($this->theadMargins['gvars'])) {
3743
				// set the correct graphic style
3744
				$this->setGraphicVars($this->theadMargins['gvars']);
3745
				$this->rMargin = $gvars['rMargin'];
3746
				$this->lMargin = $gvars['lMargin'];
3747
			}
3748
			// print table header
3749
			$this->writeHTML($this->thead, false, false, false, false, '');
3750
			$this->setGraphicVars($gvars);
3751
			// set new top margin to skip the table headers
3752
			if (!isset($this->theadMargins['top'])) {
3753
				$this->theadMargins['top'] = $this->tMargin;
3754
			}
3755
			// store end of header position
3756
			if (!isset($this->columns[0]['th'])) {
3757
				$this->columns[0]['th'] = array();
3758
			}
3759
			$this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3760
			$this->tMargin = $this->y;
3761
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
3762
			$this->lasth = 0;
3763
			$this->lMargin = $prev_lMargin;
3764
			$this->rMargin = $prev_rMargin;
3765
			$this->cell_padding = $prev_cell_padding;
3766
		}
3767
	}
3768
 
3769
	/**
3770
	 * Returns the current page number.
3771
	 * @return int page number
3772
	 * @public
3773
	 * @since 1.0
3774
	 * @see getAliasNbPages()
3775
	 */
3776
	public function PageNo() {
3777
		return $this->page;
3778
	}
3779
 
3780
	/**
3781
	 * Returns the array of spot colors.
3782
	 * @return array Spot colors array.
3783
	 * @public
3784
	 * @since 6.0.038 (2013-09-30)
3785
	 */
3786
	public function getAllSpotColors() {
3787
		return $this->spot_colors;
3788
	}
3789
 
3790
	/**
3791
	 * Defines a new spot color.
3792
	 * It can be expressed in RGB components or gray scale.
3793
	 * The method can be called before the first page is created and the value is retained from page to page.
3794
	 * @param string $name Full name of the spot color.
3795
	 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3796
	 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3797
	 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3798
	 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3799
	 * @public
3800
	 * @since 4.0.024 (2008-09-12)
3801
	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3802
	 */
3803
	public function AddSpotColor($name, $c, $m, $y, $k) {
3804
		if (!isset($this->spot_colors[$name])) {
3805
			$i = (1 + count($this->spot_colors));
3806
			$this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3807
		}
3808
	}
3809
 
3810
	/**
3811
	 * Set the spot color for the specified type ('draw', 'fill', 'text').
3812
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3813
	 * @param string $name Name of the spot color.
3814
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3815
	 * @return string PDF color command.
3816
	 * @public
3817
	 * @since 5.9.125 (2011-10-03)
3818
	 */
3819
	public function setSpotColor($type, $name, $tint=100) {
3820
		$spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3821
		if ($spotcolor === false) {
3822
			$this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3823
		}
3824
		$tint = (max(0, min(100, $tint)) / 100);
3825
		$pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3826
		switch ($type) {
3827
			case 'draw': {
3828
				$pdfcolor .= sprintf('CS %F SCN', $tint);
3829
				$this->DrawColor = $pdfcolor;
3830
				$this->strokecolor = $spotcolor;
3831
				break;
3832
			}
3833
			case 'fill': {
3834
				$pdfcolor .= sprintf('cs %F scn', $tint);
3835
				$this->FillColor = $pdfcolor;
3836
				$this->bgcolor = $spotcolor;
3837
				break;
3838
			}
3839
			case 'text': {
3840
				$pdfcolor .= sprintf('cs %F scn', $tint);
3841
				$this->TextColor = $pdfcolor;
3842
				$this->fgcolor = $spotcolor;
3843
				break;
3844
			}
3845
		}
3846
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
3847
		if ($this->state == 2) {
3848
			$this->_out($pdfcolor);
3849
		}
3850
		if ($this->inxobj) {
3851
			// we are inside an XObject template
3852
			$this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3853
		}
3854
		return $pdfcolor;
3855
	}
3856
 
3857
	/**
3858
	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3859
	 * @param string $name Name of the spot color.
3860
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3861
	 * @public
3862
	 * @since 4.0.024 (2008-09-12)
3863
	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3864
	 */
3865
	public function setDrawSpotColor($name, $tint=100) {
3866
		$this->setSpotColor('draw', $name, $tint);
3867
	}
3868
 
3869
	/**
3870
	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3871
	 * @param string $name Name of the spot color.
3872
	 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3873
	 * @public
3874
	 * @since 4.0.024 (2008-09-12)
3875
	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3876
	 */
3877
	public function setFillSpotColor($name, $tint=100) {
3878
		$this->setSpotColor('fill', $name, $tint);
3879
	}
3880
 
3881
	/**
3882
	 * Defines the spot color used for text.
3883
	 * @param string $name Name of the spot color.
3884
	 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3885
	 * @public
3886
	 * @since 4.0.024 (2008-09-12)
3887
	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3888
	 */
3889
	public function setTextSpotColor($name, $tint=100) {
3890
		$this->setSpotColor('text', $name, $tint);
3891
	}
3892
 
3893
	/**
3894
	 * Set the color array for the specified type ('draw', 'fill', 'text').
3895
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3896
	 * The method can be called before the first page is created and the value is retained from page to page.
3897
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3898
	 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3899
	 * @param boolean $ret If true do not send the PDF command.
3900
	 * @return string The PDF command or empty string.
3901
	 * @public
3902
	 * @since 3.1.000 (2008-06-11)
3903
	 */
3904
	public function setColorArray($type, $color, $ret=false) {
3905
		if (is_array($color)) {
3906
			$color = array_values($color);
3907
			// component: grey, RGB red or CMYK cyan
3908
			$c = isset($color[0]) ? $color[0] : -1;
3909
			// component: RGB green or CMYK magenta
3910
			$m = isset($color[1]) ? $color[1] : -1;
3911
			// component: RGB blue or CMYK yellow
3912
			$y = isset($color[2]) ? $color[2] : -1;
3913
			// component: CMYK black
3914
			$k = isset($color[3]) ? $color[3] : -1;
3915
			// color name
3916
			$name = isset($color[4]) ? $color[4] : '';
3917
			if ($c >= 0) {
3918
				return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3919
			}
3920
		}
3921
		return '';
3922
	}
3923
 
3924
	/**
3925
	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3926
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3927
	 * The method can be called before the first page is created and the value is retained from page to page.
3928
	 * @param array $color Array of colors (1, 3 or 4 values).
3929
	 * @param boolean $ret If true do not send the PDF command.
3930
	 * @return string the PDF command
3931
	 * @public
3932
	 * @since 3.1.000 (2008-06-11)
3933
	 * @see SetDrawColor()
3934
	 */
3935
	public function setDrawColorArray($color, $ret=false) {
3936
		return $this->setColorArray('draw', $color, $ret);
3937
	}
3938
 
3939
	/**
3940
	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3941
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3942
	 * The method can be called before the first page is created and the value is retained from page to page.
3943
	 * @param array $color Array of colors (1, 3 or 4 values).
3944
	 * @param boolean $ret If true do not send the PDF command.
3945
	 * @public
3946
	 * @since 3.1.000 (2008-6-11)
3947
	 * @see SetFillColor()
3948
	 */
3949
	public function setFillColorArray($color, $ret=false) {
3950
		return $this->setColorArray('fill', $color, $ret);
3951
	}
3952
 
3953
	/**
3954
	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3955
	 * The method can be called before the first page is created and the value is retained from page to page.
3956
	 * @param array $color Array of colors (1, 3 or 4 values).
3957
	 * @param boolean $ret If true do not send the PDF command.
3958
	 * @public
3959
	 * @since 3.1.000 (2008-6-11)
3960
	 * @see SetFillColor()
3961
	 */
3962
	public function setTextColorArray($color, $ret=false) {
3963
		return $this->setColorArray('text', $color, $ret);
3964
	}
3965
 
3966
	/**
3967
	 * Defines the color used by the specified type ('draw', 'fill', 'text').
3968
	 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3969
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3970
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3971
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3972
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3973
	 * @param boolean $ret If true do not send the command.
3974
	 * @param string $name spot color name (if any)
3975
	 * @return string The PDF command or empty string.
3976
	 * @public
3977
	 * @since 5.9.125 (2011-10-03)
3978
	 */
3979
	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3980
		// set default values
3981
		if (!is_numeric($col1)) {
3982
			$col1 = 0;
3983
		}
3984
		if (!is_numeric($col2)) {
3985
			$col2 = -1;
3986
		}
3987
		if (!is_numeric($col3)) {
3988
			$col3 = -1;
3989
		}
3990
		if (!is_numeric($col4)) {
3991
			$col4 = -1;
3992
		}
3993
		// set color by case
3994
		$suffix = '';
3995
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3996
			// Grey scale
3997
			$col1 = max(0, min(255, $col1));
3998
			$intcolor = array('G' => $col1);
3999
			$pdfcolor = sprintf('%F ', ($col1 / 255));
4000
			$suffix = 'g';
4001
		} elseif ($col4 == -1) {
4002
			// RGB
4003
			$col1 = max(0, min(255, $col1));
4004
			$col2 = max(0, min(255, $col2));
4005
			$col3 = max(0, min(255, $col3));
4006
			$intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4007
			$pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4008
			$suffix = 'rg';
4009
		} else {
4010
			$col1 = max(0, min(100, $col1));
4011
			$col2 = max(0, min(100, $col2));
4012
			$col3 = max(0, min(100, $col3));
4013
			$col4 = max(0, min(100, $col4));
4014
			if (empty($name)) {
4015
				// CMYK
4016
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4017
				$pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4018
				$suffix = 'k';
4019
			} else {
4020
				// SPOT COLOR
4021
				$intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4022
				$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4023
				$pdfcolor = $this->setSpotColor($type, $name, 100);
4024
			}
4025
		}
4026
		switch ($type) {
4027
			case 'draw': {
4028
				$pdfcolor .= strtoupper($suffix);
4029
				$this->DrawColor = $pdfcolor;
4030
				$this->strokecolor = $intcolor;
4031
				break;
4032
			}
4033
			case 'fill': {
4034
				$pdfcolor .= $suffix;
4035
				$this->FillColor = $pdfcolor;
4036
				$this->bgcolor = $intcolor;
4037
				break;
4038
			}
4039
			case 'text': {
4040
				$pdfcolor .= $suffix;
4041
				$this->TextColor = $pdfcolor;
4042
				$this->fgcolor = $intcolor;
4043
				break;
4044
			}
4045
		}
4046
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4047
		if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4048
			if (!$ret) {
4049
				$this->_out($pdfcolor);
4050
			}
4051
			return $pdfcolor;
4052
		}
4053
		return '';
4054
	}
4055
 
4056
	/**
4057
	 * 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.
4058
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4059
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4060
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4061
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4062
	 * @param boolean $ret If true do not send the command.
4063
	 * @param string $name spot color name (if any)
4064
	 * @return string the PDF command
4065
	 * @public
4066
	 * @since 1.3
4067
	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4068
	 */
4069
	public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4070
		return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4071
	}
4072
 
4073
	/**
4074
	 * 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.
4075
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4076
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4077
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4078
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4079
	 * @param boolean $ret If true do not send the command.
4080
	 * @param string $name Spot color name (if any).
4081
	 * @return string The PDF command.
4082
	 * @public
4083
	 * @since 1.3
4084
	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4085
	 */
4086
	public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4087
		return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4088
	}
4089
 
4090
	/**
4091
	 * 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.
4092
	 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4093
	 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4094
	 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4095
	 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4096
	 * @param boolean $ret If true do not send the command.
4097
	 * @param string $name Spot color name (if any).
4098
	 * @return string Empty string.
4099
	 * @public
4100
	 * @since 1.3
4101
	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4102
	 */
4103
	public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4104
		return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4105
	}
4106
 
4107
	/**
4108
	 * Returns the length of a string in user unit. A font must be selected.<br>
4109
	 * @param string $s The string whose length is to be computed
4110
	 * @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.
4111
	 * @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.
4112
	 * @param float $fontsize Font size in points. The default value is the current size.
4113
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4114
	 * @return float[]|float total string length or array of characted widths
4115
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4116
	 * @author Nicola Asuni
4117
	 * @public
4118
	 * @since 1.2
4119
	 */
4120
	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4121
		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);
4122
	}
4123
 
4124
	/**
4125
	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4126
	 * @param array $sa The array of chars whose total length is to be computed
4127
	 * @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.
4128
	 * @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.
4129
	 * @param float $fontsize Font size in points. The default value is the current size.
4130
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4131
	 * @return float[]|float total string length or array of characted widths
4132
	 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4133
	 * @author Nicola Asuni
4134
	 * @public
4135
	 * @since 2.4.000 (2008-03-06)
4136
	 */
4137
	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4138
		// store current values
4139
		if (!TCPDF_STATIC::empty_string($fontname)) {
4140
			$prev_FontFamily = $this->FontFamily;
4141
			$prev_FontStyle = $this->FontStyle;
4142
			$prev_FontSizePt = $this->FontSizePt;
4143
			$this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4144
		}
4145
		// convert UTF-8 array to Latin1 if required
4146
		if ($this->isunicode AND (!$this->isUnicodeFont())) {
4147
			$sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4148
		}
4149
		$w = 0; // total width
4150
		$wa = array(); // array of characters widths
4151
		foreach ($sa as $ck => $char) {
4152
			// character width
4153
			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4154
			$wa[] = $cw;
4155
			$w += $cw;
4156
		}
4157
		// restore previous values
4158
		if (!TCPDF_STATIC::empty_string($fontname)) {
4159
			$this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4160
		}
4161
		if ($getarray) {
4162
			return $wa;
4163
		}
4164
		return $w;
4165
	}
4166
 
4167
	/**
4168
	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4169
	 * @param int $char The char code whose length is to be returned
4170
	 * @param boolean $notlast If false ignore the font-spacing.
4171
	 * @return float char width
4172
	 * @author Nicola Asuni
4173
	 * @public
4174
	 * @since 2.4.000 (2008-03-06)
4175
	 */
4176
	public function GetCharWidth($char, $notlast=true) {
4177
		// get raw width
4178
		$chw = $this->getRawCharWidth($char);
4179
		if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4180
			// increase/decrease font spacing
4181
			$chw += $this->font_spacing;
4182
		}
4183
		if ($this->font_stretching != 100) {
4184
			// fixed stretching mode
4185
			$chw *= ($this->font_stretching / 100);
4186
		}
4187
		return $chw;
4188
	}
4189
 
4190
	/**
4191
	 * Returns the length of the char in user unit for the current font.
4192
	 * @param int $char The char code whose length is to be returned
4193
	 * @return float char width
4194
	 * @author Nicola Asuni
4195
	 * @public
4196
	 * @since 5.9.000 (2010-09-28)
4197
	 */
4198
	public function getRawCharWidth($char) {
4199
		if ($char == 173) {
4200
			// SHY character will not be printed
4201
			return (0);
4202
		}
4203
		if (isset($this->CurrentFont['cw'][intval($char)])) {
4204
			$w = $this->CurrentFont['cw'][intval($char)];
4205
		} elseif (isset($this->CurrentFont['dw'])) {
4206
			// default width
4207
			$w = $this->CurrentFont['dw'];
4208
		} elseif (isset($this->CurrentFont['cw'][32])) {
4209
			// default width
4210
			$w = $this->CurrentFont['cw'][32];
4211
		} else {
4212
			$w = 600;
4213
		}
4214
		return $this->getAbsFontMeasure($w);
4215
	}
4216
 
4217
	/**
4218
	 * Returns the numbero of characters in a string.
4219
	 * @param string $s The input string.
4220
	 * @return int number of characters
4221
	 * @public
4222
	 * @since 2.0.0001 (2008-01-07)
4223
	 */
4224
	public function GetNumChars($s) {
4225
		if ($this->isUnicodeFont()) {
4226
			return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4227
		}
4228
		return strlen($s);
4229
	}
4230
 
4231
	/**
4232
	 * Fill the list of available fonts ($this->fontlist).
4233
	 * @protected
4234
	 * @since 4.0.013 (2008-07-28)
4235
	 */
4236
	protected function getFontsList() {
4237
		if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4238
			while (($file = readdir($fontsdir)) !== false) {
4239
				if (substr($file, -4) == '.php') {
4240
					array_push($this->fontlist, strtolower(basename($file, '.php')));
4241
				}
4242
			}
4243
			closedir($fontsdir);
4244
		}
4245
	}
4246
 
4247
	/**
4248
	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4249
	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4250
	 * 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.
4251
	 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4252
	 * @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>
4253
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4254
	 * @return array|false array containing the font data, or false in case of error.
4255
	 * @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.
4256
	 * @public
4257
	 * @since 1.5
4258
	 * @see SetFont(), setFontSubsetting()
4259
	 */
4260
	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4261
		if ($subset === 'default') {
4262
			$subset = $this->font_subsetting;
4263
		}
4264
		if ($this->pdfa_mode) {
4265
			$subset = false;
4266
		}
4267
		if (TCPDF_STATIC::empty_string($family)) {
4268
			if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4269
				$family = $this->FontFamily;
4270
			} else {
4271
				$this->Error('Empty font family');
4272
			}
4273
		}
4274
		// move embedded styles on $style
4275
		if (substr($family, -1) == 'I') {
4276
			$style .= 'I';
4277
			$family = substr($family, 0, -1);
4278
		}
4279
		if (substr($family, -1) == 'B') {
4280
			$style .= 'B';
4281
			$family = substr($family, 0, -1);
4282
		}
4283
		// normalize family name
4284
		$family = strtolower($family);
4285
		if ((!$this->isunicode) AND ($family == 'arial')) {
4286
			$family = 'helvetica';
4287
		}
4288
		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4289
			$style = '';
4290
		}
4291
		if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4292
			// all fonts must be embedded
4293
			$family = 'pdfa'.$family;
4294
		}
4295
		$tempstyle = strtoupper($style === null ? '' : $style);
4296
		$style = '';
4297
		// underline
4298
		if (strpos($tempstyle, 'U') !== false) {
4299
			$this->underline = true;
4300
		} else {
4301
			$this->underline = false;
4302
		}
4303
		// line-through (deleted)
4304
		if (strpos($tempstyle, 'D') !== false) {
4305
			$this->linethrough = true;
4306
		} else {
4307
			$this->linethrough = false;
4308
		}
4309
		// overline
4310
		if (strpos($tempstyle, 'O') !== false) {
4311
			$this->overline = true;
4312
		} else {
4313
			$this->overline = false;
4314
		}
4315
		// bold
4316
		if (strpos($tempstyle, 'B') !== false) {
4317
			$style .= 'B';
4318
		}
4319
		// oblique
4320
		if (strpos($tempstyle, 'I') !== false) {
4321
			$style .= 'I';
4322
		}
4323
		$bistyle = $style;
4324
		$fontkey = $family.$style;
4325
		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4326
		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4327
		// check if the font has been already added
4328
		$fb = $this->getFontBuffer($fontkey);
4329
		if ($fb !== false) {
4330
			if ($this->inxobj) {
4331
				// we are inside an XObject template
4332
				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4333
			}
4334
			return $fontdata;
4335
		}
4336
		// get specified font directory (if any)
4337
		$fontdir = false;
4338
		if (!TCPDF_STATIC::empty_string($fontfile)) {
4339
			$fontdir = dirname($fontfile);
4340
			if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4341
				$fontdir = '';
4342
			} else {
4343
				$fontdir .= '/';
4344
			}
4345
		}
4346
		// true when the font style variation is missing
4347
		$missing_style = false;
4348
		// search and include font file
4349
		if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4350
			// build a standard filenames for specified font
4351
			$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4352
			$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4353
			if (TCPDF_STATIC::empty_string($fontfile)) {
4354
				$missing_style = true;
4355
				// try to remove the style part
4356
				$tmp_fontfile = str_replace(' ', '', $family).'.php';
4357
				$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4358
			}
4359
		}
4360
		// include font file
4361
		if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4362
			$type=null;
4363
			$name=null;
4364
			$desc=null;
4365
			$up=-null;
4366
			$ut=null;
4367
			$cw=null;
4368
			$cbbox=null;
4369
			$dw=null;
4370
			$enc=null;
4371
			$cidinfo=null;
4372
			$file=null;
4373
			$ctg=null;
4374
			$diff=null;
4375
			$originalsize=null;
4376
			$size1=null;
4377
			$size2=null;
4378
			include($fontfile);
4379
		} else {
4380
			$this->Error('Could not include font definition file: '.$family.'');
4381
		}
4382
		// check font parameters
4383
		if ((!isset($type)) OR (!isset($cw))) {
4384
			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4385
		}
4386
		// SET default parameters
4387
		if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4388
			$file = '';
4389
		}
4390
		if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4391
			$enc = '';
4392
		}
4393
		if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4394
			$cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4395
			$cidinfo['uni2cid'] = array();
4396
		}
4397
		if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4398
			$ctg = '';
4399
		}
4400
		if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4401
			$desc = array();
4402
		}
4403
		if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4404
			$up = -100;
4405
		}
4406
		if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4407
			$ut = 50;
4408
		}
4409
		if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4410
			$cw = array();
4411
		}
4412
		if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4413
			// set default width
4414
			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4415
				$dw = $desc['MissingWidth'];
4416
			} elseif (isset($cw[32])) {
4417
				$dw = $cw[32];
4418
			} else {
4419
				$dw = 600;
4420
			}
4421
		}
4422
		++$this->numfonts;
4423
		if ($type == 'core') {
4424
			$name = $this->CoreFonts[$fontkey];
4425
			$subset = false;
4426
		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4427
			$subset = false;
4428
		} elseif ($type == 'TrueTypeUnicode') {
4429
			$enc = 'Identity-H';
4430
		} elseif ($type == 'cidfont0') {
4431
			if ($this->pdfa_mode) {
4432
				$this->Error('All fonts must be embedded in PDF/A mode!');
4433
			}
4434
		} else {
4435
			$this->Error('Unknow font type: '.$type.'');
4436
		}
4437
		// set name if unset
1441 ariadna 4438
		if (empty($name)) {
1 efrain 4439
			$name = $fontkey;
4440
		}
4441
		// create artificial font style variations if missing (only works with non-embedded fonts)
4442
		if (($type != 'core') AND $missing_style) {
4443
			// style variations
4444
			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4445
			$name .= $styles[$bistyle];
4446
			// artificial bold
4447
			if (strpos($bistyle, 'B') !== false) {
4448
				if (isset($desc['StemV'])) {
4449
					// from normal to bold
4450
					$desc['StemV'] = round($desc['StemV'] * 1.75);
4451
				} else {
4452
					// bold
4453
					$desc['StemV'] = 123;
4454
				}
4455
			}
4456
			// artificial italic
4457
			if (strpos($bistyle, 'I') !== false) {
4458
				if (isset($desc['ItalicAngle'])) {
4459
					$desc['ItalicAngle'] -= 11;
4460
				} else {
4461
					$desc['ItalicAngle'] = -11;
4462
				}
4463
				if (isset($desc['Flags'])) {
4464
					$desc['Flags'] |= 64; //bit 7
4465
				} else {
4466
					$desc['Flags'] = 64;
4467
				}
4468
			}
4469
		}
4470
		// check if the array of characters bounding boxes is defined
4471
		if (!isset($cbbox)) {
4472
			$cbbox = array();
4473
		}
4474
		// initialize subsetchars
4475
		$subsetchars = array_fill(0, 255, true);
4476
		$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));
4477
		if ($this->inxobj) {
4478
			// we are inside an XObject template
4479
			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4480
		}
1441 ariadna 4481
		if (!empty($diff)) {
1 efrain 4482
			//Search existing encodings
4483
			$d = 0;
4484
			$nb = count($this->diffs);
4485
			for ($i=1; $i <= $nb; ++$i) {
4486
				if ($this->diffs[$i] == $diff) {
4487
					$d = $i;
4488
					break;
4489
				}
4490
			}
4491
			if ($d == 0) {
4492
				$d = $nb + 1;
4493
				$this->diffs[$d] = $diff;
4494
			}
4495
			$this->setFontSubBuffer($fontkey, 'diff', $d);
4496
		}
4497
		if (!TCPDF_STATIC::empty_string($file)) {
4498
			if (!isset($this->FontFiles[$file])) {
4499
				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4500
					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4501
				} elseif ($type != 'core') {
4502
					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4503
				}
4504
			} else {
4505
				// update fontkeys that are sharing this font file
4506
				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4507
				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4508
					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4509
				}
4510
			}
4511
		}
4512
		return $fontdata;
4513
	}
4514
 
4515
	/**
4516
	 * Sets the font used to print character strings.
4517
	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4518
	 * The method can be called before the first page is created and the font is retained from page to page.
4519
	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4520
	 * 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 />
4521
	 * @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.
4522
	 * @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.
4523
	 * @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
4524
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4525
	 * @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.
4526
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4527
	 * @author Nicola Asuni
4528
	 * @public
4529
	 * @since 1.0
4530
	 * @see AddFont(), SetFontSize()
4531
	 */
4532
	public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4533
		//Select a font; size given in points
4534
		if ($size === null) {
4535
			$size = $this->FontSizePt;
4536
		}
4537
		if ($size < 0) {
4538
			$size = 0;
4539
		}
4540
		// try to add font (if not already added)
4541
		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4542
		$this->FontFamily = $fontdata['family'];
4543
		$this->FontStyle = $fontdata['style'];
4544
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4545
			// save subset chars of the previous font
4546
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4547
		}
4548
		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4549
		$this->setFontSize($size, $out);
4550
	}
4551
 
4552
	/**
4553
	 * Defines the size of the current font.
4554
	 * @param float $size The font size in points.
4555
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4556
	 * @public
4557
	 * @since 1.0
4558
	 * @see SetFont()
4559
	 */
4560
	public function setFontSize($size, $out=true) {
4561
		$size = (float)$size;
4562
		// font size in points
4563
		$this->FontSizePt = $size;
4564
		// font size in user units
4565
		$this->FontSize = $size / $this->k;
4566
		// calculate some font metrics
4567
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4568
			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4569
			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4570
		} else {
4571
			$font_height = $size * 1.219;
4572
		}
4573
		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4574
			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4575
		}
4576
		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4577
			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4578
		}
4579
		if (!isset($font_ascent) AND !isset($font_descent)) {
4580
			// core font
4581
			$font_ascent = 0.76 * $font_height;
4582
			$font_descent = $font_height - $font_ascent;
4583
		} elseif (!isset($font_descent)) {
4584
			$font_descent = $font_height - $font_ascent;
4585
		} elseif (!isset($font_ascent)) {
4586
			$font_ascent = $font_height - $font_descent;
4587
		}
4588
		$this->FontAscent = ($font_ascent / $this->k);
4589
		$this->FontDescent = ($font_descent / $this->k);
4590
		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4591
			$this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4592
		}
4593
	}
4594
 
4595
	/**
4596
	 * Returns the bounding box of the current font in user units.
4597
	 * @return array
4598
	 * @public
4599
	 * @since 5.9.152 (2012-03-23)
4600
	 */
4601
	public function getFontBBox() {
4602
		$fbbox = array();
4603
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4604
			$tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4605
			$fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4606
		} else {
4607
			// Find max width
4608
			if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4609
				$maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4610
			} else {
4611
				$maxw = 0;
4612
				if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4613
					$maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4614
				}
4615
				if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4616
					$maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4617
				}
4618
				if (isset($this->CurrentFont['dw'])) {
4619
					$maxw = max($maxw, $this->CurrentFont['dw']);
4620
				}
4621
				foreach ($this->CurrentFont['cw'] as $char => $w) {
4622
					$maxw = max($maxw, $w);
4623
				}
4624
				if ($maxw == 0) {
4625
					$maxw = 600;
4626
				}
4627
				$maxw = $this->getAbsFontMeasure($maxw);
4628
			}
4629
			$fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4630
		}
4631
		return $fbbox;
4632
	}
4633
 
4634
	/**
4635
	 * Convert a relative font measure into absolute value.
4636
	 * @param int $s Font measure.
4637
	 * @return float Absolute measure.
4638
	 * @since 5.9.186 (2012-09-13)
4639
	 */
4640
	public function getAbsFontMeasure($s) {
4641
		return ($s * $this->FontSize / 1000);
4642
	}
4643
 
4644
	/**
4645
	 * Returns the glyph bounding box of the specified character in the current font in user units.
4646
	 * @param int $char Input character code.
4647
	 * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4648
	 * @since 5.9.186 (2012-09-13)
4649
	 */
4650
	public function getCharBBox($char) {
4651
		$c = intval($char);
4652
		if (isset($this->CurrentFont['cw'][$c])) {
4653
			// glyph is defined ... use zero width & height for glyphs without outlines
4654
			$result = array(0,0,0,0);
4655
			if (isset($this->CurrentFont['cbbox'][$c])) {
4656
				$result = $this->CurrentFont['cbbox'][$c];
4657
			}
4658
			return array_map(array($this,'getAbsFontMeasure'), $result);
4659
		}
4660
		return false;
4661
	}
4662
 
4663
	/**
4664
	 * Return the font descent value
4665
	 * @param string $font font name
4666
	 * @param string $style font style
4667
	 * @param float $size The size (in points)
4668
	 * @return int font descent
4669
	 * @public
4670
	 * @author Nicola Asuni
4671
	 * @since 4.9.003 (2010-03-30)
4672
	 */
4673
	public function getFontDescent($font, $style='', $size=0) {
4674
		$fontdata = $this->AddFont($font, $style);
4675
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4676
		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4677
			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4678
		} else {
4679
			$descent = (1.219 * 0.24 * $size);
4680
		}
4681
		return ($descent / $this->k);
4682
	}
4683
 
4684
	/**
4685
	 * Return the font ascent value.
4686
	 * @param string $font font name
4687
	 * @param string $style font style
4688
	 * @param float $size The size (in points)
4689
	 * @return int font ascent
4690
	 * @public
4691
	 * @author Nicola Asuni
4692
	 * @since 4.9.003 (2010-03-30)
4693
	 */
4694
	public function getFontAscent($font, $style='', $size=0) {
4695
		$fontdata = $this->AddFont($font, $style);
4696
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4697
		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4698
			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4699
		} else {
4700
			$ascent = 1.219 * 0.76 * $size;
4701
		}
4702
		return ($ascent / $this->k);
4703
	}
4704
 
4705
	/**
4706
	 * Return true in the character is present in the specified font.
4707
	 * @param mixed $char Character to check (integer value or string)
4708
	 * @param string $font Font name (family name).
4709
	 * @param string $style Font style.
4710
	 * @return bool true if the char is defined, false otherwise.
4711
	 * @public
4712
	 * @since 5.9.153 (2012-03-28)
4713
	 */
4714
	public function isCharDefined($char, $font='', $style='') {
4715
		if (is_string($char)) {
4716
			// get character code
4717
			$char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4718
			$char = $char[0];
4719
		}
4720
		if (TCPDF_STATIC::empty_string($font)) {
4721
			if (TCPDF_STATIC::empty_string($style)) {
4722
				return (isset($this->CurrentFont['cw'][intval($char)]));
4723
			}
4724
			$font = $this->FontFamily;
4725
		}
4726
		$fontdata = $this->AddFont($font, $style);
4727
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4728
		return (isset($fontinfo['cw'][intval($char)]));
4729
	}
4730
 
4731
	/**
4732
	 * Replace missing font characters on selected font with specified substitutions.
4733
	 * @param string $text Text to process.
4734
	 * @param string $font Font name (family name).
4735
	 * @param string $style Font style.
4736
	 * @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.
4737
	 * @return string Processed text.
4738
	 * @public
4739
	 * @since 5.9.153 (2012-03-28)
4740
	 */
4741
	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4742
		if (empty($subs)) {
4743
			return $text;
4744
		}
4745
		if (TCPDF_STATIC::empty_string($font)) {
4746
			$font = $this->FontFamily;
4747
		}
4748
		$fontdata = $this->AddFont($font, $style);
4749
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4750
		$uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4751
		foreach ($uniarr as $k => $chr) {
4752
			if (!isset($fontinfo['cw'][$chr])) {
4753
				// this character is missing on the selected font
4754
				if (isset($subs[$chr])) {
4755
					// we have available substitutions
4756
					if (is_array($subs[$chr])) {
4757
						foreach($subs[$chr] as $s) {
4758
							if (isset($fontinfo['cw'][$s])) {
4759
								$uniarr[$k] = $s;
4760
								break;
4761
							}
4762
						}
4763
					} elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4764
						$uniarr[$k] = $subs[$chr];
4765
					}
4766
				}
4767
			}
4768
		}
4769
		return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4770
	}
4771
 
4772
	/**
4773
	 * Defines the default monospaced font.
4774
	 * @param string $font Font name.
4775
	 * @public
4776
	 * @since 4.5.025
4777
	 */
4778
	public function setDefaultMonospacedFont($font) {
4779
		$this->default_monospaced_font = $font;
4780
	}
4781
 
4782
	/**
4783
	 * 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 />
4784
	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4785
	 * @public
4786
	 * @since 1.5
4787
	 * @see Cell(), Write(), Image(), Link(), SetLink()
4788
	 */
4789
	public function AddLink() {
4790
		// create a new internal link
4791
		$n = count($this->links) + 1;
4792
		$this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4793
		return $n;
4794
	}
4795
 
4796
	/**
4797
	 * Defines the page and position a link points to.
4798
	 * @param int $link The link identifier returned by AddLink()
4799
	 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4800
	 * @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.
4801
	 * @public
4802
	 * @since 1.5
4803
	 * @see AddLink()
4804
	 */
4805
	public function setLink($link, $y=0, $page=-1) {
4806
		$fixed = false;
4807
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4808
			$page = intval(substr($page, 1));
4809
			// this page number will not be changed when moving/add/deleting pages
4810
			$fixed = true;
4811
		}
4812
		if ($page < 0) {
4813
			$page = $this->page;
4814
		}
4815
		if ($y == -1) {
4816
			$y = $this->y;
4817
		}
4818
		$this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4819
	}
4820
 
4821
	/**
4822
	 * Puts a link on a rectangular area of the page.
4823
	 * 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.
4824
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4825
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4826
	 * @param float $w Width of the rectangle
4827
	 * @param float $h Height of the rectangle
4828
	 * @param mixed $link URL or identifier returned by AddLink()
4829
	 * @param int $spaces number of spaces on the text to link
4830
	 * @public
4831
	 * @since 1.5
4832
	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4833
	 */
4834
	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4835
		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4836
	}
4837
 
4838
	/**
4839
	 * Puts a markup annotation on a rectangular area of the page.
4840
	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4841
	 * @param float $x Abscissa of the upper-left corner of the rectangle
4842
	 * @param float $y Ordinate of the upper-left corner of the rectangle
4843
	 * @param float $w Width of the rectangle
4844
	 * @param float $h Height of the rectangle
4845
	 * @param string $text annotation text or alternate content
4846
	 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4847
	 * @param int $spaces number of spaces on the text to link
4848
	 * @public
4849
	 * @since 4.0.018 (2008-08-06)
4850
	 */
4851
	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4852
		if ($this->inxobj) {
4853
			// store parameters for later use on template
4854
			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4855
			return;
4856
		}
4857
		if ($x === '') {
4858
			$x = $this->x;
4859
		}
4860
		if ($y === '') {
4861
			$y = $this->y;
4862
		}
4863
		// check page for no-write regions and adapt page margins if necessary
4864
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
4865
		// recalculate coordinates to account for graphic transformations
4866
		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4867
			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4868
				$maxid = count($this->transfmatrix[$i]) - 1;
4869
				for ($j=$maxid; $j >= 0; --$j) {
4870
					$ctm = $this->transfmatrix[$i][$j];
4871
					if (isset($ctm['a'])) {
4872
						$x = $x * $this->k;
4873
						$y = ($this->h - $y) * $this->k;
4874
						$w = $w * $this->k;
4875
						$h = $h * $this->k;
4876
						// top left
4877
						$xt = $x;
4878
						$yt = $y;
4879
						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4880
						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4881
						// top right
4882
						$xt = $x + $w;
4883
						$yt = $y;
4884
						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4885
						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4886
						// bottom left
4887
						$xt = $x;
4888
						$yt = $y - $h;
4889
						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4890
						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4891
						// bottom right
4892
						$xt = $x + $w;
4893
						$yt = $y - $h;
4894
						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4895
						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4896
						// new coordinates (rectangle area)
4897
						$x = min($x1, $x2, $x3, $x4);
4898
						$y = max($y1, $y2, $y3, $y4);
4899
						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4900
						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4901
						$x = $x / $this->k;
4902
						$y = $this->h - ($y / $this->k);
4903
					}
4904
				}
4905
			}
4906
		}
4907
		if ($this->page <= 0) {
4908
			$page = 1;
4909
		} else {
4910
			$page = $this->page;
4911
		}
4912
		if (!isset($this->PageAnnots[$page])) {
4913
			$this->PageAnnots[$page] = array();
4914
		}
4915
		$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4916
		if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4917
			if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4918
				AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4919
				AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4920
				$this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4921
			}
4922
		}
4923
		// Add widgets annotation's icons
4924
		if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4925
			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4926
		}
4927
		if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4928
			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4929
		}
4930
		if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4931
			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4932
		}
4933
	}
4934
 
4935
	/**
4936
	 * Embedd the attached files.
4937
	 * @since 4.4.000 (2008-12-07)
4938
	 * @protected
4939
	 * @see Annotation()
4940
	 */
4941
	protected function _putEmbeddedFiles() {
4942
		if ($this->pdfa_mode && $this->pdfa_version != 3)  {
4943
			// embedded files are not allowed in PDF/A mode version 1 and 2
4944
			return;
4945
		}
4946
		reset($this->embeddedfiles);
4947
		foreach ($this->embeddedfiles as $filename => $filedata) {
4948
		    $data = $this->getCachedFileContents($filedata['file']);
4949
			if ($data !== FALSE) {
4950
				$rawsize = strlen($data);
4951
				if ($rawsize > 0) {
4952
					// update name tree
4953
					$this->efnames[$filename] = $filedata['f'].' 0 R';
4954
					// embedded file specification object
4955
					$out = $this->_getobj($filedata['f'])."\n";
4956
					$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4957
					$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4958
					$out .= ' /AFRelationship /Source';
4959
					$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4960
					$out .= "\n".'endobj';
4961
					$this->_out($out);
4962
					// embedded file object
4963
					$filter = '';
4964
					if ($this->compress) {
4965
						$data = gzcompress($data);
4966
						$filter = ' /Filter /FlateDecode';
4967
					}
4968
 
4969
					if ($this->pdfa_version == 3) {
4970
						$filter = ' /Subtype /text#2Fxml';
4971
					}
4972
 
4973
					$stream = $this->_getrawstream($data, $filedata['n']);
4974
					$out = $this->_getobj($filedata['n'])."\n";
4975
					$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4976
					$out .= ' stream'."\n".$stream."\n".'endstream';
4977
					$out .= "\n".'endobj';
4978
					$this->_out($out);
4979
				}
4980
			}
4981
		}
4982
	}
4983
 
4984
	/**
4985
	 * Prints a text cell at the specified position.
4986
	 * This method allows to place a string precisely on the page.
4987
	 * @param float $x Abscissa of the cell origin
4988
	 * @param float $y Ordinate of the cell origin
4989
	 * @param string $txt String to print
4990
	 * @param int $fstroke outline size in user units (0 = disable)
4991
	 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4992
	 * @param boolean $ffill if true fills the text
4993
	 * @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)))
4994
	 * @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.
4995
	 * @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>
4996
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
4997
	 * @param mixed $link URL or identifier returned by AddLink().
4998
	 * @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.
4999
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5000
	 * @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>
5001
	 * @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>
5002
	 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
5003
	 * @public
5004
	 * @since 1.0
5005
	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5006
	 */
5007
	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) {
5008
		$textrendermode = $this->textrendermode;
5009
		$textstrokewidth = $this->textstrokewidth;
5010
		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
5011
		$this->setXY($x, $y, $rtloff);
5012
		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5013
		// restore previous rendering mode
5014
		$this->textrendermode = $textrendermode;
5015
		$this->textstrokewidth = $textstrokewidth;
5016
	}
5017
 
5018
	/**
5019
	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5020
	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5021
	 * This method is called automatically and should not be called directly by the application.
5022
	 * @return bool
5023
	 * @public
5024
	 * @since 1.4
5025
	 * @see SetAutoPageBreak()
5026
	 */
5027
	public function AcceptPageBreak() {
5028
		if ($this->num_columns > 1) {
5029
			// multi column mode
5030
			if ($this->current_column < ($this->num_columns - 1)) {
5031
				// go to next column
5032
				$this->selectColumn($this->current_column + 1);
5033
			} elseif ($this->AutoPageBreak) {
5034
				// add a new page
5035
				$this->AddPage();
5036
				// set first column
5037
				$this->selectColumn(0);
5038
			}
5039
			// avoid page breaking from checkPageBreak()
5040
			return false;
5041
		}
5042
		return $this->AutoPageBreak;
5043
	}
5044
 
5045
	/**
5046
	 * Add page if needed.
5047
	 * @param float $h Cell height. Default value: 0.
5048
	 * @param float|null $y starting y position, leave empty for current position.
5049
	 * @param bool  $addpage if true add a page, otherwise only return the true/false state
5050
	 * @return bool true in case of page break, false otherwise.
5051
	 * @since 3.2.000 (2008-07-01)
5052
	 * @protected
5053
	 */
5054
	protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5055
		if (TCPDF_STATIC::empty_string($y)) {
5056
			$y = $this->y;
5057
		}
5058
		$current_page = $this->page;
5059
		if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5060
			if ($addpage) {
5061
				//Automatic page break
5062
				$x = $this->x;
5063
				$this->AddPage($this->CurOrientation);
5064
				$this->y = $this->tMargin;
5065
				$oldpage = $this->page - 1;
5066
				if ($this->rtl) {
5067
					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5068
						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5069
					} else {
5070
						$this->x = $x;
5071
					}
5072
				} else {
5073
					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5074
						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5075
					} else {
5076
						$this->x = $x;
5077
					}
5078
				}
5079
			}
5080
			return true;
5081
		}
5082
		if ($current_page != $this->page) {
5083
			// account for columns mode
5084
			return true;
5085
		}
5086
		return false;
5087
	}
5088
 
5089
	/**
5090
	 * 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 />
5091
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5092
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5093
	 * @param float $h Cell height. Default value: 0.
5094
	 * @param string $txt String to print. Default value: empty string.
5095
	 * @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)))
5096
	 * @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.
5097
	 * @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>
5098
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5099
	 * @param mixed $link URL or identifier returned by AddLink().
5100
	 * @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.
5101
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5102
	 * @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>
5103
	 * @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>
5104
	 * @public
5105
	 * @since 1.0
5106
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5107
	 */
5108
	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') {
5109
		$prev_cell_margin = $this->cell_margin;
5110
		$prev_cell_padding = $this->cell_padding;
5111
		$this->adjustCellPadding($border);
5112
		if (!$ignore_min_height) {
5113
			$min_cell_height = $this->getCellHeight($this->FontSize);
5114
			if ($h < $min_cell_height) {
5115
				$h = $min_cell_height;
5116
			}
5117
		}
5118
		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5119
		// apply text shadow if enabled
5120
		if ($this->txtshadow['enabled']) {
5121
			// save data
5122
			$x = $this->x;
5123
			$y = $this->y;
5124
			$bc = $this->bgcolor;
5125
			$fc = $this->fgcolor;
5126
			$sc = $this->strokecolor;
5127
			$alpha = $this->alpha;
5128
			// print shadow
5129
			$this->x += $this->txtshadow['depth_w'];
5130
			$this->y += $this->txtshadow['depth_h'];
5131
			$this->setFillColorArray($this->txtshadow['color']);
5132
			$this->setTextColorArray($this->txtshadow['color']);
5133
			$this->setDrawColorArray($this->txtshadow['color']);
5134
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5135
				$this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5136
			}
5137
			if ($this->state == 2) {
5138
				$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5139
			}
5140
			//restore data
5141
			$this->x = $x;
5142
			$this->y = $y;
5143
			$this->setFillColorArray($bc);
5144
			$this->setTextColorArray($fc);
5145
			$this->setDrawColorArray($sc);
5146
			if ($this->txtshadow['opacity'] != $alpha['CA']) {
5147
				$this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5148
			}
5149
		}
5150
		if ($this->state == 2) {
5151
			$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5152
		}
5153
		$this->cell_padding = $prev_cell_padding;
5154
		$this->cell_margin = $prev_cell_margin;
5155
	}
5156
 
5157
	/**
5158
	 * 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 />
5159
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5160
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5161
	 * @param float $h Cell height. Default value: 0.
5162
	 * @param string $txt String to print. Default value: empty string.
5163
	 * @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)))
5164
	 * @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.
5165
	 * @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>
5166
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5167
	 * @param mixed $link URL or identifier returned by AddLink().
5168
	 * @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.
5169
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5170
	 * @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>
5171
	 * @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>
5172
	 * @return string containing cell code
5173
	 * @protected
5174
	 * @since 1.0
5175
	 * @see Cell()
5176
	 */
5177
	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') {
5178
		// replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5179
		$txt = is_null($txt) ? '' : $txt;
5180
		$txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5181
		$prev_cell_margin = $this->cell_margin;
5182
		$prev_cell_padding = $this->cell_padding;
5183
		$txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5184
		$rs = ''; //string to be returned
5185
		$this->adjustCellPadding($border);
5186
		if (!$ignore_min_height) {
5187
			$min_cell_height = $this->getCellHeight($this->FontSize);
5188
			if ($h < $min_cell_height) {
5189
				$h = $min_cell_height;
5190
			}
5191
		}
5192
		$k = $this->k;
5193
		// check page for no-write regions and adapt page margins if necessary
5194
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5195
		if ($this->rtl) {
5196
			$x = $this->x - $this->cell_margin['R'];
5197
		} else {
5198
			$x = $this->x + $this->cell_margin['L'];
5199
		}
5200
		$y = $this->y + $this->cell_margin['T'];
5201
		$prev_font_stretching = $this->font_stretching;
5202
		$prev_font_spacing = $this->font_spacing;
5203
		// cell vertical alignment
5204
		switch ($calign) {
5205
			case 'A': {
5206
				// font top
5207
				switch ($valign) {
5208
					case 'T': {
5209
						// top
5210
						$y -= $this->cell_padding['T'];
5211
						break;
5212
					}
5213
					case 'B': {
5214
						// bottom
5215
						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5216
						break;
5217
					}
5218
					default:
5219
					case 'C':
5220
					case 'M': {
5221
						// center
5222
						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5223
						break;
5224
					}
5225
				}
5226
				break;
5227
			}
5228
			case 'L': {
5229
				// font baseline
5230
				switch ($valign) {
5231
					case 'T': {
5232
						// top
5233
						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5234
						break;
5235
					}
5236
					case 'B': {
5237
						// bottom
5238
						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5239
						break;
5240
					}
5241
					default:
5242
					case 'C':
5243
					case 'M': {
5244
						// center
5245
						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5246
						break;
5247
					}
5248
				}
5249
				break;
5250
			}
5251
			case 'D': {
5252
				// font bottom
5253
				switch ($valign) {
5254
					case 'T': {
5255
						// top
5256
						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5257
						break;
5258
					}
5259
					case 'B': {
5260
						// bottom
5261
						$y -= ($h - $this->cell_padding['B']);
5262
						break;
5263
					}
5264
					default:
5265
					case 'C':
5266
					case 'M': {
5267
						// center
5268
						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5269
						break;
5270
					}
5271
				}
5272
				break;
5273
			}
5274
			case 'B': {
5275
				// cell bottom
5276
				$y -= $h;
5277
				break;
5278
			}
5279
			case 'C':
5280
			case 'M': {
5281
				// cell center
5282
				$y -= ($h / 2);
5283
				break;
5284
			}
5285
			default:
5286
			case 'T': {
5287
				// cell top
5288
				break;
5289
			}
5290
		}
5291
		// text vertical alignment
5292
		switch ($valign) {
5293
			case 'T': {
5294
				// top
5295
				$yt = $y + $this->cell_padding['T'];
5296
				break;
5297
			}
5298
			case 'B': {
5299
				// bottom
5300
				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5301
				break;
5302
			}
5303
			default:
5304
			case 'C':
5305
			case 'M': {
5306
				// center
5307
				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5308
				break;
5309
			}
5310
		}
5311
		$basefonty = $yt + $this->FontAscent;
5312
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5313
			if ($this->rtl) {
5314
				$w = $x - $this->lMargin;
5315
			} else {
5316
				$w = $this->w - $this->rMargin - $x;
5317
			}
5318
		}
5319
		$s = '';
5320
		// fill and borders
5321
		if (is_string($border) AND (strlen($border) == 4)) {
5322
			// full border
5323
			$border = 1;
5324
		}
5325
		if ($fill OR ($border == 1)) {
5326
			if ($fill) {
5327
				$op = ($border == 1) ? 'B' : 'f';
5328
			} else {
5329
				$op = 'S';
5330
			}
5331
			if ($this->rtl) {
5332
				$xk = (($x - $w) * $k);
5333
			} else {
5334
				$xk = ($x * $k);
5335
			}
5336
			$s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5337
		}
5338
		// draw borders
5339
		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5340
		if ($txt != '') {
5341
			$txt2 = $txt;
5342
			if ($this->isunicode) {
5343
				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5344
					$txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5345
				} else {
5346
					$unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5347
					$unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5348
					// replace thai chars (if any)
5349
					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5350
						// number of chars
5351
						$numchars = count($unicode);
5352
						// po pla, for far, for fan
5353
						$longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5354
						// do chada, to patak
5355
						$lowtail = array(0x0e0e, 0x0e0f);
5356
						// mai hun arkad, sara i, sara ii, sara ue, sara uee
5357
						$upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5358
						// mai ek, mai tho, mai tri, mai chattawa, karan
5359
						$tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5360
						// sara u, sara uu, pinthu
5361
						$lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5362
						$output = array();
5363
						for ($i = 0; $i < $numchars; $i++) {
5364
							if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5365
								$ch0 = $unicode[$i];
5366
								$ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5367
								$ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5368
								$chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5369
								if (in_array($ch0, $tonemark)) {
5370
									if ($chn == 0x0e33) {
5371
										// sara um
5372
										if (in_array($ch1, $longtail)) {
5373
											// tonemark at upper left
5374
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5375
										} else {
5376
											// tonemark at upper right (normal position)
5377
											$output[] = $ch0;
5378
										}
5379
									} elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5380
										// tonemark at lower left
5381
										$output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5382
									} elseif (in_array($ch1, $upvowel)) {
5383
										if (in_array($ch2, $longtail)) {
5384
											// tonemark at upper left
5385
											$output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5386
										} else {
5387
											// tonemark at upper right (normal position)
5388
											$output[] = $ch0;
5389
										}
5390
									} else {
5391
										// tonemark at lower right
5392
										$output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5393
									}
5394
								} elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5395
									// add lower left nikhahit and sara aa
5396
									if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5397
										$output[] = 0xf711;
5398
										$this->CurrentFont['subsetchars'][0xf711] = true;
5399
										$output[] = 0x0e32;
5400
										$this->CurrentFont['subsetchars'][0x0e32] = true;
5401
									} else {
5402
										$output[] = $ch0;
5403
									}
5404
								} elseif (in_array($ch1, $longtail)) {
5405
									if ($ch0 == 0x0e31) {
5406
										// lower left mai hun arkad
5407
										$output[] = $this->replaceChar($ch0, 0xf710);
5408
									} elseif (in_array($ch0, $upvowel)) {
5409
										// lower left
5410
										$output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5411
									} elseif ($ch0 == 0x0e47) {
5412
										// lower left mai tai koo
5413
										$output[] = $this->replaceChar($ch0, 0xf712);
5414
									} else {
5415
										// normal character
5416
										$output[] = $ch0;
5417
									}
5418
								} elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5419
									// lower vowel
5420
									$output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5421
								} elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5422
									// yo ying without lower part
5423
									$output[] = $this->replaceChar($ch0, 0xf70f);
5424
								} elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5425
									// tho santan without lower part
5426
									$output[] = $this->replaceChar($ch0, 0xf700);
5427
								} else {
5428
									$output[] = $ch0;
5429
								}
5430
							} else {
5431
								// non-thai character
5432
								$output[] = $unicode[$i];
5433
							}
5434
						}
5435
						$unicode = $output;
5436
						// update font subsetchars
5437
						$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5438
					} // end of K_THAI_TOPCHARS
5439
					$txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5440
				}
5441
			}
5442
			$txt2 = TCPDF_STATIC::_escape($txt2);
5443
			// get current text width (considering general font stretching and spacing)
5444
			$txwidth = $this->GetStringWidth($txt);
5445
			$width = $txwidth;
5446
			// check for stretch mode
5447
			if ($stretch > 0) {
5448
				// calculate ratio between cell width and text width
5449
				if ($width <= 0) {
5450
					$ratio = 1;
5451
				} else {
5452
					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5453
				}
5454
				// check if stretching is required
5455
				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5456
					// the text will be stretched to fit cell width
5457
					if ($stretch > 2) {
5458
						// set new character spacing
5459
						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5460
					} else {
5461
						// set new horizontal stretching
5462
						$this->font_stretching *= $ratio;
5463
					}
5464
					// recalculate text width (the text fills the entire cell)
5465
					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5466
					// reset alignment
5467
					$align = '';
5468
				}
5469
			}
5470
			if ($this->font_stretching != 100) {
5471
				// apply font stretching
5472
				$rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5473
			}
5474
			if ($this->font_spacing != 0) {
5475
				// increase/decrease font spacing
5476
				$rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5477
			}
5478
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5479
				$s .= 'q '.$this->TextColor.' ';
5480
			}
5481
			// rendering mode
5482
			$s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5483
			// count number of spaces
5484
			$ns = substr_count($txt, chr(32));
5485
			// Justification
5486
			$spacewidth = 0;
5487
			if (($align == 'J') AND ($ns > 0)) {
5488
				if ($this->isUnicodeFont()) {
5489
					// get string width without spaces
5490
					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5491
					// calculate average space width
5492
					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5493
					if ($this->font_stretching != 100) {
5494
						// word spacing is affected by stretching
5495
						$spacewidth /= ($this->font_stretching / 100);
5496
					}
5497
					// set word position to be used with TJ operator
5498
					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5499
					$unicode_justification = true;
5500
				} else {
5501
					// get string width
5502
					$width = $txwidth;
5503
					// new space width
5504
					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5505
					if ($this->font_stretching != 100) {
5506
						// word spacing (Tw) is affected by stretching
5507
						$spacewidth /= ($this->font_stretching / 100);
5508
					}
5509
					// set word spacing
5510
					$rs .= sprintf('BT %F Tw ET ', $spacewidth);
5511
				}
5512
				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5513
			}
5514
			// replace carriage return characters
5515
			$txt2 = str_replace("\r", ' ', $txt2);
5516
			switch ($align) {
5517
				case 'C': {
5518
					$dx = ($w - $width) / 2;
5519
					break;
5520
				}
5521
				case 'R': {
5522
					if ($this->rtl) {
5523
						$dx = $this->cell_padding['R'];
5524
					} else {
5525
						$dx = $w - $width - $this->cell_padding['R'];
5526
					}
5527
					break;
5528
				}
5529
				case 'L': {
5530
					if ($this->rtl) {
5531
						$dx = $w - $width - $this->cell_padding['L'];
5532
					} else {
5533
						$dx = $this->cell_padding['L'];
5534
					}
5535
					break;
5536
				}
5537
				case 'J':
5538
				default: {
5539
					if ($this->rtl) {
5540
						$dx = $this->cell_padding['R'];
5541
					} else {
5542
						$dx = $this->cell_padding['L'];
5543
					}
5544
					break;
5545
				}
5546
			}
5547
			if ($this->rtl) {
5548
				$xdx = $x - $dx - $width;
5549
			} else {
5550
				$xdx = $x + $dx;
5551
			}
5552
			$xdk = $xdx * $k;
5553
			// print text
5554
			$s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5555
			if (isset($uniblock)) { // @phpstan-ignore-line
5556
				// print overlapping characters as separate string
5557
				$xshift = 0; // horizontal shift
5558
				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5559
				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5560
				foreach ($uniblock as $uk => $uniarr) { // @phpstan-ignore-line
5561
					if (($uk % 2) == 0) {
5562
						// x space to skip
5563
						if ($spacewidth != 0) {
5564
							// justification shift
5565
							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5566
						}
5567
						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5568
					} else {
5569
						// character to print
5570
						$topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5571
						$topchr = TCPDF_STATIC::_escape($topchr);
5572
						$s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5573
					}
5574
				}
5575
			}
5576
			if ($this->underline) {
5577
				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5578
			}
5579
			if ($this->linethrough) {
5580
				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5581
			}
5582
			if ($this->overline) {
5583
				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5584
			}
5585
			if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5586
				$s .= ' Q';
5587
			}
5588
			if ($link) {
5589
				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5590
			}
5591
		}
5592
		// output cell
5593
		if ($s) {
5594
			// output cell
5595
			$rs .= $s;
5596
			if ($this->font_spacing != 0) {
5597
				// reset font spacing mode
5598
				$rs .= ' BT 0 Tc ET';
5599
			}
5600
			if ($this->font_stretching != 100) {
5601
				// reset font stretching mode
5602
				$rs .= ' BT 100 Tz ET';
5603
			}
5604
		}
5605
		// reset word spacing
5606
		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5607
			$rs .= ' BT 0 Tw ET';
5608
		}
5609
		// reset stretching and spacing
5610
		$this->font_stretching = $prev_font_stretching;
5611
		$this->font_spacing = $prev_font_spacing;
5612
		$this->lasth = $h;
5613
		if ($ln > 0) {
5614
			//Go to the beginning of the next line
5615
			$this->y = $y + $h + $this->cell_margin['B'];
5616
			if ($ln == 1) {
5617
				if ($this->rtl) {
5618
					$this->x = $this->w - $this->rMargin;
5619
				} else {
5620
					$this->x = $this->lMargin;
5621
				}
5622
			}
5623
		} else {
5624
			// go left or right by case
5625
			if ($this->rtl) {
5626
				$this->x = $x - $w - $this->cell_margin['L'];
5627
			} else {
5628
				$this->x = $x + $w + $this->cell_margin['R'];
5629
			}
5630
		}
5631
		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5632
		$rs = $gstyles.$rs;
5633
		$this->cell_padding = $prev_cell_padding;
5634
		$this->cell_margin = $prev_cell_margin;
5635
		return $rs;
5636
	}
5637
 
5638
	/**
5639
	 * Replace a char if is defined on the current font.
5640
	 * @param int $oldchar Integer code (unicode) of the character to replace.
5641
	 * @param int $newchar Integer code (unicode) of the new character.
5642
	 * @return int the replaced char or the old char in case the new char i not defined
5643
	 * @protected
5644
	 * @since 5.9.167 (2012-06-22)
5645
	 */
5646
	protected function replaceChar($oldchar, $newchar) {
5647
		if ($this->isCharDefined($newchar)) {
5648
			// add the new char on the subset list
5649
			$this->CurrentFont['subsetchars'][$newchar] = true;
5650
			// return the new character
5651
			return $newchar;
5652
		}
5653
		// return the old char
5654
		return $oldchar;
5655
	}
5656
 
5657
	/**
5658
	 * Returns the code to draw the cell border
5659
	 * @param float $x X coordinate.
5660
	 * @param float $y Y coordinate.
5661
	 * @param float $w Cell width.
5662
	 * @param float $h Cell height.
5663
	 * @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)))
5664
	 * @return string containing cell border code
5665
	 * @protected
5666
	 * @see SetLineStyle()
5667
	 * @since 5.7.000 (2010-08-02)
5668
	 */
5669
	protected function getCellBorder($x, $y, $w, $h, $brd) {
5670
		$s = ''; // string to be returned
5671
		if (empty($brd)) {
5672
			return $s;
5673
		}
5674
		if ($brd == 1) {
5675
			$brd = array('LRTB' => true);
5676
		}
5677
		// calculate coordinates for border
5678
		$k = $this->k;
5679
		if ($this->rtl) {
5680
			$xeL = ($x - $w) * $k;
5681
			$xeR = $x * $k;
5682
		} else {
5683
			$xeL = $x * $k;
5684
			$xeR = ($x + $w) * $k;
5685
		}
5686
		$yeL = (($this->h - ($y + $h)) * $k);
5687
		$yeT = (($this->h - $y) * $k);
5688
		$xeT = $xeL;
5689
		$xeB = $xeR;
5690
		$yeR = $yeT;
5691
		$yeB = $yeL;
5692
		if (is_string($brd)) {
5693
			// convert string to array
5694
			$slen = strlen($brd);
5695
			$newbrd = array();
5696
			for ($i = 0; $i < $slen; ++$i) {
5697
				$newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5698
			}
5699
			$brd = $newbrd;
5700
		}
5701
		if (isset($brd['mode'])) {
5702
			$mode = $brd['mode'];
5703
			unset($brd['mode']);
5704
		} else {
5705
			$mode = 'normal';
5706
		}
5707
		foreach ($brd as $border => $style) {
5708
			if (is_array($style) AND !empty($style)) {
5709
				// apply border style
5710
				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5711
				$s .= $this->setLineStyle($style, true)."\n";
5712
			}
5713
			switch ($mode) {
5714
				case 'ext': {
5715
					$off = (($this->LineWidth / 2) * $k);
5716
					$xL = $xeL - $off;
5717
					$xR = $xeR + $off;
5718
					$yT = $yeT + $off;
5719
					$yL = $yeL - $off;
5720
					$xT = $xL;
5721
					$xB = $xR;
5722
					$yR = $yT;
5723
					$yB = $yL;
5724
					$w += $this->LineWidth;
5725
					$h += $this->LineWidth;
5726
					break;
5727
				}
5728
				case 'int': {
5729
					$off = ($this->LineWidth / 2) * $k;
5730
					$xL = $xeL + $off;
5731
					$xR = $xeR - $off;
5732
					$yT = $yeT - $off;
5733
					$yL = $yeL + $off;
5734
					$xT = $xL;
5735
					$xB = $xR;
5736
					$yR = $yT;
5737
					$yB = $yL;
5738
					$w -= $this->LineWidth;
5739
					$h -= $this->LineWidth;
5740
					break;
5741
				}
5742
				case 'normal':
5743
				default: {
5744
					$xL = $xeL;
5745
					$xT = $xeT;
5746
					$xB = $xeB;
5747
					$xR = $xeR;
5748
					$yL = $yeL;
5749
					$yT = $yeT;
5750
					$yB = $yeB;
5751
					$yR = $yeR;
5752
					break;
5753
				}
5754
			}
5755
			// draw borders by case
5756
			if (strlen($border) == 4) {
5757
				$s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5758
			} elseif (strlen($border) == 3) {
5759
				if (strpos($border,'B') === false) { // LTR
5760
					$s .= sprintf('%F %F m ', $xL, $yL);
5761
					$s .= sprintf('%F %F l ', $xT, $yT);
5762
					$s .= sprintf('%F %F l ', $xR, $yR);
5763
					$s .= sprintf('%F %F l ', $xB, $yB);
5764
					$s .= 'S ';
5765
				} elseif (strpos($border,'L') === false) { // TRB
5766
					$s .= sprintf('%F %F m ', $xT, $yT);
5767
					$s .= sprintf('%F %F l ', $xR, $yR);
5768
					$s .= sprintf('%F %F l ', $xB, $yB);
5769
					$s .= sprintf('%F %F l ', $xL, $yL);
5770
					$s .= 'S ';
5771
				} elseif (strpos($border,'T') === false) { // RBL
5772
					$s .= sprintf('%F %F m ', $xR, $yR);
5773
					$s .= sprintf('%F %F l ', $xB, $yB);
5774
					$s .= sprintf('%F %F l ', $xL, $yL);
5775
					$s .= sprintf('%F %F l ', $xT, $yT);
5776
					$s .= 'S ';
5777
				} elseif (strpos($border,'R') === false) { // BLT
5778
					$s .= sprintf('%F %F m ', $xB, $yB);
5779
					$s .= sprintf('%F %F l ', $xL, $yL);
5780
					$s .= sprintf('%F %F l ', $xT, $yT);
5781
					$s .= sprintf('%F %F l ', $xR, $yR);
5782
					$s .= 'S ';
5783
				}
5784
			} elseif (strlen($border) == 2) {
5785
				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5786
					$s .= sprintf('%F %F m ', $xL, $yL);
5787
					$s .= sprintf('%F %F l ', $xT, $yT);
5788
					$s .= sprintf('%F %F l ', $xR, $yR);
5789
					$s .= 'S ';
5790
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5791
					$s .= sprintf('%F %F m ', $xT, $yT);
5792
					$s .= sprintf('%F %F l ', $xR, $yR);
5793
					$s .= sprintf('%F %F l ', $xB, $yB);
5794
					$s .= 'S ';
5795
				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5796
					$s .= sprintf('%F %F m ', $xR, $yR);
5797
					$s .= sprintf('%F %F l ', $xB, $yB);
5798
					$s .= sprintf('%F %F l ', $xL, $yL);
5799
					$s .= 'S ';
5800
				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5801
					$s .= sprintf('%F %F m ', $xB, $yB);
5802
					$s .= sprintf('%F %F l ', $xL, $yL);
5803
					$s .= sprintf('%F %F l ', $xT, $yT);
5804
					$s .= 'S ';
5805
				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5806
					$s .= sprintf('%F %F m ', $xL, $yL);
5807
					$s .= sprintf('%F %F l ', $xT, $yT);
5808
					$s .= 'S ';
5809
					$s .= sprintf('%F %F m ', $xR, $yR);
5810
					$s .= sprintf('%F %F l ', $xB, $yB);
5811
					$s .= 'S ';
5812
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5813
					$s .= sprintf('%F %F m ', $xT, $yT);
5814
					$s .= sprintf('%F %F l ', $xR, $yR);
5815
					$s .= 'S ';
5816
					$s .= sprintf('%F %F m ', $xB, $yB);
5817
					$s .= sprintf('%F %F l ', $xL, $yL);
5818
					$s .= 'S ';
5819
				}
5820
			} else { // strlen($border) == 1
5821
				if (strpos($border,'L') !== false) { // L
5822
					$s .= sprintf('%F %F m ', $xL, $yL);
5823
					$s .= sprintf('%F %F l ', $xT, $yT);
5824
					$s .= 'S ';
5825
				} elseif (strpos($border,'T') !== false) { // T
5826
					$s .= sprintf('%F %F m ', $xT, $yT);
5827
					$s .= sprintf('%F %F l ', $xR, $yR);
5828
					$s .= 'S ';
5829
				} elseif (strpos($border,'R') !== false) { // R
5830
					$s .= sprintf('%F %F m ', $xR, $yR);
5831
					$s .= sprintf('%F %F l ', $xB, $yB);
5832
					$s .= 'S ';
5833
				} elseif (strpos($border,'B') !== false) { // B
5834
					$s .= sprintf('%F %F m ', $xB, $yB);
5835
					$s .= sprintf('%F %F l ', $xL, $yL);
5836
					$s .= 'S ';
5837
				}
5838
			}
5839
			if (is_array($style) AND !empty($style)) {
5840
				// reset border style to previous value
5841
				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5842
			}
5843
		}
5844
		return $s;
5845
	}
5846
 
5847
	/**
5848
	 * This method allows printing text with line breaks.
5849
	 * 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 />
5850
	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5851
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5852
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
5853
	 * @param string $txt String to print
5854
	 * @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)))
5855
	 * @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>
5856
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5857
	 * @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>
5858
	 * @param float|null $x x position in user units
5859
	 * @param float|null $y y position in user units
5860
	 * @param boolean $reseth if true reset the last cell height (default true).
5861
	 * @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.
5862
	 * @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.
5863
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5864
	 * @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.
5865
	 * @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.
5866
	 * @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.
5867
	 * @return int Return the number of cells or 1 for html mode.
5868
	 * @public
5869
	 * @since 1.3
5870
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5871
	 */
5872
	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) {
5873
		$prev_cell_margin = $this->cell_margin;
5874
		$prev_cell_padding = $this->cell_padding;
5875
		// adjust internal padding
5876
		$this->adjustCellPadding($border);
5877
		$mc_padding = $this->cell_padding;
5878
		$mc_margin = $this->cell_margin;
5879
		$this->cell_padding['T'] = 0;
5880
		$this->cell_padding['B'] = 0;
5881
		$this->setCellMargins(0, 0, 0, 0);
5882
		if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5883
			// reset row height
5884
			$this->resetLastH();
5885
		}
5886
		if (!TCPDF_STATIC::empty_string($y)) {
5887
			$this->setY($y); // set y in order to convert negative y values to positive ones
5888
		}
5889
		$y = $this->GetY();
5890
		$resth = 0;
5891
		if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5892
			// spit cell in more pages/columns
5893
			$newh = ($this->PageBreakTrigger - $y);
5894
			$resth = ($h - $newh); // cell to be printed on the next page/column
5895
			$h = $newh;
5896
		}
5897
		// get current page number
5898
		$startpage = $this->page;
5899
		// get current column
5900
		$startcolumn = $this->current_column;
5901
		if (!TCPDF_STATIC::empty_string($x)) {
5902
			$this->setX($x);
5903
		} else {
5904
			$x = $this->GetX();
5905
		}
5906
		// check page for no-write regions and adapt page margins if necessary
5907
		list($x, $y) = $this->checkPageRegions(0, $x, $y);
5908
		// apply margins
5909
		$oy = $y + $mc_margin['T'];
5910
		if ($this->rtl) {
5911
			$ox = ($this->w - $x - $mc_margin['R']);
5912
		} else {
5913
			$ox = ($x + $mc_margin['L']);
5914
		}
5915
		$this->x = $ox;
5916
		$this->y = $oy;
5917
		// set width
5918
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5919
			if ($this->rtl) {
5920
				$w = ($this->x - $this->lMargin - $mc_margin['L']);
5921
			} else {
5922
				$w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5923
			}
5924
		}
5925
		// store original margin values
5926
		$lMargin = $this->lMargin;
5927
		$rMargin = $this->rMargin;
5928
		if ($this->rtl) {
5929
			$this->rMargin = ($this->w - $this->x);
5930
			$this->lMargin = ($this->x - $w);
5931
		} else {
5932
			$this->lMargin = ($this->x);
5933
			$this->rMargin = ($this->w - $this->x - $w);
5934
		}
5935
		$this->clMargin = $this->lMargin;
5936
		$this->crMargin = $this->rMargin;
5937
		if ($autopadding) {
5938
			// add top padding
5939
			$this->y += $mc_padding['T'];
5940
		}
5941
		if ($ishtml) { // ******* Write HTML text
5942
			$this->writeHTML($txt, true, false, $reseth, true, $align);
5943
			$nl = 1;
5944
		} else { // ******* Write simple text
5945
			$prev_FontSizePt = $this->FontSizePt;
5946
			if ($fitcell) {
5947
				// ajust height values
5948
				$tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5949
				$h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5950
			}
5951
			// vertical alignment
5952
			if ($maxh > 0) {
5953
				// get text height
5954
				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5955
				if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5956
					// try to reduce font size to fit text on cell (use a quick search algorithm)
5957
					$fmin = 1;
5958
					$fmax = $this->FontSizePt;
5959
					$diff_epsilon = (1 / $this->k); // one point (min resolution)
5960
					$maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5961
					while ($maxit >= 0) {
5962
						$fmid = (($fmax + $fmin) / 2);
5963
						$this->setFontSize($fmid, false);
5964
						$this->resetLastH();
5965
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5966
						$diff = ($maxh - $text_height);
5967
						if ($diff >= 0) {
5968
							if ($diff <= $diff_epsilon) {
5969
								break;
5970
							}
5971
							$fmin = $fmid;
5972
						} else {
5973
							$fmax = $fmid;
5974
						}
5975
						--$maxit;
5976
					}
5977
					if ($maxit < 0) {
5978
						// premature exit, we get the minimum font value to fit the cell
5979
						$this->setFontSize($fmin);
5980
						$this->resetLastH();
5981
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5982
					} else {
5983
						$this->setFontSize($fmid);
5984
						$this->resetLastH();
5985
					}
5986
				}
5987
				if ($text_height < $maxh) {
5988
					if ($valign == 'M') {
5989
						// text vertically centered
5990
						$this->y += (($maxh - $text_height) / 2);
5991
					} elseif ($valign == 'B') {
5992
						// text vertically aligned on bottom
5993
						$this->y += ($maxh - $text_height);
5994
					}
5995
				}
5996
			}
5997
			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5998
			if ($fitcell) {
5999
				// restore font size
6000
				$this->setFontSize($prev_FontSizePt);
6001
			}
6002
		}
6003
		if ($autopadding) {
6004
			// add bottom padding
6005
			$this->y += $mc_padding['B'];
6006
		}
6007
		// Get end-of-text Y position
6008
		$currentY = $this->y;
6009
		// get latest page number
6010
		$endpage = $this->page;
6011
		if ($resth > 0) {
6012
			$skip = ($endpage - $startpage);
6013
			$tmpresth = $resth;
6014
			while ($tmpresth > 0) {
6015
				if ($skip <= 0) {
6016
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
6017
					$this->checkPageBreak($this->PageBreakTrigger + 1);
6018
				}
6019
				if ($this->num_columns > 1) {
6020
					$tmpresth -= ($this->h - $this->y - $this->bMargin);
6021
				} else {
6022
					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6023
				}
6024
				--$skip;
6025
			}
6026
			$currentY = $this->y;
6027
			$endpage = $this->page;
6028
		}
6029
		// get latest column
6030
		$endcolumn = $this->current_column;
6031
		if ($this->num_columns == 0) {
6032
			$this->num_columns = 1;
6033
		}
6034
		// disable page regions check
6035
		$check_page_regions = $this->check_page_regions;
6036
		$this->check_page_regions = false;
6037
		// get border modes
6038
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
6039
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
6040
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6041
		// design borders around HTML cells.
6042
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6043
			$ccode = '';
6044
			$this->setPage($page);
6045
			if ($this->num_columns < 2) {
6046
				// single-column mode
6047
				$this->setX($x);
6048
				$this->y = $this->tMargin;
6049
			}
6050
			// account for margin changes
6051
			if ($page > $startpage) {
6052
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6053
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6054
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6055
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6056
				}
6057
			}
6058
			if ($startpage == $endpage) {
6059
				// single page
6060
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6061
					if ($column != $this->current_column) {
6062
						$this->selectColumn($column);
6063
					}
6064
					if ($this->rtl) {
6065
						$this->x -= $mc_margin['R'];
6066
					} else {
6067
						$this->x += $mc_margin['L'];
6068
					}
6069
					if ($startcolumn == $endcolumn) { // single column
6070
						$cborder = $border;
6071
						$h = max($h, ($currentY - $oy));
6072
						$this->y = $oy;
6073
					} elseif ($column == $startcolumn) { // first column
6074
						$cborder = $border_start;
6075
						$this->y = $oy;
6076
						$h = $this->h - $this->y - $this->bMargin;
6077
					} elseif ($column == $endcolumn) { // end column
6078
						$cborder = $border_end;
6079
						$h = $currentY - $this->y;
6080
						if ($resth > $h) {
6081
							$h = $resth;
6082
						}
6083
					} else { // middle column
6084
						$cborder = $border_middle;
6085
						$h = $this->h - $this->y - $this->bMargin;
6086
						$resth -= $h;
6087
					}
6088
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6089
				} // end for each column
6090
			} elseif ($page == $startpage) { // first page
6091
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6092
					if ($column != $this->current_column) {
6093
						$this->selectColumn($column);
6094
					}
6095
					if ($this->rtl) {
6096
						$this->x -= $mc_margin['R'];
6097
					} else {
6098
						$this->x += $mc_margin['L'];
6099
					}
6100
					if ($column == $startcolumn) { // first column
6101
						$cborder = $border_start;
6102
						$this->y = $oy;
6103
						$h = $this->h - $this->y - $this->bMargin;
6104
					} else { // middle column
6105
						$cborder = $border_middle;
6106
						$h = $this->h - $this->y - $this->bMargin;
6107
						$resth -= $h;
6108
					}
6109
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6110
				} // end for each column
6111
			} elseif ($page == $endpage) { // last page
6112
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6113
					if ($column != $this->current_column) {
6114
						$this->selectColumn($column);
6115
					}
6116
					if ($this->rtl) {
6117
						$this->x -= $mc_margin['R'];
6118
					} else {
6119
						$this->x += $mc_margin['L'];
6120
					}
6121
					if ($column == $endcolumn) {
6122
						// end column
6123
						$cborder = $border_end;
6124
						$h = $currentY - $this->y;
6125
						if ($resth > $h) {
6126
							$h = $resth;
6127
						}
6128
					} else {
6129
						// middle column
6130
						$cborder = $border_middle;
6131
						$h = $this->h - $this->y - $this->bMargin;
6132
						$resth -= $h;
6133
					}
6134
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6135
				} // end for each column
6136
			} else { // middle page
6137
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6138
					$this->selectColumn($column);
6139
					if ($this->rtl) {
6140
						$this->x -= $mc_margin['R'];
6141
					} else {
6142
						$this->x += $mc_margin['L'];
6143
					}
6144
					$cborder = $border_middle;
6145
					$h = $this->h - $this->y - $this->bMargin;
6146
					$resth -= $h;
6147
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6148
				} // end for each column
6149
			}
6150
			if ($cborder OR $fill) {
6151
				$offsetlen = strlen($ccode);
6152
				// draw border and fill
6153
				if ($this->inxobj) {
6154
					// we are inside an XObject template
6155
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6156
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6157
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6158
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6159
					} else {
6160
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6161
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6162
					}
6163
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6164
					$pstart = substr($pagebuff, 0, $pagemark);
6165
					$pend = substr($pagebuff, $pagemark);
6166
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6167
				} else {
6168
					if (end($this->transfmrk[$this->page]) !== false) {
6169
						$pagemarkkey = key($this->transfmrk[$this->page]);
6170
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6171
						$this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6172
					} elseif ($this->InFooter) {
6173
						$pagemark = $this->footerpos[$this->page];
6174
						$this->footerpos[$this->page] += $offsetlen;
6175
					} else {
6176
						$pagemark = $this->intmrk[$this->page];
6177
						$this->intmrk[$this->page] += $offsetlen;
6178
					}
6179
					$pagebuff = $this->getPageBuffer($this->page);
6180
					$pstart = substr($pagebuff, 0, $pagemark);
6181
					$pend = substr($pagebuff, $pagemark);
6182
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6183
				}
6184
			}
6185
		} // end for each page
6186
		// restore page regions check
6187
		$this->check_page_regions = $check_page_regions;
6188
		// Get end-of-cell Y position
6189
		$currentY = $this->GetY();
6190
		// restore previous values
6191
		if ($this->num_columns > 1) {
6192
			$this->selectColumn();
6193
		} else {
6194
			// restore original margins
6195
			$this->lMargin = $lMargin;
6196
			$this->rMargin = $rMargin;
6197
			if ($this->page > $startpage) {
6198
				// check for margin variations between pages (i.e. booklet mode)
6199
				$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6200
				$dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6201
				if (($dl != 0) OR ($dr != 0)) {
6202
					$this->lMargin += $dl;
6203
					$this->rMargin += $dr;
6204
				}
6205
			}
6206
		}
6207
		if ($ln > 0) {
6208
			//Go to the beginning of the next line
6209
			$this->setY($currentY + $mc_margin['B']);
6210
			if ($ln == 2) {
6211
				$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6212
			}
6213
		} else {
6214
			// go left or right by case
6215
			$this->setPage($startpage);
6216
			$this->y = $y;
6217
			$this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6218
		}
6219
		$this->setContentMark();
6220
		$this->cell_padding = $prev_cell_padding;
6221
		$this->cell_margin = $prev_cell_margin;
6222
		$this->clMargin = $this->lMargin;
6223
		$this->crMargin = $this->rMargin;
6224
		return $nl;
6225
	}
6226
 
6227
	/**
6228
	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6229
	 * @param string $txt String for calculating his height
6230
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6231
	 * @param boolean $reseth if true reset the last cell height (default false).
6232
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6233
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6234
	 * @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)))
6235
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6236
	 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6237
	 * @public
6238
	 * @since 4.5.011
6239
	 */
6240
	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6241
		if ($txt === NULL) {
6242
			return 0;
6243
		}
6244
		if ($txt === '') {
6245
			// empty string
6246
			return 1;
6247
		}
6248
		// adjust internal padding
6249
		$prev_cell_padding = $this->cell_padding;
6250
		$prev_lasth = $this->lasth;
6251
		if (is_array($cellpadding)) {
6252
			$this->cell_padding = $cellpadding;
6253
		}
6254
		$this->adjustCellPadding($border);
6255
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6256
			if ($this->rtl) {
6257
				$w = $this->x - $this->lMargin;
6258
			} else {
6259
				$w = $this->w - $this->rMargin - $this->x;
6260
			}
6261
		}
6262
		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6263
		if ($reseth) {
6264
			// reset row height
6265
			$this->resetLastH();
6266
		}
6267
		$lines = 1;
6268
		$sum = 0;
6269
		$chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6270
		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6271
		$length = count($chars);
6272
		$lastSeparator = -1;
6273
		for ($i = 0; $i < $length; ++$i) {
6274
			$c = $chars[$i];
6275
			$charWidth = $charsWidth[$i];
6276
			if (($c != 160)
6277
					AND (($c == 173)
6278
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6279
						OR (($c == 45)
6280
							AND ($i > 0) AND ($i < ($length - 1))
6281
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6282
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6283
						)
6284
					)
6285
				) {
6286
				$lastSeparator = $i;
6287
			}
6288
			if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6289
				++$lines;
6290
				if ($c == 10) {
6291
					$lastSeparator = -1;
6292
					$sum = 0;
6293
				} elseif ($lastSeparator != -1) {
6294
					$i = $lastSeparator;
6295
					$lastSeparator = -1;
6296
					$sum = 0;
6297
				} else {
6298
					$sum = $charWidth;
6299
				}
6300
			} else {
6301
				$sum += $charWidth;
6302
			}
6303
		}
6304
		if ($chars[($length - 1)] == 10) {
6305
			--$lines;
6306
		}
6307
		$this->cell_padding = $prev_cell_padding;
6308
		$this->lasth = $prev_lasth;
6309
		return $lines;
6310
	}
6311
 
6312
	/**
6313
	 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6314
	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6315
	 * @pre
6316
	 *  // store current object
6317
	 *  $pdf->startTransaction();
6318
	 *  // store starting values
6319
	 *  $start_y = $pdf->GetY();
6320
	 *  $start_page = $pdf->getPage();
6321
	 *  // call your printing functions with your parameters
6322
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6323
	 *  $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);
6324
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6325
	 *  // get the new Y
6326
	 *  $end_y = $pdf->GetY();
6327
	 *  $end_page = $pdf->getPage();
6328
	 *  // calculate height
6329
	 *  $height = 0;
6330
	 *  if ($end_page == $start_page) {
6331
	 *  	$height = $end_y - $start_y;
6332
	 *  } else {
6333
	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6334
	 *  		$this->setPage($page);
6335
	 *  		if ($page == $start_page) {
6336
	 *  			// first page
6337
	 *  			$height += $this->h - $start_y - $this->bMargin;
6338
	 *  		} elseif ($page == $end_page) {
6339
	 *  			// last page
6340
	 *  			$height += $end_y - $this->tMargin;
6341
	 *  		} else {
6342
	 *  			$height += $this->h - $this->tMargin - $this->bMargin;
6343
	 *  		}
6344
	 *  	}
6345
	 *  }
6346
	 *  // restore previous object
6347
	 *  $pdf = $pdf->rollbackTransaction();
6348
	 *
6349
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6350
	 * @param string $txt String for calculating his height
6351
	 * @param boolean $reseth if true reset the last cell height (default false).
6352
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6353
	 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6354
	 * @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)))
6355
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6356
	 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6357
	 * @public
6358
	 */
6359
	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6360
		// adjust internal padding
6361
		$prev_cell_padding = $this->cell_padding;
6362
		$prev_lasth = $this->lasth;
6363
		if (is_array($cellpadding)) {
6364
			$this->cell_padding = $cellpadding;
6365
		}
6366
		$this->adjustCellPadding($border);
6367
		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6368
		$height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6369
		$this->cell_padding = $prev_cell_padding;
6370
		$this->lasth = $prev_lasth;
6371
		return $height;
6372
	}
6373
 
6374
	/**
6375
	 * This method prints text from the current position.<br />
6376
	 * @param float $h Line height
6377
	 * @param string $txt String to print
6378
	 * @param mixed $link URL or identifier returned by AddLink()
6379
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6380
	 * @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>
6381
	 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6382
	 * @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.
6383
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
6384
	 * @param boolean $firstblock if true the string is the starting of a line.
6385
	 * @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.
6386
	 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6387
	 * @param array|null $margin margin array of the parent container
6388
	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6389
	 * @public
6390
	 * @since 1.5
6391
	 */
6392
	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) {
6393
		// check page for no-write regions and adapt page margins if necessary
6394
		list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6395
		if (strlen($txt) == 0) {
6396
			// fix empty text
6397
			$txt = ' ';
6398
		}
6399
		if (!is_array($margin)) {
6400
			// set default margins
6401
			$margin = $this->cell_margin;
6402
		}
6403
		// remove carriage returns
6404
		$s = str_replace("\r", '', $txt);
6405
		// check if string contains arabic text
6406
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6407
			$arabic = true;
6408
		} else {
6409
			$arabic = false;
6410
		}
6411
		// check if string contains RTL text
6412
		if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6413
			$rtlmode = true;
6414
		} else {
6415
			$rtlmode = false;
6416
		}
6417
		// get a char width
6418
		$chrwidth = $this->GetCharWidth(46); // dot character
6419
		// get array of unicode values
6420
		$chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6421
		// calculate maximum width for a single character on string
6422
		$chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6423
		array_walk($chrw, array($this, 'getRawCharWidth'));
6424
		$maxchwidth = ((is_array($chrw) || $chrw instanceof Countable) && count($chrw) > 0) ? max($chrw) : 0;
6425
		// get array of chars
6426
		$uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6427
		// get the number of characters
6428
		$nb = count($chars);
6429
		// replacement for SHY character (minus symbol)
6430
		$shy_replacement = 45;
6431
		$shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6432
		// widht for SHY replacement
6433
		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6434
		// page width
6435
		$pw = $w = $this->w - $this->lMargin - $this->rMargin;
6436
		// calculate remaining line width ($w)
6437
		if ($this->rtl) {
6438
			$w = $this->x - $this->lMargin;
6439
		} else {
6440
			$w = $this->w - $this->rMargin - $this->x;
6441
		}
6442
		// max column width
6443
		$wmax = ($w - $wadj);
6444
		if (!$firstline) {
6445
			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6446
		}
6447
		if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6448
			// the maximum width character do not fit on column
6449
			return '';
6450
		}
6451
		// minimum row height
6452
		$row_height = max($h, $this->getCellHeight($this->FontSize));
6453
		// max Y
6454
		$maxy = $this->y + $maxh - max($row_height, $h);
6455
		$start_page = $this->page;
6456
		$i = 0; // character position
6457
		$j = 0; // current starting position
6458
		$sep = -1; // position of the last blank space
6459
		$prevsep = $sep; // previous separator
6460
		$shy = false; // true if the last blank is a soft hypen (SHY)
6461
		$prevshy = $shy; // previous shy mode
6462
		$l = 0; // current string length
6463
		$nl = 0; //number of lines
6464
		$linebreak = false;
6465
		$pc = 0; // previous character
6466
		// for each character
6467
		while ($i < $nb) {
6468
			if (($maxh > 0) AND ($this->y > $maxy) ) {
6469
				break;
6470
			}
6471
			//Get the current character
6472
			$c = $chars[$i];
6473
			if ($c == 10) { // 10 = "\n" = new line
6474
				//Explicit line break
6475
				if ($align == 'J') {
6476
					if ($this->rtl) {
6477
						$talign = 'R';
6478
					} else {
6479
						$talign = 'L';
6480
					}
6481
				} else {
6482
					$talign = $align;
6483
				}
6484
				$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6485
				if ($firstline) {
6486
					$startx = $this->x;
6487
					$tmparr = array_slice($chars, $j, ($i - $j));
6488
					if ($rtlmode) {
6489
						$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6490
					}
6491
					$linew = $this->GetArrStringWidth($tmparr);
6492
					unset($tmparr);
6493
					if ($this->rtl) {
6494
						$this->endlinex = $startx - $linew;
6495
					} else {
6496
						$this->endlinex = $startx + $linew;
6497
					}
6498
					$w = $linew;
6499
					$tmpcellpadding = $this->cell_padding;
6500
					if ($maxh == 0) {
6501
						$this->setCellPadding(0);
6502
					}
6503
				}
6504
				if ($firstblock AND $this->isRTLTextDir()) {
6505
					$tmpstr = $this->stringRightTrim($tmpstr);
6506
				}
6507
				// Skip newlines at the beginning of a page or column
6508
				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6509
					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6510
				}
6511
				unset($tmpstr);
6512
				if ($firstline) {
6513
					$this->cell_padding = $tmpcellpadding;
6514
					return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6515
				}
6516
				++$nl;
6517
				$j = $i + 1;
6518
				$l = 0;
6519
				$sep = -1;
6520
				$prevsep = $sep;
6521
				$shy = false;
6522
				// account for margin changes
6523
				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6524
					if ($this->AcceptPageBreak())
6525
					{
6526
						if ($this->rtl) {
6527
							$this->x -= $margin['R'];
6528
						} else {
6529
							$this->x += $margin['L'];
6530
						}
6531
						$this->lMargin += $margin['L'];
6532
						$this->rMargin += $margin['R'];
6533
					}
6534
				}
6535
				$w = $this->getRemainingWidth();
6536
				$wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6537
			} else {
6538
				// 160 is the non-breaking space.
6539
				// 173 is SHY (Soft Hypen).
6540
				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6541
				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6542
				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6543
				if (($c != 160)
6544
					AND (($c == 173)
6545
						OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6546
						OR (($c == 45)
6547
							AND ($i < ($nb - 1))
6548
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6549
							AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6550
						)
6551
					)
6552
				) {
6553
					// update last blank space position
6554
					$prevsep = $sep;
6555
					$sep = $i;
6556
					// check if is a SHY
6557
					if (($c == 173) OR ($c == 45)) {
6558
						$prevshy = $shy;
6559
						$shy = true;
6560
						if ($pc == 45) {
6561
							$tmp_shy_replacement_width = 0;
6562
							$tmp_shy_replacement_char = '';
6563
						} else {
6564
							$tmp_shy_replacement_width = $shy_replacement_width;
6565
							$tmp_shy_replacement_char = $shy_replacement_char;
6566
						}
6567
					} else {
6568
						$shy = false;
6569
					}
6570
				}
6571
				// update string length
6572
				if ($this->isUnicodeFont() AND ($arabic)) {
6573
					// with bidirectional algorithm some chars may be changed affecting the line length
6574
					// *** very slow ***
6575
					$l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6576
				} else {
6577
					$l += $this->GetCharWidth($c, ($i+1 < $nb));
6578
				}
6579
				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6580
					if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6581
						$sep = $prevsep;
6582
						$shy = $prevshy;
6583
					}
6584
					// we have reached the end of column
6585
					if ($sep == -1) {
6586
						// check if the line was already started
6587
						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6588
							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6589
							// print a void cell and go to next line
6590
							$this->Cell($w, $h, '', 0, 1);
6591
							$linebreak = true;
6592
							if ($firstline) {
6593
								return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6594
							}
6595
						} else {
6596
							// truncate the word because do not fit on column
6597
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6598
							if ($firstline) {
6599
								$startx = $this->x;
6600
								$tmparr = array_slice($chars, $j, ($i - $j));
6601
								if ($rtlmode) {
6602
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6603
								}
6604
								$linew = $this->GetArrStringWidth($tmparr);
6605
								unset($tmparr);
6606
								if ($this->rtl) {
6607
									$this->endlinex = $startx - $linew;
6608
								} else {
6609
									$this->endlinex = $startx + $linew;
6610
								}
6611
								$w = $linew;
6612
								$tmpcellpadding = $this->cell_padding;
6613
								if ($maxh == 0) {
6614
									$this->setCellPadding(0);
6615
								}
6616
							}
6617
							if ($firstblock AND $this->isRTLTextDir()) {
6618
								$tmpstr = $this->stringRightTrim($tmpstr);
6619
							}
6620
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6621
							unset($tmpstr);
6622
							if ($firstline) {
6623
								$this->cell_padding = $tmpcellpadding;
6624
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6625
							}
6626
							$j = $i;
6627
							--$i;
6628
						}
6629
					} else {
6630
						// word wrapping
6631
						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6632
							$endspace = 1;
6633
						} else {
6634
							$endspace = 0;
6635
						}
6636
						// check the length of the next string
6637
						$strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6638
						$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6639
						if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6640
							// truncate the word because do not fit on a full page width
6641
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6642
							if ($firstline) {
6643
								$startx = $this->x;
6644
								$tmparr = array_slice($chars, $j, ($i - $j));
6645
								if ($rtlmode) {
6646
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6647
								}
6648
								$linew = $this->GetArrStringWidth($tmparr);
6649
								unset($tmparr);
6650
								if ($this->rtl) {
6651
									$this->endlinex = ($startx - $linew);
6652
								} else {
6653
									$this->endlinex = ($startx + $linew);
6654
								}
6655
								$w = $linew;
6656
								$tmpcellpadding = $this->cell_padding;
6657
								if ($maxh == 0) {
6658
									$this->setCellPadding(0);
6659
								}
6660
							}
6661
							if ($firstblock AND $this->isRTLTextDir()) {
6662
								$tmpstr = $this->stringRightTrim($tmpstr);
6663
							}
6664
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6665
							unset($tmpstr);
6666
							if ($firstline) {
6667
								$this->cell_padding = $tmpcellpadding;
6668
								return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6669
							}
6670
							$j = $i;
6671
							--$i;
6672
						} else {
6673
							// word wrapping
6674
							if ($shy) {
6675
								// add hypen (minus symbol) at the end of the line
6676
								$shy_width = $tmp_shy_replacement_width;
6677
								if ($this->rtl) {
6678
									$shy_char_left = $tmp_shy_replacement_char;
6679
									$shy_char_right = '';
6680
								} else {
6681
									$shy_char_left = '';
6682
									$shy_char_right = $tmp_shy_replacement_char;
6683
								}
6684
							} else {
6685
								$shy_width = 0;
6686
								$shy_char_left = '';
6687
								$shy_char_right = '';
6688
							}
6689
							$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6690
							if ($firstline) {
6691
								$startx = $this->x;
6692
								$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6693
								if ($rtlmode) {
6694
									$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6695
								}
6696
								$linew = $this->GetArrStringWidth($tmparr);
6697
								unset($tmparr);
6698
								if ($this->rtl) {
6699
									$this->endlinex = $startx - $linew - $shy_width;
6700
								} else {
6701
									$this->endlinex = $startx + $linew + $shy_width;
6702
								}
6703
								$w = $linew;
6704
								$tmpcellpadding = $this->cell_padding;
6705
								if ($maxh == 0) {
6706
									$this->setCellPadding(0);
6707
								}
6708
							}
6709
							// print the line
6710
							if ($firstblock AND $this->isRTLTextDir()) {
6711
								$tmpstr = $this->stringRightTrim($tmpstr);
6712
							}
6713
							$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6714
							unset($tmpstr);
6715
							if ($firstline) {
6716
								if ($chars[$sep] == 45) {
6717
									$endspace += 1;
6718
								}
6719
								// return the remaining text
6720
								$this->cell_padding = $tmpcellpadding;
6721
								return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6722
							}
6723
							$i = $sep;
6724
							$sep = -1;
6725
							$shy = false;
6726
							$j = ($i + 1);
6727
						}
6728
					}
6729
					// account for margin changes
6730
					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6731
						if ($this->AcceptPageBreak())
6732
						{
6733
							if ($this->rtl) {
6734
								$this->x -= $margin['R'];
6735
							} else {
6736
								$this->x += $margin['L'];
6737
							}
6738
							$this->lMargin += $margin['L'];
6739
							$this->rMargin += $margin['R'];
6740
						}
6741
					}
6742
					$w = $this->getRemainingWidth();
6743
					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6744
					if ($linebreak) {
6745
						$linebreak = false;
6746
					} else {
6747
						++$nl;
6748
						$l = 0;
6749
					}
6750
				}
6751
			}
6752
			// save last character
6753
			$pc = $c;
6754
			++$i;
6755
		} // end while i < nb
6756
		// print last substring (if any)
6757
		if ($l > 0) {
6758
			switch ($align) {
6759
				case 'J':
6760
				case 'C': {
6761
					break;
6762
				}
6763
				case 'L': {
6764
					if (!$this->rtl) {
6765
						$w = $l;
6766
					}
6767
					break;
6768
				}
6769
				case 'R': {
6770
					if ($this->rtl) {
6771
						$w = $l;
6772
					}
6773
					break;
6774
				}
6775
				default: {
6776
					$w = $l;
6777
					break;
6778
				}
6779
			}
6780
			$tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6781
			if ($firstline) {
6782
				$startx = $this->x;
6783
				$tmparr = array_slice($chars, $j, ($nb - $j));
6784
				if ($rtlmode) {
6785
					$tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6786
				}
6787
				$linew = $this->GetArrStringWidth($tmparr);
6788
				unset($tmparr);
6789
				if ($this->rtl) {
6790
					$this->endlinex = $startx - $linew;
6791
				} else {
6792
					$this->endlinex = $startx + $linew;
6793
				}
6794
				$w = $linew;
6795
				$tmpcellpadding = $this->cell_padding;
6796
				if ($maxh == 0) {
6797
					$this->setCellPadding(0);
6798
				}
6799
			}
6800
			if ($firstblock AND $this->isRTLTextDir()) {
6801
				$tmpstr = $this->stringRightTrim($tmpstr);
6802
			}
6803
			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6804
			unset($tmpstr);
6805
			if ($firstline) {
6806
				$this->cell_padding = $tmpcellpadding;
6807
				return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6808
			}
6809
			++$nl;
6810
		}
6811
		if ($firstline) {
6812
			return '';
6813
		}
6814
		return $nl;
6815
	}
6816
 
6817
	/**
6818
	 * Returns the remaining width between the current position and margins.
6819
	 * @return float Return the remaining width
6820
	 * @protected
6821
	 */
6822
	protected function getRemainingWidth() {
6823
		list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6824
		if ($this->rtl) {
6825
			return ($this->x - $this->lMargin);
6826
		} else {
6827
			return ($this->w - $this->rMargin - $this->x);
6828
		}
6829
	}
6830
 
6831
	/**
6832
	 * Set the block dimensions accounting for page breaks and page/column fitting
6833
	 * @param float $w width
6834
	 * @param float $h height
6835
	 * @param float $x X coordinate
6836
	 * @param float $y Y coodiante
6837
	 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6838
	 * @return array array($w, $h, $x, $y)
6839
	 * @protected
6840
	 * @since 5.5.009 (2010-07-05)
6841
	 */
6842
	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6843
		if ($w <= 0) {
6844
			// set maximum width
6845
			$w = ($this->w - $this->lMargin - $this->rMargin);
6846
			if ($w <= 0) {
6847
				$w = 1;
6848
			}
6849
		}
6850
		if ($h <= 0) {
6851
			// set maximum height
6852
			$h = ($this->PageBreakTrigger - $this->tMargin);
6853
			if ($h <= 0) {
6854
				$h = 1;
6855
			}
6856
		}
6857
		// resize the block to be vertically contained on a single page or single column
6858
		if ($fitonpage OR $this->AutoPageBreak) {
6859
			$ratio_wh = ($w / $h);
6860
			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6861
				$h = $this->PageBreakTrigger - $this->tMargin;
6862
				$w = ($h * $ratio_wh);
6863
			}
6864
			// resize the block to be horizontally contained on a single page or single column
6865
			if ($fitonpage) {
6866
				$maxw = ($this->w - $this->lMargin - $this->rMargin);
6867
				if ($w > $maxw) {
6868
					$w = $maxw;
6869
					$h = ($w / $ratio_wh);
6870
				}
6871
			}
6872
		}
6873
		// Check whether we need a new page or new column first as this does not fit
6874
		$prev_x = $this->x;
6875
		$prev_y = $this->y;
6876
		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6877
			$y = $this->y;
6878
			if ($this->rtl) {
6879
				$x += ($prev_x - $this->x);
6880
			} else {
6881
				$x += ($this->x - $prev_x);
6882
			}
6883
			$this->newline = true;
6884
		}
6885
		// resize the block to be contained on the remaining available page or column space
6886
		if ($fitonpage) {
6887
			// fallback to avoid division by zero
6888
			$h = $h == 0 ? 1 : $h;
6889
			$ratio_wh = ($w / $h);
6890
			if (($y + $h) > $this->PageBreakTrigger) {
6891
				$h = $this->PageBreakTrigger - $y;
6892
				$w = ($h * $ratio_wh);
6893
			}
6894
			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6895
				$w = $this->w - $this->rMargin - $x;
6896
				$h = ($w / $ratio_wh);
6897
			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6898
				$w = $x - $this->lMargin;
6899
				$h = ($w / $ratio_wh);
6900
			}
6901
		}
6902
		return array($w, $h, $x, $y);
6903
	}
6904
 
6905
	/**
6906
	 * Puts an image in the page.
6907
	 * The upper-left corner must be given.
6908
	 * The dimensions can be specified in different ways:<ul>
6909
	 * <li>explicit width and height (expressed in user unit)</li>
6910
	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6911
	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6912
	 * 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;
6913
	 * The format can be specified explicitly or inferred from the file extension.<br />
6914
	 * It is possible to put a link on the image.<br />
6915
	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6916
	 * @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').
6917
	 * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6918
	 * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6919
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6920
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6921
	 * @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.
6922
	 * @param mixed $link URL or identifier returned by AddLink().
6923
	 * @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>
6924
	 * @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).
6925
	 * @param int $dpi dot-per-inch resolution used on resize
6926
	 * @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>
6927
	 * @param boolean $ismask true if this image is a mask, false otherwise
6928
	 * @param mixed $imgmask image object returned by this function or false
6929
	 * @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)))
6930
	 * @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).
6931
	 * @param boolean $hidden If true do not display the image.
6932
	 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6933
	 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6934
	 * @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.
6935
	 * @return mixed|false image information
6936
	 * @public
6937
	 * @since 1.1
6938
	 */
6939
	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()) {
6940
		if ($this->state != 2) {
6941
			return false;
6942
		}
6943
		if (TCPDF_STATIC::empty_string($x)) {
6944
			$x = $this->x;
6945
		}
6946
		if (TCPDF_STATIC::empty_string($y)) {
6947
			$y = $this->y;
6948
		}
6949
		// check page for no-write regions and adapt page margins if necessary
6950
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
6951
		$exurl = ''; // external streams
6952
		$imsize = FALSE;
6953
 
6954
        // Make sure the file variable is not empty or null because accessing $file[0] later
6955
        // results in error when running PHP 7.4
6956
        if (empty($file)) {
6957
            return false;
6958
        }
6959
		// check if we are passing an image as file or string
6960
		if ($file[0] === '@') {
6961
			// image from string
6962
			$imgdata = substr($file, 1);
6963
		} else { // image file
6964
			if ($file[0] === '*') {
6965
				// image as external stream
6966
				$file = substr($file, 1);
6967
				$exurl = $file;
6968
			}
6969
			// check if file exist and it is valid
6970
			if (!@$this->fileExists($file)) {
6971
				return false;
6972
			}
6973
            if (false !== $info = $this->getImageBuffer($file)) {
6974
                $imsize = array($info['w'], $info['h']);
6975
            } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
6976
                $imgdata = $this->getCachedFileContents($file);
6977
            }
6978
		}
6979
		if (!empty($imgdata)) {
6980
			// copy image to cache
6981
			$original_file = $file;
6982
			$file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6983
			$fp = TCPDF_STATIC::fopenLocal($file, 'w');
6984
			if (!$fp) {
6985
				$this->Error('Unable to write file: '.$file);
6986
			}
6987
			fwrite($fp, $imgdata);
6988
			fclose($fp);
6989
			unset($imgdata);
6990
			$imsize = @getimagesize($file);
6991
			if ($imsize === FALSE) {
6992
				unlink($file);
6993
				$file = $original_file;
6994
			}
6995
		}
6996
		if ($imsize === FALSE) {
6997
			if (($w > 0) AND ($h > 0)) {
6998
				// get measures from specified data
6999
				$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7000
				$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7001
				$imsize = array($pw, $ph);
7002
			} else {
7003
				$this->Error('[Image] Unable to get the size of the image: '.$file);
7004
			}
7005
		}
7006
		// file hash
7007
		$filehash = md5($file);
7008
		// get original image width and height in pixels
7009
		list($pixw, $pixh) = $imsize;
7010
		// calculate image width and height on document
7011
		if (($w <= 0) AND ($h <= 0)) {
7012
			// convert image size to document unit
7013
			$w = $this->pixelsToUnits($pixw);
7014
			$h = $this->pixelsToUnits($pixh);
7015
		} elseif ($w <= 0) {
7016
			$w = $h * $pixw / $pixh;
7017
		} elseif ($h <= 0) {
7018
			$h = $w * $pixh / $pixw;
7019
		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7020
			if (strlen($fitbox) !== 2) {
7021
				// set default alignment
7022
				$fitbox = '--';
7023
			}
7024
			// scale image dimensions proportionally to fit within the ($w, $h) box
7025
			if ((($w * $pixh) / ($h * $pixw)) < 1) {
7026
				// store current height
7027
				$oldh = $h;
7028
				// calculate new height
7029
				$h = $w * $pixh / $pixw;
7030
				// height difference
7031
				$hdiff = ($oldh - $h);
7032
				// vertical alignment
7033
				switch (strtoupper($fitbox[1])) {
7034
					case 'T': {
7035
						break;
7036
					}
7037
					case 'M': {
7038
						$y += ($hdiff / 2);
7039
						break;
7040
					}
7041
					case 'B': {
7042
						$y += $hdiff;
7043
						break;
7044
					}
7045
				}
7046
			} else {
7047
				// store current width
7048
				$oldw = $w;
7049
				// calculate new width
7050
				$w = $h * $pixw / $pixh;
7051
				// width difference
7052
				$wdiff = ($oldw - $w);
7053
				// horizontal alignment
7054
				switch (strtoupper($fitbox[0])) {
7055
					case 'L': {
7056
						if ($this->rtl) {
7057
							$x -= $wdiff;
7058
						}
7059
						break;
7060
					}
7061
					case 'C': {
7062
						if ($this->rtl) {
7063
							$x -= ($wdiff / 2);
7064
						} else {
7065
							$x += ($wdiff / 2);
7066
						}
7067
						break;
7068
					}
7069
					case 'R': {
7070
						if (!$this->rtl) {
7071
							$x += $wdiff;
7072
						}
7073
						break;
7074
					}
7075
				}
7076
			}
7077
		}
7078
		// fit the image on available space
7079
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7080
		// calculate new minimum dimensions in pixels
7081
		$neww = round($w * $this->k * $dpi / $this->dpi);
7082
		$newh = round($h * $this->k * $dpi / $this->dpi);
7083
		// check if resize is necessary (resize is used only to reduce the image)
7084
		$newsize = ($neww * $newh);
7085
		$pixsize = ($pixw * $pixh);
7086
		if (intval($resize) == 2) {
7087
			$resize = true;
7088
		} elseif ($newsize >= $pixsize) {
7089
			$resize = false;
7090
		}
7091
		// check if image has been already added on document
7092
		$newimage = true;
7093
		if (in_array($file, $this->imagekeys)) {
7094
			$newimage = false;
7095
			// get existing image data
7096
			$info = $this->getImageBuffer($file);
7097
			if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7098
				// check if the newer image is larger
7099
				$oldsize = ($info['w'] * $info['h']);
7100
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7101
					$newimage = true;
7102
				}
7103
			}
7104
		} elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7105
			// create temp image file (without alpha channel)
7106
			$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7107
			// create temp alpha file
7108
			$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7109
			// check for cached images
7110
			if (in_array($tempfile_plain, $this->imagekeys)) {
7111
				// get existing image data
7112
				$info = $this->getImageBuffer($tempfile_plain);
7113
				// check if the newer image is larger
7114
				$oldsize = ($info['w'] * $info['h']);
7115
				if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7116
					$newimage = true;
7117
				} else {
7118
					$newimage = false;
7119
					// embed mask image
7120
					$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7121
					// embed image, masked with previously embedded mask
7122
					return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7123
				}
7124
			}
7125
		}
7126
		if ($newimage) {
7127
			//First use of image, get info
7128
			$type = strtolower($type);
7129
			if ($type == '') {
7130
				$type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7131
			} elseif ($type == 'jpg') {
7132
				$type = 'jpeg';
7133
			}
7134
			// Specific image handlers (defined on TCPDF_IMAGES CLASS)
7135
			$mtd = '_parse'.$type;
7136
			// GD image handler function
7137
			$gdfunction = 'imagecreatefrom'.$type;
7138
			$info = false;
7139
			if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7140
				// TCPDF image functions
7141
				$info = TCPDF_IMAGES::$mtd($file);
7142
				if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7143
					AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7144
					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7145
				}
7146
			}
7147
			if (($info === false) AND function_exists($gdfunction)) {
7148
				try {
7149
					// GD library
7150
					$img = $gdfunction($file);
7151
					if ($img !== false) {
7152
						if ($resize) {
7153
							$imgr = imagecreatetruecolor($neww, $newh);
7154
							if (($type == 'gif') OR ($type == 'png')) {
7155
								$imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7156
							}
7157
							imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7158
							$img = $imgr;
7159
						}
7160
						if (($type == 'gif') OR ($type == 'png')) {
7161
							$info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7162
						} else {
7163
							$info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7164
						}
7165
					}
7166
				} catch(Exception $e) {
7167
					$info = false;
7168
				}
7169
			}
7170
			if (($info === false) AND extension_loaded('imagick')) {
7171
				try {
7172
					// ImageMagick library
7173
					$img = new Imagick();
7174
					if ($type == 'svg') {
7175
						if ($file[0] === '@') {
7176
							// image from string
7177
							$svgimg = substr($file, 1);
7178
						} else {
7179
							// get SVG file content
7180
                            $svgimg = $this->getCachedFileContents($file);
7181
						}
7182
						if ($svgimg !== FALSE) {
7183
							// get width and height
7184
							$regs = array();
7185
							if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7186
								$svgtag = $regs[1];
7187
								$tmp = array();
7188
								if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7189
									$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7190
									$owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7191
									$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7192
								} else {
7193
									$ow = $w;
7194
								}
7195
								$tmp = array();
7196
								if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7197
									$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7198
									$ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7199
									$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7200
								} else {
7201
									$oh = $h;
7202
								}
7203
								$tmp = array();
7204
								if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7205
									$vbw = ($ow * $this->imgscale * $this->k);
7206
									$vbh = ($oh * $this->imgscale * $this->k);
7207
									$vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7208
									$svgtag = $vbox.$svgtag;
7209
								}
7210
								$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7211
							}
7212
							$img->readImageBlob($svgimg);
7213
						}
7214
					} else {
7215
						$img->readImage($file);
7216
					}
7217
					if ($resize) {
7218
						$img->resizeImage($neww, $newh, 10, 1, false);
7219
					}
7220
					$img->setCompressionQuality($this->jpeg_quality);
7221
					$img->setImageFormat('jpeg');
7222
					$tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7223
					$img->writeImage($tempname);
7224
					$info = TCPDF_IMAGES::_parsejpeg($tempname);
7225
					unlink($tempname);
7226
					$img->destroy();
7227
				} catch(Exception $e) {
7228
					$info = false;
7229
				}
7230
			}
7231
			if ($info === false) {
7232
				// unable to process image
7233
				return false;
7234
			}
7235
			if ($ismask) {
7236
				// force grayscale
7237
				$info['cs'] = 'DeviceGray';
7238
			}
7239
			if ($imgmask !== false) {
7240
				$info['masked'] = $imgmask;
7241
			}
7242
			if (!empty($exurl)) {
7243
				$info['exurl'] = $exurl;
7244
			}
7245
			// array of alternative images
7246
			$info['altimgs'] = $altimgs;
7247
			// add image to document
7248
			$info['i'] = $this->setImageBuffer($file, $info);
7249
		}
7250
		// set alignment
7251
		$this->img_rb_x = $x + $w;
7252
		$this->img_rb_y = $y + $h;
7253
 
7254
		// set alignment
7255
		if ($palign == 'L') {
7256
			$ximg = $this->lMargin;
7257
		} elseif ($palign == 'C') {
7258
			$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7259
		} elseif ($palign == 'R') {
7260
			$ximg = $this->w - $this->rMargin - $w;
7261
		} else {
7262
			$ximg = $this->rtl ? $x - $w : $x;
7263
		}
7264
 
7265
		if ($ismask OR $hidden) {
7266
			// image is not displayed
7267
			return $info['i'];
7268
		}
7269
		$xkimg = $ximg * $this->k;
7270
		if (!$alt) {
7271
			// only non-alternative immages will be set
7272
			$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']));
7273
		}
7274
		if (!empty($border)) {
7275
			$bx = $this->x;
7276
			$by = $this->y;
7277
			$this->x = $ximg;
7278
			if ($this->rtl) {
7279
				$this->x += $w;
7280
			}
7281
			$this->y = $y;
7282
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7283
			$this->x = $bx;
7284
			$this->y = $by;
7285
		}
7286
		if ($link) {
7287
			$this->Link($ximg, $y, $w, $h, $link, 0);
7288
		}
7289
		// set pointer to align the next text/objects
7290
		switch($align) {
7291
			case 'T': {
7292
				$this->y = $y;
7293
				$this->x = $this->img_rb_x;
7294
				break;
7295
			}
7296
			case 'M': {
7297
				$this->y = $y + round($h/2);
7298
				$this->x = $this->img_rb_x;
7299
				break;
7300
			}
7301
			case 'B': {
7302
				$this->y = $this->img_rb_y;
7303
				$this->x = $this->img_rb_x;
7304
				break;
7305
			}
7306
			case 'N': {
7307
				$this->setY($this->img_rb_y);
7308
				break;
7309
			}
7310
			default:{
7311
				break;
7312
			}
7313
		}
7314
		$this->endlinex = $this->img_rb_x;
7315
		if ($this->inxobj) {
7316
			// we are inside an XObject template
7317
			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7318
		}
7319
		return $info['i'];
7320
	}
7321
 
7322
	/**
7323
	 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7324
	 * @param string $file Name of the file containing the image.
7325
	 * @param float $x Abscissa of the upper-left corner.
7326
	 * @param float $y Ordinate of the upper-left corner.
7327
	 * @param float $wpx Original width of the image in pixels.
7328
	 * @param float $hpx original height of the image in pixels.
7329
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7330
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7331
	 * @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.
7332
	 * @param mixed $link URL or identifier returned by AddLink().
7333
	 * @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>
7334
	 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7335
	 * @param int $dpi dot-per-inch resolution used on resize
7336
	 * @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>
7337
	 * @param string $filehash File hash used to build unique file names.
7338
	 * @author Nicola Asuni
7339
	 * @protected
7340
	 * @since 4.3.007 (2008-12-04)
7341
	 * @see Image()
7342
	 */
7343
	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7344
		// create temp images
7345
		if (empty($filehash)) {
7346
			$filehash = md5($file);
7347
		}
7348
		// create temp image file (without alpha channel)
7349
		$tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7350
		// create temp alpha file
7351
		$tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7352
		$parsed = false;
7353
		$parse_error = '';
7354
		// ImageMagick extension
7355
		if (($parsed === false) AND extension_loaded('imagick')) {
7356
			try {
7357
				// ImageMagick library
7358
				$img = new Imagick();
7359
				$img->readImage($file);
7360
				// clone image object
7361
				$imga = TCPDF_STATIC::objclone($img);
7362
				// extract alpha channel
7363
				if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7364
					$img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7365
				} else {
7366
					$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7367
					$img->negateImage(true);
7368
				}
7369
				$img->setImageFormat('png');
7370
				$img->writeImage($tempfile_alpha);
7371
				// remove alpha channel
7372
				if (method_exists($imga, 'setImageMatte')) {
7373
					$imga->setImageMatte(false);
7374
				} else {
7375
					$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7376
				}
7377
				$imga->setImageFormat('png');
7378
				$imga->writeImage($tempfile_plain);
7379
				$parsed = true;
7380
			} catch (Exception $e) {
7381
				// Imagemagick fails, try with GD
7382
				$parse_error = 'Imagick library error: '.$e->getMessage();
7383
			}
7384
		}
7385
		// GD extension
7386
		if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7387
			try {
7388
				// generate images
7389
				$img = imagecreatefrompng($file);
7390
				$imgalpha = imagecreate($wpx, $hpx);
7391
				// generate gray scale palette (0 -> 255)
7392
				for ($c = 0; $c < 256; ++$c) {
7393
					ImageColorAllocate($imgalpha, $c, $c, $c);
7394
				}
7395
				// extract alpha channel
7396
				for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7397
					for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7398
						$color = imagecolorat($img, $xpx, $ypx);
7399
						// get and correct gamma color
7400
						$alpha = $this->getGDgamma($img, $color);
7401
						imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha);
7402
					}
7403
				}
7404
				imagepng($imgalpha, $tempfile_alpha);
7405
				imagedestroy($imgalpha);
7406
				// extract image without alpha channel
7407
				$imgplain = imagecreatetruecolor($wpx, $hpx);
7408
				imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7409
				imagepng($imgplain, $tempfile_plain);
7410
				imagedestroy($imgplain);
7411
				$parsed = true;
7412
			} catch (Exception $e) {
7413
				// GD fails
7414
				$parse_error = 'GD library error: '.$e->getMessage();
7415
			}
7416
		}
7417
		if ($parsed === false) {
7418
			if (empty($parse_error)) {
7419
				$this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7420
			} else {
7421
				$this->Error($parse_error);
7422
			}
7423
		}
7424
		// embed mask image
7425
		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7426
		// embed image, masked with previously embedded mask
7427
		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7428
	}
7429
 
7430
	/**
7431
	 * Get the GD-corrected PNG gamma value from alpha color
7432
	 * @param resource $img GD image Resource ID.
7433
	 * @param int $c alpha color
7434
	 * @protected
7435
	 * @since 4.3.007 (2008-12-04)
7436
	 */
7437
	protected function getGDgamma($img, $c) {
7438
		if (!isset($this->gdgammacache['#'.$c])) {
7439
			$colors = imagecolorsforindex($img, $c);
7440
			// GD alpha is only 7 bit (0 -> 127)
7441
			$this->gdgammacache['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255);
7442
			// correct gamma
7443
			$this->gdgammacache['#'.$c] = (int) (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7444
			// store the latest values on cache to improve performances
7445
			if (count($this->gdgammacache) > 8) {
7446
				// remove one element from the cache array
7447
				array_shift($this->gdgammacache);
7448
			}
7449
		}
7450
		return $this->gdgammacache['#'.$c];
7451
	}
7452
 
7453
	/**
7454
	 * Performs a line break.
7455
	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7456
	 * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell.
7457
	 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7458
	 * @public
7459
	 * @since 1.0
7460
	 * @see Cell()
7461
	 */
7462
	public function Ln($h=null, $cell=false) {
7463
		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'])) {
7464
			// revove vertical space from the top of the column
7465
			return;
7466
		}
7467
		if ($cell) {
7468
			if ($this->rtl) {
7469
				$cellpadding = $this->cell_padding['R'];
7470
			} else {
7471
				$cellpadding = $this->cell_padding['L'];
7472
			}
7473
		} else {
7474
			$cellpadding = 0;
7475
		}
7476
		if ($this->rtl) {
7477
			$this->x = $this->w - $this->rMargin - $cellpadding;
7478
		} else {
7479
			$this->x = $this->lMargin + $cellpadding;
7480
		}
7481
		if (TCPDF_STATIC::empty_string($h)) {
7482
			$h = $this->lasth;
7483
		}
7484
		$this->y += $h;
7485
		$this->newline = true;
7486
	}
7487
 
7488
	/**
7489
	 * Returns the relative X value of current position.
7490
	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7491
	 * @return float
7492
	 * @public
7493
	 * @since 1.2
7494
	 * @see SetX(), GetY(), SetY()
7495
	 */
7496
	public function GetX() {
7497
		//Get x position
7498
		if ($this->rtl) {
7499
			return ($this->w - $this->x);
7500
		} else {
7501
			return $this->x;
7502
		}
7503
	}
7504
 
7505
	/**
7506
	 * Returns the absolute X value of current position.
7507
	 * @return float
7508
	 * @public
7509
	 * @since 1.2
7510
	 * @see SetX(), GetY(), SetY()
7511
	 */
7512
	public function GetAbsX() {
7513
		return $this->x;
7514
	}
7515
 
7516
	/**
7517
	 * Returns the ordinate of the current position.
7518
	 * @return float
7519
	 * @public
7520
	 * @since 1.0
7521
	 * @see SetY(), GetX(), SetX()
7522
	 */
7523
	public function GetY() {
7524
		return $this->y;
7525
	}
7526
 
7527
	/**
7528
	 * Defines the abscissa of the current position.
7529
	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7530
	 * @param float $x The value of the abscissa in user units.
7531
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7532
	 * @public
7533
	 * @since 1.2
7534
	 * @see GetX(), GetY(), SetY(), SetXY()
7535
	 */
7536
	public function setX($x, $rtloff=false) {
7537
		$x = floatval($x);
7538
		if (!$rtloff AND $this->rtl) {
7539
			if ($x >= 0) {
7540
				$this->x = $this->w - $x;
7541
			} else {
7542
				$this->x = abs($x);
7543
			}
7544
		} else {
7545
			if ($x >= 0) {
7546
				$this->x = $x;
7547
			} else {
7548
				$this->x = $this->w + $x;
7549
			}
7550
		}
7551
		if ($this->x < 0) {
7552
			$this->x = 0;
7553
		}
7554
		if ($this->x > $this->w) {
7555
			$this->x = $this->w;
7556
		}
7557
	}
7558
 
7559
	/**
7560
	 * Moves the current abscissa back to the left margin and sets the ordinate.
7561
	 * If the passed value is negative, it is relative to the bottom of the page.
7562
	 * @param float $y The value of the ordinate in user units.
7563
	 * @param bool $resetx if true (default) reset the X position.
7564
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7565
	 * @public
7566
	 * @since 1.0
7567
	 * @see GetX(), GetY(), SetY(), SetXY()
7568
	 */
7569
	public function setY($y, $resetx=true, $rtloff=false) {
7570
		$y = floatval($y);
7571
		if ($resetx) {
7572
			//reset x
7573
			if (!$rtloff AND $this->rtl) {
7574
				$this->x = $this->w - $this->rMargin;
7575
			} else {
7576
				$this->x = $this->lMargin;
7577
			}
7578
		}
7579
		if ($y >= 0) {
7580
			$this->y = $y;
7581
		} else {
7582
			$this->y = $this->h + $y;
7583
		}
7584
		if ($this->y < 0) {
7585
			$this->y = 0;
7586
		}
7587
		if ($this->y > $this->h) {
7588
			$this->y = $this->h;
7589
		}
7590
	}
7591
 
7592
	/**
7593
	 * Defines the abscissa and ordinate of the current position.
7594
	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7595
	 * @param float $x The value of the abscissa.
7596
	 * @param float $y The value of the ordinate.
7597
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7598
	 * @public
7599
	 * @since 1.2
7600
	 * @see SetX(), SetY()
7601
	 */
7602
	public function setXY($x, $y, $rtloff=false) {
7603
		$this->setY($y, false, $rtloff);
7604
		$this->setX($x, $rtloff);
7605
	}
7606
 
7607
	/**
7608
	 * Set the absolute X coordinate of the current pointer.
7609
	 * @param float $x The value of the abscissa in user units.
7610
	 * @public
7611
	 * @since 5.9.186 (2012-09-13)
7612
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7613
	 */
7614
	public function setAbsX($x) {
7615
		$this->x = floatval($x);
7616
	}
7617
 
7618
	/**
7619
	 * Set the absolute Y coordinate of the current pointer.
7620
	 * @param float $y (float) The value of the ordinate in user units.
7621
	 * @public
7622
	 * @since 5.9.186 (2012-09-13)
7623
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7624
	 */
7625
	public function setAbsY($y) {
7626
		$this->y = floatval($y);
7627
	}
7628
 
7629
	/**
7630
	 * Set the absolute X and Y coordinates of the current pointer.
7631
	 * @param float $x The value of the abscissa in user units.
7632
	 * @param float $y (float) The value of the ordinate in user units.
7633
	 * @public
7634
	 * @since 5.9.186 (2012-09-13)
7635
	 * @see setAbsX(), setAbsY(), SetAbsXY()
7636
	 */
7637
	public function setAbsXY($x, $y) {
7638
		$this->setAbsX($x);
7639
		$this->setAbsY($y);
7640
	}
7641
 
7642
	/**
7643
	 * Send the document to a given destination: string, local file or browser.
7644
	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7645
	 * The method first calls Close() if necessary to terminate the document.
7646
	 * @param string $name The name of the file when saved
7647
	 * @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>
7648
	 * @return string
7649
	 * @public
7650
	 * @since 1.0
7651
	 * @see Close()
7652
	 */
7653
	public function Output($name='doc.pdf', $dest='I') {
7654
		//Output PDF to some destination
7655
		//Finish document if necessary
7656
		if ($this->state < 3) {
7657
			$this->Close();
7658
		}
7659
		//Normalize parameters
7660
		if (is_bool($dest)) {
7661
			$dest = $dest ? 'D' : 'F';
7662
		}
7663
		$dest = strtoupper($dest);
7664
 
7665
		if ($this->sign) {
7666
			// *** apply digital signature to the document ***
7667
			// get the document content
7668
			$pdfdoc = $this->getBuffer();
7669
			// remove last newline
7670
			$pdfdoc = substr($pdfdoc, 0, -1);
7671
			// remove filler space
7672
			$byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7673
			// define the ByteRange
7674
			$byte_range = array();
7675
			$byte_range[0] = 0;
7676
			$byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7677
			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7678
			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7679
			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7680
			// replace the ByteRange
7681
			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7682
			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7683
			$pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7684
			// write the document to a temporary folder
7685
			$tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7686
			$f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7687
			if (!$f) {
7688
				$this->Error('Unable to create temporary file: '.$tempdoc);
7689
			}
7690
			$pdfdoc_length = strlen($pdfdoc);
7691
			fwrite($f, $pdfdoc, $pdfdoc_length);
7692
			fclose($f);
7693
			// get digital signature via openssl library
7694
			$tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7695
			if (empty($this->signature_data['extracerts'])) {
7696
				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7697
			} else {
7698
				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']);
7699
			}
7700
			// read signature
7701
			$signature = file_get_contents($tempsign);
7702
			// extract signature
7703
			$signature = substr($signature, $pdfdoc_length);
7704
			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7705
			$tmparr = explode("\n\n", $signature);
7706
			$signature = $tmparr[1];
7707
			// decode signature
7708
			$signature = base64_decode(trim($signature));
7709
			// add TSA timestamp to signature
7710
			$signature = $this->applyTSA($signature);
7711
			// convert signature to hex
7712
			$signature = current(unpack('H*', $signature));
7713
			$signature = str_pad($signature, $this->signature_max_length, '0');
7714
			// Add signature to the document
7715
			$this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7716
			$this->bufferlen = strlen($this->buffer);
7717
		}
7718
		switch($dest) {
7719
			case 'I': {
7720
				// Send PDF to the standard output
7721
				if (ob_get_contents()) {
7722
					$this->Error('Some data has already been output, can\'t send PDF file');
7723
				}
7724
				if (php_sapi_name() != 'cli') {
7725
					// send output to a browser
7726
					header('Content-Type: application/pdf');
7727
					if (headers_sent()) {
7728
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7729
					}
7730
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7731
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7732
					header('Pragma: public');
7733
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7734
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7735
					header('Content-Disposition: inline; filename="' . rawurlencode(basename($name)) . '"; ' .
7736
						'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7737
					TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7738
				} else {
7739
					echo $this->getBuffer();
7740
				}
7741
				break;
7742
			}
7743
			case 'D': {
7744
				// download PDF as file
7745
				if (ob_get_contents()) {
7746
					$this->Error('Some data has already been output, can\'t send PDF file');
7747
				}
7748
				header('Content-Description: File Transfer');
7749
				if (headers_sent()) {
7750
					$this->Error('Some data has already been output to browser, can\'t send PDF file');
7751
				}
7752
				header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7753
				//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7754
				header('Pragma: public');
7755
				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7756
				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7757
				// force download dialog
7758
				if (strpos(php_sapi_name(), 'cgi') === false) {
7759
					header('Content-Type: application/force-download');
7760
					header('Content-Type: application/octet-stream', false);
7761
					header('Content-Type: application/download', false);
7762
					header('Content-Type: application/pdf', false);
7763
				} else {
7764
					header('Content-Type: application/pdf');
7765
				}
7766
				// use the Content-Disposition header to supply a recommended filename
7767
				header('Content-Disposition: attachment; filename="' . rawurlencode(basename($name)) . '"; ' .
7768
					'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7769
				header('Content-Transfer-Encoding: binary');
7770
				TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7771
				break;
7772
			}
7773
			case 'F':
7774
			case 'FI':
7775
			case 'FD': {
7776
				// save PDF to a local file
7777
				$f = TCPDF_STATIC::fopenLocal($name, 'wb');
7778
				if (!$f) {
7779
					$this->Error('Unable to create output file: '.$name);
7780
				}
7781
				fwrite($f, $this->getBuffer(), $this->bufferlen);
7782
				fclose($f);
7783
				if ($dest == 'FI') {
7784
					// send headers to browser
7785
					header('Content-Type: application/pdf');
7786
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7787
					//header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7788
					header('Pragma: public');
7789
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7790
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7791
					header('Content-Disposition: inline; filename="'.basename($name).'"');
7792
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7793
				} elseif ($dest == 'FD') {
7794
					// send headers to browser
7795
					if (ob_get_contents()) {
7796
						$this->Error('Some data has already been output, can\'t send PDF file');
7797
					}
7798
					header('Content-Description: File Transfer');
7799
					if (headers_sent()) {
7800
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
7801
					}
7802
					header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7803
					header('Pragma: public');
7804
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7805
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7806
					// force download dialog
7807
					if (strpos(php_sapi_name(), 'cgi') === false) {
7808
						header('Content-Type: application/force-download');
7809
						header('Content-Type: application/octet-stream', false);
7810
						header('Content-Type: application/download', false);
7811
						header('Content-Type: application/pdf', false);
7812
					} else {
7813
						header('Content-Type: application/pdf');
7814
					}
7815
					// use the Content-Disposition header to supply a recommended filename
7816
					header('Content-Disposition: attachment; filename="'.basename($name).'"');
7817
					header('Content-Transfer-Encoding: binary');
7818
					TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7819
				}
7820
				break;
7821
			}
7822
			case 'E': {
7823
				// return PDF as base64 mime multi-part email attachment (RFC 2045)
7824
				$retval = 'Content-Type: application/pdf;'."\r\n";
7825
				$retval .= ' name="'.$name.'"'."\r\n";
7826
				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7827
				$retval .= 'Content-Disposition: attachment;'."\r\n";
7828
				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
7829
				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7830
				return $retval;
7831
			}
7832
			case 'S': {
7833
				// returns PDF as a string
7834
				return $this->getBuffer();
7835
			}
7836
			default: {
7837
				$this->Error('Incorrect output destination: '.$dest);
7838
			}
7839
		}
7840
		return '';
7841
	}
7842
 
7843
	protected static $cleaned_ids = array();
7844
	/**
7845
	 * Unset all class variables except the following critical variables.
7846
	 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7847
	 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7848
	 * @public
7849
	 * @since 4.5.016 (2009-02-24)
7850
	 */
7851
	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7852
		if (isset(self::$cleaned_ids[$this->file_id])) {
7853
			$destroyall = false;
7854
		}
7855
		if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7856
			self::$cleaned_ids[$this->file_id] = true;
7857
			// remove all temporary files
7858
			if ($handle = @opendir(K_PATH_CACHE)) {
7859
				while ( false !== ( $file_name = readdir( $handle ) ) ) {
7860
					if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7861
						unlink(K_PATH_CACHE.$file_name);
7862
					}
7863
				}
7864
				closedir($handle);
7865
			}
7866
			if (isset($this->imagekeys)) {
7867
				foreach($this->imagekeys as $file) {
7868
					if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
7869
						@unlink($file);
7870
					}
7871
				}
7872
			}
7873
		}
7874
		$preserve = array(
7875
			'file_id',
7876
			'state',
7877
			'bufferlen',
7878
			'buffer',
7879
			'cached_files',
7880
			'imagekeys',
7881
			'sign',
7882
			'signature_data',
7883
			'signature_max_length',
7884
			'byterange_string',
7885
			'tsa_timestamp',
7886
			'tsa_data'
7887
		);
7888
		foreach (array_keys(get_object_vars($this)) as $val) {
7889
			if ($destroyall OR !in_array($val, $preserve)) {
7890
				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7891
					unset($this->$val);
7892
				}
7893
			}
7894
		}
7895
	}
7896
 
7897
	/**
7898
	 * Check for locale-related bug
7899
	 * @protected
7900
	 */
7901
	protected function _dochecks() {
7902
		//Check for locale-related bug
7903
		if (1.1 == 1) {
7904
			$this->Error('Don\'t alter the locale before including class file');
7905
		}
7906
		//Check for decimal separator
7907
		if (sprintf('%.1F', 1.0) != '1.0') {
7908
			setlocale(LC_NUMERIC, 'C');
7909
		}
7910
	}
7911
 
7912
	/**
7913
	 * Return an array containing variations for the basic page number alias.
7914
	 * @param string $a Base alias.
7915
	 * @return array of page number aliases
7916
	 * @protected
7917
	 */
7918
	protected function getInternalPageNumberAliases($a= '') {
7919
		$alias = array();
7920
		// build array of Unicode + ASCII variants (the order is important)
7921
		$alias = array('u' => array(), 'a' => array());
7922
		$u = '{'.$a.'}';
7923
		$alias['u'][] = TCPDF_STATIC::_escape($u);
7924
		if ($this->isunicode) {
7925
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7926
			$alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7927
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7928
			$alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7929
		}
7930
		$alias['a'][] = TCPDF_STATIC::_escape($a);
7931
		return $alias;
7932
	}
7933
 
7934
	/**
7935
	 * Return an array containing all internal page aliases.
7936
	 * @return array of page number aliases
7937
	 * @protected
7938
	 */
7939
	protected function getAllInternalPageNumberAliases() {
7940
		$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);
7941
		$pnalias = array();
7942
		foreach($basic_alias as $k => $a) {
7943
			$pnalias[$k] = $this->getInternalPageNumberAliases($a);
7944
		}
7945
		return $pnalias;
7946
	}
7947
 
7948
	/**
7949
	 * Replace right shift page number aliases with spaces to correct right alignment.
7950
	 * This works perfectly only when using monospaced fonts.
7951
	 * @param string $page Page content.
7952
	 * @param array $aliases Array of page aliases.
7953
	 * @param int $diff initial difference to add.
7954
	 * @return string replaced page content.
7955
	 * @protected
7956
	 */
7957
	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7958
		foreach ($aliases as $type => $alias) {
7959
			foreach ($alias as $a) {
7960
				// find position of compensation factor
7961
				$startnum = (strpos($a, ':') + 1);
7962
				$a = substr($a, 0, $startnum);
7963
				if (($pos = strpos($page, $a)) !== false) {
7964
					// end of alias
7965
					$endnum = strpos($page, '}', $pos);
7966
					// string to be replaced
7967
					$aa = substr($page, $pos, ($endnum - $pos + 1));
7968
					// get compensation factor
7969
					$ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7970
					$ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7971
					$ratio = floatval($ratio);
7972
					if ($type == 'u') {
7973
						$chrdiff = floor(($diff + 12) * $ratio);
7974
						$shift = str_repeat(' ', $chrdiff);
7975
						$shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7976
					} else {
7977
						$chrdiff = floor(($diff + 11) * $ratio);
7978
						$shift = str_repeat(' ', $chrdiff);
7979
					}
7980
					$page = str_replace($aa, $shift, $page);
7981
				}
7982
			}
7983
		}
7984
		return $page;
7985
	}
7986
 
7987
	/**
7988
	 * Set page boxes to be included on page descriptions.
7989
	 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7990
	 * @protected
7991
	 */
7992
	protected function setPageBoxTypes($boxes) {
7993
		$this->page_boxes = array();
7994
		foreach ($boxes as $box) {
7995
			if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7996
				$this->page_boxes[] = $box;
7997
			}
7998
		}
7999
	}
8000
 
8001
	/**
8002
	 * Output pages (and replace page number aliases).
8003
	 * @protected
8004
	 */
8005
	protected function _putpages() {
8006
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8007
		// get internal aliases for page numbers
8008
		$pnalias = $this->getAllInternalPageNumberAliases();
8009
		$num_pages = $this->numpages;
8010
		$ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
8011
		$ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
8012
		$ptp_num_chars = $this->GetNumChars($ptpa);
8013
		$pagegroupnum = 0;
8014
		$groupnum = 0;
8015
		$ptgu = 1;
8016
		$ptga = 1;
8017
		$ptg_num_chars = 1;
8018
		for ($n = 1; $n <= $num_pages; ++$n) {
8019
			// get current page
8020
			$temppage = $this->getPageBuffer($n);
8021
			$pagelen = strlen($temppage);
8022
			// set replacements for total pages number
8023
			$pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
8024
			$pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
8025
			$pnp_num_chars = $this->GetNumChars($pnpa);
8026
			$pdiff = 0; // difference used for right shift alignment of page numbers
8027
			$gdiff = 0; // difference used for right shift alignment of page group numbers
8028
			if (!empty($this->pagegroups)) {
8029
				if (isset($this->newpagegroup[$n])) {
8030
					$pagegroupnum = 0;
8031
					++$groupnum;
8032
					$ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
8033
					$ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
8034
					$ptg_num_chars = $this->GetNumChars($ptga);
8035
				}
8036
				++$pagegroupnum;
8037
				$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
8038
				$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
8039
				$png_num_chars = $this->GetNumChars($pnga);
8040
				// replace page numbers
8041
				$replace = array();
8042
				$replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8043
				$replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8044
				$replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8045
				$replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8046
				list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8047
			}
8048
			// replace page numbers
8049
			$replace = array();
8050
			$replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8051
			$replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8052
			$replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8053
			$replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8054
			list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8055
			// replace right shift alias
8056
			$temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8057
			// replace EPS marker
8058
			$temppage = str_replace($this->epsmarker, '', $temppage);
8059
			//Page
8060
			$this->page_obj_id[$n] = $this->_newobj();
8061
			$out = '<<';
8062
			$out .= ' /Type /Page';
8063
			$out .= ' /Parent 1 0 R';
8064
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8065
				$out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8066
			}
8067
			$out .= ' /Resources 2 0 R';
8068
			foreach ($this->page_boxes as $box) {
8069
				$out .= ' /'.$box;
8070
				$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']);
8071
			}
8072
			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8073
				$out .= ' /BoxColorInfo <<';
8074
				foreach ($this->page_boxes as $box) {
8075
					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8076
						$out .= ' /'.$box.' <<';
8077
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8078
							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8079
							$out .= ' /C [';
8080
							$out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8081
							$out .= ' ]';
8082
						}
8083
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8084
							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8085
						}
8086
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8087
							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8088
						}
8089
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8090
							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8091
							$out .= ' /D [';
8092
							foreach ($dashes as $dash) {
8093
								$out .= sprintf(' %F', ($dash * $this->k));
8094
							}
8095
							$out .= ' ]';
8096
						}
8097
						$out .= ' >>';
8098
					}
8099
				}
8100
				$out .= ' >>';
8101
			}
8102
			$out .= ' /Contents '.($this->n + 1).' 0 R';
8103
			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8104
			if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
8105
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8106
			}
8107
			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8108
				// page transitions
8109
				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8110
					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8111
				}
8112
				$out .= ' /Trans <<';
8113
				$out .= ' /Type /Trans';
8114
				if (isset($this->pagedim[$n]['trans']['S'])) {
8115
					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8116
				}
8117
				if (isset($this->pagedim[$n]['trans']['D'])) {
8118
					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8119
				}
8120
				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8121
					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8122
				}
8123
				if (isset($this->pagedim[$n]['trans']['M'])) {
8124
					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8125
				}
8126
				if (isset($this->pagedim[$n]['trans']['Di'])) {
8127
					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8128
				}
8129
				if (isset($this->pagedim[$n]['trans']['SS'])) {
8130
					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8131
				}
8132
				if (isset($this->pagedim[$n]['trans']['B'])) {
8133
					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8134
				}
8135
				$out .= ' >>';
8136
			}
8137
			$out .= $this->_getannotsrefs($n);
8138
			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8139
			$out .= ' >>';
8140
			$out .= "\n".'endobj';
8141
			$this->_out($out);
8142
			//Page content
8143
			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8144
			$this->_newobj();
8145
			$p = $this->_getrawstream($p);
8146
			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8147
		}
8148
		//Pages root
8149
		$out = $this->_getobj(1)."\n";
8150
		$out .= '<< /Type /Pages /Kids [';
8151
		foreach($this->page_obj_id as $page_obj) {
8152
			$out .= ' '.$page_obj.' 0 R';
8153
		}
8154
		$out .= ' ] /Count '.$num_pages.' >>';
8155
		$out .= "\n".'endobj';
8156
		$this->_out($out);
8157
	}
8158
 
8159
	/**
8160
	 * Get references to page annotations.
8161
	 * @param int $n page number
8162
	 * @return string
8163
	 * @protected
8164
	 * @author Nicola Asuni
8165
	 * @since 5.0.010 (2010-05-17)
8166
	 */
8167
	protected function _getannotsrefs($n) {
1441 ariadna 8168
		if (!(isset($this->PageAnnots[$n]) OR count($this->empty_signature_appearance)>0 OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
1 efrain 8169
			return '';
8170
		}
8171
		$out = ' /Annots [';
8172
		if (isset($this->PageAnnots[$n])) {
8173
			foreach ($this->PageAnnots[$n] as $key => $val) {
8174
				if (!in_array($val['n'], $this->radio_groups)) {
8175
					$out .= ' '.$val['n'].' 0 R';
8176
				}
8177
			}
8178
			// add radiobutton groups
8179
			if (isset($this->radiobutton_groups[$n])) {
8180
				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8181
					if (isset($data['n'])) {
8182
						$out .= ' '.$data['n'].' 0 R';
8183
					}
8184
				}
8185
			}
8186
		}
8187
		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8188
			// set reference for signature object
8189
			$out .= ' '.$this->sig_obj_id.' 0 R';
8190
		}
8191
		if (!empty($this->empty_signature_appearance)) {
8192
			foreach ($this->empty_signature_appearance as $esa) {
8193
				if ($esa['page'] == $n) {
8194
					// set reference for empty signature objects
8195
					$out .= ' '.$esa['objid'].' 0 R';
8196
				}
8197
			}
8198
		}
8199
		$out .= ' ]';
8200
		return $out;
8201
	}
8202
 
8203
	/**
8204
	 * Output annotations objects for all pages.
8205
	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8206
	 * See section 12.5 of PDF 32000_2008 reference.
8207
	 * @protected
8208
	 * @author Nicola Asuni
8209
	 * @since 4.0.018 (2008-08-06)
8210
	 */
8211
	protected function _putannotsobjs() {
8212
		// reset object counter
8213
		for ($n=1; $n <= $this->numpages; ++$n) {
8214
			if (isset($this->PageAnnots[$n])) {
8215
				// set page annotations
8216
				foreach ($this->PageAnnots[$n] as $key => $pl) {
8217
					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8218
					// create annotation object for grouping radiobuttons
8219
					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8220
						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8221
						$annots = '<<';
8222
						$annots .= ' /Type /Annot';
8223
						$annots .= ' /Subtype /Widget';
8224
						$annots .= ' /Rect [0 0 0 0]';
8225
						if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8226
							// read only
8227
							$annots .= ' /F 68';
8228
							$annots .= ' /Ff 49153';
8229
						} else {
8230
							$annots .= ' /F 4'; // default print for PDF/A
8231
							$annots .= ' /Ff 49152';
8232
						}
8233
						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8234
						if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8235
							$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8236
						}
8237
						$annots .= ' /FT /Btn';
8238
						$annots .= ' /Kids [';
8239
						$defval = '';
8240
						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8241
							if (isset($data['kid'])) {
8242
								$annots .= ' '.$data['kid'].' 0 R';
8243
								if ($data['def'] !== 'Off') {
8244
									$defval = $data['def'];
8245
								}
8246
							}
8247
						}
8248
						$annots .= ' ]';
8249
						if (!empty($defval)) {
8250
							$annots .= ' /V /'.$defval;
8251
						}
8252
						$annots .= ' >>';
8253
						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8254
						$this->form_obj_id[] = $radio_button_obj_id;
8255
						// store object id to be used on Parent entry of Kids
8256
						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8257
					}
8258
					$formfield = false;
8259
					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8260
					$a = $pl['x'] * $this->k;
8261
					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8262
					$c = $pl['w'] * $this->k;
8263
					$d = $pl['h'] * $this->k;
8264
					$rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8265
					// create new annotation object
8266
					$annots = '<</Type /Annot';
8267
					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8268
					$annots .= ' /Rect ['.$rect.']';
8269
					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8270
					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8271
						$annots .= ' /FT /'.$pl['opt']['ft'];
8272
						$formfield = true;
8273
					}
8274
					if ($pl['opt']['subtype'] !== 'Link') {
8275
						$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8276
					}
8277
					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8278
					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8279
					$annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8280
					if (isset($pl['opt']['f'])) {
8281
						$fval = 0;
8282
						if (is_array($pl['opt']['f'])) {
8283
							foreach ($pl['opt']['f'] as $f) {
8284
								switch (strtolower($f)) {
8285
									case 'invisible': {
8286
										$fval += 1 << 0;
8287
										break;
8288
									}
8289
									case 'hidden': {
8290
										$fval += 1 << 1;
8291
										break;
8292
									}
8293
									case 'print': {
8294
										$fval += 1 << 2;
8295
										break;
8296
									}
8297
									case 'nozoom': {
8298
										$fval += 1 << 3;
8299
										break;
8300
									}
8301
									case 'norotate': {
8302
										$fval += 1 << 4;
8303
										break;
8304
									}
8305
									case 'noview': {
8306
										$fval += 1 << 5;
8307
										break;
8308
									}
8309
									case 'readonly': {
8310
										$fval += 1 << 6;
8311
										break;
8312
									}
8313
									case 'locked': {
1441 ariadna 8314
										$fval += 1 << 7;
1 efrain 8315
										break;
8316
									}
8317
									case 'togglenoview': {
1441 ariadna 8318
										$fval += 1 << 8;
1 efrain 8319
										break;
8320
									}
8321
									case 'lockedcontents': {
1441 ariadna 8322
										$fval += 1 << 9;
1 efrain 8323
										break;
8324
									}
8325
									default: {
8326
										break;
8327
									}
8328
								}
8329
							}
8330
						} else {
8331
							$fval = intval($pl['opt']['f']);
8332
						}
8333
					} else {
8334
						$fval = 4;
8335
					}
8336
					if ($this->pdfa_mode) {
8337
						// force print flag for PDF/A mode
8338
						$fval |= 4;
8339
					}
8340
					$annots .= ' /F '.intval($fval);
8341
					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8342
						$annots .= ' /AS /'.$pl['opt']['as'];
8343
					}
8344
					if (isset($pl['opt']['ap'])) {
8345
						// appearance stream
8346
						$annots .= ' /AP <<';
8347
						if (is_array($pl['opt']['ap'])) {
8348
							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8349
								// $apmode can be: n = normal; r = rollover; d = down;
8350
								$annots .= ' /'.strtoupper($apmode);
8351
								if (is_array($apdef)) {
8352
									$annots .= ' <<';
8353
									foreach ($apdef as $apstate => $stream) {
8354
										// reference to XObject that define the appearance for this mode-state
8355
										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8356
										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8357
									}
8358
									$annots .= ' >>';
8359
								} else {
8360
									// reference to XObject that define the appearance for this mode
8361
									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8362
									$annots .= ' '.$apsobjid.' 0 R';
8363
								}
8364
							}
8365
						} else {
8366
							$annots .= $pl['opt']['ap'];
8367
						}
8368
						$annots .= ' >>';
8369
					}
8370
					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8371
						$annots .= ' /BS <<';
8372
						$annots .= ' /Type /Border';
8373
						if (isset($pl['opt']['bs']['w'])) {
8374
							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8375
						}
8376
						$bstyles = array('S', 'D', 'B', 'I', 'U');
8377
						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8378
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8379
						}
8380
						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8381
							$annots .= ' /D [';
8382
							foreach ($pl['opt']['bs']['d'] as $cord) {
8383
								$annots .= ' '.intval($cord);
8384
							}
8385
							$annots .= ']';
8386
						}
8387
						$annots .= ' >>';
8388
					} else {
8389
						$annots .= ' /Border [';
8390
						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8391
							$annots .= intval($pl['opt']['border'][0]).' ';
8392
							$annots .= intval($pl['opt']['border'][1]).' ';
8393
							$annots .= intval($pl['opt']['border'][2]);
8394
							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8395
								$annots .= ' [';
8396
								foreach ($pl['opt']['border'][3] as $dash) {
8397
									$annots .= intval($dash).' ';
8398
								}
8399
								$annots .= ']';
8400
							}
8401
						} else {
8402
							$annots .= '0 0 0';
8403
						}
8404
						$annots .= ']';
8405
					}
8406
					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8407
						$annots .= ' /BE <<';
8408
						$bstyles = array('S', 'C');
8409
						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8410
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8411
						} else {
8412
							$annots .= ' /S /S';
8413
						}
8414
						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8415
							$annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8416
						}
8417
						$annots .= '>>';
8418
					}
8419
					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8420
						$annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8421
					}
8422
					//$annots .= ' /StructParent ';
8423
					//$annots .= ' /OC ';
8424
					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8425
					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8426
						// this is a markup type
8427
						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8428
							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8429
						}
8430
						//$annots .= ' /Popup ';
8431
						if (isset($pl['opt']['ca'])) {
8432
							$annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8433
						}
8434
						if (isset($pl['opt']['rc'])) {
8435
							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8436
						}
8437
						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8438
						//$annots .= ' /IRT ';
8439
						if (isset($pl['opt']['subj'])) {
8440
							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8441
						}
8442
						//$annots .= ' /RT ';
8443
						//$annots .= ' /IT ';
8444
						//$annots .= ' /ExData ';
8445
					}
8446
					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8447
					// Annotation types
8448
					switch (strtolower($pl['opt']['subtype'])) {
8449
						case 'text': {
8450
							if (isset($pl['opt']['open'])) {
8451
								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8452
							}
8453
							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8454
							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8455
								$annots .= ' /Name /'.$pl['opt']['name'];
8456
							} else {
8457
								$annots .= ' /Name /Note';
8458
							}
8459
							$hasStateModel = isset($pl['opt']['statemodel']);
8460
							$hasState = isset($pl['opt']['state']);
8461
							$statemodels = array('Marked', 'Review');
8462
							if (!$hasStateModel && !$hasState) {
8463
								break;
8464
							}
8465
							if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8466
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8467
							} else {
8468
								$pl['opt']['statemodel'] = 'Marked';
8469
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8470
							}
8471
							if ($pl['opt']['statemodel'] == 'Marked') {
8472
								$states = array('Accepted', 'Unmarked');
8473
							} else {
8474
								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8475
							}
8476
							if ($hasState AND in_array($pl['opt']['state'], $states)) {
8477
								$annots .= ' /State /'.$pl['opt']['state'];
8478
							} else {
8479
								if ($pl['opt']['statemodel'] == 'Marked') {
8480
									$annots .= ' /State /Unmarked';
8481
								} else {
8482
									$annots .= ' /State /None';
8483
								}
8484
							}
8485
							break;
8486
						}
8487
						case 'link': {
8488
							if (is_string($pl['txt']) && !empty($pl['txt'])) {
8489
								if ($pl['txt'][0] == '#') {
8490
									// internal destination
8491
									$annots .= ' /A <</S /GoTo /D /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8492
								} elseif ($pl['txt'][0] == '%') {
8493
									// embedded PDF file
8494
									$filename = basename(substr($pl['txt'], 1));
8495
									$annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8496
								} elseif ($pl['txt'][0] == '*') {
8497
									// embedded generic file
8498
									$filename = basename(substr($pl['txt'], 1));
8499
									$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});';
8500
									$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8501
								} else {
8502
									$parsedUrl = parse_url($pl['txt']);
8503
									if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8504
										// relative link to a PDF file
8505
										$dest = '[0 /Fit]'; // default page 0
8506
										if (!empty($parsedUrl['fragment'])) {
8507
											// check for named destination
8508
											$tmp = explode('=', $parsedUrl['fragment']);
8509
											$dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8510
										}
8511
										$annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8512
									} else {
8513
										// external URI link
8514
										$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8515
									}
8516
								}
8517
							} elseif (isset($this->links[$pl['txt']])) {
8518
								// internal link ID
8519
								$l = $this->links[$pl['txt']];
8520
								if (isset($this->page_obj_id[($l['p'])])) {
8521
									$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)));
8522
								}
8523
							}
8524
							$hmodes = array('N', 'I', 'O', 'P');
8525
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8526
								$annots .= ' /H /'.$pl['opt']['h'];
8527
							} else {
8528
								$annots .= ' /H /I';
8529
							}
8530
							//$annots .= ' /PA ';
8531
							//$annots .= ' /Quadpoints ';
8532
							break;
8533
						}
8534
						case 'freetext': {
8535
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
1441 ariadna 8536
								$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
1 efrain 8537
							}
8538
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8539
								$annots .= ' /Q '.intval($pl['opt']['q']);
8540
							}
8541
							if (isset($pl['opt']['rc'])) {
8542
								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8543
							}
8544
							if (isset($pl['opt']['ds'])) {
8545
								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8546
							}
8547
							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8548
								$annots .= ' /CL [';
8549
								foreach ($pl['opt']['cl'] as $cl) {
8550
									$annots .= sprintf('%F ', $cl * $this->k);
8551
								}
8552
								$annots .= ']';
8553
							}
8554
							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8555
							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8556
								$annots .= ' /IT /'.$pl['opt']['it'];
8557
							}
8558
							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8559
								$l = $pl['opt']['rd'][0] * $this->k;
8560
								$r = $pl['opt']['rd'][1] * $this->k;
8561
								$t = $pl['opt']['rd'][2] * $this->k;
8562
								$b = $pl['opt']['rd'][3] * $this->k;
8563
								$annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8564
							}
8565
							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8566
								$annots .= ' /LE /'.$pl['opt']['le'];
8567
							}
8568
							break;
8569
						}
8570
						case 'line': {
8571
							break;
8572
						}
8573
						case 'square': {
8574
							break;
8575
						}
8576
						case 'circle': {
8577
							break;
8578
						}
8579
						case 'polygon': {
8580
							break;
8581
						}
8582
						case 'polyline': {
8583
							break;
8584
						}
8585
						case 'highlight': {
8586
							break;
8587
						}
8588
						case 'underline': {
8589
							break;
8590
						}
8591
						case 'squiggly': {
8592
							break;
8593
						}
8594
						case 'strikeout': {
8595
							break;
8596
						}
8597
						case 'stamp': {
8598
							break;
8599
						}
8600
						case 'caret': {
8601
							break;
8602
						}
8603
						case 'ink': {
8604
							break;
8605
						}
8606
						case 'popup': {
8607
							break;
8608
						}
8609
						case 'fileattachment': {
8610
							if ($this->pdfa_mode && $this->pdfa_version != 3) {
8611
								// embedded files are not allowed in PDF/A mode version 1 and 2
8612
								break;
8613
							}
8614
							if (!isset($pl['opt']['fs'])) {
8615
								break;
8616
							}
8617
							$filename = basename($pl['opt']['fs']);
8618
							if (isset($this->embeddedfiles[$filename]['f'])) {
8619
								$annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8620
								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8621
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8622
									$annots .= ' /Name /'.$pl['opt']['name'];
8623
								} else {
8624
									$annots .= ' /Name /PushPin';
8625
								}
8626
								// index (zero-based) of the annotation in the Annots array of this page
8627
								$this->embeddedfiles[$filename]['a'] = $key;
8628
							}
8629
							break;
8630
						}
8631
						case 'sound': {
8632
							if (!isset($pl['opt']['fs'])) {
8633
								break;
8634
							}
8635
							$filename = basename($pl['opt']['fs']);
8636
							if (isset($this->embeddedfiles[$filename]['f'])) {
8637
								// ... TO BE COMPLETED ...
8638
								// /R /C /B /E /CO /CP
8639
								$annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8640
								$iconsapp = array('Speaker', 'Mic');
8641
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8642
									$annots .= ' /Name /'.$pl['opt']['name'];
8643
								} else {
8644
									$annots .= ' /Name /Speaker';
8645
								}
8646
							}
8647
							break;
8648
						}
8649
						case 'movie': {
8650
							break;
8651
						}
8652
						case 'widget': {
8653
							$hmode = array('N', 'I', 'O', 'P', 'T');
8654
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8655
								$annots .= ' /H /'.$pl['opt']['h'];
8656
							}
8657
							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8658
								$annots .= ' /MK <<';
8659
								if (isset($pl['opt']['mk']['r'])) {
8660
									$annots .= ' /R '.$pl['opt']['mk']['r'];
8661
								}
8662
								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8663
									$annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8664
								}
8665
								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8666
									$annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8667
								}
8668
								if (isset($pl['opt']['mk']['ca'])) {
8669
									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8670
								}
8671
								if (isset($pl['opt']['mk']['rc'])) {
8672
									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8673
								}
8674
								if (isset($pl['opt']['mk']['ac'])) {
8675
									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8676
								}
8677
								if (isset($pl['opt']['mk']['i'])) {
8678
									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8679
									if ($info !== false) {
8680
										$annots .= ' /I '.$info['n'].' 0 R';
8681
									}
8682
								}
8683
								if (isset($pl['opt']['mk']['ri'])) {
8684
									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8685
									if ($info !== false) {
8686
										$annots .= ' /RI '.$info['n'].' 0 R';
8687
									}
8688
								}
8689
								if (isset($pl['opt']['mk']['ix'])) {
8690
									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8691
									if ($info !== false) {
8692
										$annots .= ' /IX '.$info['n'].' 0 R';
8693
									}
8694
								}
8695
								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8696
									$annots .= ' /IF <<';
8697
									$if_sw = array('A', 'B', 'S', 'N');
8698
									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8699
										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8700
									}
8701
									$if_s = array('A', 'P');
8702
									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8703
										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8704
									}
8705
									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8706
										$annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8707
									}
8708
									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8709
										$annots .= ' /FB true';
8710
									}
8711
									$annots .= '>>';
8712
								}
8713
								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8714
									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8715
								}
8716
								$annots .= '>>';
8717
							} // end MK
8718
							// --- Entries for field dictionaries ---
8719
							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8720
								// set parent
8721
								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8722
							}
8723
							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8724
								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8725
							}
8726
							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8727
								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8728
							}
8729
							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8730
								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8731
							}
8732
							if (isset($pl['opt']['ff'])) {
8733
								if (is_array($pl['opt']['ff'])) {
8734
									// array of bit settings
8735
									$flag = 0;
8736
									foreach($pl['opt']['ff'] as $val) {
8737
										$flag += 1 << ($val - 1);
8738
									}
8739
								} else {
8740
									$flag = intval($pl['opt']['ff']);
8741
								}
8742
								$annots .= ' /Ff '.$flag;
8743
							}
8744
							if (isset($pl['opt']['maxlen'])) {
8745
								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8746
							}
8747
							if (isset($pl['opt']['v'])) {
8748
								$annots .= ' /V';
8749
								if (is_array($pl['opt']['v'])) {
8750
									foreach ($pl['opt']['v'] AS $optval) {
8751
										if (is_float($optval)) {
8752
											$optval = sprintf('%F', $optval);
8753
										}
8754
										$annots .= ' '.$optval;
8755
									}
8756
								} else {
8757
									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8758
								}
8759
							}
8760
							if (isset($pl['opt']['dv'])) {
8761
								$annots .= ' /DV';
8762
								if (is_array($pl['opt']['dv'])) {
8763
									foreach ($pl['opt']['dv'] AS $optval) {
8764
										if (is_float($optval)) {
8765
											$optval = sprintf('%F', $optval);
8766
										}
8767
										$annots .= ' '.$optval;
8768
									}
8769
								} else {
8770
									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8771
								}
8772
							}
8773
							if (isset($pl['opt']['rv'])) {
8774
								$annots .= ' /RV';
8775
								if (is_array($pl['opt']['rv'])) {
8776
									foreach ($pl['opt']['rv'] AS $optval) {
8777
										if (is_float($optval)) {
8778
											$optval = sprintf('%F', $optval);
8779
										}
8780
										$annots .= ' '.$optval;
8781
									}
8782
								} else {
8783
									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8784
								}
8785
							}
8786
							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8787
								$annots .= ' /A << '.$pl['opt']['a'].' >>';
8788
							}
8789
							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8790
								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8791
							}
8792
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
1441 ariadna 8793
								$annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
1 efrain 8794
							}
8795
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8796
								$annots .= ' /Q '.intval($pl['opt']['q']);
8797
							}
8798
							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8799
								$annots .= ' /Opt [';
8800
								foreach($pl['opt']['opt'] AS $copt) {
8801
									if (is_array($copt)) {
8802
										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8803
									} else {
8804
										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8805
									}
8806
								}
8807
								$annots .= ']';
8808
							}
8809
							if (isset($pl['opt']['ti'])) {
8810
								$annots .= ' /TI '.intval($pl['opt']['ti']);
8811
							}
8812
							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8813
								$annots .= ' /I [';
8814
								foreach($pl['opt']['i'] AS $copt) {
8815
									$annots .= intval($copt).' ';
8816
								}
8817
								$annots .= ']';
8818
							}
8819
							break;
8820
						}
8821
						case 'screen': {
8822
							break;
8823
						}
8824
						case 'printermark': {
8825
							break;
8826
						}
8827
						case 'trapnet': {
8828
							break;
8829
						}
8830
						case 'watermark': {
8831
							break;
8832
						}
8833
						case '3d': {
8834
							break;
8835
						}
8836
						default: {
8837
							break;
8838
						}
8839
					}
8840
					$annots .= '>>';
8841
					// create new annotation object
8842
					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8843
					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8844
						// store reference of form object
8845
						$this->form_obj_id[] = $annot_obj_id;
8846
					}
8847
				}
8848
			}
8849
		} // end for each page
8850
	}
8851
 
8852
	/**
8853
	 * Put appearance streams XObject used to define annotation's appearance states.
8854
	 * @param int $w annotation width
8855
	 * @param int $h annotation height
8856
	 * @param string $stream appearance stream
8857
	 * @return int object ID
8858
	 * @protected
8859
	 * @since 4.8.001 (2009-09-09)
8860
	 */
8861
	protected function _putAPXObject($w=0, $h=0, $stream='') {
8862
		$stream = trim($stream);
8863
		$out = $this->_getobj()."\n";
8864
		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
8865
		$out .= '<<';
8866
		$out .= ' /Type /XObject';
8867
		$out .= ' /Subtype /Form';
8868
		$out .= ' /FormType 1';
8869
		if ($this->compress) {
8870
			$stream = gzcompress($stream);
8871
			$out .= ' /Filter /FlateDecode';
8872
		}
8873
		$rect = sprintf('%F %F', $w, $h);
8874
		$out .= ' /BBox [0 0 '.$rect.']';
8875
		$out .= ' /Matrix [1 0 0 1 0 0]';
8876
		$out .= ' /Resources 2 0 R';
8877
		$stream = $this->_getrawstream($stream);
8878
		$out .= ' /Length '.strlen($stream);
8879
		$out .= ' >>';
8880
		$out .= ' stream'."\n".$stream."\n".'endstream';
8881
		$out .= "\n".'endobj';
8882
		$this->_out($out);
8883
		return $this->n;
8884
	}
8885
 
8886
	/**
8887
	 * Output fonts.
8888
	 * @author Nicola Asuni
8889
	 * @protected
8890
	 */
8891
	protected function _putfonts() {
8892
		$nf = $this->n;
8893
		foreach ($this->diffs as $diff) {
8894
			//Encodings
8895
			$this->_newobj();
8896
			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8897
		}
8898
		foreach ($this->FontFiles as $file => $info) {
8899
			// search and get font file to embedd
8900
			$fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8901
			if (!TCPDF_STATIC::empty_string($fontfile)) {
8902
				$font = file_get_contents($fontfile);
8903
				$compressed = (substr($file, -2) == '.z');
8904
				if ((!$compressed) AND (isset($info['length2']))) {
8905
					$header = (ord($font[0]) == 128);
8906
					if ($header) {
8907
						// strip first binary header
8908
						$font = substr($font, 6);
8909
					}
8910
					if ($header AND (ord($font[$info['length1']]) == 128)) {
8911
						// strip second binary header
8912
						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8913
					}
8914
				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8915
					if ($compressed) {
8916
						// uncompress font
8917
						$font = gzuncompress($font);
8918
					}
8919
					// merge subset characters
8920
					$subsetchars = array(); // used chars
8921
					foreach ($info['fontkeys'] as $fontkey) {
8922
						$fontinfo = $this->getFontBuffer($fontkey);
8923
						$subsetchars += $fontinfo['subsetchars'];
8924
					}
8925
					// rebuild a font subset
8926
					$font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8927
					// calculate new font length
8928
					$info['length1'] = strlen($font);
8929
					if ($compressed) {
8930
						// recompress font
8931
						$font = gzcompress($font);
8932
					}
8933
				}
8934
				$this->_newobj();
8935
				$this->FontFiles[$file]['n'] = $this->n;
8936
				$stream = $this->_getrawstream($font);
8937
				$out = '<< /Length '.strlen($stream);
8938
				if ($compressed) {
8939
					$out .= ' /Filter /FlateDecode';
8940
				}
8941
				$out .= ' /Length1 '.$info['length1'];
8942
				if (isset($info['length2'])) {
8943
					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
8944
				}
8945
				$out .= ' >>';
8946
				$out .= ' stream'."\n".$stream."\n".'endstream';
8947
				$out .= "\n".'endobj';
8948
				$this->_out($out);
8949
			}
8950
		}
8951
		foreach ($this->fontkeys as $k) {
8952
			//Font objects
8953
			$font = $this->getFontBuffer($k);
8954
			$type = $font['type'];
8955
			$name = $font['name'];
8956
			if ($type == 'core') {
8957
				// standard core font
8958
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8959
				$out .= '<</Type /Font';
8960
				$out .= ' /Subtype /Type1';
8961
				$out .= ' /BaseFont /'.$name;
8962
				$out .= ' /Name /F'.$font['i'];
8963
				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8964
					$out .= ' /Encoding /WinAnsiEncoding';
8965
				}
8966
				if ($k == 'helvetica') {
8967
					// add default font for annotations
8968
					$this->annotation_fonts[$k] = $font['i'];
8969
				}
8970
				$out .= ' >>';
8971
				$out .= "\n".'endobj';
8972
				$this->_out($out);
8973
			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8974
				// additional Type1 or TrueType font
8975
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
8976
				$out .= '<</Type /Font';
8977
				$out .= ' /Subtype /'.$type;
8978
				$out .= ' /BaseFont /'.$name;
8979
				$out .= ' /Name /F'.$font['i'];
8980
				$out .= ' /FirstChar 32 /LastChar 255';
8981
				$out .= ' /Widths '.($this->n + 1).' 0 R';
8982
				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8983
				if ($font['enc']) {
8984
					if (isset($font['diff'])) {
8985
						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8986
					} else {
8987
						$out .= ' /Encoding /WinAnsiEncoding';
8988
					}
8989
				}
8990
				$out .= ' >>';
8991
				$out .= "\n".'endobj';
8992
				$this->_out($out);
8993
				// Widths
8994
				$this->_newobj();
8995
				$s = '[';
8996
				for ($i = 32; $i < 256; ++$i) {
8997
					if (isset($font['cw'][$i])) {
8998
						$s .= $font['cw'][$i].' ';
8999
					} else {
9000
						$s .= $font['dw'].' ';
9001
					}
9002
				}
9003
				$s .= ']';
9004
				$s .= "\n".'endobj';
9005
				$this->_out($s);
9006
				//Descriptor
9007
				$this->_newobj();
9008
				$s = '<</Type /FontDescriptor /FontName /'.$name;
9009
				foreach ($font['desc'] as $fdk => $fdv) {
9010
					if (is_float($fdv)) {
9011
						$fdv = sprintf('%F', $fdv);
9012
					}
9013
					$s .= ' /'.$fdk.' '.$fdv.'';
9014
				}
9015
				if (!TCPDF_STATIC::empty_string($font['file'])) {
9016
					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9017
				}
9018
				$s .= '>>';
9019
				$s .= "\n".'endobj';
9020
				$this->_out($s);
9021
			} else {
9022
				// additional types
9023
				$mtd = '_put'.strtolower($type);
9024
				if (!method_exists($this, $mtd)) {
9025
					$this->Error('Unsupported font type: '.$type);
9026
				}
9027
				$this->$mtd($font);
9028
			}
9029
		}
9030
	}
9031
 
9032
	/**
9033
	 * Adds unicode fonts.<br>
9034
	 * Based on PDF Reference 1.3 (section 5)
9035
	 * @param array $font font data
9036
	 * @protected
9037
	 * @author Nicola Asuni
9038
	 * @since 1.52.0.TC005 (2005-01-05)
9039
	 */
9040
	protected function _puttruetypeunicode($font) {
9041
		$fontname = '';
9042
		if ($font['subset']) {
9043
			// change name for font subsetting
9044
			$subtag = sprintf('%06u', $font['i']);
9045
			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9046
			$fontname .= $subtag.'+';
9047
		}
9048
		$fontname .= $font['name'];
9049
		// Type0 Font
9050
		// A composite font composed of other fonts, organized hierarchically
9051
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9052
		$out .= '<< /Type /Font';
9053
		$out .= ' /Subtype /Type0';
9054
		$out .= ' /BaseFont /'.$fontname;
9055
		$out .= ' /Name /F'.$font['i'];
9056
		$out .= ' /Encoding /'.$font['enc'];
9057
		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9058
		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9059
		$out .= ' >>';
9060
		$out .= "\n".'endobj';
9061
		$this->_out($out);
9062
		// ToUnicode map for Identity-H
9063
		$stream = TCPDF_FONT_DATA::$uni_identity_h;
9064
		// ToUnicode Object
9065
		$this->_newobj();
9066
		$stream = ($this->compress) ? gzcompress($stream) : $stream;
9067
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9068
		$stream = $this->_getrawstream($stream);
9069
		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9070
		// CIDFontType2
9071
		// A CIDFont whose glyph descriptions are based on TrueType font technology
9072
		$oid = $this->_newobj();
9073
		$out = '<< /Type /Font';
9074
		$out .= ' /Subtype /CIDFontType2';
9075
		$out .= ' /BaseFont /'.$fontname;
9076
		// A dictionary containing entries that define the character collection of the CIDFont.
9077
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9078
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9079
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9080
		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9081
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9082
		$out .= ' /DW '.$font['dw']; // default width
9083
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9084
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9085
			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9086
		}
9087
		$out .= ' >>';
9088
		$out .= "\n".'endobj';
9089
		$this->_out($out);
9090
		// Font descriptor
9091
		// A font descriptor describing the CIDFont default metrics other than its glyph widths
9092
		$this->_newobj();
9093
		$out = '<< /Type /FontDescriptor';
9094
		$out .= ' /FontName /'.$fontname;
9095
		foreach ($font['desc'] as $key => $value) {
9096
			if (is_float($value)) {
9097
				$value = sprintf('%F', $value);
9098
			}
9099
			$out .= ' /'.$key.' '.$value;
9100
		}
9101
		$fontdir = false;
9102
		if (!TCPDF_STATIC::empty_string($font['file'])) {
9103
			// A stream containing a TrueType font
9104
			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9105
			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
9106
		}
9107
		$out .= ' >>';
9108
		$out .= "\n".'endobj';
9109
		$this->_out($out);
9110
		if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9111
			$this->_newobj();
9112
			// Embed CIDToGIDMap
9113
			// A specification of the mapping from CIDs to glyph indices
9114
			// search and get CTG font file to embedd
9115
			$ctgfile = strtolower($font['ctg']);
9116
			// search and get ctg font file to embedd
9117
			$fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9118
			if (TCPDF_STATIC::empty_string($fontfile)) {
9119
				$this->Error('Font file not found: '.$ctgfile);
9120
			}
9121
			$stream = $this->_getrawstream(file_get_contents($fontfile));
9122
			$out = '<< /Length '.strlen($stream).'';
9123
			if (substr($fontfile, -2) == '.z') { // check file extension
9124
				// Decompresses data encoded using the public-domain
9125
				// zlib/deflate compression method, reproducing the
9126
				// original text or binary data
9127
				$out .= ' /Filter /FlateDecode';
9128
			}
9129
			$out .= ' >>';
9130
			$out .= ' stream'."\n".$stream."\n".'endstream';
9131
			$out .= "\n".'endobj';
9132
			$this->_out($out);
9133
		}
9134
	}
9135
 
9136
	/**
9137
	 * Output CID-0 fonts.
9138
	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9139
	 * @param array $font font data
9140
	 * @protected
9141
	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9142
	 * @since 3.2.000 (2008-06-23)
9143
	 */
9144
	protected function _putcidfont0($font) {
9145
		$cidoffset = 0;
9146
		if (!isset($font['cw'][1])) {
9147
			$cidoffset = 31;
9148
		}
9149
		if (isset($font['cidinfo']['uni2cid'])) {
9150
			// convert unicode to cid.
9151
			$uni2cid = $font['cidinfo']['uni2cid'];
9152
			$cw = array();
9153
			foreach ($font['cw'] as $uni => $width) {
9154
				if (isset($uni2cid[$uni])) {
9155
					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
9156
				} elseif ($uni < 256) {
9157
					$cw[$uni] = $width;
9158
				} // else unknown character
9159
			}
9160
			$font = array_merge($font, array('cw' => $cw));
9161
		}
9162
		$name = $font['name'];
9163
		$enc = $font['enc'];
9164
		if ($enc) {
9165
			$longname = $name.'-'.$enc;
9166
		} else {
9167
			$longname = $name;
9168
		}
9169
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9170
		$out .= '<</Type /Font';
9171
		$out .= ' /Subtype /Type0';
9172
		$out .= ' /BaseFont /'.$longname;
9173
		$out .= ' /Name /F'.$font['i'];
9174
		if ($enc) {
9175
			$out .= ' /Encoding /'.$enc;
9176
		}
9177
		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9178
		$out .= ' >>';
9179
		$out .= "\n".'endobj';
9180
		$this->_out($out);
9181
		$oid = $this->_newobj();
9182
		$out = '<</Type /Font';
9183
		$out .= ' /Subtype /CIDFontType0';
9184
		$out .= ' /BaseFont /'.$name;
9185
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9186
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9187
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9188
		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9189
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9190
		$out .= ' /DW '.$font['dw'];
9191
		$out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9192
		$out .= ' >>';
9193
		$out .= "\n".'endobj';
9194
		$this->_out($out);
9195
		$this->_newobj();
9196
		$s = '<</Type /FontDescriptor /FontName /'.$name;
9197
		foreach ($font['desc'] as $k => $v) {
9198
			if ($k != 'Style') {
9199
				if (is_float($v)) {
9200
					$v = sprintf('%F', $v);
9201
				}
9202
				$s .= ' /'.$k.' '.$v.'';
9203
			}
9204
		}
9205
		$s .= '>>';
9206
		$s .= "\n".'endobj';
9207
		$this->_out($s);
9208
	}
9209
 
9210
	/**
9211
	 * Output images.
9212
	 * @protected
9213
	 */
9214
	protected function _putimages() {
9215
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9216
		foreach ($this->imagekeys as $file) {
9217
			$info = $this->getImageBuffer($file);
9218
			// set object for alternate images array
9219
			$altoid = null;
9220
			if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9221
				$altoid = $this->_newobj();
9222
				$out = '[';
9223
				foreach ($info['altimgs'] as $altimage) {
9224
					if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9225
						$out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9226
						$out .= ' /DefaultForPrinting';
9227
						if ($altimage[1] === true) {
9228
							$out .= ' true';
9229
						} else {
9230
							$out .= ' false';
9231
						}
9232
						$out .= ' >>';
9233
					}
9234
				}
9235
				$out .= ' ]';
9236
				$out .= "\n".'endobj';
9237
				$this->_out($out);
9238
			}
9239
			// set image object
9240
			$oid = $this->_newobj();
9241
			$this->xobjects['I'.$info['i']] = array('n' => $oid);
9242
			$this->setImageSubBuffer($file, 'n', $this->n);
9243
			$out = '<</Type /XObject';
9244
			$out .= ' /Subtype /Image';
9245
			$out .= ' /Width '.$info['w'];
9246
			$out .= ' /Height '.$info['h'];
9247
			if (array_key_exists('masked', $info)) {
9248
				$out .= ' /SMask '.($this->n - 1).' 0 R';
9249
			}
9250
			// set color space
9251
			$icc = false;
9252
			if (isset($info['icc']) AND ($info['icc'] !== false)) {
9253
				// ICC Colour Space
9254
				$icc = true;
9255
				$out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9256
			} elseif ($info['cs'] == 'Indexed') {
9257
				// Indexed Colour Space
9258
				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9259
			} else {
9260
				// Device Colour Space
9261
				$out .= ' /ColorSpace /'.$info['cs'];
9262
			}
9263
			if ($info['cs'] == 'DeviceCMYK') {
9264
				$out .= ' /Decode [1 0 1 0 1 0 1 0]';
9265
			}
9266
			$out .= ' /BitsPerComponent '.$info['bpc'];
9267
			if ($altoid > 0) {
9268
				// reference to alternate images dictionary
9269
				$out .= ' /Alternates '.$altoid.' 0 R';
9270
			}
9271
			if (isset($info['exurl']) AND !empty($info['exurl'])) {
9272
				// external stream
9273
				$out .= ' /Length 0';
9274
				$out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9275
				if (isset($info['f'])) {
9276
					$out .= ' /FFilter /'.$info['f'];
9277
				}
9278
				$out .= ' >>';
9279
				$out .= ' stream'."\n".'endstream';
9280
			} else {
9281
				if (isset($info['f'])) {
9282
					$out .= ' /Filter /'.$info['f'];
9283
				}
9284
				if (isset($info['parms'])) {
9285
					$out .= ' '.$info['parms'];
9286
				}
9287
				if (isset($info['trns']) AND is_array($info['trns'])) {
9288
					$trns = '';
9289
					$count_info = count($info['trns']);
9290
					if ($info['cs'] == 'Indexed') {
9291
						$maxval =(pow(2, $info['bpc']) - 1);
9292
						for ($i = 0; $i < $count_info; ++$i) {
9293
							if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9294
								// this is not a binary type mask @TODO: create a SMask
9295
								$trns = '';
9296
								break;
9297
							} elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9298
								// store the first fully transparent value
9299
								$trns .= $i.' '.$i.' ';
9300
							}
9301
						}
9302
					} else {
9303
						// grayscale or RGB
9304
						for ($i = 0; $i < $count_info; ++$i) {
9305
							if ($info['trns'][$i] == 0) {
9306
								$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9307
							}
9308
						}
9309
					}
9310
					// Colour Key Masking
9311
					if (!empty($trns)) {
9312
						$out .= ' /Mask ['.$trns.']';
9313
					}
9314
				}
9315
				$stream = $this->_getrawstream($info['data']);
9316
				$out .= ' /Length '.strlen($stream).' >>';
9317
				$out .= ' stream'."\n".$stream."\n".'endstream';
9318
			}
9319
			$out .= "\n".'endobj';
9320
			$this->_out($out);
9321
			if ($icc) {
9322
				// ICC colour profile
9323
				$this->_newobj();
9324
				$icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9325
				$icc = $this->_getrawstream($icc);
9326
				$this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9327
			} elseif ($info['cs'] == 'Indexed') {
9328
				// colour palette
9329
				$this->_newobj();
9330
				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9331
				$pal = $this->_getrawstream($pal);
9332
				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9333
			}
9334
		}
9335
	}
9336
 
9337
	/**
9338
	 * Output Form XObjects Templates.
9339
	 * @author Nicola Asuni
9340
	 * @since 5.8.017 (2010-08-24)
9341
	 * @protected
9342
	 * @see startTemplate(), endTemplate(), printTemplate()
9343
	 */
9344
	protected function _putxobjects() {
9345
		foreach ($this->xobjects as $key => $data) {
9346
			if (isset($data['outdata'])) {
9347
				$stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9348
				$out = $this->_getobj($data['n'])."\n";
9349
				$out .= '<<';
9350
				$out .= ' /Type /XObject';
9351
				$out .= ' /Subtype /Form';
9352
				$out .= ' /FormType 1';
9353
				if ($this->compress) {
9354
					$stream = gzcompress($stream);
9355
					$out .= ' /Filter /FlateDecode';
9356
				}
9357
				$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));
9358
				$out .= ' /Matrix [1 0 0 1 0 0]';
9359
				$out .= ' /Resources <<';
9360
				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9361
				if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9362
					// transparency
9363
					if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9364
						$out .= ' /ExtGState <<';
9365
						foreach ($data['extgstates'] as $k => $extgstate) {
9366
							if (isset($this->extgstates[$k]['name'])) {
9367
								$out .= ' /'.$this->extgstates[$k]['name'];
9368
							} else {
9369
								$out .= ' /GS'.$k;
9370
							}
9371
							$out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9372
						}
9373
						$out .= ' >>';
9374
					}
9375
					if (isset($data['gradients']) AND !empty($data['gradients'])) {
9376
						$gp = '';
9377
						$gs = '';
9378
						foreach ($data['gradients'] as $id => $grad) {
9379
							// gradient patterns
9380
							$gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9381
							// gradient shadings
9382
							$gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9383
						}
9384
						$out .= ' /Pattern <<'.$gp.' >>';
9385
						$out .= ' /Shading <<'.$gs.' >>';
9386
					}
9387
				}
9388
				// spot colors
9389
				if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9390
					$out .= ' /ColorSpace <<';
9391
					foreach ($data['spot_colors'] as $name => $color) {
9392
						$out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9393
					}
9394
					$out .= ' >>';
9395
				}
9396
				// fonts
9397
				if (!empty($data['fonts'])) {
9398
					$out .= ' /Font <<';
9399
					foreach ($data['fonts'] as $fontkey => $fontid) {
9400
						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9401
					}
9402
					$out .= ' >>';
9403
				}
9404
				// images or nested xobjects
9405
				if (!empty($data['images']) OR !empty($data['xobjects'])) {
9406
					$out .= ' /XObject <<';
9407
					foreach ($data['images'] as $imgid) {
9408
						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9409
					}
9410
					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9411
						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9412
					}
9413
					$out .= ' >>';
9414
				}
9415
				$out .= ' >>'; //end resources
9416
				if (isset($data['group']) AND ($data['group'] !== false)) {
9417
					// set transparency group
9418
					$out .= ' /Group << /Type /Group /S /Transparency';
9419
					if (is_array($data['group'])) {
9420
						if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9421
							$out .= ' /CS /'.$data['group']['CS'];
9422
						}
9423
						if (isset($data['group']['I'])) {
9424
							$out .= ' /I /'.($data['group']['I']===true?'true':'false');
9425
						}
9426
						if (isset($data['group']['K'])) {
9427
							$out .= ' /K /'.($data['group']['K']===true?'true':'false');
9428
						}
9429
					}
9430
					$out .= ' >>';
9431
				}
9432
				$stream = $this->_getrawstream($stream, $data['n']);
9433
				$out .= ' /Length '.strlen($stream);
9434
				$out .= ' >>';
9435
				$out .= ' stream'."\n".$stream."\n".'endstream';
9436
				$out .= "\n".'endobj';
9437
				$this->_out($out);
9438
			}
9439
		}
9440
	}
9441
 
9442
	/**
9443
	 * Output Spot Colors Resources.
9444
	 * @protected
9445
	 * @since 4.0.024 (2008-09-12)
9446
	 */
9447
	protected function _putspotcolors() {
9448
		foreach ($this->spot_colors as $name => $color) {
9449
			$this->_newobj();
9450
			$this->spot_colors[$name]['n'] = $this->n;
9451
			$out = '[/Separation /'.str_replace(' ', '#20', $name);
9452
			$out .= ' /DeviceCMYK <<';
9453
			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9454
			$out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9455
			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9456
			$out .= "\n".'endobj';
9457
			$this->_out($out);
9458
		}
9459
	}
9460
 
9461
	/**
9462
	 * Return XObjects Dictionary.
9463
	 * @return string XObjects dictionary
9464
	 * @protected
9465
	 * @since 5.8.014 (2010-08-23)
9466
	 */
9467
	protected function _getxobjectdict() {
9468
		$out = '';
9469
		foreach ($this->xobjects as $id => $objid) {
9470
			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
9471
		}
9472
		return $out;
9473
	}
9474
 
9475
	/**
9476
	 * Output Resources Dictionary.
9477
	 * @protected
9478
	 */
9479
	protected function _putresourcedict() {
9480
		$out = $this->_getobj(2)."\n";
9481
		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9482
		$out .= ' /Font <<';
9483
		foreach ($this->fontkeys as $fontkey) {
9484
			$font = $this->getFontBuffer($fontkey);
9485
			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9486
		}
9487
		$out .= ' >>';
9488
		$out .= ' /XObject <<';
9489
		$out .= $this->_getxobjectdict();
9490
		$out .= ' >>';
9491
		// layers
9492
		if (!empty($this->pdflayers)) {
9493
			$out .= ' /Properties <<';
9494
			foreach ($this->pdflayers as $layer) {
9495
				$out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9496
			}
9497
			$out .= ' >>';
9498
		}
9499
		if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9500
			// transparency
9501
			if (isset($this->extgstates) AND !empty($this->extgstates)) {
9502
				$out .= ' /ExtGState <<';
9503
				foreach ($this->extgstates as $k => $extgstate) {
9504
					if (isset($extgstate['name'])) {
9505
						$out .= ' /'.$extgstate['name'];
9506
					} else {
9507
						$out .= ' /GS'.$k;
9508
					}
9509
					$out .= ' '.$extgstate['n'].' 0 R';
9510
				}
9511
				$out .= ' >>';
9512
			}
9513
			if (isset($this->gradients) AND !empty($this->gradients)) {
9514
				$gp = '';
9515
				$gs = '';
9516
				foreach ($this->gradients as $id => $grad) {
9517
					// gradient patterns
9518
					$gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9519
					// gradient shadings
9520
					$gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9521
				}
9522
				$out .= ' /Pattern <<'.$gp.' >>';
9523
				$out .= ' /Shading <<'.$gs.' >>';
9524
			}
9525
		}
9526
		// spot colors
9527
		if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9528
			$out .= ' /ColorSpace <<';
9529
			foreach ($this->spot_colors as $color) {
9530
				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9531
			}
9532
			$out .= ' >>';
9533
		}
9534
		$out .= ' >>';
9535
		$out .= "\n".'endobj';
9536
		$this->_out($out);
9537
	}
9538
 
9539
	/**
9540
	 * Output Resources.
9541
	 * @protected
9542
	 */
9543
	protected function _putresources() {
9544
		$this->_putextgstates();
9545
		$this->_putocg();
9546
		$this->_putfonts();
9547
		$this->_putimages();
9548
		$this->_putspotcolors();
9549
		$this->_putshaders();
9550
		$this->_putxobjects();
9551
		$this->_putresourcedict();
9552
		$this->_putdests();
9553
		$this->_putEmbeddedFiles();
9554
		$this->_putannotsobjs();
9555
		$this->_putjavascript();
9556
		$this->_putbookmarks();
9557
		$this->_putencryption();
9558
	}
9559
 
9560
	/**
9561
	 * Adds some Metadata information (Document Information Dictionary)
9562
	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9563
	 * @return int object id
9564
	 * @protected
9565
	 */
9566
	protected function _putinfo() {
9567
		$oid = $this->_newobj();
9568
		$out = '<<';
9569
		// store current isunicode value
9570
		$prev_isunicode = $this->isunicode;
9571
		if ($this->docinfounicode) {
9572
			$this->isunicode = true;
9573
		}
9574
		if (!TCPDF_STATIC::empty_string($this->title)) {
9575
			// The document's title.
9576
			$out .= ' /Title '.$this->_textstring($this->title, $oid);
9577
		}
9578
		if (!TCPDF_STATIC::empty_string($this->author)) {
9579
			// The name of the person who created the document.
9580
			$out .= ' /Author '.$this->_textstring($this->author, $oid);
9581
		}
9582
		if (!TCPDF_STATIC::empty_string($this->subject)) {
9583
			// The subject of the document.
9584
			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9585
		}
9586
		if (!TCPDF_STATIC::empty_string($this->keywords)) {
9587
			// Keywords associated with the document.
9588
			$out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9589
		}
9590
		if (!TCPDF_STATIC::empty_string($this->creator)) {
9591
			// 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.
9592
			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9593
		}
9594
		// restore previous isunicode value
9595
		$this->isunicode = $prev_isunicode;
9596
		// default producer
9597
		$out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9598
		// The date and time the document was created, in human-readable form
9599
		$out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9600
		// The date and time the document was most recently modified, in human-readable form
9601
		$out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9602
		// A name object indicating whether the document has been modified to include trapping information
9603
		$out .= ' /Trapped /False';
9604
		$out .= ' >>';
9605
		$out .= "\n".'endobj';
9606
		$this->_out($out);
9607
		return $oid;
9608
	}
9609
 
9610
	/**
9611
	 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9612
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9613
	 * @param string $xmp Custom XMP data.
9614
	 * @since 5.9.128 (2011-10-06)
9615
	 * @public
9616
	 */
9617
	public function setExtraXMP($xmp) {
9618
		$this->custom_xmp = $xmp;
9619
	}
9620
 
9621
	/**
9622
	 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9623
	 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9624
	 * @param string $xmp Custom XMP RDF data.
9625
	 * @since 6.3.0 (2019-09-19)
9626
	 * @public
9627
	 */
9628
	public function setExtraXMPRDF($xmp) {
9629
		$this->custom_xmp_rdf = $xmp;
9630
	}
9631
 
9632
	/**
9633
	 * Put XMP data object and return ID.
9634
	 * @return int The object ID.
9635
	 * @since 5.9.121 (2011-09-28)
9636
	 * @protected
9637
	 */
9638
	protected function _putXMP() {
9639
		$oid = $this->_newobj();
9640
		// store current isunicode value
9641
		$prev_isunicode = $this->isunicode;
9642
		$this->isunicode = true;
9643
		$prev_encrypted = $this->encrypted;
9644
		$this->encrypted = false;
9645
		// set XMP data
9646
		$xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9647
		$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";
9648
		$xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9649
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9650
		$xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9651
		$xmp .= "\t\t\t".'<dc:title>'."\n";
9652
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9653
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9654
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9655
		$xmp .= "\t\t\t".'</dc:title>'."\n";
9656
		$xmp .= "\t\t\t".'<dc:creator>'."\n";
9657
		$xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9658
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9659
		$xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9660
		$xmp .= "\t\t\t".'</dc:creator>'."\n";
9661
		$xmp .= "\t\t\t".'<dc:description>'."\n";
9662
		$xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9663
		$xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9664
		$xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9665
		$xmp .= "\t\t\t".'</dc:description>'."\n";
9666
		$xmp .= "\t\t\t".'<dc:subject>'."\n";
9667
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9668
		$xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9669
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9670
		$xmp .= "\t\t\t".'</dc:subject>'."\n";
9671
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9672
		// convert doc creation date format
9673
		$dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9674
		$doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9675
		$doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9676
		$doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9677
		$doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9678
		// convert doc modification date format
9679
		$dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9680
		$docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9681
		$docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9682
		$docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9683
		$docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9684
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9685
		$xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9686
		$xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9687
		$xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9688
		$xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9689
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9690
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9691
		$xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9692
		$xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9693
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9694
		$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9695
		$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);
9696
		$xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9697
		$xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9698
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9699
		if ($this->pdfa_mode) {
9700
			$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9701
			$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9702
			$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9703
			$xmp .= "\t\t".'</rdf:Description>'."\n";
9704
		}
9705
		// XMP extension schemas
9706
		$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";
9707
		$xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9708
		$xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9709
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9710
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9711
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9712
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9713
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9714
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9715
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9716
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9717
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9718
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9719
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9720
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9721
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9722
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9723
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9724
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9725
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9726
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9727
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9728
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9729
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9730
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9731
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9732
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9733
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9734
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9735
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9736
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9737
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9738
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9739
		$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9740
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9741
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9742
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9743
		$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9744
		$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9745
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9746
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9747
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9748
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9749
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9750
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9751
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9752
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9753
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9754
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9755
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9756
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9757
		$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9758
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9759
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9760
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9761
		$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9762
		$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9763
		$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9764
		$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9765
		$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9766
		$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9767
		$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9768
		$xmp .= "\t\t".'</rdf:Description>'."\n";
9769
		$xmp .= $this->custom_xmp_rdf;
9770
		$xmp .= "\t".'</rdf:RDF>'."\n";
9771
		$xmp .= $this->custom_xmp;
9772
		$xmp .= '</x:xmpmeta>'."\n";
9773
		$xmp .= '<?xpacket end="w"?>';
9774
		$out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9775
		// restore previous isunicode value
9776
		$this->isunicode = $prev_isunicode;
9777
		$this->encrypted = $prev_encrypted;
9778
		$this->_out($out);
9779
		return $oid;
9780
	}
9781
 
9782
	/**
9783
	 * Output Catalog.
9784
	 * @return int object id
9785
	 * @protected
9786
	 */
9787
	protected function _putcatalog() {
9788
		// put XMP
9789
		$xmpobj = $this->_putXMP();
9790
		// if required, add standard sRGB ICC colour profile
9791
		if ($this->pdfa_mode OR $this->force_srgb) {
9792
			$iccobj = $this->_newobj();
9793
			$icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9794
			$filter = '';
9795
			if ($this->compress) {
9796
				$filter = ' /Filter /FlateDecode';
9797
				$icc = gzcompress($icc);
9798
			}
9799
			$icc = $this->_getrawstream($icc);
9800
			$this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9801
		}
9802
		// start catalog
9803
		$oid = $this->_newobj();
9804
		$out = '<< /Type /Catalog';
9805
		$out .= ' /Version /'.$this->PDFVersion;
9806
		//$out .= ' /Extensions <<>>';
9807
		$out .= ' /Pages 1 0 R';
9808
		//$out .= ' /PageLabels ' //...;
9809
		$out .= ' /Names <<';
9810
		if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9811
			$out .= ' /JavaScript '.$this->n_js;
9812
		}
9813
		if (!empty($this->efnames)) {
9814
			$out .= ' /EmbeddedFiles <</Names [';
9815
			foreach ($this->efnames AS $fn => $fref) {
9816
				$out .= ' '.$this->_datastring($fn).' '.$fref;
9817
			}
9818
			$out .= ' ]>>';
9819
		}
9820
		$out .= ' >>';
9821
		if (!empty($this->dests)) {
9822
			$out .= ' /Dests '.($this->n_dests).' 0 R';
9823
		}
9824
		$out .= $this->_putviewerpreferences();
9825
		if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9826
			$out .= ' /PageLayout /'.$this->LayoutMode;
9827
		}
9828
		if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9829
			$out .= ' /PageMode /'.$this->PageMode;
9830
		}
9831
		if (count($this->outlines) > 0) {
9832
			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9833
			$out .= ' /PageMode /UseOutlines';
9834
		}
9835
		//$out .= ' /Threads []';
9836
		if ($this->ZoomMode == 'fullpage') {
9837
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9838
		} elseif ($this->ZoomMode == 'fullwidth') {
9839
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9840
		} elseif ($this->ZoomMode == 'real') {
9841
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9842
		} elseif (!is_string($this->ZoomMode)) {
9843
			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9844
		}
9845
		//$out .= ' /AA <<>>';
9846
		//$out .= ' /URI <<>>';
9847
		$out .= ' /Metadata '.$xmpobj.' 0 R';
9848
		//$out .= ' /StructTreeRoot <<>>';
9849
		//$out .= ' /MarkInfo <<>>';
9850
		if (isset($this->l['a_meta_language'])) {
9851
			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9852
		}
9853
		//$out .= ' /SpiderInfo <<>>';
9854
		// set OutputIntent to sRGB IEC61966-2.1 if required
9855
		if ($this->pdfa_mode OR $this->force_srgb) {
9856
			$out .= ' /OutputIntents [<<';
9857
			$out .= ' /Type /OutputIntent';
9858
			$out .= ' /S /GTS_PDFA1';
9859
			$out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9860
			$out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9861
			$out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9862
			$out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9863
			$out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9864
			$out .= ' >>]';
9865
		}
9866
		//$out .= ' /PieceInfo <<>>';
9867
		if (!empty($this->pdflayers)) {
9868
			$lyrobjs = '';
9869
			$lyrobjs_off = '';
9870
			$lyrobjs_lock = '';
9871
			foreach ($this->pdflayers as $layer) {
9872
				$layer_obj_ref = ' '.$layer['objid'].' 0 R';
9873
				$lyrobjs .= $layer_obj_ref;
9874
				if ($layer['view'] === false) {
9875
					$lyrobjs_off .= $layer_obj_ref;
9876
				}
9877
				if ($layer['lock']) {
9878
					$lyrobjs_lock .= $layer_obj_ref;
9879
				}
9880
			}
9881
			$out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9882
			$out .= ' /D <<';
9883
			$out .= ' /Name '.$this->_textstring('Layers', $oid);
9884
			$out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9885
			$out .= ' /BaseState /ON';
9886
			$out .= ' /OFF ['.$lyrobjs_off.']';
9887
			$out .= ' /Locked ['.$lyrobjs_lock.']';
9888
			$out .= ' /Intent /View';
9889
			$out .= ' /AS [';
9890
			$out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9891
			$out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9892
			$out .= ' ]';
9893
			$out .= ' /Order ['.$lyrobjs.']';
9894
			$out .= ' /ListMode /AllPages';
9895
			//$out .= ' /RBGroups ['..']';
9896
			//$out .= ' /Locked ['..']';
9897
			$out .= ' >>';
9898
			$out .= ' >>';
9899
		}
9900
		// AcroForm
9901
		if (!empty($this->form_obj_id)
9902
			OR ($this->sign AND isset($this->signature_data['cert_type']))
9903
			OR !empty($this->empty_signature_appearance)) {
9904
			$out .= ' /AcroForm <<';
9905
			$objrefs = '';
9906
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9907
				// set reference for signature object
9908
				$objrefs .= $this->sig_obj_id.' 0 R';
9909
			}
9910
			if (!empty($this->empty_signature_appearance)) {
9911
				foreach ($this->empty_signature_appearance as $esa) {
9912
					// set reference for empty signature objects
9913
					$objrefs .= ' '.$esa['objid'].' 0 R';
9914
				}
9915
			}
9916
			if (!empty($this->form_obj_id)) {
9917
				foreach($this->form_obj_id as $objid) {
9918
					$objrefs .= ' '.$objid.' 0 R';
9919
				}
9920
			}
9921
			$out .= ' /Fields ['.$objrefs.']';
9922
			// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9923
			if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9924
				$out .= ' /NeedAppearances false';
9925
			}
9926
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
9927
				if ($this->signature_data['cert_type'] > 0) {
9928
					$out .= ' /SigFlags 3';
9929
				} else {
9930
					$out .= ' /SigFlags 1';
9931
				}
9932
			}
9933
			//$out .= ' /CO ';
9934
			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9935
				$out .= ' /DR <<';
9936
				$out .= ' /Font <<';
9937
				foreach ($this->annotation_fonts as $fontkey => $fontid) {
9938
					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9939
				}
9940
				$out .= ' >> >>';
9941
			}
9942
			$font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
1441 ariadna 9943
			$out .= ' /DA ' . $this->_datastring('/F'.$font['i'].' 0 Tf 0 g');
1 efrain 9944
			$out .= ' /Q '.(($this->rtl)?'2':'0');
9945
			//$out .= ' /XFA ';
9946
			$out .= ' >>';
9947
			// signatures
9948
			if ($this->sign AND isset($this->signature_data['cert_type'])
9949
				AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9950
				if ($this->signature_data['cert_type'] > 0) {
9951
					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9952
				} else {
9953
					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9954
				}
9955
			}
9956
		}
9957
		//$out .= ' /Legal <<>>';
9958
		//$out .= ' /Requirements []';
9959
		//$out .= ' /Collection <<>>';
9960
		//$out .= ' /NeedsRendering true';
9961
		$out .= ' >>';
9962
		$out .= "\n".'endobj';
9963
		$this->_out($out);
9964
		return $oid;
9965
	}
9966
 
9967
	/**
9968
	 * Output viewer preferences.
9969
	 * @return string for viewer preferences
9970
	 * @author Nicola asuni
9971
	 * @since 3.1.000 (2008-06-09)
9972
	 * @protected
9973
	 */
9974
	protected function _putviewerpreferences() {
9975
		$vp = $this->viewer_preferences;
9976
		$out = ' /ViewerPreferences <<';
9977
		if ($this->rtl) {
9978
			$out .= ' /Direction /R2L';
9979
		} else {
9980
			$out .= ' /Direction /L2R';
9981
		}
9982
		if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9983
			$out .= ' /HideToolbar true';
9984
		}
9985
		if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9986
			$out .= ' /HideMenubar true';
9987
		}
9988
		if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9989
			$out .= ' /HideWindowUI true';
9990
		}
9991
		if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9992
			$out .= ' /FitWindow true';
9993
		}
9994
		if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9995
			$out .= ' /CenterWindow true';
9996
		}
9997
		if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9998
			$out .= ' /DisplayDocTitle true';
9999
		}
10000
		if (isset($vp['NonFullScreenPageMode'])) {
10001
			$out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
10002
		}
10003
		if (isset($vp['ViewArea'])) {
10004
			$out .= ' /ViewArea /'.$vp['ViewArea'];
10005
		}
10006
		if (isset($vp['ViewClip'])) {
10007
			$out .= ' /ViewClip /'.$vp['ViewClip'];
10008
		}
10009
		if (isset($vp['PrintArea'])) {
10010
			$out .= ' /PrintArea /'.$vp['PrintArea'];
10011
		}
10012
		if (isset($vp['PrintClip'])) {
10013
			$out .= ' /PrintClip /'.$vp['PrintClip'];
10014
		}
10015
		if (isset($vp['PrintScaling'])) {
10016
			$out .= ' /PrintScaling /'.$vp['PrintScaling'];
10017
		}
10018
		if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
10019
			$out .= ' /Duplex /'.$vp['Duplex'];
10020
		}
10021
		if (isset($vp['PickTrayByPDFSize'])) {
10022
			if ($vp['PickTrayByPDFSize']) {
10023
				$out .= ' /PickTrayByPDFSize true';
10024
			} else {
10025
				$out .= ' /PickTrayByPDFSize false';
10026
			}
10027
		}
10028
		if (isset($vp['PrintPageRange'])) {
10029
			$PrintPageRangeNum = '';
10030
			foreach ($vp['PrintPageRange'] as $k => $v) {
10031
				$PrintPageRangeNum .= ' '.($v - 1).'';
10032
			}
10033
			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10034
		}
10035
		if (isset($vp['NumCopies'])) {
10036
			$out .= ' /NumCopies '.intval($vp['NumCopies']);
10037
		}
10038
		$out .= ' >>';
10039
		return $out;
10040
	}
10041
 
10042
	/**
10043
	 * Output PDF File Header (7.5.2).
10044
	 * @protected
10045
	 */
10046
	protected function _putheader() {
10047
		$this->_out('%PDF-'.$this->PDFVersion);
10048
		$this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10049
	}
10050
 
10051
	/**
10052
	 * Output end of document (EOF).
10053
	 * @protected
10054
	 */
10055
	protected function _enddoc() {
10056
		if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10057
			// save subset chars of the previous font
10058
			$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10059
		}
10060
		$this->state = 1;
10061
		$this->_putheader();
10062
		$this->_putpages();
10063
		$this->_putresources();
10064
		// empty signature fields
10065
		if (!empty($this->empty_signature_appearance)) {
10066
			foreach ($this->empty_signature_appearance as $key => $esa) {
10067
				// widget annotation for empty signature
10068
				$out = $this->_getobj($esa['objid'])."\n";
10069
				$out .= '<< /Type /Annot';
10070
				$out .= ' /Subtype /Widget';
10071
				$out .= ' /Rect ['.$esa['rect'].']';
10072
				$out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10073
				$out .= ' /F 4';
10074
				$out .= ' /FT /Sig';
10075
				$signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10076
				$out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10077
				$out .= ' /Ff 0';
10078
				$out .= ' >>';
10079
				$out .= "\n".'endobj';
10080
				$this->_out($out);
10081
			}
10082
		}
10083
		// Signature
10084
		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10085
			// widget annotation for signature
10086
			$out = $this->_getobj($this->sig_obj_id)."\n";
10087
			$out .= '<< /Type /Annot';
10088
			$out .= ' /Subtype /Widget';
10089
			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10090
			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10091
			$out .= ' /F 4';
10092
			$out .= ' /FT /Sig';
10093
			$out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10094
			$out .= ' /Ff 0';
10095
			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10096
			$out .= ' >>';
10097
			$out .= "\n".'endobj';
10098
			$this->_out($out);
10099
			// signature
10100
			$this->_putsignature();
10101
		}
10102
		// Info
10103
		$objid_info = $this->_putinfo();
10104
		// Catalog
10105
		$objid_catalog = $this->_putcatalog();
10106
		// Cross-ref
10107
		$o = $this->bufferlen;
10108
		// XREF section
10109
		$this->_out('xref');
10110
		$this->_out('0 '.($this->n + 1));
10111
		$this->_out('0000000000 65535 f ');
10112
		$freegen = ($this->n + 2);
10113
		for ($i=1; $i <= $this->n; ++$i) {
10114
			if (!isset($this->offsets[$i]) AND ($i > 1)) {
10115
				$this->_out(sprintf('0000000000 %05d f ', $freegen));
10116
				++$freegen;
10117
			} else {
10118
				$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10119
			}
10120
		}
10121
		// TRAILER
10122
		$out = 'trailer'."\n";
10123
		$out .= '<<';
10124
		$out .= ' /Size '.($this->n + 1);
10125
		$out .= ' /Root '.$objid_catalog.' 0 R';
10126
		$out .= ' /Info '.$objid_info.' 0 R';
10127
		if ($this->encrypted) {
10128
			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10129
		}
10130
		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10131
		$out .= ' >>';
10132
		$this->_out($out);
10133
		$this->_out('startxref');
10134
		$this->_out($o);
10135
		$this->_out('%%EOF');
10136
		$this->state = 3; // end-of-doc
10137
	}
10138
 
10139
	/**
10140
	 * Initialize a new page.
10141
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10142
	 * @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().
10143
	 * @protected
10144
	 * @see getPageSizeFromFormat(), setPageFormat()
10145
	 */
10146
	protected function _beginpage($orientation='', $format='') {
10147
		++$this->page;
10148
		$this->pageobjects[$this->page] = array();
10149
		$this->setPageBuffer($this->page, '');
10150
		// initialize array for graphics tranformation positions inside a page buffer
10151
		$this->transfmrk[$this->page] = array();
10152
		$this->state = 2;
10153
		if (TCPDF_STATIC::empty_string($orientation)) {
10154
			if (isset($this->CurOrientation)) {
10155
				$orientation = $this->CurOrientation;
10156
			} elseif ($this->fwPt > $this->fhPt) {
10157
				// landscape
10158
				$orientation = 'L';
10159
			} else {
10160
				// portrait
10161
				$orientation = 'P';
10162
			}
10163
		}
10164
		if (TCPDF_STATIC::empty_string($format)) {
10165
			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10166
			$this->setPageOrientation($orientation);
10167
		} else {
10168
			$this->setPageFormat($format, $orientation);
10169
		}
10170
		if ($this->rtl) {
10171
			$this->x = $this->w - $this->rMargin;
10172
		} else {
10173
			$this->x = $this->lMargin;
10174
		}
10175
		$this->y = $this->tMargin;
10176
		if (isset($this->newpagegroup[$this->page])) {
10177
			// start a new group
10178
			$this->currpagegroup = $this->newpagegroup[$this->page];
10179
			$this->pagegroups[$this->currpagegroup] = 1;
10180
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10181
			++$this->pagegroups[$this->currpagegroup];
10182
		}
10183
	}
10184
 
10185
	/**
10186
	 * Mark end of page.
10187
	 * @protected
10188
	 */
10189
	protected function _endpage() {
10190
		$this->setVisibility('all');
10191
		$this->state = 1;
10192
	}
10193
 
10194
	/**
10195
	 * Begin a new object and return the object number.
10196
	 * @return int object number
10197
	 * @protected
10198
	 */
10199
	protected function _newobj() {
10200
		$this->_out($this->_getobj());
10201
		return $this->n;
10202
	}
10203
 
10204
	/**
10205
	 * Return the starting object string for the selected object ID.
10206
	 * @param int|null $objid Object ID (leave empty to get a new ID).
10207
	 * @return string the starting object string
10208
	 * @protected
10209
	 * @since 5.8.009 (2010-08-20)
10210
	 */
10211
	protected function _getobj($objid=null) {
10212
		if (TCPDF_STATIC::empty_string($objid)) {
10213
			++$this->n;
10214
			$objid = $this->n;
10215
		}
10216
		$this->offsets[$objid] = $this->bufferlen;
10217
		$this->pageobjects[$this->page][] = $objid;
10218
		return $objid.' 0 obj';
10219
	}
10220
 
10221
	/**
10222
	 * Underline text.
10223
	 * @param int $x X coordinate
10224
	 * @param int $y Y coordinate
10225
	 * @param string $txt text to underline
10226
	 * @protected
10227
	 */
10228
	protected function _dounderline($x, $y, $txt) {
10229
		$w = $this->GetStringWidth($txt);
10230
		return $this->_dounderlinew($x, $y, $w);
10231
	}
10232
 
10233
	/**
10234
	 * Underline for rectangular text area.
10235
	 * @param int $x X coordinate
10236
	 * @param int $y Y coordinate
10237
	 * @param int $w width to underline
10238
	 * @protected
10239
	 * @since 4.8.008 (2009-09-29)
10240
	 */
10241
	protected function _dounderlinew($x, $y, $w) {
10242
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10243
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10244
	}
10245
 
10246
	/**
10247
	 * Line through text.
10248
	 * @param int $x X coordinate
10249
	 * @param int $y Y coordinate
10250
	 * @param string $txt text to linethrough
10251
	 * @protected
10252
	 */
10253
	protected function _dolinethrough($x, $y, $txt) {
10254
		$w = $this->GetStringWidth($txt);
10255
		return $this->_dolinethroughw($x, $y, $w);
10256
	}
10257
 
10258
	/**
10259
	 * Line through for rectangular text area.
10260
	 * @param int $x X coordinate
10261
	 * @param int $y Y coordinate
10262
	 * @param int $w line length (width)
10263
	 * @protected
10264
	 * @since 4.9.008 (2009-09-29)
10265
	 */
10266
	protected function _dolinethroughw($x, $y, $w) {
10267
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10268
		return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10269
	}
10270
 
10271
	/**
10272
	 * Overline text.
10273
	 * @param int $x X coordinate
10274
	 * @param int $y Y coordinate
10275
	 * @param string $txt text to overline
10276
	 * @protected
10277
	 * @since 4.9.015 (2010-04-19)
10278
	 */
10279
	protected function _dooverline($x, $y, $txt) {
10280
		$w = $this->GetStringWidth($txt);
10281
		return $this->_dooverlinew($x, $y, $w);
10282
	}
10283
 
10284
	/**
10285
	 * Overline for rectangular text area.
10286
	 * @param int $x X coordinate
10287
	 * @param int $y Y coordinate
10288
	 * @param int $w width to overline
10289
	 * @protected
10290
	 * @since 4.9.015 (2010-04-19)
10291
	 */
10292
	protected function _dooverlinew($x, $y, $w) {
10293
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10294
		return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10295
 
10296
	}
10297
 
10298
	/**
10299
	 * Format a data string for meta information
10300
	 * @param string $s data string to escape.
10301
	 * @param int $n object ID
10302
	 * @return string escaped string.
10303
	 * @protected
10304
	 */
10305
	protected function _datastring($s, $n=0) {
10306
		if ($n == 0) {
10307
			$n = $this->n;
10308
		}
10309
		$s = $this->_encrypt_data($n, $s);
10310
		return '('. TCPDF_STATIC::_escape($s).')';
10311
	}
10312
 
10313
	/**
10314
	 * Set the document creation timestamp
10315
	 * @param mixed $time Document creation timestamp in seconds or date-time string.
10316
	 * @public
10317
	 * @since 5.9.152 (2012-03-23)
10318
	 */
10319
	public function setDocCreationTimestamp($time) {
10320
		if (is_string($time)) {
10321
			$time = TCPDF_STATIC::getTimestamp($time);
10322
		}
10323
		$this->doc_creation_timestamp = intval($time);
10324
	}
10325
 
10326
	/**
10327
	 * Set the document modification timestamp
10328
	 * @param mixed $time Document modification timestamp in seconds or date-time string.
10329
	 * @public
10330
	 * @since 5.9.152 (2012-03-23)
10331
	 */
10332
	public function setDocModificationTimestamp($time) {
10333
		if (is_string($time)) {
10334
			$time = TCPDF_STATIC::getTimestamp($time);
10335
		}
10336
		$this->doc_modification_timestamp = intval($time);
10337
	}
10338
 
10339
	/**
10340
	 * Returns document creation timestamp in seconds.
10341
	 * @return int Creation timestamp in seconds.
10342
	 * @public
10343
	 * @since 5.9.152 (2012-03-23)
10344
	 */
10345
	public function getDocCreationTimestamp() {
10346
		return $this->doc_creation_timestamp;
10347
	}
10348
 
10349
	/**
10350
	 * Returns document modification timestamp in seconds.
10351
	 * @return int Modfication timestamp in seconds.
10352
	 * @public
10353
	 * @since 5.9.152 (2012-03-23)
10354
	 */
10355
	public function getDocModificationTimestamp() {
10356
		return $this->doc_modification_timestamp;
10357
	}
10358
 
10359
	/**
10360
	 * Returns a formatted date for meta information
10361
	 * @param int $n Object ID.
10362
	 * @param int $timestamp Timestamp to convert.
10363
	 * @return string escaped date string.
10364
	 * @protected
10365
	 * @since 4.6.028 (2009-08-25)
10366
	 */
10367
	protected function _datestring($n=0, $timestamp=0) {
10368
		if ((empty($timestamp)) OR ($timestamp < 0)) {
10369
			$timestamp = $this->doc_creation_timestamp;
10370
		}
10371
		return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10372
	}
10373
 
10374
	/**
10375
	 * Format a text string for meta information
10376
	 * @param string $s string to escape.
10377
	 * @param int $n object ID
10378
	 * @return string escaped string.
10379
	 * @protected
10380
	 */
10381
	protected function _textstring($s, $n=0) {
10382
		if ($this->isunicode) {
10383
			//Convert string to UTF-16BE
10384
			$s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10385
		}
10386
		return $this->_datastring($s, $n);
10387
	}
10388
 
10389
	/**
10390
	 * get raw output stream.
10391
	 * @param string $s string to output.
10392
	 * @param int $n object reference for encryption mode
10393
	 * @protected
10394
	 * @author Nicola Asuni
10395
	 * @since 5.5.000 (2010-06-22)
10396
	 */
10397
	protected function _getrawstream($s, $n=0) {
10398
		if ($n <= 0) {
10399
			// default to current object
10400
			$n = $this->n;
10401
		}
10402
		return $this->_encrypt_data($n, $s);
10403
	}
10404
 
10405
	/**
10406
	 * Output a string to the document.
10407
	 * @param string $s string to output.
10408
	 * @protected
10409
	 */
10410
	protected function _out($s) {
10411
		if ($this->state == 2) {
10412
			if ($this->inxobj) {
10413
				// we are inside an XObject template
10414
				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10415
			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10416
				// puts data before page footer
10417
				$pagebuff = $this->getPageBuffer($this->page);
10418
				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10419
				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
10420
				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
10421
				// update footer position
10422
				$this->footerpos[$this->page] += strlen($s."\n");
10423
			} else {
10424
				// set page data
10425
				$this->setPageBuffer($this->page, $s."\n", true);
10426
			}
10427
		} elseif ($this->state > 0) {
10428
			// set general data
10429
			$this->setBuffer($s."\n");
10430
		}
10431
	}
10432
 
10433
	/**
10434
	 * Set header font.
10435
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10436
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10437
	 * @public
10438
	 * @since 1.1
10439
	 */
10440
	public function setHeaderFont($font) {
10441
		$this->header_font = $font;
10442
	}
10443
 
10444
	/**
10445
	 * Get header font.
10446
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10447
	 * @phpstan-return array{0: string, 1: string, 2: float|null}
10448
	 * @public
10449
	 * @since 4.0.012 (2008-07-24)
10450
	 */
10451
	public function getHeaderFont() {
10452
		return $this->header_font;
10453
	}
10454
 
10455
	/**
10456
	 * Set footer font.
10457
	 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10458
	 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10459
	 * @public
10460
	 * @since 1.1
10461
	 */
10462
	public function setFooterFont($font) {
10463
		$this->footer_font = $font;
10464
	}
10465
 
10466
	/**
10467
	 * Get Footer font.
10468
	 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10469
	 * @phpstan-return array{0: string, 1: string, 2: float|null} $font
10470
	 * @public
10471
	 * @since 4.0.012 (2008-07-24)
10472
	 */
10473
	public function getFooterFont() {
10474
		return $this->footer_font;
10475
	}
10476
 
10477
	/**
10478
	 * Set language array.
10479
	 * @param array $language
10480
	 * @public
10481
	 * @since 1.1
10482
	 */
10483
	public function setLanguageArray($language) {
10484
		$this->l = $language;
10485
		if (isset($this->l['a_meta_dir'])) {
10486
			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10487
		} else {
10488
			$this->rtl = false;
10489
		}
10490
	}
10491
 
10492
	/**
10493
	 * Returns the PDF data.
10494
	 * @public
10495
	 */
10496
	public function getPDFData() {
10497
		if ($this->state < 3) {
10498
			$this->Close();
10499
		}
10500
		return $this->buffer;
10501
	}
10502
 
10503
	/**
10504
	 * Output anchor link.
10505
	 * @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;)
10506
	 * @param string $name link name
10507
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10508
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
10509
	 * @param array|null $color array of RGB text color
10510
	 * @param string $style font style (U, D, B, I)
10511
	 * @param boolean $firstblock if true the string is the starting of a line.
10512
	 * @return int the number of cells used or the remaining text if $firstline = true;
10513
	 * @public
10514
	 */
10515
	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) {
10516
		if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10517
			// convert url to internal link
10518
			$lnkdata = explode(',', $url);
10519
			if (isset($lnkdata[0]) ) {
10520
				$page = substr($lnkdata[0], 1);
10521
				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10522
					$lnky = floatval($lnkdata[1]);
10523
				} else {
10524
					$lnky = 0;
10525
				}
10526
				$url = $this->AddLink();
10527
				$this->setLink($url, $lnky, $page);
10528
			}
10529
		}
10530
		// store current settings
10531
		$prevcolor = $this->fgcolor;
10532
		$prevstyle = $this->FontStyle;
10533
		if (empty($color)) {
10534
			$this->setTextColorArray($this->htmlLinkColorArray);
10535
		} else {
10536
			$this->setTextColorArray($color);
10537
		}
10538
		if ($style == -1) {
10539
			$this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10540
		} else {
10541
			$this->setFont('', $this->FontStyle.$style);
10542
		}
10543
		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10544
		// restore settings
10545
		$this->setFont('', $prevstyle);
10546
		$this->setTextColorArray($prevcolor);
10547
		return $ret;
10548
	}
10549
 
10550
	/**
10551
	 * Converts pixels to User's Units.
10552
	 * @param int $px pixels
10553
	 * @return float value in user's unit
10554
	 * @public
10555
	 * @see setImageScale(), getImageScale()
10556
	 */
10557
	public function pixelsToUnits($px) {
10558
		return ($px / ($this->imgscale * $this->k));
10559
	}
10560
 
10561
	/**
10562
	 * Reverse function for htmlentities.
10563
	 * Convert entities in UTF-8.
10564
	 * @param string $text_to_convert Text to convert.
10565
	 * @return string converted text string
10566
	 * @public
10567
	 */
10568
	public function unhtmlentities($text_to_convert) {
10569
		return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10570
	}
10571
 
10572
	// ENCRYPTION METHODS ----------------------------------
10573
 
10574
	/**
10575
	 * Compute encryption key depending on object number where the encrypted data is stored.
10576
	 * This is used for all strings and streams without crypt filter specifier.
10577
	 * @param int $n object number
10578
	 * @return int object key
10579
	 * @protected
10580
	 * @author Nicola Asuni
10581
	 * @since 2.0.000 (2008-01-02)
10582
	 */
10583
	protected function _objectkey($n) {
10584
		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
10585
		if ($this->encryptdata['mode'] == 2) { // AES-128
10586
			// AES padding
10587
			$objkey .= "\x73\x41\x6C\x54"; // sAlT
10588
		}
10589
		$objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10590
		$objkey = substr($objkey, 0, 16);
10591
		return $objkey;
10592
	}
10593
 
10594
	/**
10595
	 * Encrypt the input string.
10596
	 * @param int $n object number
10597
	 * @param string $s data string to encrypt
10598
	 * @return string encrypted string
10599
	 * @protected
10600
	 * @author Nicola Asuni
10601
	 * @since 5.0.005 (2010-05-11)
10602
	 */
10603
	protected function _encrypt_data($n, $s) {
10604
		if (!$this->encrypted) {
10605
			return $s;
10606
		}
10607
		switch ($this->encryptdata['mode']) {
10608
			case 0:   // RC4-40
10609
			case 1: { // RC4-128
10610
				$s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10611
				break;
10612
			}
10613
			case 2: { // AES-128
10614
				$s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10615
				break;
10616
			}
10617
			case 3: { // AES-256
10618
				$s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10619
				break;
10620
			}
10621
		}
10622
		return $s;
10623
	}
10624
 
10625
	/**
10626
	 * Put encryption on PDF document.
10627
	 * @protected
10628
	 * @author Nicola Asuni
10629
	 * @since 2.0.000 (2008-01-02)
10630
	 */
10631
	protected function _putencryption() {
10632
		if (!$this->encrypted) {
10633
			return;
10634
		}
10635
		$this->encryptdata['objid'] = $this->_newobj();
10636
		$out = '<<';
10637
		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10638
			$this->encryptdata['Filter'] = 'Standard';
10639
		}
10640
		$out .= ' /Filter /'.$this->encryptdata['Filter'];
10641
		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10642
			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10643
		}
10644
		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10645
			$this->encryptdata['V'] = 1;
10646
		}
10647
		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
10648
		$out .= ' /V '.$this->encryptdata['V'];
10649
		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10650
			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10651
			$out .= ' /Length '.$this->encryptdata['Length'];
10652
		} else {
10653
			$out .= ' /Length 40';
10654
		}
10655
		if ($this->encryptdata['V'] >= 4) {
10656
			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10657
				$this->encryptdata['StmF'] = 'Identity';
10658
			}
10659
			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10660
				// The name of the crypt filter that shall be used when decrypting all strings in the document.
10661
				$this->encryptdata['StrF'] = 'Identity';
10662
			}
10663
			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10664
			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10665
				$out .= ' /CF <<';
10666
				$out .= ' /'.$this->encryptdata['StmF'].' <<';
10667
				$out .= ' /Type /CryptFilter';
10668
				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10669
					// The method used
10670
					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10671
					if ($this->encryptdata['pubkey']) {
10672
						$out .= ' /Recipients [';
10673
						foreach ($this->encryptdata['Recipients'] as $rec) {
10674
							$out .= ' <'.$rec.'>';
10675
						}
10676
						$out .= ' ]';
10677
						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10678
							$out .= ' /EncryptMetadata false';
10679
						} else {
10680
							$out .= ' /EncryptMetadata true';
10681
						}
10682
					}
10683
				} else {
10684
					$out .= ' /CFM /None';
10685
				}
10686
				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10687
					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10688
					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10689
				} else {
10690
					$out .= ' /AuthEvent /DocOpen';
10691
				}
10692
				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10693
					// The bit length of the encryption key.
10694
					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
10695
				}
10696
				$out .= ' >> >>';
10697
			}
10698
			// The name of the crypt filter that shall be used by default when decrypting streams.
10699
			$out .= ' /StmF /'.$this->encryptdata['StmF'];
10700
			// The name of the crypt filter that shall be used when decrypting all strings in the document.
10701
			$out .= ' /StrF /'.$this->encryptdata['StrF'];
10702
			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10703
				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10704
				$out .= ' /EFF /'.$this->encryptdata[''];
10705
			}
10706
		}
10707
		// Additional encryption dictionary entries for the standard security handler
10708
		if ($this->encryptdata['pubkey']) {
10709
			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10710
				$out .= ' /Recipients [';
10711
				foreach ($this->encryptdata['Recipients'] as $rec) {
10712
					$out .= ' <'.$rec.'>';
10713
				}
10714
				$out .= ' ]';
10715
			}
10716
		} else {
10717
			$out .= ' /R';
10718
			if ($this->encryptdata['V'] == 5) { // AES-256
10719
				$out .= ' 5';
10720
				$out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10721
				$out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10722
				$out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10723
			} elseif ($this->encryptdata['V'] == 4) { // AES-128
10724
				$out .= ' 4';
10725
			} elseif ($this->encryptdata['V'] < 2) { // RC-40
10726
				$out .= ' 2';
10727
			} else { // RC-128
10728
				$out .= ' 3';
10729
			}
10730
			$out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10731
			$out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10732
			$out .= ' /P '.$this->encryptdata['P'];
10733
			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10734
				$out .= ' /EncryptMetadata false';
10735
			} else {
10736
				$out .= ' /EncryptMetadata true';
10737
			}
10738
		}
10739
		$out .= ' >>';
10740
		$out .= "\n".'endobj';
10741
		$this->_out($out);
10742
	}
10743
 
10744
	/**
10745
	 * Compute U value (used for encryption)
10746
	 * @return string U value
10747
	 * @protected
10748
	 * @since 2.0.000 (2008-01-02)
10749
	 * @author Nicola Asuni
10750
	 */
10751
	protected function _Uvalue() {
10752
		if ($this->encryptdata['mode'] == 0) { // RC4-40
10753
			return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10754
		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10755
			$tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10756
			$enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10757
			$len = strlen($tmp);
10758
			for ($i = 1; $i <= 19; ++$i) {
10759
				$ek = '';
10760
				for ($j = 0; $j < $len; ++$j) {
10761
					$ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10762
				}
10763
				$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10764
			}
10765
			$enc .= str_repeat("\x00", 16);
10766
			return substr($enc, 0, 32);
10767
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10768
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10769
			// User Validation Salt
10770
			$this->encryptdata['UVS'] = substr($seed, 0, 8);
10771
			// User Key Salt
10772
			$this->encryptdata['UKS'] = substr($seed, 8, 16);
10773
			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10774
		}
10775
	}
10776
 
10777
	/**
10778
	 * Compute UE value (used for encryption)
10779
	 * @return string UE value
10780
	 * @protected
10781
	 * @since 5.9.006 (2010-10-19)
10782
	 * @author Nicola Asuni
10783
	 */
10784
	protected function _UEvalue() {
10785
		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10786
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10787
	}
10788
 
10789
	/**
10790
	 * Compute O value (used for encryption)
10791
	 * @return string O value
10792
	 * @protected
10793
	 * @since 2.0.000 (2008-01-02)
10794
	 * @author Nicola Asuni
10795
	 */
10796
	protected function _Ovalue() {
10797
		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10798
			$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10799
			if ($this->encryptdata['mode'] > 0) {
10800
				for ($i = 0; $i < 50; ++$i) {
10801
					$tmp = TCPDF_STATIC::_md5_16($tmp);
10802
				}
10803
			}
10804
			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10805
			$enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10806
			if ($this->encryptdata['mode'] > 0) {
10807
				$len = strlen($owner_key);
10808
				for ($i = 1; $i <= 19; ++$i) {
10809
					$ek = '';
10810
					for ($j = 0; $j < $len; ++$j) {
10811
						$ek .= chr(ord($owner_key[$j]) ^ $i);
10812
					}
10813
					$enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10814
				}
10815
			}
10816
			return $enc;
10817
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
10818
			$seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10819
			// Owner Validation Salt
10820
			$this->encryptdata['OVS'] = substr($seed, 0, 8);
10821
			// Owner Key Salt
10822
			$this->encryptdata['OKS'] = substr($seed, 8, 16);
10823
			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10824
		}
10825
	}
10826
 
10827
	/**
10828
	 * Compute OE value (used for encryption)
10829
	 * @return string OE value
10830
	 * @protected
10831
	 * @since 5.9.006 (2010-10-19)
10832
	 * @author Nicola Asuni
10833
	 */
10834
	protected function _OEvalue() {
10835
		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10836
		return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10837
	}
10838
 
10839
	/**
10840
	 * Convert password for AES-256 encryption mode
10841
	 * @param string $password password
10842
	 * @return string password
10843
	 * @protected
10844
	 * @since 5.9.006 (2010-10-19)
10845
	 * @author Nicola Asuni
10846
	 */
10847
	protected function _fixAES256Password($password) {
10848
		$psw = ''; // password to be returned
10849
		$psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10850
		foreach ($psw_array as $c) {
10851
			$psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10852
		}
10853
		return substr($psw, 0, 127);
10854
	}
10855
 
10856
	/**
10857
	 * Compute encryption key
10858
	 * @protected
10859
	 * @since 2.0.000 (2008-01-02)
10860
	 * @author Nicola Asuni
10861
	 */
10862
	protected function _generateencryptionkey() {
10863
		$keybytelen = ($this->encryptdata['Length'] / 8);
10864
		if (!$this->encryptdata['pubkey']) { // standard mode
10865
			if ($this->encryptdata['mode'] == 3) { // AES-256
10866
				// generate 256 bit random key
10867
				$this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10868
				// truncate passwords
10869
				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10870
				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10871
				// Compute U value
10872
				$this->encryptdata['U'] = $this->_Uvalue();
10873
				// Compute UE value
10874
				$this->encryptdata['UE'] = $this->_UEvalue();
10875
				// Compute O value
10876
				$this->encryptdata['O'] = $this->_Ovalue();
10877
				// Compute OE value
10878
				$this->encryptdata['OE'] = $this->_OEvalue();
10879
				// Compute P value
10880
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10881
				// Computing the encryption dictionary's Perms (permissions) value
10882
				$perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10883
				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10884
				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10885
					$perms .= 'F';
10886
				} else {
10887
					$perms .= 'T';
10888
				}
10889
				$perms .= 'adb'; // bytes 9-11
10890
				$perms .= 'nick'; // bytes 12-15
10891
				$this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10892
			} else { // RC4-40, RC4-128, AES-128
10893
				// Pad passwords
10894
				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10895
				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10896
				// Compute O value
10897
				$this->encryptdata['O'] = $this->_Ovalue();
10898
				// get default permissions (reverse byte order)
10899
				$permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10900
				// Compute encryption key
10901
				$tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10902
				if ($this->encryptdata['mode'] > 0) {
10903
					for ($i = 0; $i < 50; ++$i) {
10904
						$tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10905
					}
10906
				}
10907
				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10908
				// Compute U value
10909
				$this->encryptdata['U'] = $this->_Uvalue();
10910
				// Compute P value
10911
				$this->encryptdata['P'] = $this->encryptdata['protection'];
10912
			}
10913
		} else { // Public-Key mode
10914
			// random 20-byte seed
10915
			$seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10916
			$recipient_bytes = '';
10917
			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10918
				// for each public certificate
10919
				if (isset($pubkey['p'])) {
10920
					$pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10921
				} else {
10922
					$pkprotection = $this->encryptdata['protection'];
10923
				}
10924
				// get default permissions (reverse byte order)
10925
				$pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10926
				// envelope data
10927
				$envelope = $seed.$pkpermissions;
10928
				// write the envelope data to a temporary file
10929
				$tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10930
				$f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10931
				if (!$f) {
10932
					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
10933
				}
10934
				$envelope_length = strlen($envelope);
10935
				fwrite($f, $envelope, $envelope_length);
10936
				fclose($f);
10937
				$tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10938
				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10939
					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
10940
				}
10941
				// read encryption signature
10942
				$signature = file_get_contents($tempencfile, false, null, $envelope_length);
10943
				// extract signature
10944
				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
10945
				$tmparr = explode("\n\n", $signature);
10946
				$signature = trim($tmparr[1]);
10947
				unset($tmparr);
10948
				// decode signature
10949
				$signature = base64_decode($signature);
10950
				// convert signature to hex
10951
				$hexsignature = current(unpack('H*', $signature));
10952
				// store signature on recipients array
10953
				$this->encryptdata['Recipients'][] = $hexsignature;
10954
				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10955
				$recipient_bytes .= $signature;
10956
			}
10957
			// calculate encryption key
10958
			if ($this->encryptdata['mode'] == 3) { // AES-256
10959
				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10960
			} else { // RC4-40, RC4-128, AES-128
10961
				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10962
			}
10963
		}
10964
	}
10965
 
10966
	/**
10967
	 * Set document protection
10968
	 * Remark: the protection against modification is for people who have the full Acrobat product.
10969
	 * 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.
10970
	 * 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.
10971
	 * @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>
10972
	 * @param string $user_pass user password. Empty by default.
10973
	 * @param string|null $owner_pass owner password. If not specified, a random value is used.
10974
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10975
	 * @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')))
10976
	 * @public
10977
	 * @since 2.0.000 (2008-01-02)
10978
	 * @author Nicola Asuni
10979
	 */
10980
	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) {
10981
		if ($this->pdfa_mode) {
10982
			// encryption is not allowed in PDF/A mode
10983
			return;
10984
		}
10985
		$this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10986
		if (($pubkeys !== null) AND (is_array($pubkeys))) {
10987
			// public-key mode
10988
			$this->encryptdata['pubkeys'] = $pubkeys;
10989
			if ($mode == 0) {
10990
				// public-Key Security requires at least 128 bit
10991
				$mode = 1;
10992
			}
10993
			if (!function_exists('openssl_pkcs7_encrypt')) {
10994
				$this->Error('Public-Key Security requires openssl library.');
10995
			}
10996
			// Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10997
			$this->encryptdata['pubkey'] = true;
10998
			$this->encryptdata['Filter'] = 'Adobe.PubSec';
10999
			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
11000
			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
11001
		} else {
11002
			// standard mode (password mode)
11003
			$this->encryptdata['pubkey'] = false;
11004
			$this->encryptdata['Filter'] = 'Standard';
11005
			$this->encryptdata['StmF'] = 'StdCF';
11006
			$this->encryptdata['StrF'] = 'StdCF';
11007
		}
11008
		if ($mode > 1) { // AES
11009
			if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
11010
				$this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
11011
			}
11012
			if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
11013
				$this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
11014
			}
11015
			if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
11016
				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11017
			}
11018
			if (($mode == 3) AND !function_exists('hash')) {
11019
				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11020
				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11021
			}
11022
		}
11023
		if ($owner_pass === null) {
11024
			$owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11025
		}
11026
		$this->encryptdata['user_password'] = $user_pass;
11027
		$this->encryptdata['owner_password'] = $owner_pass;
11028
		$this->encryptdata['mode'] = $mode;
11029
		switch ($mode) {
11030
			case 0: { // RC4 40 bit
11031
				$this->encryptdata['V'] = 1;
11032
				$this->encryptdata['Length'] = 40;
11033
				$this->encryptdata['CF']['CFM'] = 'V2';
11034
				break;
11035
			}
11036
			case 1: { // RC4 128 bit
11037
				$this->encryptdata['V'] = 2;
11038
				$this->encryptdata['Length'] = 128;
11039
				$this->encryptdata['CF']['CFM'] = 'V2';
11040
				if ($this->encryptdata['pubkey']) {
11041
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11042
					$this->encryptdata['Recipients'] = array();
11043
				}
11044
				break;
11045
			}
11046
			case 2: { // AES 128 bit
11047
				$this->encryptdata['V'] = 4;
11048
				$this->encryptdata['Length'] = 128;
11049
				$this->encryptdata['CF']['CFM'] = 'AESV2';
1441 ariadna 11050
				$this->encryptdata['CF']['Length'] = 16;
1 efrain 11051
				if ($this->encryptdata['pubkey']) {
11052
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11053
					$this->encryptdata['Recipients'] = array();
11054
				}
11055
				break;
11056
			}
11057
			case 3: { // AES 256 bit
11058
				$this->encryptdata['V'] = 5;
11059
				$this->encryptdata['Length'] = 256;
11060
				$this->encryptdata['CF']['CFM'] = 'AESV3';
1441 ariadna 11061
				$this->encryptdata['CF']['Length'] = 32;
1 efrain 11062
				if ($this->encryptdata['pubkey']) {
11063
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11064
					$this->encryptdata['Recipients'] = array();
11065
				}
11066
				break;
11067
			}
11068
		}
11069
		$this->encrypted = true;
11070
		$this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11071
		$this->_generateencryptionkey();
11072
	}
11073
 
11074
	// END OF ENCRYPTION FUNCTIONS -------------------------
11075
 
11076
	// START TRANSFORMATIONS SECTION -----------------------
11077
 
11078
	/**
11079
	 * Starts a 2D tranformation saving current graphic state.
11080
	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11081
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11082
	 * @public
11083
	 * @since 2.1.000 (2008-01-07)
11084
	 * @see StartTransform(), StopTransform()
11085
	 */
11086
	public function StartTransform() {
11087
		if ($this->state != 2) {
11088
			return;
11089
		}
11090
		$this->_outSaveGraphicsState();
11091
		if ($this->inxobj) {
11092
			// we are inside an XObject template
11093
			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11094
		} else {
11095
			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11096
		}
11097
		++$this->transfmatrix_key;
11098
		$this->transfmatrix[$this->transfmatrix_key] = array();
11099
	}
11100
 
11101
	/**
11102
	 * Stops a 2D tranformation restoring previous graphic state.
11103
	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11104
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11105
	 * @public
11106
	 * @since 2.1.000 (2008-01-07)
11107
	 * @see StartTransform(), StopTransform()
11108
	 */
11109
	public function StopTransform() {
11110
		if ($this->state != 2) {
11111
			return;
11112
		}
11113
		$this->_outRestoreGraphicsState();
11114
		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11115
			array_pop($this->transfmatrix[$this->transfmatrix_key]);
11116
			--$this->transfmatrix_key;
11117
		}
11118
		if ($this->inxobj) {
11119
			// we are inside an XObject template
11120
			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11121
		} else {
11122
			array_pop($this->transfmrk[$this->page]);
11123
		}
11124
	}
11125
	/**
11126
	 * Horizontal Scaling.
11127
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11128
	 * @param int $x abscissa of the scaling center. Default is current x position
11129
	 * @param int $y ordinate of the scaling center. Default is current y position
11130
	 * @public
11131
	 * @since 2.1.000 (2008-01-07)
11132
	 * @see StartTransform(), StopTransform()
11133
	 */
11134
	public function ScaleX($s_x, $x='', $y='') {
11135
		$this->Scale($s_x, 100, $x, $y);
11136
	}
11137
 
11138
	/**
11139
	 * Vertical Scaling.
11140
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11141
	 * @param int $x abscissa of the scaling center. Default is current x position
11142
	 * @param int $y ordinate of the scaling center. Default is current y position
11143
	 * @public
11144
	 * @since 2.1.000 (2008-01-07)
11145
	 * @see StartTransform(), StopTransform()
11146
	 */
11147
	public function ScaleY($s_y, $x='', $y='') {
11148
		$this->Scale(100, $s_y, $x, $y);
11149
	}
11150
 
11151
	/**
11152
	 * Vertical and horizontal proportional Scaling.
11153
	 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11154
	 * @param int $x abscissa of the scaling center. Default is current x position
11155
	 * @param int $y ordinate of the scaling center. Default is current y position
11156
	 * @public
11157
	 * @since 2.1.000 (2008-01-07)
11158
	 * @see StartTransform(), StopTransform()
11159
	 */
11160
	public function ScaleXY($s, $x='', $y='') {
11161
		$this->Scale($s, $s, $x, $y);
11162
	}
11163
 
11164
	/**
11165
	 * Vertical and horizontal non-proportional Scaling.
11166
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11167
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11168
	 * @param float|null $x abscissa of the scaling center. Default is current x position
11169
	 * @param float|null $y ordinate of the scaling center. Default is current y position
11170
	 * @public
11171
	 * @since 2.1.000 (2008-01-07)
11172
	 * @see StartTransform(), StopTransform()
11173
	 */
11174
	public function Scale($s_x, $s_y, $x=null, $y=null) {
11175
		if (TCPDF_STATIC::empty_string($x)) {
11176
			$x = $this->x;
11177
		}
11178
		if (TCPDF_STATIC::empty_string($y)) {
11179
			$y = $this->y;
11180
		}
11181
		if (($s_x == 0) OR ($s_y == 0)) {
11182
			$this->Error('Please do not use values equal to zero for scaling');
11183
		}
11184
		$y = ($this->h - $y) * $this->k;
11185
		$x *= $this->k;
11186
		//calculate elements of transformation matrix
11187
		$s_x /= 100;
11188
		$s_y /= 100;
11189
		$tm = array();
11190
		$tm[0] = $s_x;
11191
		$tm[1] = 0;
11192
		$tm[2] = 0;
11193
		$tm[3] = $s_y;
11194
		$tm[4] = $x * (1 - $s_x);
11195
		$tm[5] = $y * (1 - $s_y);
11196
		//scale the coordinate system
11197
		$this->Transform($tm);
11198
	}
11199
 
11200
	/**
11201
	 * Horizontal Mirroring.
11202
	 * @param float|null $x abscissa of the point. Default is current x position
11203
	 * @public
11204
	 * @since 2.1.000 (2008-01-07)
11205
	 * @see StartTransform(), StopTransform()
11206
	 */
11207
	public function MirrorH($x=null) {
11208
		$this->Scale(-100, 100, $x);
11209
	}
11210
 
11211
	/**
11212
	 * Verical Mirroring.
11213
	 * @param float|null $y ordinate of the point. Default is current y position
11214
	 * @public
11215
	 * @since 2.1.000 (2008-01-07)
11216
	 * @see StartTransform(), StopTransform()
11217
	 */
11218
	public function MirrorV($y=null) {
11219
		$this->Scale(100, -100, null, $y);
11220
	}
11221
 
11222
	/**
11223
	 * Point reflection mirroring.
11224
	 * @param float|null $x abscissa of the point. Default is current x position
11225
	 * @param float|null $y ordinate of the point. Default is current y position
11226
	 * @public
11227
	 * @since 2.1.000 (2008-01-07)
11228
	 * @see StartTransform(), StopTransform()
11229
	 */
11230
	public function MirrorP($x=null,$y=null) {
11231
		$this->Scale(-100, -100, $x, $y);
11232
	}
11233
 
11234
	/**
11235
	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11236
	 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11237
	 * @param float|null $x abscissa of the point. Default is current x position
11238
	 * @param float|null $y ordinate of the point. Default is current y position
11239
	 * @public
11240
	 * @since 2.1.000 (2008-01-07)
11241
	 * @see StartTransform(), StopTransform()
11242
	 */
11243
	public function MirrorL($angle=0, $x=null,$y=null) {
11244
		$this->Scale(-100, 100, $x, $y);
11245
		$this->Rotate(-2*($angle-90), $x, $y);
11246
	}
11247
 
11248
	/**
11249
	 * Translate graphic object horizontally.
11250
	 * @param int $t_x movement to the right (or left for RTL)
11251
	 * @public
11252
	 * @since 2.1.000 (2008-01-07)
11253
	 * @see StartTransform(), StopTransform()
11254
	 */
11255
	public function TranslateX($t_x) {
11256
		$this->Translate($t_x, 0);
11257
	}
11258
 
11259
	/**
11260
	 * Translate graphic object vertically.
11261
	 * @param int $t_y movement to the bottom
11262
	 * @public
11263
	 * @since 2.1.000 (2008-01-07)
11264
	 * @see StartTransform(), StopTransform()
11265
	 */
11266
	public function TranslateY($t_y) {
11267
		$this->Translate(0, $t_y);
11268
	}
11269
 
11270
	/**
11271
	 * Translate graphic object horizontally and vertically.
11272
	 * @param int $t_x movement to the right
11273
	 * @param int $t_y movement to the bottom
11274
	 * @public
11275
	 * @since 2.1.000 (2008-01-07)
11276
	 * @see StartTransform(), StopTransform()
11277
	 */
11278
	public function Translate($t_x, $t_y) {
11279
		//calculate elements of transformation matrix
11280
		$tm = array();
11281
		$tm[0] = 1;
11282
		$tm[1] = 0;
11283
		$tm[2] = 0;
11284
		$tm[3] = 1;
11285
		$tm[4] = $t_x * $this->k;
11286
		$tm[5] = -$t_y * $this->k;
11287
		//translate the coordinate system
11288
		$this->Transform($tm);
11289
	}
11290
 
11291
	/**
11292
	 * Rotate object.
11293
	 * @param float $angle angle in degrees for counter-clockwise rotation
11294
	 * @param float|null $x abscissa of the rotation center. Default is current x position
11295
	 * @param float|null $y ordinate of the rotation center. Default is current y position
11296
	 * @public
11297
	 * @since 2.1.000 (2008-01-07)
11298
	 * @see StartTransform(), StopTransform()
11299
	 */
11300
	public function Rotate($angle, $x=null, $y=null) {
11301
		if (TCPDF_STATIC::empty_string($x)) {
11302
			$x = $this->x;
11303
		}
11304
		if (TCPDF_STATIC::empty_string($y)) {
11305
			$y = $this->y;
11306
		}
11307
		$y = ($this->h - $y) * $this->k;
11308
		$x *= $this->k;
11309
		//calculate elements of transformation matrix
11310
		$tm = array();
11311
		$tm[0] = cos(deg2rad($angle));
11312
		$tm[1] = sin(deg2rad($angle));
11313
		$tm[2] = -$tm[1];
11314
		$tm[3] = $tm[0];
11315
		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11316
		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11317
		//rotate the coordinate system around ($x,$y)
11318
		$this->Transform($tm);
11319
	}
11320
 
11321
	/**
11322
	 * Skew horizontally.
11323
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11324
	 * @param float|null $x abscissa of the skewing center. default is current x position
11325
	 * @param float|null $y ordinate of the skewing center. default is current y position
11326
	 * @public
11327
	 * @since 2.1.000 (2008-01-07)
11328
	 * @see StartTransform(), StopTransform()
11329
	 */
11330
	public function SkewX($angle_x, $x=null, $y=null) {
11331
		$this->Skew($angle_x, 0, $x, $y);
11332
	}
11333
 
11334
	/**
11335
	 * Skew vertically.
11336
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11337
	 * @param float|null $x abscissa of the skewing center. default is current x position
11338
	 * @param float|null $y ordinate of the skewing center. default is current y position
11339
	 * @public
11340
	 * @since 2.1.000 (2008-01-07)
11341
	 * @see StartTransform(), StopTransform()
11342
	 */
11343
	public function SkewY($angle_y, $x=null, $y=null) {
11344
		$this->Skew(0, $angle_y, $x, $y);
11345
	}
11346
 
11347
	/**
11348
	 * Skew.
11349
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11350
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11351
	 * @param float|null $x abscissa of the skewing center. default is current x position
11352
	 * @param float|null $y ordinate of the skewing center. default is current y position
11353
	 * @public
11354
	 * @since 2.1.000 (2008-01-07)
11355
	 * @see StartTransform(), StopTransform()
11356
	 */
11357
	public function Skew($angle_x, $angle_y, $x=null, $y=null) {
11358
		if (TCPDF_STATIC::empty_string($x)) {
11359
			$x = $this->x;
11360
		}
11361
		if (TCPDF_STATIC::empty_string($y)) {
11362
			$y = $this->y;
11363
		}
11364
		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11365
			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
11366
		}
11367
		$x *= $this->k;
11368
		$y = ($this->h - $y) * $this->k;
11369
		//calculate elements of transformation matrix
11370
		$tm = array();
11371
		$tm[0] = 1;
11372
		$tm[1] = tan(deg2rad($angle_y));
11373
		$tm[2] = tan(deg2rad($angle_x));
11374
		$tm[3] = 1;
11375
		$tm[4] = -$tm[2] * $y;
11376
		$tm[5] = -$tm[1] * $x;
11377
		//skew the coordinate system
11378
		$this->Transform($tm);
11379
	}
11380
 
11381
	/**
11382
	 * Apply graphic transformations.
11383
	 * @param array $tm transformation matrix
11384
	 * @protected
11385
	 * @since 2.1.000 (2008-01-07)
11386
	 * @see StartTransform(), StopTransform()
11387
	 */
11388
	protected function Transform($tm) {
11389
		if ($this->state != 2) {
11390
			return;
11391
		}
11392
		$this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11393
		// add tranformation matrix
11394
		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11395
		// update transformation mark
11396
		if ($this->inxobj) {
11397
			// we are inside an XObject template
11398
			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11399
				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
11400
				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11401
			}
11402
		} elseif (end($this->transfmrk[$this->page]) !== false) {
11403
			$key = key($this->transfmrk[$this->page]);
11404
			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11405
		}
11406
	}
11407
 
11408
	// END TRANSFORMATIONS SECTION -------------------------
11409
 
11410
	// START GRAPHIC FUNCTIONS SECTION ---------------------
11411
	// The following section is based on the code provided by David Hernandez Sanz
11412
 
11413
	/**
11414
	 * 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.
11415
	 * @param float $width The width.
11416
	 * @public
11417
	 * @since 1.0
11418
	 * @see Line(), Rect(), Cell(), MultiCell()
11419
	 */
11420
	public function setLineWidth($width) {
11421
		//Set line width
11422
		$this->LineWidth = $width;
11423
		$this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11424
		if ($this->state == 2) {
11425
			$this->_out($this->linestyleWidth);
11426
		}
11427
	}
11428
 
11429
	/**
11430
	 * Returns the current the line width.
11431
	 * @return int Line width
11432
	 * @public
11433
	 * @since 2.1.000 (2008-01-07)
11434
	 * @see Line(), SetLineWidth()
11435
	 */
11436
	public function GetLineWidth() {
11437
		return $this->LineWidth;
11438
	}
11439
 
11440
	/**
11441
	 * Set line style.
11442
	 * @param array $style Line style. Array with keys among the following:
11443
	 * <ul>
11444
	 *	 <li>width (float): Width of the line in user units.</li>
11445
	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
11446
	 * butt, round, square. The difference between "square" and "butt" is that
11447
	 * "square" projects a flat end past the end of the line.</li>
11448
	 *	 <li>join (string): Type of join. Possible values are: miter, round,
11449
	 * bevel.</li>
11450
	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11451
	 * series of length values, which are the lengths of the on and off dashes.
11452
	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11453
	 * 1 off, 2 on, 1 off, ...</li>
11454
	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
11455
	 * the point at which the pattern starts.</li>
11456
	 *	 <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>
11457
	 * </ul>
11458
	 * @param boolean $ret if true do not send the command.
11459
	 * @return string the PDF command
11460
	 * @public
11461
	 * @since 2.1.000 (2008-01-08)
11462
	 */
11463
	public function setLineStyle($style, $ret=false) {
11464
		$s = ''; // string to be returned
11465
		if (!is_array($style)) {
11466
			return $s;
11467
		}
11468
		if (isset($style['width'])) {
11469
			$this->LineWidth = $style['width'];
11470
			$this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11471
			$s .= $this->linestyleWidth.' ';
11472
		}
11473
		if (isset($style['cap'])) {
11474
			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11475
			if (isset($ca[$style['cap']])) {
11476
				$this->linestyleCap = $ca[$style['cap']].' J';
11477
				$s .= $this->linestyleCap.' ';
11478
			}
11479
		}
11480
		if (isset($style['join'])) {
11481
			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11482
			if (isset($ja[$style['join']])) {
11483
				$this->linestyleJoin = $ja[$style['join']].' j';
11484
				$s .= $this->linestyleJoin.' ';
11485
			}
11486
		}
11487
		if (isset($style['dash'])) {
11488
			$dash_string = '';
11489
			if ($style['dash']) {
11490
				if (preg_match('/^.+,/', $style['dash']) > 0) {
11491
					$tab = explode(',', $style['dash']);
11492
				} else {
11493
					$tab = array($style['dash']);
11494
				}
11495
				$dash_string = '';
11496
				foreach ($tab as $i => $v) {
11497
					if ($i) {
11498
						$dash_string .= ' ';
11499
					}
11500
					$dash_string .= sprintf('%F', $v);
11501
				}
11502
			}
11503
			if (!isset($style['phase']) OR !$style['dash']) {
11504
				$style['phase'] = 0;
11505
			}
11506
			$this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11507
			$s .= $this->linestyleDash.' ';
11508
		}
11509
		if (isset($style['color'])) {
11510
			$s .= $this->setDrawColorArray($style['color'], true).' ';
11511
		}
11512
		if (!$ret AND ($this->state == 2)) {
11513
			$this->_out($s);
11514
		}
11515
		return $s;
11516
	}
11517
 
11518
	/**
11519
	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11520
	 * @param float $x Abscissa of point.
11521
	 * @param float $y Ordinate of point.
11522
	 * @protected
11523
	 * @since 2.1.000 (2008-01-08)
11524
	 */
11525
	protected function _outPoint($x, $y) {
11526
		if ($this->state == 2) {
11527
			$this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11528
		}
11529
	}
11530
 
11531
	/**
11532
	 * Append a straight line segment from the current point to the point (x, y).
11533
	 * The new current point shall be (x, y).
11534
	 * @param float $x Abscissa of end point.
11535
	 * @param float $y Ordinate of end point.
11536
	 * @protected
11537
	 * @since 2.1.000 (2008-01-08)
11538
	 */
11539
	protected function _outLine($x, $y) {
11540
		if ($this->state == 2) {
11541
			$this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11542
		}
11543
	}
11544
 
11545
	/**
11546
	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11547
	 * @param float $x Abscissa of upper-left corner.
11548
	 * @param float $y Ordinate of upper-left corner.
11549
	 * @param float $w Width.
11550
	 * @param float $h Height.
11551
	 * @param string $op options
11552
	 * @protected
11553
	 * @since 2.1.000 (2008-01-08)
11554
	 */
11555
	protected function _outRect($x, $y, $w, $h, $op) {
11556
		if ($this->state == 2) {
11557
			$this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11558
		}
11559
	}
11560
 
11561
	/**
11562
	 * 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.
11563
	 * The new current point shall be (x3, y3).
11564
	 * @param float $x1 Abscissa of control point 1.
11565
	 * @param float $y1 Ordinate of control point 1.
11566
	 * @param float $x2 Abscissa of control point 2.
11567
	 * @param float $y2 Ordinate of control point 2.
11568
	 * @param float $x3 Abscissa of end point.
11569
	 * @param float $y3 Ordinate of end point.
11570
	 * @protected
11571
	 * @since 2.1.000 (2008-01-08)
11572
	 */
11573
	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11574
		if ($this->state == 2) {
11575
			$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)));
11576
		}
11577
	}
11578
 
11579
	/**
11580
	 * 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.
11581
	 * The new current point shall be (x3, y3).
11582
	 * @param float $x2 Abscissa of control point 2.
11583
	 * @param float $y2 Ordinate of control point 2.
11584
	 * @param float $x3 Abscissa of end point.
11585
	 * @param float $y3 Ordinate of end point.
11586
	 * @protected
11587
	 * @since 4.9.019 (2010-04-26)
11588
	 */
11589
	protected function _outCurveV($x2, $y2, $x3, $y3) {
11590
		if ($this->state == 2) {
11591
			$this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11592
		}
11593
	}
11594
 
11595
	/**
11596
	 * 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.
11597
	 * The new current point shall be (x3, y3).
11598
	 * @param float $x1 Abscissa of control point 1.
11599
	 * @param float $y1 Ordinate of control point 1.
11600
	 * @param float $x3 Abscissa of end point.
11601
	 * @param float $y3 Ordinate of end point.
11602
	 * @protected
11603
	 * @since 2.1.000 (2008-01-08)
11604
	 */
11605
	protected function _outCurveY($x1, $y1, $x3, $y3) {
11606
		if ($this->state == 2) {
11607
			$this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11608
		}
11609
	}
11610
 
11611
	/**
11612
	 * Draws a line between two points.
11613
	 * @param float $x1 Abscissa of first point.
11614
	 * @param float $y1 Ordinate of first point.
11615
	 * @param float $x2 Abscissa of second point.
11616
	 * @param float $y2 Ordinate of second point.
11617
	 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11618
	 * @public
11619
	 * @since 1.0
11620
	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11621
	 */
11622
	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11623
		if ($this->state != 2) {
11624
			return;
11625
		}
11626
		if (is_array($style)) {
11627
			$this->setLineStyle($style);
11628
		}
11629
		$this->_outPoint($x1, $y1);
11630
		$this->_outLine($x2, $y2);
11631
		$this->_out('S');
11632
	}
11633
 
11634
	/**
11635
	 * Draws a rectangle.
11636
	 * @param float $x Abscissa of upper-left corner.
11637
	 * @param float $y Ordinate of upper-left corner.
11638
	 * @param float $w Width.
11639
	 * @param float $h Height.
11640
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11641
	 * @param array $border_style Border style of rectangle. Array with keys among the following:
11642
	 * <ul>
11643
	 *	 <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11644
	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11645
	 * </ul>
11646
	 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11647
	 * @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).
11648
	 * @public
11649
	 * @since 1.0
11650
	 * @see SetLineStyle()
11651
	 */
11652
	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11653
		if ($this->state != 2) {
11654
			return;
11655
		}
11656
		if (empty($style)) {
11657
			$style = 'S';
11658
		}
11659
		if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11660
			// set background color
11661
			$this->setFillColorArray($fill_color);
11662
		}
11663
		if (!empty($border_style)) {
11664
			if (isset($border_style['all']) AND !empty($border_style['all'])) {
11665
				//set global style for border
11666
				$this->setLineStyle($border_style['all']);
11667
				$border_style = array();
11668
			} else {
11669
				// remove stroke operator from style
11670
				$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*' );
11671
				if (isset($opnostroke[$style])) {
11672
					$style = $opnostroke[$style];
11673
				}
11674
			}
11675
		}
11676
		if (!empty($style)) {
11677
			$op = TCPDF_STATIC::getPathPaintOperator($style);
11678
			$this->_outRect($x, $y, $w, $h, $op);
11679
		}
11680
		if (!empty($border_style)) {
11681
			$border_style2 = array();
11682
			foreach ($border_style as $line => $value) {
11683
				$length = strlen($line);
11684
				for ($i = 0; $i < $length; ++$i) {
11685
					$border_style2[$line[$i]] = $value;
11686
				}
11687
			}
11688
			$border_style = $border_style2;
11689
			if (isset($border_style['L']) AND $border_style['L']) {
11690
				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
11691
			}
11692
			if (isset($border_style['T']) AND $border_style['T']) {
11693
				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
11694
			}
11695
			if (isset($border_style['R']) AND $border_style['R']) {
11696
				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11697
			}
11698
			if (isset($border_style['B']) AND $border_style['B']) {
11699
				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11700
			}
11701
		}
11702
	}
11703
 
11704
	/**
11705
	 * Draws a Bezier curve.
11706
	 * The Bezier curve is a tangent to the line between the control points at
11707
	 * either end of the curve.
11708
	 * @param float $x0 Abscissa of start point.
11709
	 * @param float $y0 Ordinate of start point.
11710
	 * @param float $x1 Abscissa of control point 1.
11711
	 * @param float $y1 Ordinate of control point 1.
11712
	 * @param float $x2 Abscissa of control point 2.
11713
	 * @param float $y2 Ordinate of control point 2.
11714
	 * @param float $x3 Abscissa of end point.
11715
	 * @param float $y3 Ordinate of end point.
11716
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11717
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11718
	 * @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).
11719
	 * @public
11720
	 * @see SetLineStyle()
11721
	 * @since 2.1.000 (2008-01-08)
11722
	 */
11723
	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11724
		if ($this->state != 2) {
11725
			return;
11726
		}
11727
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11728
			$this->setFillColorArray($fill_color);
11729
		}
11730
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11731
		if ($line_style) {
11732
			$this->setLineStyle($line_style);
11733
		}
11734
		$this->_outPoint($x0, $y0);
11735
		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11736
		$this->_out($op);
11737
	}
11738
 
11739
	/**
11740
	 * Draws a poly-Bezier curve.
11741
	 * Each Bezier curve segment is a tangent to the line between the control points at
11742
	 * either end of the curve.
11743
	 * @param float $x0 Abscissa of start point.
11744
	 * @param float $y0 Ordinate of start point.
11745
	 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11746
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11747
	 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11748
	 * @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).
11749
	 * @public
11750
	 * @see SetLineStyle()
11751
	 * @since 3.0008 (2008-05-12)
11752
	 */
11753
	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11754
		if ($this->state != 2) {
11755
			return;
11756
		}
11757
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11758
			$this->setFillColorArray($fill_color);
11759
		}
11760
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11761
		if ($op == 'f') {
11762
			$line_style = array();
11763
		}
11764
		if ($line_style) {
11765
			$this->setLineStyle($line_style);
11766
		}
11767
		$this->_outPoint($x0, $y0);
11768
		foreach ($segments as $segment) {
11769
			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11770
			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11771
		}
11772
		$this->_out($op);
11773
	}
11774
 
11775
	/**
11776
	 * Draws an ellipse.
11777
	 * An ellipse is formed from n Bezier curves.
11778
	 * @param float $x0 Abscissa of center point.
11779
	 * @param float $y0 Ordinate of center point.
11780
	 * @param float $rx Horizontal radius.
11781
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11782
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11783
	 * @param float $astart Angle start of draw line. Default value: 0.
11784
	 * @param float $afinish Angle finish of draw line. Default value: 360.
11785
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11786
	 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11787
	 * @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).
11788
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11789
	 * @author Nicola Asuni
11790
	 * @public
11791
	 * @since 2.1.000 (2008-01-08)
11792
	 */
11793
	public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11794
		if ($this->state != 2) {
11795
			return;
11796
		}
11797
		if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11798
			$ry = $rx;
11799
		}
11800
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11801
			$this->setFillColorArray($fill_color);
11802
		}
11803
		$op = TCPDF_STATIC::getPathPaintOperator($style);
11804
		if ($op == 'f') {
11805
			$line_style = array();
11806
		}
11807
		if ($line_style) {
11808
			$this->setLineStyle($line_style);
11809
		}
11810
		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11811
		$this->_out($op);
11812
	}
11813
 
11814
	/**
11815
	 * Append an elliptical arc to the current path.
11816
	 * An ellipse is formed from n Bezier curves.
11817
	 * @param float $xc Abscissa of center point.
11818
	 * @param float $yc Ordinate of center point.
11819
	 * @param float $rx Horizontal radius.
11820
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11821
	 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11822
	 * @param float $angs Angle start of draw line. Default value: 0.
11823
	 * @param float $angf Angle finish of draw line. Default value: 360.
11824
	 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11825
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11826
	 * @param boolean $startpoint if true output a starting point.
11827
	 * @param boolean $ccw if true draws in counter-clockwise.
11828
	 * @param boolean $svg if true the angles are in svg mode (already calculated).
11829
	 * @return array bounding box coordinates (x min, y min, x max, y max)
11830
	 * @author Nicola Asuni
11831
	 * @protected
11832
	 * @since 4.9.019 (2010-04-26)
11833
	 */
11834
	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11835
		if (($rx <= 0) OR ($ry < 0)) {
11836
			return;
11837
		}
11838
		$k = $this->k;
11839
		if ($nc < 2) {
11840
			$nc = 2;
11841
		}
11842
		$xmin = 2147483647;
11843
		$ymin = 2147483647;
11844
		$xmax = 0;
11845
		$ymax = 0;
11846
		if ($pie) {
11847
			// center of the arc
11848
			$this->_outPoint($xc, $yc);
11849
		}
11850
		$xang = deg2rad((float) $xang);
11851
		$angs = deg2rad((float) $angs);
11852
		$angf = deg2rad((float) $angf);
11853
		if ($svg) {
11854
			$as = $angs;
11855
			$af = $angf;
11856
		} else {
11857
			$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11858
			$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11859
		}
11860
		if ($as < 0) {
11861
			$as += (2 * M_PI);
11862
		}
11863
		if ($af < 0) {
11864
			$af += (2 * M_PI);
11865
		}
11866
		if ($ccw AND ($as > $af)) {
11867
			// reverse rotation
11868
			$as -= (2 * M_PI);
11869
		} elseif (!$ccw AND ($as < $af)) {
11870
			// reverse rotation
11871
			$af -= (2 * M_PI);
11872
		}
11873
		$total_angle = ($af - $as);
11874
		if ($nc < 2) {
11875
			$nc = 2;
11876
		}
11877
		// total arcs to draw
11878
		$nc *= (2 * abs($total_angle) / M_PI);
11879
		$nc = round($nc) + 1;
11880
		// angle of each arc
11881
		$arcang = ($total_angle / $nc);
11882
		// center point in PDF coordinates
11883
		$x0 = $xc;
11884
		$y0 = ($this->h - $yc);
11885
		// starting angle
11886
		$ang = $as;
11887
		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11888
		$cos_xang = cos($xang);
11889
		$sin_xang = sin($xang);
11890
		$cos_ang = cos($ang);
11891
		$sin_ang = sin($ang);
11892
		// first arc point
11893
		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11894
		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11895
		// first Bezier control point
11896
		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11897
		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11898
		if ($pie) {
11899
			// line from center to arc starting point
11900
			$this->_outLine($px1, $this->h - $py1);
11901
		} elseif ($startpoint) {
11902
			// arc starting point
11903
			$this->_outPoint($px1, $this->h - $py1);
11904
		}
11905
		// draw arcs
11906
		for ($i = 1; $i <= $nc; ++$i) {
11907
			// starting angle
11908
			$ang = $as + ($i * $arcang);
11909
			if ($i == $nc) {
11910
				$ang = $af;
11911
			}
11912
			$cos_ang = cos($ang);
11913
			$sin_ang = sin($ang);
11914
			// second arc point
11915
			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11916
			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11917
			// second Bezier control point
11918
			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11919
			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11920
			// draw arc
11921
			$cx1 = ($px1 + $qx1);
11922
			$cy1 = ($this->h - ($py1 + $qy1));
11923
			$cx2 = ($px2 - $qx2);
11924
			$cy2 = ($this->h - ($py2 - $qy2));
11925
			$cx3 = $px2;
11926
			$cy3 = ($this->h - $py2);
11927
			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11928
			// get bounding box coordinates
11929
			$xmin = min($xmin, $cx1, $cx2, $cx3);
11930
			$ymin = min($ymin, $cy1, $cy2, $cy3);
11931
			$xmax = max($xmax, $cx1, $cx2, $cx3);
11932
			$ymax = max($ymax, $cy1, $cy2, $cy3);
11933
			// move to next point
11934
			$px1 = $px2;
11935
			$py1 = $py2;
11936
			$qx1 = $qx2;
11937
			$qy1 = $qy2;
11938
		}
11939
		if ($pie) {
11940
			$this->_outLine($xc, $yc);
11941
			// get bounding box coordinates
11942
			$xmin = min($xmin, $xc);
11943
			$ymin = min($ymin, $yc);
11944
			$xmax = max($xmax, $xc);
11945
			$ymax = max($ymax, $yc);
11946
		}
11947
		return array($xmin, $ymin, $xmax, $ymax);
11948
	}
11949
 
11950
	/**
11951
	 * Draws a circle.
11952
	 * A circle is formed from n Bezier curves.
11953
	 * @param float $x0 Abscissa of center point.
11954
	 * @param float $y0 Ordinate of center point.
11955
	 * @param float $r Radius.
11956
	 * @param float $angstr Angle start of draw line. Default value: 0.
11957
	 * @param float $angend Angle finish of draw line. Default value: 360.
11958
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11959
	 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11960
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11961
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
11962
	 * @public
11963
	 * @since 2.1.000 (2008-01-08)
11964
	 */
11965
	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11966
		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11967
	}
11968
 
11969
	/**
11970
	 * Draws a polygonal line
11971
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11972
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11973
	 * @param array $line_style Line style of polygon. Array with keys among the following:
11974
	 * <ul>
11975
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11976
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11977
	 * </ul>
11978
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11979
	 * @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).
11980
	 * @since 4.8.003 (2009-09-15)
11981
	 * @public
11982
	 */
11983
	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11984
		$this->Polygon($p, $style, $line_style, $fill_color, false);
11985
	}
11986
 
11987
	/**
11988
	 * Draws a polygon.
11989
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11990
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11991
	 * @param array $line_style Line style of polygon. Array with keys among the following:
11992
	 * <ul>
11993
	 *	 <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11994
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11995
	 * </ul>
11996
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11997
	 * @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).
11998
	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
11999
	 * @public
12000
	 * @since 2.1.000 (2008-01-08)
12001
	 */
12002
	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
12003
		if ($this->state != 2) {
12004
			return;
12005
		}
12006
		$nc = count($p); // number of coordinates
12007
		$np = $nc / 2; // number of points
12008
		if ($closed) {
12009
			// close polygon by adding the first 2 points at the end (one line)
12010
			for ($i = 0; $i < 4; ++$i) {
12011
				$p[$nc + $i] = $p[$i];
12012
			}
12013
			// copy style for the last added line
12014
			if (isset($line_style[0])) {
12015
				$line_style[$np] = $line_style[0];
12016
			}
12017
			$nc += 4;
12018
		}
12019
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12020
			$this->setFillColorArray($fill_color);
12021
		}
12022
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12023
		if ($op == 'f') {
12024
			$line_style = array();
12025
		}
12026
		$draw = true;
12027
		if ($line_style) {
12028
			if (isset($line_style['all'])) {
12029
				$this->setLineStyle($line_style['all']);
12030
			} else {
12031
				$draw = false;
12032
				if ($op == 'B') {
12033
					// draw fill
12034
					$op = 'f';
12035
					$this->_outPoint($p[0], $p[1]);
12036
					for ($i = 2; $i < $nc; $i = $i + 2) {
12037
						$this->_outLine($p[$i], $p[$i + 1]);
12038
					}
12039
					$this->_out($op);
12040
				}
12041
				// draw outline
12042
				$this->_outPoint($p[0], $p[1]);
12043
				for ($i = 2; $i < $nc; $i = $i + 2) {
12044
					$line_num = ($i / 2) - 1;
12045
					if (isset($line_style[$line_num])) {
12046
						if ($line_style[$line_num] != 0) {
12047
							if (is_array($line_style[$line_num])) {
12048
								$this->_out('S');
12049
								$this->setLineStyle($line_style[$line_num]);
12050
								$this->_outPoint($p[$i - 2], $p[$i - 1]);
12051
								$this->_outLine($p[$i], $p[$i + 1]);
12052
								$this->_out('S');
12053
								$this->_outPoint($p[$i], $p[$i + 1]);
12054
							} else {
12055
								$this->_outLine($p[$i], $p[$i + 1]);
12056
							}
12057
						}
12058
					} else {
12059
						$this->_outLine($p[$i], $p[$i + 1]);
12060
					}
12061
				}
12062
				$this->_out($op);
12063
			}
12064
		}
12065
		if ($draw) {
12066
			$this->_outPoint($p[0], $p[1]);
12067
			for ($i = 2; $i < $nc; $i = $i + 2) {
12068
				$this->_outLine($p[$i], $p[$i + 1]);
12069
			}
12070
			$this->_out($op);
12071
		}
12072
	}
12073
 
12074
	/**
12075
	 * Draws a regular polygon.
12076
	 * @param float $x0 Abscissa of center point.
12077
	 * @param float $y0 Ordinate of center point.
12078
	 * @param float $r Radius of inscribed circle.
12079
	 * @param integer $ns Number of sides.
12080
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12081
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12082
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12083
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12084
	 * <ul>
12085
	 *	 <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12086
	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12087
	 * </ul>
12088
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12089
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12090
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12091
	 * <ul>
12092
	 *	 <li>D or empty string: Draw (default).</li>
12093
	 *	 <li>F: Fill.</li>
12094
	 *	 <li>DF or FD: Draw and fill.</li>
12095
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12096
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12097
	 * </ul>
12098
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12099
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12100
	 * @public
12101
	 * @since 2.1.000 (2008-01-08)
12102
	 */
12103
	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()) {
12104
		if (3 > $ns) {
12105
			$ns = 3;
12106
		}
12107
		if ($draw_circle) {
12108
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12109
		}
12110
		$p = array();
12111
		for ($i = 0; $i < $ns; ++$i) {
12112
			$a = $angle + ($i * 360 / $ns);
12113
			$a_rad = deg2rad((float) $a);
12114
			$p[] = $x0 + ($r * sin($a_rad));
12115
			$p[] = $y0 + ($r * cos($a_rad));
12116
		}
12117
		$this->Polygon($p, $style, $line_style, $fill_color);
12118
	}
12119
 
12120
	/**
12121
	 * Draws a star polygon
12122
	 * @param float $x0 Abscissa of center point.
12123
	 * @param float $y0 Ordinate of center point.
12124
	 * @param float $r Radius of inscribed circle.
12125
	 * @param integer $nv Number of vertices.
12126
	 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12127
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12128
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12129
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12130
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12131
	 * <ul>
12132
	 *	 <li>all: Line style of all sides. Array like for
12133
	 * SetLineStyle().</li>
12134
	 *	 <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12135
	 * </ul>
12136
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12137
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12138
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12139
	 * <ul>
12140
	 *	 <li>D or empty string: Draw (default).</li>
12141
	 *	 <li>F: Fill.</li>
12142
	 *	 <li>DF or FD: Draw and fill.</li>
12143
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12144
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12145
	 * </ul>
12146
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12147
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12148
	 * @public
12149
	 * @since 2.1.000 (2008-01-08)
12150
	 */
12151
	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()) {
12152
		if ($nv < 2) {
12153
			$nv = 2;
12154
		}
12155
		if ($draw_circle) {
12156
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12157
		}
12158
		$p2 = array();
12159
		$visited = array();
12160
		for ($i = 0; $i < $nv; ++$i) {
12161
			$a = $angle + ($i * 360 / $nv);
12162
			$a_rad = deg2rad((float) $a);
12163
			$p2[] = $x0 + ($r * sin($a_rad));
12164
			$p2[] = $y0 + ($r * cos($a_rad));
12165
			$visited[] = false;
12166
		}
12167
		$p = array();
12168
		$i = 0;
12169
		do {
12170
			$p[] = $p2[$i * 2];
12171
			$p[] = $p2[($i * 2) + 1];
12172
			$visited[$i] = true;
12173
			$i += $ng;
12174
			$i %= $nv;
12175
		} while (!$visited[$i]);
12176
		$this->Polygon($p, $style, $line_style, $fill_color);
12177
	}
12178
 
12179
	/**
12180
	 * Draws a rounded rectangle.
12181
	 * @param float $x Abscissa of upper-left corner.
12182
	 * @param float $y Ordinate of upper-left corner.
12183
	 * @param float $w Width.
12184
	 * @param float $h Height.
12185
	 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12186
	 * @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").
12187
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12188
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12189
	 * @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).
12190
	 * @public
12191
	 * @since 2.1.000 (2008-01-08)
12192
	 */
12193
	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12194
		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12195
	}
12196
 
12197
	/**
12198
	 * Draws a rounded rectangle.
12199
	 * @param float $x Abscissa of upper-left corner.
12200
	 * @param float $y Ordinate of upper-left corner.
12201
	 * @param float $w Width.
12202
	 * @param float $h Height.
12203
	 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12204
	 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12205
	 * @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").
12206
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12207
	 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12208
	 * @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).
12209
	 * @public
12210
	 * @since 4.9.019 (2010-04-22)
12211
	 */
12212
	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12213
		if ($this->state != 2) {
12214
			return;
12215
		}
12216
		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12217
			// Not rounded
12218
			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12219
			return;
12220
		}
12221
		// Rounded
12222
		if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12223
			$this->setFillColorArray($fill_color);
12224
		}
12225
		$op = TCPDF_STATIC::getPathPaintOperator($style);
12226
		if ($op == 'f') {
12227
			$border_style = array();
12228
		}
12229
		if ($border_style) {
12230
			$this->setLineStyle($border_style);
12231
		}
12232
		$MyArc = 4 / 3 * (sqrt(2) - 1);
12233
		$this->_outPoint($x + $rx, $y);
12234
		$xc = $x + $w - $rx;
12235
		$yc = $y + $ry;
12236
		$this->_outLine($xc, $y);
12237
		if ($round_corner[0]) {
12238
			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12239
		} else {
12240
			$this->_outLine($x + $w, $y);
12241
		}
12242
		$xc = $x + $w - $rx;
12243
		$yc = $y + $h - $ry;
12244
		$this->_outLine($x + $w, $yc);
12245
		if ($round_corner[1]) {
12246
			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12247
		} else {
12248
			$this->_outLine($x + $w, $y + $h);
12249
		}
12250
		$xc = $x + $rx;
12251
		$yc = $y + $h - $ry;
12252
		$this->_outLine($xc, $y + $h);
12253
		if ($round_corner[2]) {
12254
			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12255
		} else {
12256
			$this->_outLine($x, $y + $h);
12257
		}
12258
		$xc = $x + $rx;
12259
		$yc = $y + $ry;
12260
		$this->_outLine($x, $yc);
12261
		if ($round_corner[3]) {
12262
			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12263
		} else {
12264
			$this->_outLine($x, $y);
12265
			$this->_outLine($x + $rx, $y);
12266
		}
12267
		$this->_out($op);
12268
	}
12269
 
12270
	/**
12271
	 * Draws a grahic arrow.
12272
	 * @param float $x0 Abscissa of first point.
12273
	 * @param float $y0 Ordinate of first point.
12274
	 * @param float $x1 Abscissa of second point.
12275
	 * @param float $y1 Ordinate of second point.
12276
	 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12277
	 * @param float $arm_size length of arrowhead arms
12278
	 * @param int $arm_angle angle between an arm and the shaft
12279
	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12280
	 * @since 4.6.018 (2009-07-10)
12281
	 */
12282
	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12283
		// getting arrow direction angle
12284
		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
12285
		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12286
		if ($dir_angle < 0) {
12287
			$dir_angle += (2 * M_PI);
12288
		}
12289
		$arm_angle = deg2rad($arm_angle);
12290
		$sx1 = $x1;
12291
		$sy1 = $y1;
12292
		if ($head_style > 0) {
12293
			// calculate the stopping point for the arrow shaft
12294
			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12295
			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12296
		}
12297
		// main arrow line / shaft
12298
		$this->Line($x0, $y0, $sx1, $sy1);
12299
		// left arrowhead arm tip
12300
		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12301
		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12302
		// right arrowhead arm tip
12303
		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12304
		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12305
		$mode = 'D';
12306
		$style = array();
12307
		switch ($head_style) {
12308
			case 0: {
12309
				// draw only arrowhead arms
12310
				$mode = 'D';
12311
				$style = array(1, 1, 0);
12312
				break;
12313
			}
12314
			case 1: {
12315
				// draw closed arrowhead, but no fill
12316
				$mode = 'D';
12317
				break;
12318
			}
12319
			case 2: {
12320
				// closed and filled arrowhead
12321
				$mode = 'DF';
12322
				break;
12323
			}
12324
			case 3: {
12325
				// filled arrowhead
12326
				$mode = 'F';
12327
				break;
12328
			}
12329
		}
12330
		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12331
	}
12332
 
12333
	// END GRAPHIC FUNCTIONS SECTION -----------------------
12334
 
12335
	/**
12336
	 * Add a Named Destination.
12337
	 * NOTE: destination names are unique, so only last entry will be saved.
12338
	 * @param string $name Destination name.
12339
	 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12340
	 * @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.
12341
	 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12342
	 * @return string|false Stripped named destination identifier or false in case of error.
12343
	 * @public
12344
	 * @author Christian Deligant, Nicola Asuni
12345
	 * @since 5.9.097 (2011-06-23)
12346
	 */
12347
	public function setDestination($name, $y=-1, $page='', $x=-1) {
12348
		// remove unsupported characters
12349
		$name = TCPDF_STATIC::encodeNameObject($name);
12350
		if (TCPDF_STATIC::empty_string($name)) {
12351
			return false;
12352
		}
12353
		if ($y == -1) {
12354
			$y = $this->GetY();
12355
		} elseif ($y < 0) {
12356
			$y = 0;
12357
		} elseif ($y > $this->h) {
12358
			$y = $this->h;
12359
		}
12360
		if ($x == -1) {
12361
			$x = $this->GetX();
12362
		} elseif ($x < 0) {
12363
			$x = 0;
12364
		} elseif ($x > $this->w) {
12365
			$x = $this->w;
12366
		}
12367
		$fixed = false;
12368
		if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12369
			$page = intval(substr($page, 1));
12370
			// this page number will not be changed when moving/add/deleting pages
12371
			$fixed = true;
12372
		}
12373
		if (empty($page)) {
12374
			$page = $this->PageNo();
12375
			if (empty($page)) {
12376
				return;
12377
			}
12378
		}
12379
		$this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12380
		return $name;
12381
	}
12382
 
12383
	/**
12384
	 * Return the Named Destination array.
12385
	 * @return array Named Destination array.
12386
	 * @public
12387
	 * @author Nicola Asuni
12388
	 * @since 5.9.097 (2011-06-23)
12389
	 */
12390
	public function getDestination() {
12391
		return $this->dests;
12392
	}
12393
 
12394
	/**
12395
	 * Insert Named Destinations.
12396
	 * @protected
12397
	 * @author Johannes G\FCntert, Nicola Asuni
12398
	 * @since 5.9.098 (2011-06-23)
12399
	 */
12400
	protected function _putdests() {
12401
		if (empty($this->dests)) {
12402
			return;
12403
		}
12404
		$this->n_dests = $this->_newobj();
12405
		$out = ' <<';
12406
		foreach($this->dests as $name => $o) {
12407
			$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)));
12408
		}
12409
		$out .= ' >>';
12410
		$out .= "\n".'endobj';
12411
		$this->_out($out);
12412
	}
12413
 
12414
	/**
12415
	 * Adds a bookmark - alias for Bookmark().
12416
	 * @param string $txt Bookmark description.
12417
	 * @param int $level Bookmark level (minimum value is 0).
12418
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12419
	 * @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.
12420
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12421
	 * @param array $color RGB color array (values from 0 to 255).
12422
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12423
	 * @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).
12424
	 * @public
12425
	 */
12426
	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12427
		$this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12428
	}
12429
 
12430
	/**
12431
	 * Adds a bookmark.
12432
	 * @param string $txt Bookmark description.
12433
	 * @param int $level Bookmark level (minimum value is 0).
12434
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12435
	 * @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.
12436
	 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12437
	 * @param array $color RGB color array (values from 0 to 255).
12438
	 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12439
	 * @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).
12440
	 * @public
12441
	 * @since 2.1.002 (2008-02-12)
12442
	 */
12443
	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12444
		if ($level < 0) {
12445
			$level = 0;
12446
		}
12447
		if (isset($this->outlines[0])) {
12448
			$lastoutline = end($this->outlines);
12449
			$maxlevel = $lastoutline['l'] + 1;
12450
		} else {
12451
			$maxlevel = 0;
12452
		}
12453
		if ($level > $maxlevel) {
12454
			$level = $maxlevel;
12455
		}
12456
		if ($y == -1) {
12457
			$y = $this->GetY();
12458
		} elseif ($y < 0) {
12459
			$y = 0;
12460
		} elseif ($y > $this->h) {
12461
			$y = $this->h;
12462
		}
12463
		if ($x == -1) {
12464
			$x = $this->GetX();
12465
		} elseif ($x < 0) {
12466
			$x = 0;
12467
		} elseif ($x > $this->w) {
12468
			$x = $this->w;
12469
		}
12470
		$fixed = false;
12471
		$pageAsString = (string) $page;
12472
		if ($pageAsString && $pageAsString[0] == '*') {
12473
			$page = intval(substr($page, 1));
12474
			// this page number will not be changed when moving/add/deleting pages
12475
			$fixed = true;
12476
		}
12477
		if (empty($page)) {
12478
			$page = $this->PageNo();
12479
			if (empty($page)) {
12480
				return;
12481
			}
12482
		}
12483
		$this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12484
	}
12485
 
12486
	/**
12487
	 * Sort bookmarks for page and key.
12488
	 * @protected
12489
	 * @since 5.9.119 (2011-09-19)
12490
	 */
12491
	protected function sortBookmarks() {
12492
		// get sorting columns
12493
		$outline_p = array();
12494
		$outline_y = array();
12495
		foreach ($this->outlines as $key => $row) {
12496
			$outline_p[$key] = $row['p'];
12497
			$outline_k[$key] = $key;
12498
		}
12499
		// sort outlines by page and original position
12500
		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12501
	}
12502
 
12503
	/**
12504
	 * Create a bookmark PDF string.
12505
	 * @protected
12506
	 * @author Olivier Plathey, Nicola Asuni
12507
	 * @since 2.1.002 (2008-02-12)
12508
	 */
12509
	protected function _putbookmarks() {
12510
		$nb = count($this->outlines);
12511
		if ($nb == 0) {
12512
			return;
12513
		}
12514
		// sort bookmarks
12515
		$this->sortBookmarks();
12516
		$lru = array();
12517
		$level = 0;
12518
		foreach ($this->outlines as $i => $o) {
12519
			if ($o['l'] > 0) {
12520
				$parent = $lru[($o['l'] - 1)];
12521
				//Set parent and last pointers
12522
				$this->outlines[$i]['parent'] = $parent;
12523
				$this->outlines[$parent]['last'] = $i;
12524
				if ($o['l'] > $level) {
12525
					//Level increasing: set first pointer
12526
					$this->outlines[$parent]['first'] = $i;
12527
				}
12528
			} else {
12529
				$this->outlines[$i]['parent'] = $nb;
12530
			}
12531
			if (($o['l'] <= $level) AND ($i > 0)) {
12532
				//Set prev and next pointers
12533
				$prev = $lru[$o['l']];
12534
				$this->outlines[$prev]['next'] = $i;
12535
				$this->outlines[$i]['prev'] = $prev;
12536
			}
12537
			$lru[$o['l']] = $i;
12538
			$level = $o['l'];
12539
		}
12540
		//Outline items
12541
		$n = $this->n + 1;
12542
		$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';
12543
		foreach ($this->outlines as $i => $o) {
12544
			$oid = $this->_newobj();
12545
			// covert HTML title to string
12546
			$title = preg_replace($nltags, "\n", $o['t']);
12547
			$title = preg_replace("/[\r]+/si", '', $title);
12548
			$title = preg_replace("/[\n]+/si", "\n", $title);
12549
			$title = strip_tags($title);
12550
			$title = $this->stringTrim($title);
12551
			$out = '<</Title '.$this->_textstring($title, $oid);
12552
			$out .= ' /Parent '.($n + $o['parent']).' 0 R';
12553
			if (isset($o['prev'])) {
12554
				$out .= ' /Prev '.($n + $o['prev']).' 0 R';
12555
			}
12556
			if (isset($o['next'])) {
12557
				$out .= ' /Next '.($n + $o['next']).' 0 R';
12558
			}
12559
			if (isset($o['first'])) {
12560
				$out .= ' /First '.($n + $o['first']).' 0 R';
12561
			}
12562
			if (isset($o['last'])) {
12563
				$out .= ' /Last '.($n + $o['last']).' 0 R';
12564
			}
12565
			if (isset($o['u']) AND !empty($o['u'])) {
12566
				// link
12567
				if (is_string($o['u'])) {
12568
					if ($o['u'][0] == '#') {
12569
						// internal destination
12570
						$out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12571
					} elseif ($o['u'][0] == '%') {
12572
						// embedded PDF file
12573
						$filename = basename(substr($o['u'], 1));
12574
						$out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12575
					} elseif ($o['u'][0] == '*') {
12576
						// embedded generic file
12577
						$filename = basename(substr($o['u'], 1));
12578
						$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});';
12579
						$out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12580
					} else {
12581
						// external URI link
12582
						$out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12583
					}
12584
				} elseif (isset($this->links[$o['u']])) {
12585
					// internal link ID
12586
					$l = $this->links[$o['u']];
12587
					if (isset($this->page_obj_id[($l['p'])])) {
12588
						$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)));
12589
					}
12590
				}
12591
			} elseif (isset($this->page_obj_id[($o['p'])])) {
12592
				// link to a page
12593
				$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)));
12594
			}
12595
			// set font style
12596
			$style = 0;
12597
			if (!empty($o['s'])) {
12598
				// bold
12599
				if (strpos($o['s'], 'B') !== false) {
12600
					$style |= 2;
12601
				}
12602
				// oblique
12603
				if (strpos($o['s'], 'I') !== false) {
12604
					$style |= 1;
12605
				}
12606
			}
12607
			$out .= sprintf(' /F %d', $style);
12608
			// set bookmark color
12609
			if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12610
				$color = array_values($o['c']);
12611
				$out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12612
			} else {
12613
				// black
12614
				$out .= ' /C [0.0 0.0 0.0]';
12615
			}
12616
			$out .= ' /Count 0'; // normally closed item
12617
			$out .= ' >>';
12618
			$out .= "\n".'endobj';
12619
			$this->_out($out);
12620
		}
12621
		//Outline root
12622
		$this->OutlineRoot = $this->_newobj();
12623
		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12624
	}
12625
 
12626
	// --- JAVASCRIPT ------------------------------------------------------
12627
 
12628
	/**
12629
	 * Adds a javascript
12630
	 * @param string $script Javascript code
12631
	 * @public
12632
	 * @author Johannes G\FCntert, Nicola Asuni
12633
	 * @since 2.1.002 (2008-02-12)
12634
	 */
12635
	public function IncludeJS($script) {
12636
		$this->javascript .= $script;
12637
	}
12638
 
12639
	/**
12640
	 * Adds a javascript object and return object ID
12641
	 * @param string $script Javascript code
12642
	 * @param boolean $onload if true executes this object when opening the document
12643
	 * @return int internal object ID
12644
	 * @public
12645
	 * @author Nicola Asuni
12646
	 * @since 4.8.000 (2009-09-07)
12647
	 */
12648
	public function addJavascriptObject($script, $onload=false) {
12649
		if ($this->pdfa_mode) {
12650
			// javascript is not allowed in PDF/A mode
12651
			return false;
12652
		}
12653
		++$this->n;
12654
		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12655
		return $this->n;
12656
	}
12657
 
12658
	/**
12659
	 * Create a javascript PDF string.
12660
	 * @protected
12661
	 * @author Johannes G\FCntert, Nicola Asuni
12662
	 * @since 2.1.002 (2008-02-12)
12663
	 */
12664
	protected function _putjavascript() {
12665
		if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12666
			return;
12667
		}
12668
		if (strpos($this->javascript, 'this.addField') > 0) {
12669
			if (!$this->ur['enabled']) {
12670
				//$this->setUserRights();
12671
			}
12672
			// the following two lines are used to avoid form fields duplication after saving
12673
			// The addField method only works when releasing user rights (UR3)
12674
			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12675
			$jsb = "getField('tcpdfdocsaved').value='saved';";
12676
			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12677
		}
12678
		// name tree for javascript
12679
		$this->n_js = '<< /Names [';
12680
		if (!empty($this->javascript)) {
12681
			$this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12682
		}
12683
		if (!empty($this->js_objects)) {
12684
			foreach ($this->js_objects as $key => $val) {
12685
				if ($val['onload']) {
12686
					$this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12687
				}
12688
			}
12689
		}
12690
		$this->n_js .= ' ] >>';
12691
		// default Javascript object
12692
		if (!empty($this->javascript)) {
12693
			$obj_id = $this->_newobj();
12694
			$out = '<< /S /JavaScript';
12695
			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12696
			$out .= ' >>';
12697
			$out .= "\n".'endobj';
12698
			$this->_out($out);
12699
		}
12700
		// additional Javascript objects
12701
		if (!empty($this->js_objects)) {
12702
			foreach ($this->js_objects as $key => $val) {
12703
				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12704
				$this->_out($out);
12705
			}
12706
		}
12707
	}
12708
 
12709
	/**
12710
	 * Adds a javascript form field.
12711
	 * @param string $type field type
12712
	 * @param string $name field name
12713
	 * @param int $x horizontal position
12714
	 * @param int $y vertical position
12715
	 * @param int $w width
12716
	 * @param int $h height
12717
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12718
	 * @protected
12719
	 * @author Denis Van Nuffelen, Nicola Asuni
12720
	 * @since 2.1.002 (2008-02-12)
12721
	 */
12722
	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12723
		if ($this->rtl) {
12724
			$x = $x - $w;
12725
		}
12726
		// the followind avoid fields duplication after saving the document
12727
		$this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12728
		$k = $this->k;
12729
		$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";
12730
		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12731
		foreach($prop as $key => $val) {
12732
			if (strcmp(substr($key, -5), 'Color') == 0) {
12733
				$val = TCPDF_COLORS::_JScolor($val);
12734
			} else {
12735
				$val = "'".$val."'";
12736
			}
12737
			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12738
		}
12739
		if ($this->rtl) {
12740
			$this->x -= $w;
12741
		} else {
12742
			$this->x += $w;
12743
		}
12744
		$this->javascript .= '}';
12745
	}
12746
 
12747
	// --- FORM FIELDS -----------------------------------------------------
12748
 
12749
 
12750
 
12751
	/**
12752
	 * Set default properties for form fields.
12753
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12754
	 * @public
12755
	 * @author Nicola Asuni
12756
	 * @since 4.8.000 (2009-09-06)
12757
	 */
12758
	public function setFormDefaultProp($prop=array()) {
12759
		$this->default_form_prop = $prop;
12760
	}
12761
 
12762
	/**
12763
	 * Return the default properties for form fields.
12764
	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12765
	 * @public
12766
	 * @author Nicola Asuni
12767
	 * @since 4.8.000 (2009-09-06)
12768
	 */
12769
	public function getFormDefaultProp() {
12770
		return $this->default_form_prop;
12771
	}
12772
 
12773
	/**
12774
	 * Creates a text field
12775
	 * @param string $name field name
12776
	 * @param float $w Width of the rectangle
12777
	 * @param float $h Height of the rectangle
12778
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12779
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12780
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12781
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12782
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12783
	 * @public
12784
	 * @author Nicola Asuni
12785
	 * @since 4.8.000 (2009-09-07)
12786
	 */
12787
	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
12788
		if (TCPDF_STATIC::empty_string($x)) {
12789
			$x = $this->x;
12790
		}
12791
		if (TCPDF_STATIC::empty_string($y)) {
12792
			$y = $this->y;
12793
		}
12794
		// check page for no-write regions and adapt page margins if necessary
12795
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
12796
		if ($js) {
12797
			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12798
			return;
12799
		}
12800
		// get default style
12801
		$prop = array_merge($this->getFormDefaultProp(), $prop);
12802
		// get annotation data
12803
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12804
		// set default appearance stream
12805
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12806
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12807
		$popt['da'] = $fontstyle;
12808
		// build appearance stream
12809
		$popt['ap'] = array();
12810
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12811
		$text = '';
12812
		if (isset($prop['value']) AND !empty($prop['value'])) {
12813
			$text = $prop['value'];
12814
		} elseif (isset($opt['v']) AND !empty($opt['v'])) {
12815
			$text = $opt['v'];
12816
		}
12817
		$tmpid = $this->startTemplate($w, $h, false);
12818
		$align = '';
12819
		if (isset($popt['q'])) {
12820
			switch ($popt['q']) {
12821
				case 0: {
12822
					$align = 'L';
12823
					break;
12824
				}
12825
				case 1: {
12826
					$align = 'C';
12827
					break;
12828
				}
12829
				case 2: {
12830
					$align = 'R';
12831
					break;
12832
				}
12833
				default: {
12834
					$align = '';
12835
					break;
12836
				}
12837
			}
12838
		}
12839
		$this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12840
		$this->endTemplate();
12841
		--$this->n;
12842
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12843
		unset($this->xobjects[$tmpid]);
12844
		$popt['ap']['n'] .= 'Q EMC';
12845
		// merge options
12846
		$opt = array_merge($popt, $opt);
12847
		// remove some conflicting options
12848
		unset($opt['bs']);
12849
		// set remaining annotation data
12850
		$opt['Subtype'] = 'Widget';
12851
		$opt['ft'] = 'Tx';
12852
		$opt['t'] = $name;
12853
		// Additional annotation's parameters (check _putannotsobj() method):
12854
		//$opt['f']
12855
		//$opt['as']
12856
		//$opt['bs']
12857
		//$opt['be']
12858
		//$opt['c']
12859
		//$opt['border']
12860
		//$opt['h']
12861
		//$opt['mk'];
12862
		//$opt['mk']['r']
12863
		//$opt['mk']['bc'];
12864
		//$opt['mk']['bg'];
12865
		unset($opt['mk']['ca']);
12866
		unset($opt['mk']['rc']);
12867
		unset($opt['mk']['ac']);
12868
		unset($opt['mk']['i']);
12869
		unset($opt['mk']['ri']);
12870
		unset($opt['mk']['ix']);
12871
		unset($opt['mk']['if']);
12872
		//$opt['mk']['if']['sw'];
12873
		//$opt['mk']['if']['s'];
12874
		//$opt['mk']['if']['a'];
12875
		//$opt['mk']['if']['fb'];
12876
		unset($opt['mk']['tp']);
12877
		//$opt['tu']
12878
		//$opt['tm']
12879
		//$opt['ff']
12880
		//$opt['v']
12881
		//$opt['dv']
12882
		//$opt['a']
12883
		//$opt['aa']
12884
		//$opt['q']
12885
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12886
		if ($this->rtl) {
12887
			$this->x -= $w;
12888
		} else {
12889
			$this->x += $w;
12890
		}
12891
	}
12892
 
12893
	/**
12894
	 * Creates a RadioButton field.
12895
	 * @param string $name Field name.
12896
	 * @param int $w Width of the radio button.
12897
	 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12898
	 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12899
	 * @param string $onvalue Value to be returned if selected.
12900
	 * @param boolean $checked Define the initial state.
12901
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12902
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12903
	 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12904
	 * @public
12905
	 * @author Nicola Asuni
12906
	 * @since 4.8.000 (2009-09-07)
12907
	 */
12908
	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) {
12909
		if (TCPDF_STATIC::empty_string($x)) {
12910
			$x = $this->x;
12911
		}
12912
		if (TCPDF_STATIC::empty_string($y)) {
12913
			$y = $this->y;
12914
		}
12915
		// check page for no-write regions and adapt page margins if necessary
12916
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
12917
		if ($js) {
12918
			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12919
			return;
12920
		}
12921
		if (TCPDF_STATIC::empty_string($onvalue)) {
12922
			$onvalue = 'On';
12923
		}
12924
		if ($checked) {
12925
			$defval = $onvalue;
12926
		} else {
12927
			$defval = 'Off';
12928
		}
12929
		// set font
12930
		$font = 'zapfdingbats';
12931
		if ($this->pdfa_mode) {
12932
			// all fonts must be embedded
12933
			$font = 'pdfa'.$font;
12934
		}
12935
		$this->AddFont($font);
12936
		$tmpfont = $this->getFontBuffer($font);
12937
		// set data for parent group
12938
		if (!isset($this->radiobutton_groups[$this->page])) {
12939
			$this->radiobutton_groups[$this->page] = array();
12940
		}
12941
		if (!isset($this->radiobutton_groups[$this->page][$name])) {
12942
			$this->radiobutton_groups[$this->page][$name] = array();
12943
			++$this->n;
12944
			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12945
			$this->radio_groups[] = $this->n;
12946
		}
12947
		$kid = ($this->n + 1);
12948
		// save object ID to be added on Kids entry on parent object
12949
		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12950
		// get default style
12951
		$prop = array_merge($this->getFormDefaultProp(), $prop);
12952
		$prop['NoToggleToOff'] = 'true';
12953
		$prop['Radio'] = 'true';
12954
		$prop['borderStyle'] = 'inset';
12955
		// get annotation data
12956
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12957
		// set additional default options
12958
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12959
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12960
		$popt['da'] = $fontstyle;
12961
		// build appearance stream
12962
		$popt['ap'] = array();
12963
		$popt['ap']['n'] = array();
12964
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12965
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12966
		$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);
12967
		$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);
12968
		if (!isset($popt['mk'])) {
12969
			$popt['mk'] = array();
12970
		}
12971
		$popt['mk']['ca'] = '(l)';
12972
		// merge options
12973
		$opt = array_merge($popt, $opt);
12974
		// set remaining annotation data
12975
		$opt['Subtype'] = 'Widget';
12976
		$opt['ft'] = 'Btn';
12977
		if ($checked) {
12978
			$opt['v'] = array('/'.$onvalue);
12979
			$opt['as'] = $onvalue;
12980
		} else {
12981
			$opt['as'] = 'Off';
12982
		}
12983
		// store readonly flag
12984
		if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12985
			$this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12986
		}
12987
		$this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12988
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12989
		if ($this->rtl) {
12990
			$this->x -= $w;
12991
		} else {
12992
			$this->x += $w;
12993
		}
12994
	}
12995
 
12996
	/**
12997
	 * Creates a List-box field
12998
	 * @param string $name field name
12999
	 * @param int $w width
13000
	 * @param int $h height
13001
	 * @param array $values array containing the list of values.
13002
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13003
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13004
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13005
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13006
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13007
	 * @public
13008
	 * @author Nicola Asuni
13009
	 * @since 4.8.000 (2009-09-07)
13010
	 */
13011
	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13012
		if (TCPDF_STATIC::empty_string($x)) {
13013
			$x = $this->x;
13014
		}
13015
		if (TCPDF_STATIC::empty_string($y)) {
13016
			$y = $this->y;
13017
		}
13018
		// check page for no-write regions and adapt page margins if necessary
13019
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13020
		if ($js) {
13021
			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13022
			$s = '';
13023
			foreach ($values as $value) {
13024
				if (is_array($value)) {
13025
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13026
				} else {
13027
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13028
				}
13029
			}
13030
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13031
			return;
13032
		}
13033
		// get default style
13034
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13035
		// get annotation data
13036
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13037
		// set additional default values
13038
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13039
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13040
		$popt['da'] = $fontstyle;
13041
		// build appearance stream
13042
		$popt['ap'] = array();
13043
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13044
		$text = '';
13045
		foreach($values as $item) {
13046
			if (is_array($item)) {
13047
				$text .= $item[1]."\n";
13048
			} else {
13049
				$text .= $item."\n";
13050
			}
13051
		}
13052
		$tmpid = $this->startTemplate($w, $h, false);
13053
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13054
		$this->endTemplate();
13055
		--$this->n;
13056
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13057
		unset($this->xobjects[$tmpid]);
13058
		$popt['ap']['n'] .= 'Q EMC';
13059
		// merge options
13060
		$opt = array_merge($popt, $opt);
13061
		// set remaining annotation data
13062
		$opt['Subtype'] = 'Widget';
13063
		$opt['ft'] = 'Ch';
13064
		$opt['t'] = $name;
13065
		$opt['opt'] = $values;
13066
		unset($opt['mk']['ca']);
13067
		unset($opt['mk']['rc']);
13068
		unset($opt['mk']['ac']);
13069
		unset($opt['mk']['i']);
13070
		unset($opt['mk']['ri']);
13071
		unset($opt['mk']['ix']);
13072
		unset($opt['mk']['if']);
13073
		unset($opt['mk']['tp']);
13074
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13075
		if ($this->rtl) {
13076
			$this->x -= $w;
13077
		} else {
13078
			$this->x += $w;
13079
		}
13080
	}
13081
 
13082
	/**
13083
	 * Creates a Combo-box field
13084
	 * @param string $name field name
13085
	 * @param int $w width
13086
	 * @param int $h height
13087
	 * @param array $values array containing the list of values.
13088
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13089
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13090
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13091
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13092
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13093
	 * @public
13094
	 * @author Nicola Asuni
13095
	 * @since 4.8.000 (2009-09-07)
13096
	 */
13097
	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13098
		if (TCPDF_STATIC::empty_string($x)) {
13099
			$x = $this->x;
13100
		}
13101
		if (TCPDF_STATIC::empty_string($y)) {
13102
			$y = $this->y;
13103
		}
13104
		// check page for no-write regions and adapt page margins if necessary
13105
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13106
		if ($js) {
13107
			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13108
			$s = '';
13109
			foreach ($values as $value) {
13110
				if (is_array($value)) {
13111
					$s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13112
				} else {
13113
					$s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13114
				}
13115
			}
13116
			$this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13117
			return;
13118
		}
13119
		// get default style
13120
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13121
		$prop['Combo'] = true;
13122
		// get annotation data
13123
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13124
		// set additional default options
13125
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13126
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13127
		$popt['da'] = $fontstyle;
13128
		// build appearance stream
13129
		$popt['ap'] = array();
13130
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13131
		$text = '';
13132
		foreach($values as $item) {
13133
			if (is_array($item)) {
13134
				$text .= $item[1]."\n";
13135
			} else {
13136
				$text .= $item."\n";
13137
			}
13138
		}
13139
		$tmpid = $this->startTemplate($w, $h, false);
13140
		$this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13141
		$this->endTemplate();
13142
		--$this->n;
13143
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13144
		unset($this->xobjects[$tmpid]);
13145
		$popt['ap']['n'] .= 'Q EMC';
13146
		// merge options
13147
		$opt = array_merge($popt, $opt);
13148
		// set remaining annotation data
13149
		$opt['Subtype'] = 'Widget';
13150
		$opt['ft'] = 'Ch';
13151
		$opt['t'] = $name;
13152
		$opt['opt'] = $values;
13153
		unset($opt['mk']['ca']);
13154
		unset($opt['mk']['rc']);
13155
		unset($opt['mk']['ac']);
13156
		unset($opt['mk']['i']);
13157
		unset($opt['mk']['ri']);
13158
		unset($opt['mk']['ix']);
13159
		unset($opt['mk']['if']);
13160
		unset($opt['mk']['tp']);
13161
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13162
		if ($this->rtl) {
13163
			$this->x -= $w;
13164
		} else {
13165
			$this->x += $w;
13166
		}
13167
	}
13168
 
13169
	/**
13170
	 * Creates a CheckBox field
13171
	 * @param string $name field name
13172
	 * @param int $w width
13173
	 * @param boolean $checked define the initial state.
13174
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13175
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13176
	 * @param string $onvalue value to be returned if selected.
13177
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13178
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13179
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13180
	 * @public
13181
	 * @author Nicola Asuni
13182
	 * @since 4.8.000 (2009-09-07)
13183
	 */
13184
	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) {
13185
		if (TCPDF_STATIC::empty_string($x)) {
13186
			$x = $this->x;
13187
		}
13188
		if (TCPDF_STATIC::empty_string($y)) {
13189
			$y = $this->y;
13190
		}
13191
		// check page for no-write regions and adapt page margins if necessary
13192
		list($x, $y) = $this->checkPageRegions($w, $x, $y);
13193
		if ($js) {
13194
			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13195
			return;
13196
		}
13197
		if (!isset($prop['value'])) {
13198
			$prop['value'] = array('Yes');
13199
		}
13200
		// get default style
13201
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13202
		$prop['borderStyle'] = 'inset';
13203
		// get annotation data
13204
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13205
		// set additional default options
13206
		$font = 'zapfdingbats';
13207
		if ($this->pdfa_mode) {
13208
			// all fonts must be embedded
13209
			$font = 'pdfa'.$font;
13210
		}
13211
		$this->AddFont($font);
13212
		$tmpfont = $this->getFontBuffer($font);
13213
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13214
		$fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13215
		$popt['da'] = $fontstyle;
13216
		// build appearance stream
13217
		$popt['ap'] = array();
13218
		$popt['ap']['n'] = array();
13219
		$fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13220
		$fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13221
		$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);
13222
		$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);
13223
		// merge options
13224
		$opt = array_merge($popt, $opt);
13225
		// set remaining annotation data
13226
		$opt['Subtype'] = 'Widget';
13227
		$opt['ft'] = 'Btn';
13228
		$opt['t'] = $name;
13229
		if (TCPDF_STATIC::empty_string($onvalue)) {
13230
			$onvalue = 'Yes';
13231
		}
13232
		$opt['opt'] = array($onvalue);
13233
		if ($checked) {
13234
			$opt['v'] = array('/Yes');
13235
			$opt['as'] = 'Yes';
13236
		} else {
13237
			$opt['v'] = array('/Off');
13238
			$opt['as'] = 'Off';
13239
		}
13240
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13241
		if ($this->rtl) {
13242
			$this->x -= $w;
13243
		} else {
13244
			$this->x += $w;
13245
		}
13246
	}
13247
 
13248
	/**
13249
	 * Creates a button field
13250
	 * @param string $name field name
13251
	 * @param int $w width
13252
	 * @param int $h height
13253
	 * @param string $caption caption.
13254
	 * @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.
13255
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13256
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13257
	 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13258
	 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13259
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13260
	 * @public
13261
	 * @author Nicola Asuni
13262
	 * @since 4.8.000 (2009-09-07)
13263
	 */
13264
	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13265
		if (TCPDF_STATIC::empty_string($x)) {
13266
			$x = $this->x;
13267
		}
13268
		if (TCPDF_STATIC::empty_string($y)) {
13269
			$y = $this->y;
13270
		}
13271
		// check page for no-write regions and adapt page margins if necessary
13272
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
13273
		if ($js) {
13274
			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13275
			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13276
			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13277
			$this->javascript .= 'f'.$name.".highlight='push';\n";
13278
			$this->javascript .= 'f'.$name.".print=false;\n";
13279
			return;
13280
		}
13281
		// get default style
13282
		$prop = array_merge($this->getFormDefaultProp(), $prop);
13283
		$prop['Pushbutton'] = 'true';
13284
		$prop['highlight'] = 'push';
13285
		$prop['display'] = 'display.noPrint';
13286
		// get annotation data
13287
		$popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13288
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13289
		$fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13290
		$popt['da'] = $fontstyle;
13291
		// build appearance stream
13292
		$popt['ap'] = array();
13293
		$popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13294
		$tmpid = $this->startTemplate($w, $h, false);
13295
		$bw = (2 / $this->k); // border width
13296
		$border = array(
13297
			'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13298
			'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13299
			'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13300
			'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13301
		$this->setFillColor(204);
13302
		$this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13303
		$this->endTemplate();
13304
		--$this->n;
13305
		$popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13306
		unset($this->xobjects[$tmpid]);
13307
		$popt['ap']['n'] .= 'Q EMC';
13308
		// set additional default options
13309
		if (!isset($popt['mk'])) {
13310
			$popt['mk'] = array();
13311
		}
13312
		$ann_obj_id = ($this->n + 1);
13313
		if (!empty($action) AND !is_array($action)) {
13314
			$ann_obj_id = ($this->n + 2);
13315
		}
13316
		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13317
		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13318
		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13319
		// merge options
13320
		$opt = array_merge($popt, $opt);
13321
		// set remaining annotation data
13322
		$opt['Subtype'] = 'Widget';
13323
		$opt['ft'] = 'Btn';
13324
		$opt['t'] = $caption;
13325
		$opt['v'] = $name;
13326
		if (!empty($action)) {
13327
			if (is_array($action)) {
13328
				// form action options as on section 12.7.5 of PDF32000_2008.
13329
				$opt['aa'] = '/D <<';
13330
				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13331
				foreach ($action AS $key => $val) {
13332
					if (($key == 'S') AND in_array($val, $bmode)) {
13333
						$opt['aa'] .= ' /S /'.$val;
13334
					} elseif (($key == 'F') AND (!empty($val))) {
13335
						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13336
					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13337
						$opt['aa'] .= ' /Fields [';
13338
						foreach ($val AS $field) {
13339
							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13340
						}
13341
						$opt['aa'] .= ']';
13342
					} elseif (($key == 'Flags')) {
13343
						$ff = 0;
13344
						if (is_array($val)) {
13345
							foreach ($val AS $flag) {
13346
								switch ($flag) {
13347
									case 'Include/Exclude': {
13348
										$ff += 1 << 0;
13349
										break;
13350
									}
13351
									case 'IncludeNoValueFields': {
13352
										$ff += 1 << 1;
13353
										break;
13354
									}
13355
									case 'ExportFormat': {
13356
										$ff += 1 << 2;
13357
										break;
13358
									}
13359
									case 'GetMethod': {
13360
										$ff += 1 << 3;
13361
										break;
13362
									}
13363
									case 'SubmitCoordinates': {
13364
										$ff += 1 << 4;
13365
										break;
13366
									}
13367
									case 'XFDF': {
13368
										$ff += 1 << 5;
13369
										break;
13370
									}
13371
									case 'IncludeAppendSaves': {
13372
										$ff += 1 << 6;
13373
										break;
13374
									}
13375
									case 'IncludeAnnotations': {
13376
										$ff += 1 << 7;
13377
										break;
13378
									}
13379
									case 'SubmitPDF': {
13380
										$ff += 1 << 8;
13381
										break;
13382
									}
13383
									case 'CanonicalFormat': {
13384
										$ff += 1 << 9;
13385
										break;
13386
									}
13387
									case 'ExclNonUserAnnots': {
13388
										$ff += 1 << 10;
13389
										break;
13390
									}
13391
									case 'ExclFKey': {
13392
										$ff += 1 << 11;
13393
										break;
13394
									}
13395
									case 'EmbedForm': {
13396
										$ff += 1 << 13;
13397
										break;
13398
									}
13399
								}
13400
							}
13401
						} else {
13402
							$ff = intval($val);
13403
						}
13404
						$opt['aa'] .= ' /Flags '.$ff;
13405
					}
13406
				}
13407
				$opt['aa'] .= ' >>';
13408
			} else {
13409
				// Javascript action or raw action command
13410
				$js_obj_id = $this->addJavascriptObject($action);
13411
				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
13412
			}
13413
		}
13414
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13415
		if ($this->rtl) {
13416
			$this->x -= $w;
13417
		} else {
13418
			$this->x += $w;
13419
		}
13420
	}
13421
 
13422
	// --- END FORMS FIELDS ------------------------------------------------
13423
 
13424
	/**
13425
	 * Add certification signature (DocMDP or UR3)
13426
	 * You can set only one signature type
13427
	 * @protected
13428
	 * @author Nicola Asuni
13429
	 * @since 4.6.008 (2009-05-07)
13430
	 */
13431
	protected function _putsignature() {
13432
		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13433
			return;
13434
		}
13435
		$sigobjid = ($this->sig_obj_id + 1);
13436
		$out = $this->_getobj($sigobjid)."\n";
13437
		$out .= '<< /Type /Sig';
13438
		$out .= ' /Filter /Adobe.PPKLite';
13439
		$out .= ' /SubFilter /adbe.pkcs7.detached';
13440
		$out .= ' '.TCPDF_STATIC::$byterange_string;
13441
		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13442
		if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13443
			$out .= ' /Reference ['; // array of signature reference dictionaries
13444
			$out .= ' << /Type /SigRef';
13445
			if ($this->signature_data['cert_type'] > 0) {
13446
				$out .= ' /TransformMethod /DocMDP';
13447
				$out .= ' /TransformParams <<';
13448
				$out .= ' /Type /TransformParams';
13449
				$out .= ' /P '.$this->signature_data['cert_type'];
13450
				$out .= ' /V /1.2';
13451
			} else {
13452
				$out .= ' /TransformMethod /UR3';
13453
				$out .= ' /TransformParams <<';
13454
				$out .= ' /Type /TransformParams';
13455
				$out .= ' /V /2.2';
13456
				if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13457
					$out .= ' /Document['.$this->ur['document'].']';
13458
				}
13459
				if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13460
					$out .= ' /Form['.$this->ur['form'].']';
13461
				}
13462
				if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13463
					$out .= ' /Signature['.$this->ur['signature'].']';
13464
				}
13465
				if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13466
					$out .= ' /Annots['.$this->ur['annots'].']';
13467
				}
13468
				if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13469
					$out .= ' /EF['.$this->ur['ef'].']';
13470
				}
13471
				if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13472
					$out .= ' /FormEX['.$this->ur['formex'].']';
13473
				}
13474
			}
13475
			$out .= ' >>'; // close TransformParams
13476
			// optional digest data (values must be calculated and replaced later)
13477
			//$out .= ' /Data ********** 0 R';
13478
			//$out .= ' /DigestMethod/MD5';
13479
			//$out .= ' /DigestLocation[********** 34]';
13480
			//$out .= ' /DigestValue<********************************>';
13481
			$out .= ' >>';
13482
			$out .= ' ]'; // end of reference
13483
		}
13484
		if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13485
			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13486
		}
13487
		if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13488
			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13489
		}
13490
		if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13491
			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13492
		}
13493
		if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13494
			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13495
		}
13496
		$out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13497
		$out .= ' >>';
13498
		$out .= "\n".'endobj';
13499
		$this->_out($out);
13500
	}
13501
 
13502
	/**
13503
	 * Set User's Rights for PDF Reader
13504
	 * WARNING: This is experimental and currently do not work.
13505
	 * Check the PDF Reference 8.7.1 Transform Methods,
13506
	 * Table 8.105 Entries in the UR transform parameters dictionary
13507
	 * @param boolean $enable if true enable user's rights on PDF reader
13508
	 * @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.
13509
	 * @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.
13510
	 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13511
	 * @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.
13512
	 * @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
13513
	 Names specifying additional embedded-files-related usage rights for the document.
13514
	 * @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.
13515
	 * @public
13516
	 * @author Nicola Asuni
13517
	 * @since 2.9.000 (2008-03-26)
13518
	 */
13519
	public function setUserRights(
13520
			$enable=true,
13521
			$document='/FullSave',
13522
			$annots='/Create/Delete/Modify/Copy/Import/Export',
13523
			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13524
			$signature='/Modify',
13525
			$ef='/Create/Delete/Modify/Import',
13526
			$formex='') {
13527
		$this->ur['enabled'] = $enable;
13528
		$this->ur['document'] = $document;
13529
		$this->ur['annots'] = $annots;
13530
		$this->ur['form'] = $form;
13531
		$this->ur['signature'] = $signature;
13532
		$this->ur['ef'] = $ef;
13533
		$this->ur['formex'] = $formex;
13534
		if (!$this->sign) {
13535
			$this->setSignature('', '', '', '', 0, array());
13536
		}
13537
	}
13538
 
13539
	/**
13540
	 * Enable document signature (requires the OpenSSL Library).
13541
	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13542
	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13543
	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13544
	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13545
	 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13546
	 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13547
	 * @param string $private_key_password password
13548
	 * @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.
13549
	 * @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.
13550
	 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13551
	 * @param string $approval Enable approval signature eg. for PDF incremental update
13552
	 * @public
13553
	 * @author Nicola Asuni
13554
	 * @since 4.6.005 (2009-04-24)
13555
	 */
13556
	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13557
		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13558
		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13559
		// to convert pfx certificate to pem: openssl
13560
		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13561
		$this->sign = true;
13562
		++$this->n;
13563
		$this->sig_obj_id = $this->n; // signature widget
13564
		++$this->n; // signature object ($this->sig_obj_id + 1)
13565
		$this->signature_data = array();
13566
		if (strlen($signing_cert) == 0) {
13567
			$this->Error('Please provide a certificate file and password!');
13568
		}
13569
		if (strlen($private_key) == 0) {
13570
			$private_key = $signing_cert;
13571
		}
13572
		$this->signature_data['signcert'] = $signing_cert;
13573
		$this->signature_data['privkey'] = $private_key;
13574
		$this->signature_data['password'] = $private_key_password;
13575
		$this->signature_data['extracerts'] = $extracerts;
13576
		$this->signature_data['cert_type'] = $cert_type;
13577
		$this->signature_data['info'] = $info;
13578
		$this->signature_data['approval'] = $approval;
13579
	}
13580
 
13581
	/**
13582
	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13583
	 * @param float $x Abscissa of the upper-left corner.
13584
	 * @param float $y Ordinate of the upper-left corner.
13585
	 * @param float $w Width of the signature area.
13586
	 * @param float $h Height of the signature area.
13587
	 * @param int $page option page number (if < 0 the current page is used).
13588
	 * @param string $name Name of the signature.
13589
	 * @public
13590
	 * @author Nicola Asuni
13591
	 * @since 5.3.011 (2010-06-17)
13592
	 */
13593
	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13594
		$this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13595
	}
13596
 
13597
	/**
13598
	 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13599
	 * @param float $x Abscissa of the upper-left corner.
13600
	 * @param float $y Ordinate of the upper-left corner.
13601
	 * @param float $w Width of the signature area.
13602
	 * @param float $h Height of the signature area.
13603
	 * @param int $page option page number (if < 0 the current page is used).
13604
	 * @param string $name Name of the signature.
13605
	 * @public
13606
	 * @author Nicola Asuni
13607
	 * @since 5.9.101 (2011-07-06)
13608
	 */
13609
	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13610
		++$this->n;
13611
		$this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13612
	}
13613
 
13614
	/**
13615
	 * Get the array that defines the signature appearance (page and rectangle coordinates).
13616
	 * @param float $x Abscissa of the upper-left corner.
13617
	 * @param float $y Ordinate of the upper-left corner.
13618
	 * @param float $w Width of the signature area.
13619
	 * @param float $h Height of the signature area.
13620
	 * @param int $page option page number (if < 0 the current page is used).
13621
	 * @param string $name Name of the signature.
13622
	 * @return array Array defining page and rectangle coordinates of signature appearance.
13623
	 * @protected
13624
	 * @author Nicola Asuni
13625
	 * @since 5.9.101 (2011-07-06)
13626
	 */
13627
	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13628
		$sigapp = array();
13629
		if (($page < 1) OR ($page > $this->numpages)) {
13630
			$sigapp['page'] = $this->page;
13631
		} else {
13632
			$sigapp['page'] = intval($page);
13633
		}
13634
		if (empty($name)) {
13635
			$sigapp['name'] = 'Signature';
13636
		} else {
13637
			$sigapp['name'] = $name;
13638
		}
13639
		$a = $x * $this->k;
13640
		$b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13641
		$c = $w * $this->k;
13642
		$d = $h * $this->k;
13643
		$sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13644
		return $sigapp;
13645
	}
13646
 
13647
	/**
13648
	 * Enable document timestamping (requires the OpenSSL Library).
13649
	 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13650
	 * Use with digital signature only!
13651
	 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13652
	 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13653
	 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13654
	 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13655
	 * @public
13656
	 * @author Richard Stockinger
13657
	 * @since 6.0.090 (2014-06-16)
13658
	 */
13659
	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13660
		$this->tsa_data = array();
13661
		if (!function_exists('curl_init')) {
13662
			$this->Error('Please enable cURL PHP extension!');
13663
		}
13664
		if (strlen($tsa_host) == 0) {
13665
			$this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13666
		}
13667
		$this->tsa_data['tsa_host'] = $tsa_host;
13668
		if (is_file($tsa_username)) {
13669
			$this->tsa_data['tsa_auth'] = $tsa_username;
13670
		} else {
13671
			$this->tsa_data['tsa_username'] = $tsa_username;
13672
		}
13673
		$this->tsa_data['tsa_password'] = $tsa_password;
13674
		$this->tsa_data['tsa_cert'] = $tsa_cert;
13675
		$this->tsa_timestamp = true;
13676
	}
13677
 
13678
	/**
13679
	 * NOT YET IMPLEMENTED
13680
	 * Request TSA for a timestamp
13681
	 * @param string $signature Digital signature as binary string
13682
	 * @return string Timestamped digital signature
13683
	 * @protected
13684
	 * @author Richard Stockinger
13685
	 * @since 6.0.090 (2014-06-16)
13686
	 */
13687
	protected function applyTSA($signature) {
13688
		if (!$this->tsa_timestamp) {
13689
			return $signature;
13690
		}
13691
		//@TODO: implement this feature
13692
		return $signature;
13693
	}
13694
 
13695
	/**
13696
	 * Create a new page group.
13697
	 * NOTE: call this function before calling AddPage()
13698
	 * @param int|null $page starting group page (leave empty for next page).
13699
	 * @public
13700
	 * @since 3.0.000 (2008-03-27)
13701
	 */
13702
	public function startPageGroup($page=null) {
13703
		if (empty($page)) {
13704
			$page = $this->page + 1;
13705
		}
13706
		$this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13707
	}
13708
 
13709
	/**
13710
	 * Set the starting page number.
13711
	 * @param int $num Starting page number.
13712
	 * @since 5.9.093 (2011-06-16)
13713
	 * @public
13714
	 */
13715
	public function setStartingPageNumber($num=1) {
13716
		$this->starting_page_number = max(0, intval($num));
13717
	}
13718
 
13719
	/**
13720
	 * Returns the string alias used right align page numbers.
13721
	 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13722
	 * @return string
13723
	 * @since 5.9.099 (2011-06-27)
13724
	 * @public
13725
	 */
13726
	public function getAliasRightShift() {
13727
		// calculate aproximatively the ratio between widths of aliases and replacements.
13728
		$ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13729
		$rep = str_repeat(' ', $this->GetNumChars($ref));
13730
		$wrep = $this->GetStringWidth($rep);
13731
		if ($wrep > 0) {
13732
			$wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13733
		} else {
13734
			$wdiff = 1;
13735
		}
13736
		$sdiff = sprintf('%F', $wdiff);
13737
		$alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13738
		if ($this->isUnicodeFont()) {
13739
			$alias = '{'.$alias;
13740
		}
13741
		return $alias;
13742
	}
13743
 
13744
	/**
13745
	 * Returns the string alias used for the total number of pages.
13746
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13747
	 * This alias will be replaced by the total number of pages in the document.
13748
	 * @return string
13749
	 * @since 4.0.018 (2008-08-08)
13750
	 * @public
13751
	 */
13752
	public function getAliasNbPages() {
13753
		if ($this->isUnicodeFont()) {
13754
			return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13755
		}
13756
		return TCPDF_STATIC::$alias_tot_pages;
13757
	}
13758
 
13759
	/**
13760
	 * Returns the string alias used for the page number.
13761
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13762
	 * This alias will be replaced by the page number.
13763
	 * @return string
13764
	 * @since 4.5.000 (2009-01-02)
13765
	 * @public
13766
	 */
13767
	public function getAliasNumPage() {
13768
		if ($this->isUnicodeFont()) {
13769
			return '{'.TCPDF_STATIC::$alias_num_page.'}';
13770
		}
13771
		return TCPDF_STATIC::$alias_num_page;
13772
	}
13773
 
13774
	/**
13775
	 * Return the alias for the total number of pages in the current page group.
13776
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13777
	 * This alias will be replaced by the total number of pages in this group.
13778
	 * @return string alias of the current page group
13779
	 * @public
13780
	 * @since 3.0.000 (2008-03-27)
13781
	 */
13782
	public function getPageGroupAlias() {
13783
		if ($this->isUnicodeFont()) {
13784
			return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13785
		}
13786
		return TCPDF_STATIC::$alias_group_tot_pages;
13787
	}
13788
 
13789
	/**
13790
	 * Return the alias for the page number on the current page group.
13791
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13792
	 * This alias will be replaced by the page number (relative to the belonging group).
13793
	 * @return string alias of the current page group
13794
	 * @public
13795
	 * @since 4.5.000 (2009-01-02)
13796
	 */
13797
	public function getPageNumGroupAlias() {
13798
		if ($this->isUnicodeFont()) {
13799
			return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13800
		}
13801
		return TCPDF_STATIC::$alias_group_num_page;
13802
	}
13803
 
13804
	/**
13805
	 * Return the current page in the group.
13806
	 * @return int current page in the group
13807
	 * @public
13808
	 * @since 3.0.000 (2008-03-27)
13809
	 */
13810
	public function getGroupPageNo() {
13811
		return $this->pagegroups[$this->currpagegroup];
13812
	}
13813
 
13814
	/**
13815
	 * Returns the current group page number formatted as a string.
13816
	 * @public
13817
	 * @since 4.3.003 (2008-11-18)
13818
	 * @see PaneNo(), formatPageNumber()
13819
	 */
13820
	public function getGroupPageNoFormatted() {
13821
		return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13822
	}
13823
 
13824
	/**
13825
	 * Returns the current page number formatted as a string.
13826
	 * @public
13827
	 * @since 4.2.005 (2008-11-06)
13828
	 * @see PaneNo(), formatPageNumber()
13829
	 */
13830
	public function PageNoFormatted() {
13831
		return TCPDF_STATIC::formatPageNumber($this->PageNo());
13832
	}
13833
 
13834
	/**
13835
	 * Put pdf layers.
13836
	 * @protected
13837
	 * @since 3.0.000 (2008-03-27)
13838
	 */
13839
	protected function _putocg() {
13840
		if (empty($this->pdflayers)) {
13841
			return;
13842
		}
13843
		foreach ($this->pdflayers as $key => $layer) {
13844
			 $this->pdflayers[$key]['objid'] = $this->_newobj();
13845
			 $out = '<< /Type /OCG';
13846
			 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13847
			 $out .= ' /Usage <<';
13848
			 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13849
				$out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13850
			 }
13851
			 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13852
			 $out .= ' >> >>';
13853
			 $out .= "\n".'endobj';
13854
			 $this->_out($out);
13855
		}
13856
	}
13857
 
13858
	/**
13859
	 * Start a new pdf layer.
13860
	 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13861
	 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13862
	 * @param boolean $view Set to true to view this layer.
13863
	 * @param boolean $lock If true lock the layer
13864
	 * @public
13865
	 * @since 5.9.102 (2011-07-13)
13866
	 */
13867
	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13868
		if ($this->state != 2) {
13869
			return;
13870
		}
13871
		$layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13872
		if (empty($name)) {
13873
			$name = $layer;
13874
		} else {
13875
			$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13876
		}
13877
		$this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13878
		$this->openMarkedContent = true;
13879
		$this->_out('/OC /'.$layer.' BDC');
13880
	}
13881
 
13882
	/**
13883
	 * End the current PDF layer.
13884
	 * @public
13885
	 * @since 5.9.102 (2011-07-13)
13886
	 */
13887
	public function endLayer() {
13888
		if ($this->state != 2) {
13889
			return;
13890
		}
13891
		if ($this->openMarkedContent) {
13892
			// close existing open marked-content layer
13893
			$this->_out('EMC');
13894
			$this->openMarkedContent = false;
13895
		}
13896
	}
13897
 
13898
	/**
13899
	 * Set the visibility of the successive elements.
13900
	 * This can be useful, for instance, to put a background
13901
	 * image or color that will show on screen but won't print.
13902
	 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13903
	 * @public
13904
	 * @since 3.0.000 (2008-03-27)
13905
	 */
13906
	public function setVisibility($v) {
13907
		if ($this->state != 2) {
13908
			return;
13909
		}
13910
		$this->endLayer();
13911
		switch($v) {
13912
			case 'print': {
13913
				$this->startLayer('Print', true, false);
13914
				break;
13915
			}
13916
			case 'view':
13917
			case 'screen': {
13918
				$this->startLayer('View', false, true);
13919
				break;
13920
			}
13921
			case 'all': {
13922
				$this->_out('');
13923
				break;
13924
			}
13925
			default: {
13926
				$this->Error('Incorrect visibility: '.$v);
13927
				break;
13928
			}
13929
		}
13930
	}
13931
 
13932
	/**
13933
	 * Add transparency parameters to the current extgstate
13934
	 * @param array $parms parameters
13935
	 * @return int|void the number of extgstates
13936
	 * @protected
13937
	 * @since 3.0.000 (2008-03-27)
13938
	 */
13939
	protected function addExtGState($parms) {
1441 ariadna 13940
		if (($this->pdfa_mode && $this->pdfa_version < 2) || ($this->state != 2)) {
13941
			// transparency is not allowed in PDF/A-1 mode
1 efrain 13942
			return;
13943
		}
13944
		// check if this ExtGState already exist
13945
		foreach ($this->extgstates as $i => $ext) {
13946
			if ($ext['parms'] == $parms) {
13947
				if ($this->inxobj) {
13948
					// we are inside an XObject template
13949
					$this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13950
				}
13951
				// return reference to existing ExtGState
13952
				return $i;
13953
			}
13954
		}
13955
		$n = (count($this->extgstates) + 1);
13956
		$this->extgstates[$n] = array('parms' => $parms);
13957
		if ($this->inxobj) {
13958
			// we are inside an XObject template
13959
			$this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13960
		}
13961
		return $n;
13962
	}
13963
 
13964
	/**
13965
	 * Add an extgstate
13966
	 * @param int $gs extgstate
13967
	 * @protected
13968
	 * @since 3.0.000 (2008-03-27)
13969
	 */
13970
	protected function setExtGState($gs) {
13971
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
13972
			// transparency is not allowed in PDF/A-1 mode
13973
			return;
13974
		}
13975
		$this->_out(sprintf('/GS%d gs', $gs));
13976
	}
13977
 
13978
	/**
13979
	 * Put extgstates for object transparency
13980
	 * @protected
13981
	 * @since 3.0.000 (2008-03-27)
13982
	 */
13983
	protected function _putextgstates() {
13984
		foreach ($this->extgstates as $i => $ext) {
13985
			$this->extgstates[$i]['n'] = $this->_newobj();
13986
			$out = '<< /Type /ExtGState';
13987
			foreach ($ext['parms'] as $k => $v) {
13988
				if (is_float($v)) {
13989
					$v = sprintf('%F', $v);
13990
				} elseif ($v === true) {
13991
					$v = 'true';
13992
				} elseif ($v === false) {
13993
					$v = 'false';
13994
				}
13995
				$out .= ' /'.$k.' '.$v;
13996
			}
13997
			$out .= ' >>';
13998
			$out .= "\n".'endobj';
13999
			$this->_out($out);
14000
		}
14001
	}
14002
 
14003
	/**
14004
	 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
14005
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14006
	 * @param boolean $stroking If true apply overprint for stroking operations.
14007
	 * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking.
14008
	 * @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).
14009
	 * @public
14010
	 * @since 5.9.152 (2012-03-23)
14011
	 */
14012
	public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14013
		if ($this->state != 2) {
14014
			return;
14015
		}
14016
		$stroking = $stroking ? true : false;
14017
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14018
			// default value if not set
14019
			$nonstroking = $stroking;
14020
		} else {
14021
			$nonstroking = $nonstroking ? true : false;
14022
		}
14023
		if (($mode != 0) AND ($mode != 1)) {
14024
			$mode = 0;
14025
		}
14026
		$this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14027
		$gs = $this->addExtGState($this->overprint);
14028
		$this->setExtGState($gs);
14029
	}
14030
 
14031
	/**
14032
	 * Get the overprint mode array (OP, op, OPM).
14033
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14034
	 * @return array<string,bool|int>
14035
	 * @public
14036
	 * @since 5.9.152 (2012-03-23)
14037
	 */
14038
	public function getOverprint() {
14039
		return $this->overprint;
14040
	}
14041
 
14042
	/**
14043
	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14044
	 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14045
	 * @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
14046
	 * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14047
	 * @param boolean $ais
14048
	 * @public
14049
	 * @since 3.0.000 (2008-03-27)
14050
	 */
14051
	public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) {
14052
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14053
			// transparency is not allowed in PDF/A-1 mode
14054
			return;
14055
		}
14056
		$stroking = floatval($stroking);
14057
		if (TCPDF_STATIC::empty_string($nonstroking)) {
14058
			// default value if not set
14059
			$nonstroking = $stroking;
14060
		} else {
14061
			$nonstroking = floatval($nonstroking);
14062
		}
14063
		if ($bm[0] == '/') {
14064
			// remove trailing slash
14065
			$bm = substr($bm, 1);
14066
		}
14067
		if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14068
			$bm = 'Normal';
14069
		}
14070
		$ais = $ais ? true : false;
14071
		$this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14072
		$gs = $this->addExtGState($this->alpha);
14073
		$this->setExtGState($gs);
14074
	}
14075
 
14076
	/**
14077
	 * Get the alpha mode array (CA, ca, BM, AIS).
14078
	 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14079
	 * @return array<string,bool|string>
14080
	 * @public
14081
	 * @since 5.9.152 (2012-03-23)
14082
	 */
14083
	public function getAlpha() {
14084
		return $this->alpha;
14085
	}
14086
 
14087
	/**
14088
	 * Set the default JPEG compression quality (1-100)
14089
	 * @param int $quality JPEG quality, integer between 1 and 100
14090
	 * @public
14091
	 * @since 3.0.000 (2008-03-27)
14092
	 */
14093
	public function setJPEGQuality($quality) {
14094
		if (($quality < 1) OR ($quality > 100)) {
14095
			$quality = 75;
14096
		}
14097
		$this->jpeg_quality = intval($quality);
14098
	}
14099
 
14100
	/**
14101
	 * Set the default number of columns in a row for HTML tables.
14102
	 * @param int $cols number of columns
14103
	 * @public
14104
	 * @since 3.0.014 (2008-06-04)
14105
	 */
14106
	public function setDefaultTableColumns($cols=4) {
14107
		$this->default_table_columns = intval($cols);
14108
	}
14109
 
14110
	/**
14111
	 * Set the height of the cell (line height) respect the font height.
14112
	 * @param float $h cell proportion respect font height (typical value = 1.25).
14113
	 * @public
14114
	 * @since 3.0.014 (2008-06-04)
14115
	 */
14116
	public function setCellHeightRatio($h) {
14117
		$this->cell_height_ratio = $h;
14118
	}
14119
 
14120
	/**
14121
	 * return the height of cell repect font height.
14122
	 * @public
14123
	 * @return float
14124
	 * @since 4.0.012 (2008-07-24)
14125
	 */
14126
	public function getCellHeightRatio() {
14127
		return $this->cell_height_ratio;
14128
	}
14129
 
14130
	/**
14131
	 * Set the PDF version (check PDF reference for valid values).
14132
	 * @param string $version PDF document version.
14133
	 * @public
14134
	 * @since 3.1.000 (2008-06-09)
14135
	 */
14136
	public function setPDFVersion($version='1.7') {
14137
		if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14138
			// PDF/A-1 mode
14139
			$this->PDFVersion = '1.4';
14140
		} elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14141
            // PDF/A-2 mode
14142
            $this->PDFVersion = '1.7';
14143
        } else {
14144
			$this->PDFVersion = $version;
14145
		}
14146
	}
14147
 
14148
	/**
14149
	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14150
	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14151
	 * <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>
14152
	 * @param array $preferences array of options.
14153
	 * @author Nicola Asuni
14154
	 * @public
14155
	 * @since 3.1.000 (2008-06-09)
14156
	 */
14157
	public function setViewerPreferences($preferences) {
14158
		$this->viewer_preferences = $preferences;
14159
	}
14160
 
14161
	/**
14162
	 * Paints color transition registration bars
14163
	 * @param float $x abscissa of the top left corner of the rectangle.
14164
	 * @param float $y ordinate of the top left corner of the rectangle.
14165
	 * @param float $w width of the rectangle.
14166
	 * @param float $h height of the rectangle.
14167
	 * @param boolean $transition if true prints tcolor transitions to white.
14168
	 * @param boolean $vertical if true prints bar vertically.
14169
	 * @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.
14170
	 * @author Nicola Asuni
14171
	 * @since 4.9.000 (2010-03-26)
14172
	 * @public
14173
	 */
14174
	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14175
		if (strpos($colors, 'ALLSPOT') !== false) {
14176
			// expand spot colors
14177
			$spot_colors = '';
14178
			foreach ($this->spot_colors as $spot_color_name => $v) {
14179
				$spot_colors .= ','.$spot_color_name;
14180
			}
14181
			if (!empty($spot_colors)) {
14182
				$spot_colors = substr($spot_colors, 1);
14183
				$colors = str_replace('ALLSPOT', $spot_colors, $colors);
14184
			} else {
14185
				$colors = str_replace('ALLSPOT', 'NONE', $colors);
14186
			}
14187
		}
14188
		$bars = explode(',', $colors);
14189
		$numbars = count($bars); // number of bars to print
14190
		if ($numbars <= 0) {
14191
			return;
14192
		}
14193
		// set bar measures
14194
		if ($vertical) {
14195
			$coords = array(0, 0, 0, 1);
14196
			$wb = $w / $numbars; // bar width
14197
			$hb = $h; // bar height
14198
			$xd = $wb; // delta x
14199
			$yd = 0; // delta y
14200
		} else {
14201
			$coords = array(1, 0, 0, 0);
14202
			$wb = $w; // bar width
14203
			$hb = $h / $numbars; // bar height
14204
			$xd = 0; // delta x
14205
			$yd = $hb; // delta y
14206
		}
14207
		$xb = $x;
14208
		$yb = $y;
14209
		foreach ($bars as $col) {
14210
			switch ($col) {
14211
				// set transition colors
14212
				case 'A': { // BLACK (GRAYSCALE)
14213
					$col_a = array(255);
14214
					$col_b = array(0);
14215
					break;
14216
				}
14217
				case 'W': { // WHITE (GRAYSCALE)
14218
					$col_a = array(0);
14219
					$col_b = array(255);
14220
					break;
14221
				}
14222
				case 'R': { // RED (RGB)
14223
					$col_a = array(255,255,255);
14224
					$col_b = array(255,0,0);
14225
					break;
14226
				}
14227
				case 'G': { // GREEN (RGB)
14228
					$col_a = array(255,255,255);
14229
					$col_b = array(0,255,0);
14230
					break;
14231
				}
14232
				case 'B': { // BLUE (RGB)
14233
					$col_a = array(255,255,255);
14234
					$col_b = array(0,0,255);
14235
					break;
14236
				}
14237
				case 'C': { // CYAN (CMYK)
14238
					$col_a = array(0,0,0,0);
14239
					$col_b = array(100,0,0,0);
14240
					break;
14241
				}
14242
				case 'M': { // MAGENTA (CMYK)
14243
					$col_a = array(0,0,0,0);
14244
					$col_b = array(0,100,0,0);
14245
					break;
14246
				}
14247
				case 'Y': { // YELLOW (CMYK)
14248
					$col_a = array(0,0,0,0);
14249
					$col_b = array(0,0,100,0);
14250
					break;
14251
				}
14252
				case 'K': { // KEY - BLACK (CMYK)
14253
					$col_a = array(0,0,0,0);
14254
					$col_b = array(0,0,0,100);
14255
					break;
14256
				}
14257
				case 'RGB': { // BLACK REGISTRATION (RGB)
14258
					$col_a = array(255,255,255);
14259
					$col_b = array(0,0,0);
14260
					break;
14261
				}
14262
				case 'CMYK': { // BLACK REGISTRATION (CMYK)
14263
					$col_a = array(0,0,0,0);
14264
					$col_b = array(100,100,100,100);
14265
					break;
14266
				}
14267
				case 'ALL': { // SPOT COLOR REGISTRATION
14268
					$col_a = array(0,0,0,0,'None');
14269
					$col_b = array(100,100,100,100,'All');
14270
					break;
14271
				}
14272
				case 'NONE': { // SKIP THIS COLOR
14273
					$col_a = array(0,0,0,0,'None');
14274
					$col_b = array(0,0,0,0,'None');
14275
					break;
14276
				}
14277
				default: { // SPECIFIC SPOT COLOR NAME
14278
					$col_a = array(0,0,0,0,'None');
14279
					$col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14280
					if ($col_b === false) {
14281
						// in case of error defaults to the registration color
14282
						$col_b = array(100,100,100,100,'All');
14283
					}
14284
					break;
14285
				}
14286
			}
14287
			if ($col != 'NONE') {
14288
				if ($transition) {
14289
					// color gradient
14290
					$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14291
				} else {
14292
					$this->setFillColorArray($col_b);
14293
					// colored rectangle
14294
					$this->Rect($xb, $yb, $wb, $hb, 'F', array());
14295
				}
14296
				$xb += $xd;
14297
				$yb += $yd;
14298
			}
14299
		}
14300
	}
14301
 
14302
	/**
14303
	 * Paints crop marks.
14304
	 * @param float $x abscissa of the crop mark center.
14305
	 * @param float $y ordinate of the crop mark center.
14306
	 * @param float $w width of the crop mark.
14307
	 * @param float $h height of the crop mark.
14308
	 * @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.
14309
	 * @param array $color crop mark color (default spot registration color).
14310
	 * @author Nicola Asuni
14311
	 * @since 4.9.000 (2010-03-26)
14312
	 * @public
14313
	 */
14314
	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14315
		$this->setLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14316
		$type = strtoupper($type);
14317
		$type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14318
		// split type in single components
14319
		$type = str_replace('-', ',', $type);
14320
		$type = str_replace('TL', 'T,L', $type);
14321
		$type = str_replace('TR', 'T,R', $type);
14322
		$type = str_replace('BL', 'F,L', $type);
14323
		$type = str_replace('BR', 'F,R', $type);
14324
		$type = str_replace('A', 'T,L', $type);
14325
		$type = str_replace('B', 'T,R', $type);
14326
		$type = str_replace('T,RO', 'BO', $type);
14327
		$type = str_replace('C', 'F,L', $type);
14328
		$type = str_replace('D', 'F,R', $type);
14329
		$crops = explode(',', strtoupper($type));
14330
		// remove duplicates
14331
		$crops = array_unique($crops);
14332
		$dw = ($w / 4); // horizontal space to leave before the intersection point
14333
		$dh = ($h / 4); // vertical space to leave before the intersection point
14334
		foreach ($crops as $crop) {
14335
			switch ($crop) {
14336
				case 'T':
14337
				case 'TOP': {
14338
					$x1 = $x;
14339
					$y1 = ($y - $h);
14340
					$x2 = $x;
14341
					$y2 = ($y - $dh);
14342
					break;
14343
				}
14344
				case 'F':
14345
				case 'BOTTOM': {
14346
					$x1 = $x;
14347
					$y1 = ($y + $dh);
14348
					$x2 = $x;
14349
					$y2 = ($y + $h);
14350
					break;
14351
				}
14352
				case 'L':
14353
				case 'LEFT': {
14354
					$x1 = ($x - $w);
14355
					$y1 = $y;
14356
					$x2 = ($x - $dw);
14357
					$y2 = $y;
14358
					break;
14359
				}
14360
				case 'R':
14361
				case 'RIGHT': {
14362
					$x1 = ($x + $dw);
14363
					$y1 = $y;
14364
					$x2 = ($x + $w);
14365
					$y2 = $y;
14366
					break;
14367
				}
14368
			}
14369
			$this->Line($x1, $y1, $x2, $y2);
14370
		}
14371
	}
14372
 
14373
	/**
14374
	 * Paints a registration mark
14375
	 * @param float $x abscissa of the registration mark center.
14376
	 * @param float $y ordinate of the registration mark center.
14377
	 * @param float $r radius of the crop mark.
14378
	 * @param boolean $double if true print two concentric crop marks.
14379
	 * @param array $cola crop mark color (default spot registration color 'All').
14380
	 * @param array $colb second crop mark color (default spot registration color 'None').
14381
	 * @author Nicola Asuni
14382
	 * @since 4.9.000 (2010-03-26)
14383
	 * @public
14384
	 */
14385
	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14386
		$line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14387
		$this->setFillColorArray($cola);
14388
		$this->PieSector($x, $y, $r, 90, 180, 'F');
14389
		$this->PieSector($x, $y, $r, 270, 360, 'F');
14390
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14391
		if ($double) {
14392
			$ri = $r * 0.5;
14393
			$this->setFillColorArray($colb);
14394
			$this->PieSector($x, $y, $ri, 90, 180, 'F');
14395
			$this->PieSector($x, $y, $ri, 270, 360, 'F');
14396
			$this->setFillColorArray($cola);
14397
			$this->PieSector($x, $y, $ri, 0, 90, 'F');
14398
			$this->PieSector($x, $y, $ri, 180, 270, 'F');
14399
			$this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14400
		}
14401
	}
14402
 
14403
	/**
14404
	 * Paints a CMYK registration mark
14405
	 * @param float $x abscissa of the registration mark center.
14406
	 * @param float $y ordinate of the registration mark center.
14407
	 * @param float $r radius of the crop mark.
14408
	 * @author Nicola Asuni
14409
	 * @since 6.0.038 (2013-09-30)
14410
	 * @public
14411
	 */
14412
	public function registrationMarkCMYK($x, $y, $r) {
14413
		// line width
14414
		$lw = max((0.5 / $this->k),($r / 8));
14415
		// internal radius
14416
		$ri = ($r * 0.6);
14417
		// external radius
14418
		$re = ($r * 1.3);
14419
		// Cyan
14420
		$this->setFillColorArray(array(100,0,0,0));
14421
		$this->PieSector($x, $y, $ri, 270, 360, 'F');
14422
		// Magenta
14423
		$this->setFillColorArray(array(0,100,0,0));
14424
		$this->PieSector($x, $y, $ri, 0, 90, 'F');
14425
		// Yellow
14426
		$this->setFillColorArray(array(0,0,100,0));
14427
		$this->PieSector($x, $y, $ri, 90, 180, 'F');
14428
		// Key - black
14429
		$this->setFillColorArray(array(0,0,0,100));
14430
		$this->PieSector($x, $y, $ri, 180, 270, 'F');
14431
		// registration color
14432
		$line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14433
		$this->setFillColorArray(array(100,100,100,100,'All'));
14434
		// external circle
14435
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14436
		// cross lines
14437
		$this->Line($x, ($y - $re), $x, ($y - $ri));
14438
		$this->Line($x, ($y + $ri), $x, ($y + $re));
14439
		$this->Line(($x - $re), $y, ($x - $ri), $y);
14440
		$this->Line(($x + $ri), $y, ($x + $re), $y);
14441
	}
14442
 
14443
	/**
14444
	 * Paints a linear colour gradient.
14445
	 * @param float $x abscissa of the top left corner of the rectangle.
14446
	 * @param float $y ordinate of the top left corner of the rectangle.
14447
	 * @param float $w width of the rectangle.
14448
	 * @param float $h height of the rectangle.
14449
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14450
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14451
	 * @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).
14452
	 * @author Andreas W\FCrmser, Nicola Asuni
14453
	 * @since 3.1.000 (2008-06-09)
14454
	 * @public
14455
	 */
14456
	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14457
		$this->Clip($x, $y, $w, $h);
14458
		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14459
	}
14460
 
14461
	/**
14462
	 * Paints a radial colour gradient.
14463
	 * @param float $x abscissa of the top left corner of the rectangle.
14464
	 * @param float $y ordinate of the top left corner of the rectangle.
14465
	 * @param float $w width of the rectangle.
14466
	 * @param float $h height of the rectangle.
14467
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14468
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14469
	 * @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.
14470
	 * @author Andreas W\FCrmser, Nicola Asuni
14471
	 * @since 3.1.000 (2008-06-09)
14472
	 * @public
14473
	 */
14474
	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14475
		$this->Clip($x, $y, $w, $h);
14476
		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14477
	}
14478
 
14479
	/**
14480
	 * Paints a coons patch mesh.
14481
	 * @param float $x abscissa of the top left corner of the rectangle.
14482
	 * @param float $y ordinate of the top left corner of the rectangle.
14483
	 * @param float $w width of the rectangle.
14484
	 * @param float $h height of the rectangle.
14485
	 * @param array $col1 first color (lower left corner) (RGB components).
14486
	 * @param array $col2 second color (lower right corner) (RGB components).
14487
	 * @param array $col3 third color (upper right corner) (RGB components).
14488
	 * @param array $col4 fourth color (upper left corner) (RGB components).
14489
	 * @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>
14490
	 * @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
14491
	 * @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
14492
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14493
	 * @author Andreas W\FCrmser, Nicola Asuni
14494
	 * @since 3.1.000 (2008-06-09)
14495
	 * @public
14496
	 */
14497
	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) {
14498
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14499
			return;
14500
		}
14501
		$this->Clip($x, $y, $w, $h);
14502
		$n = count($this->gradients) + 1;
14503
		$this->gradients[$n] = array();
14504
		$this->gradients[$n]['type'] = 6; //coons patch mesh
14505
		$this->gradients[$n]['coords'] = array();
14506
		$this->gradients[$n]['antialias'] = $antialias;
14507
		$this->gradients[$n]['colors'] = array();
14508
		$this->gradients[$n]['transparency'] = false;
14509
		//check the coords array if it is the simple array or the multi patch array
14510
		if (!isset($coords[0]['f'])) {
14511
			//simple array -> convert to multi patch array
14512
			if (!isset($col1[1])) {
14513
				$col1[1] = $col1[2] = $col1[0];
14514
			}
14515
			if (!isset($col2[1])) {
14516
				$col2[1] = $col2[2] = $col2[0];
14517
			}
14518
			if (!isset($col3[1])) {
14519
				$col3[1] = $col3[2] = $col3[0];
14520
			}
14521
			if (!isset($col4[1])) {
14522
				$col4[1] = $col4[2] = $col4[0];
14523
			}
14524
			$patch_array[0]['f'] = 0;
14525
			$patch_array[0]['points'] = $coords;
14526
			$patch_array[0]['colors'][0]['r'] = $col1[0];
14527
			$patch_array[0]['colors'][0]['g'] = $col1[1];
14528
			$patch_array[0]['colors'][0]['b'] = $col1[2];
14529
			$patch_array[0]['colors'][1]['r'] = $col2[0];
14530
			$patch_array[0]['colors'][1]['g'] = $col2[1];
14531
			$patch_array[0]['colors'][1]['b'] = $col2[2];
14532
			$patch_array[0]['colors'][2]['r'] = $col3[0];
14533
			$patch_array[0]['colors'][2]['g'] = $col3[1];
14534
			$patch_array[0]['colors'][2]['b'] = $col3[2];
14535
			$patch_array[0]['colors'][3]['r'] = $col4[0];
14536
			$patch_array[0]['colors'][3]['g'] = $col4[1];
14537
			$patch_array[0]['colors'][3]['b'] = $col4[2];
14538
		} else {
14539
			//multi patch array
14540
			$patch_array = $coords;
14541
		}
14542
		$bpcd = 65535; //16 bits per coordinate
14543
		//build the data stream
14544
		$this->gradients[$n]['stream'] = '';
14545
		$count_patch = count($patch_array);
14546
		for ($i=0; $i < $count_patch; ++$i) {
14547
			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14548
			$count_points = count($patch_array[$i]['points']);
14549
			for ($j=0; $j < $count_points; ++$j) {
14550
				//each point as 16 bit
14551
				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14552
				if ($patch_array[$i]['points'][$j] < 0) {
14553
					$patch_array[$i]['points'][$j] = 0;
14554
				}
14555
				if ($patch_array[$i]['points'][$j] > $bpcd) {
14556
					$patch_array[$i]['points'][$j] = $bpcd;
14557
				}
14558
				$this->gradients[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256));
14559
				$this->gradients[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) % 256));
14560
			}
14561
			$count_cols = count($patch_array[$i]['colors']);
14562
			for ($j=0; $j < $count_cols; ++$j) {
14563
				//each color component as 8 bit
14564
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14565
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14566
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14567
			}
14568
		}
14569
		//paint the gradient
14570
		$this->_out('/Sh'.$n.' sh');
14571
		//restore previous Graphic State
14572
		$this->_outRestoreGraphicsState();
14573
		if ($this->inxobj) {
14574
			// we are inside an XObject template
14575
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14576
		}
14577
	}
14578
 
14579
	/**
14580
	 * Set a rectangular clipping area.
14581
	 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14582
	 * @param float $y ordinate of the top left corner of the rectangle.
14583
	 * @param float $w width of the rectangle.
14584
	 * @param float $h height of the rectangle.
14585
	 * @author Andreas W\FCrmser, Nicola Asuni
14586
	 * @since 3.1.000 (2008-06-09)
14587
	 * @protected
14588
	 */
14589
	protected function Clip($x, $y, $w, $h) {
14590
		if ($this->state != 2) {
14591
			 return;
14592
		}
14593
		if ($this->rtl) {
14594
			$x = $this->w - $x - $w;
14595
		}
14596
		//save current Graphic State
14597
		$s = 'q';
14598
		//set clipping area
14599
		$s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14600
		//set up transformation matrix for gradient
14601
		$s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14602
		$this->_out($s);
14603
	}
14604
 
14605
	/**
14606
	 * Output gradient.
14607
	 * @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)
14608
	 * @param array $coords array of coordinates.
14609
	 * @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).
14610
	 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14611
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14612
	 * @author Nicola Asuni
14613
	 * @since 3.1.000 (2008-06-09)
14614
	 * @public
14615
	 */
14616
	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14617
		if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14618
			return;
14619
		}
14620
		$n = count($this->gradients) + 1;
14621
		$this->gradients[$n] = array();
14622
		$this->gradients[$n]['type'] = $type;
14623
		$this->gradients[$n]['coords'] = $coords;
14624
		$this->gradients[$n]['antialias'] = $antialias;
14625
		$this->gradients[$n]['colors'] = array();
14626
		$this->gradients[$n]['transparency'] = false;
14627
		// color space
14628
		$numcolspace = count($stops[0]['color']);
14629
		$bcolor = array_values($background);
14630
		switch($numcolspace) {
14631
			case 5:   // SPOT
14632
			case 4: { // CMYK
14633
				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
14634
				if (!empty($background)) {
14635
					$this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14636
				}
14637
				break;
14638
			}
14639
			case 3: { // RGB
14640
				$this->gradients[$n]['colspace'] = 'DeviceRGB';
14641
				if (!empty($background)) {
14642
					$this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14643
				}
14644
				break;
14645
			}
14646
			case 1: { // GRAY SCALE
14647
				$this->gradients[$n]['colspace'] = 'DeviceGray';
14648
				if (!empty($background)) {
14649
					$this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14650
				}
14651
				break;
14652
			}
14653
		}
14654
		$num_stops = count($stops);
14655
		$last_stop_id = $num_stops - 1;
14656
		foreach ($stops as $key => $stop) {
14657
			$this->gradients[$n]['colors'][$key] = array();
14658
			// offset represents a location along the gradient vector
14659
			if (isset($stop['offset'])) {
14660
				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14661
			} else {
14662
				if ($key == 0) {
14663
					$this->gradients[$n]['colors'][$key]['offset'] = 0;
14664
				} elseif ($key == $last_stop_id) {
14665
					$this->gradients[$n]['colors'][$key]['offset'] = 1;
14666
				} else {
14667
					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14668
					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14669
				}
14670
			}
14671
			if (isset($stop['opacity'])) {
14672
				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14673
				if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) {
14674
					$this->gradients[$n]['transparency'] = true;
14675
				}
14676
			} else {
14677
				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
14678
			}
14679
			// exponent for the exponential interpolation function
14680
			if (isset($stop['exponent'])) {
14681
				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14682
			} else {
14683
				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
14684
			}
14685
			// set colors
14686
			$color = array_values($stop['color']);
14687
			switch($numcolspace) {
14688
				case 5:   // SPOT
14689
				case 4: { // CMYK
14690
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14691
					break;
14692
				}
14693
				case 3: { // RGB
14694
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14695
					break;
14696
				}
14697
				case 1: { // GRAY SCALE
14698
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14699
					break;
14700
				}
14701
			}
14702
		}
14703
		if ($this->gradients[$n]['transparency']) {
14704
			// paint luminosity gradient
14705
			$this->_out('/TGS'.$n.' gs');
14706
		}
14707
		//paint the gradient
14708
		$this->_out('/Sh'.$n.' sh');
14709
		//restore previous Graphic State
14710
		$this->_outRestoreGraphicsState();
14711
		if ($this->inxobj) {
14712
			// we are inside an XObject template
14713
			$this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14714
		}
14715
	}
14716
 
14717
	/**
14718
	 * Output gradient shaders.
14719
	 * @author Nicola Asuni
14720
	 * @since 3.1.000 (2008-06-09)
14721
	 * @protected
14722
	 */
14723
	function _putshaders() {
14724
		if ($this->pdfa_mode && $this->pdfa_version < 2) {
14725
			return;
14726
		}
14727
		$idt = count($this->gradients); //index for transparency gradients
14728
		foreach ($this->gradients as $id => $grad) {
14729
			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14730
				$fc = $this->_newobj();
14731
				$out = '<<';
14732
				$out .= ' /FunctionType 3';
14733
				$out .= ' /Domain [0 1]';
14734
				$functions = '';
14735
				$bounds = '';
14736
				$encode = '';
14737
				$i = 1;
14738
				$num_cols = count($grad['colors']);
14739
				$lastcols = $num_cols - 1;
14740
				for ($i = 1; $i < $num_cols; ++$i) {
14741
					$functions .= ($fc + $i).' 0 R ';
14742
					if ($i < $lastcols) {
14743
						$bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14744
					}
14745
					$encode .= '0 1 ';
14746
				}
14747
				$out .= ' /Functions ['.trim($functions).']';
14748
				$out .= ' /Bounds ['.trim($bounds).']';
14749
				$out .= ' /Encode ['.trim($encode).']';
14750
				$out .= ' >>';
14751
				$out .= "\n".'endobj';
14752
				$this->_out($out);
14753
				for ($i = 1; $i < $num_cols; ++$i) {
14754
					$this->_newobj();
14755
					$out = '<<';
14756
					$out .= ' /FunctionType 2';
14757
					$out .= ' /Domain [0 1]';
14758
					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14759
					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14760
					$out .= ' /N '.$grad['colors'][$i]['exponent'];
14761
					$out .= ' >>';
14762
					$out .= "\n".'endobj';
14763
					$this->_out($out);
14764
				}
14765
				// set transparency functions
14766
				if ($grad['transparency']) {
14767
					$ft = $this->_newobj();
14768
					$out = '<<';
14769
					$out .= ' /FunctionType 3';
14770
					$out .= ' /Domain [0 1]';
14771
					$functions = '';
14772
					$i = 1;
14773
					$num_cols = count($grad['colors']);
14774
					for ($i = 1; $i < $num_cols; ++$i) {
14775
						$functions .= ($ft + $i).' 0 R ';
14776
					}
14777
					$out .= ' /Functions ['.trim($functions).']';
14778
					$out .= ' /Bounds ['.trim($bounds).']';
14779
					$out .= ' /Encode ['.trim($encode).']';
14780
					$out .= ' >>';
14781
					$out .= "\n".'endobj';
14782
					$this->_out($out);
14783
					for ($i = 1; $i < $num_cols; ++$i) {
14784
						$this->_newobj();
14785
						$out = '<<';
14786
						$out .= ' /FunctionType 2';
14787
						$out .= ' /Domain [0 1]';
14788
						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14789
						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14790
						$out .= ' /N '.$grad['colors'][$i]['exponent'];
14791
						$out .= ' >>';
14792
						$out .= "\n".'endobj';
14793
						$this->_out($out);
14794
					}
14795
				}
14796
			}
14797
			// set shading object
14798
			$this->_newobj();
14799
			$out = '<< /ShadingType '.$grad['type'];
14800
			if (isset($grad['colspace'])) {
14801
				$out .= ' /ColorSpace /'.$grad['colspace'];
14802
			} else {
14803
				$out .= ' /ColorSpace /DeviceRGB';
14804
			}
14805
			if (isset($grad['background']) AND !empty($grad['background'])) {
14806
				$out .= ' /Background ['.$grad['background'].']';
14807
			}
14808
			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14809
				$out .= ' /AntiAlias true';
14810
			}
14811
			if ($grad['type'] == 2) {
14812
				$out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14813
				$out .= ' /Domain [0 1]';
14814
				$out .= ' /Function '.$fc.' 0 R';
14815
				$out .= ' /Extend [true true]';
14816
				$out .= ' >>';
14817
			} elseif ($grad['type'] == 3) {
14818
				//x0, y0, r0, x1, y1, r1
14819
				//at this this time radius of inner circle is 0
14820
				$out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14821
				$out .= ' /Domain [0 1]';
14822
				$out .= ' /Function '.$fc.' 0 R';
14823
				$out .= ' /Extend [true true]';
14824
				$out .= ' >>';
14825
			} elseif ($grad['type'] == 6) {
14826
				$out .= ' /BitsPerCoordinate 16';
14827
				$out .= ' /BitsPerComponent 8';
14828
				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14829
				$out .= ' /BitsPerFlag 8';
14830
				$stream = $this->_getrawstream($grad['stream']);
14831
				$out .= ' /Length '.strlen($stream);
14832
				$out .= ' >>';
14833
				$out .= ' stream'."\n".$stream."\n".'endstream';
14834
			}
14835
			$out .= "\n".'endobj';
14836
			$this->_out($out);
14837
			if ($grad['transparency']) {
14838
				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14839
				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14840
			}
14841
			$this->gradients[$id]['id'] = $this->n;
14842
			// set pattern object
14843
			$this->_newobj();
14844
			$out = '<< /Type /Pattern /PatternType 2';
14845
			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14846
			$out .= ' >>';
14847
			$out .= "\n".'endobj';
14848
			$this->_out($out);
14849
			$this->gradients[$id]['pattern'] = $this->n;
14850
			// set shading and pattern for transparency mask
14851
			if ($grad['transparency']) {
14852
				// luminosity pattern
14853
				$idgs = $id + $idt;
14854
				$this->_newobj();
14855
				$this->_out($shading_transparency);
14856
				$this->gradients[$idgs]['id'] = $this->n;
14857
				$this->_newobj();
14858
				$out = '<< /Type /Pattern /PatternType 2';
14859
				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14860
				$out .= ' >>';
14861
				$out .= "\n".'endobj';
14862
				$this->_out($out);
14863
				$this->gradients[$idgs]['pattern'] = $this->n;
14864
				// luminosity XObject
14865
				$oid = $this->_newobj();
14866
				$this->xobjects['LX'.$oid] = array('n' => $oid);
14867
				$filter = '';
14868
				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14869
				if ($this->compress) {
14870
					$filter = ' /Filter /FlateDecode';
14871
					$stream = gzcompress($stream);
14872
				}
14873
				$stream = $this->_getrawstream($stream);
14874
				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14875
				$out .= ' /Length '.strlen($stream);
14876
				$rect = sprintf('%F %F', $this->wPt, $this->hPt);
14877
				$out .= ' /BBox [0 0 '.$rect.']';
14878
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14879
				$out .= ' /Resources <<';
14880
				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14881
				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14882
				$out .= ' >>';
14883
				$out .= ' >> ';
14884
				$out .= ' stream'."\n".$stream."\n".'endstream';
14885
				$out .= "\n".'endobj';
14886
				$this->_out($out);
14887
				// SMask
14888
				$this->_newobj();
14889
				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14890
				$this->_out($out);
14891
				// ExtGState
14892
				$this->_newobj();
14893
				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14894
				$this->_out($out);
14895
				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14896
			}
14897
		}
14898
	}
14899
 
14900
	/**
14901
	 * Draw the sector of a circle.
14902
	 * It can be used for instance to render pie charts.
14903
	 * @param float $xc abscissa of the center.
14904
	 * @param float $yc ordinate of the center.
14905
	 * @param float $r radius.
14906
	 * @param float $a start angle (in degrees).
14907
	 * @param float $b end angle (in degrees).
14908
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14909
	 * @param float $cw indicates whether to go clockwise (default: true).
14910
	 * @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.
14911
	 * @author Maxime Delorme, Nicola Asuni
14912
	 * @since 3.1.000 (2008-06-09)
14913
	 * @public
14914
	 */
14915
	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14916
		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14917
	}
14918
 
14919
	/**
14920
	 * Draw the sector of an ellipse.
14921
	 * It can be used for instance to render pie charts.
14922
	 * @param float $xc abscissa of the center.
14923
	 * @param float $yc ordinate of the center.
14924
	 * @param float $rx the x-axis radius.
14925
	 * @param float $ry the y-axis radius.
14926
	 * @param float $a start angle (in degrees).
14927
	 * @param float $b end angle (in degrees).
14928
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14929
	 * @param float $cw indicates whether to go clockwise.
14930
	 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14931
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14932
	 * @author Maxime Delorme, Nicola Asuni
14933
	 * @since 3.1.000 (2008-06-09)
14934
	 * @public
14935
	 */
14936
	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14937
		if ($this->state != 2) {
14938
			 return;
14939
		}
14940
		if ($this->rtl) {
14941
			$xc = ($this->w - $xc);
14942
		}
14943
		$op = TCPDF_STATIC::getPathPaintOperator($style);
14944
		if ($op == 'f') {
14945
			$line_style = array();
14946
		}
14947
		if ($cw) {
14948
			$d = $b;
14949
			$b = (360 - $a + $o);
14950
			$a = (360 - $d + $o);
14951
		} else {
14952
			$b += $o;
14953
			$a += $o;
14954
		}
14955
		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14956
		$this->_out($op);
14957
	}
14958
 
14959
	/**
14960
	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14961
	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14962
	 * Only vector drawing is supported, not text or bitmap.
14963
	 * 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).
14964
	 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14965
	 * @param float|null $x Abscissa of the upper-left corner.
14966
	 * @param float|null $y Ordinate of the upper-left corner.
14967
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14968
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14969
	 * @param mixed $link URL or identifier returned by AddLink().
14970
	 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14971
	 * @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>
14972
	 * @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>
14973
	 * @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)))
14974
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
14975
	 * @param boolean $fixoutvals if true remove values outside the bounding box.
14976
	 * @author Valentin Schmidt, Nicola Asuni
14977
	 * @since 3.1.000 (2008-06-09)
14978
	 * @public
14979
	 */
14980
	public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14981
		if ($this->state != 2) {
14982
			 return;
14983
		}
14984
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14985
			// convert EPS to raster image using GD or ImageMagick libraries
14986
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14987
		}
14988
		if (TCPDF_STATIC::empty_string($x)) {
14989
			$x = $this->x;
14990
		}
14991
		if (TCPDF_STATIC::empty_string($y)) {
14992
			$y = $this->y;
14993
		}
14994
		// check page for no-write regions and adapt page margins if necessary
14995
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
14996
		$k = $this->k;
14997
		if ($file[0] === '@') { // image from string
14998
			$data = substr($file, 1);
14999
		} else { // EPS/AI file
15000
            $data = $this->getCachedFileContents($file);
15001
		}
15002
		if ($data === FALSE) {
15003
			$this->Error('EPS file not found: '.$file);
15004
		}
15005
		$regs = array();
15006
		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
15007
		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
15008
		if (count($regs) > 1) {
15009
			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
15010
			if (strpos($version_str, 'Adobe Illustrator') !== false) {
15011
				$versexp = explode(' ', $version_str);
15012
				$version = (float)array_pop($versexp);
15013
				if ($version >= 9) {
15014
					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
15015
				}
15016
			}
15017
		}
15018
		// strip binary bytes in front of PS-header
15019
		$start = strpos($data, '%!PS-Adobe');
15020
		if ($start > 0) {
15021
			$data = substr($data, $start);
15022
		}
15023
		// find BoundingBox params
15024
		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15025
		if (count($regs) > 1) {
15026
			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15027
		} else {
15028
			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
15029
		}
15030
		$start = strpos($data, '%%EndSetup');
15031
		if ($start === false) {
15032
			$start = strpos($data, '%%EndProlog');
15033
		}
15034
		if ($start === false) {
15035
			$start = strpos($data, '%%BoundingBox');
15036
		}
15037
		$data = substr($data, $start);
15038
		$end = strpos($data, '%%PageTrailer');
15039
		if ($end===false) {
15040
			$end = strpos($data, 'showpage');
15041
		}
15042
		if ($end) {
15043
			$data = substr($data, 0, $end);
15044
		}
15045
		// calculate image width and height on document
15046
		if (($w <= 0) AND ($h <= 0)) {
15047
			$w = ($x2 - $x1) / $k;
15048
			$h = ($y2 - $y1) / $k;
15049
		} elseif ($w <= 0) {
15050
			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15051
		} elseif ($h <= 0) {
15052
			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15053
		}
15054
		// fit the image on available space
15055
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15056
		if ($this->rasterize_vector_images) {
15057
			// convert EPS to raster image using GD or ImageMagick libraries
15058
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15059
		}
15060
		// set scaling factors
15061
		$scale_x = $w / (($x2 - $x1) / $k);
15062
		$scale_y = $h / (($y2 - $y1) / $k);
15063
		// set alignment
15064
		$this->img_rb_y = $y + $h;
15065
		// set alignment
15066
		if ($this->rtl) {
15067
			if ($palign == 'L') {
15068
				$ximg = $this->lMargin;
15069
			} elseif ($palign == 'C') {
15070
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15071
			} elseif ($palign == 'R') {
15072
				$ximg = $this->w - $this->rMargin - $w;
15073
			} else {
15074
				$ximg = $x - $w;
15075
			}
15076
			$this->img_rb_x = $ximg;
15077
		} else {
15078
			if ($palign == 'L') {
15079
				$ximg = $this->lMargin;
15080
			} elseif ($palign == 'C') {
15081
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15082
			} elseif ($palign == 'R') {
15083
				$ximg = $this->w - $this->rMargin - $w;
15084
			} else {
15085
				$ximg = $x;
15086
			}
15087
			$this->img_rb_x = $ximg + $w;
15088
		}
15089
		if ($useBoundingBox) {
15090
			$dx = $ximg * $k - $x1;
15091
			$dy = $y * $k - $y1;
15092
		} else {
15093
			$dx = $ximg * $k;
15094
			$dy = $y * $k;
15095
		}
15096
		// save the current graphic state
15097
		$this->_out('q'.$this->epsmarker);
15098
		// translate
15099
		$this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15100
		// scale
15101
		$this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15102
		// handle pc/unix/mac line endings
15103
		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15104
		$u=0;
15105
		$cnt = count($lines);
15106
		for ($i=0; $i < $cnt; ++$i) {
15107
			$line = $lines[$i];
15108
			if (($line == '') OR ($line[0] == '%')) {
15109
				continue;
15110
			}
15111
			$len = strlen($line);
15112
			// check for spot color names
15113
			$color_name = '';
15114
			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15115
				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15116
					// extract spot color name
15117
					$color_name = $matches[0];
15118
					// remove color name from string
15119
					$line = str_replace(' '.$color_name, '', $line);
15120
					// remove pharentesis from color name
15121
					$color_name = substr($color_name, 1, -1);
15122
				}
15123
			}
15124
			$chunks = explode(' ', $line);
15125
			$cmd = trim(array_pop($chunks));
15126
			// RGB
15127
			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15128
				$b = array_pop($chunks);
15129
				$g = array_pop($chunks);
15130
				$r = array_pop($chunks);
15131
				$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!
15132
				continue;
15133
			}
15134
			$skip = false;
15135
			if ($fixoutvals) {
15136
				// check for values outside the bounding box
15137
				switch ($cmd) {
15138
					case 'm':
15139
					case 'l':
15140
					case 'L': {
15141
						// skip values outside bounding box
15142
						foreach ($chunks as $key => $val) {
15143
							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15144
								$skip = true;
15145
							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15146
								$skip = true;
15147
							}
15148
						}
15149
					}
15150
				}
15151
			}
15152
			switch ($cmd) {
15153
				case 'm':
15154
				case 'l':
15155
				case 'v':
15156
				case 'y':
15157
				case 'c':
15158
				case 'k':
15159
				case 'K':
15160
				case 'g':
15161
				case 'G':
15162
				case 's':
15163
				case 'S':
15164
				case 'J':
15165
				case 'j':
15166
				case 'w':
15167
				case 'M':
15168
				case 'd':
15169
				case 'n': {
15170
					if ($skip) {
15171
						break;
15172
					}
15173
					$this->_out($line);
15174
					break;
15175
				}
15176
				case 'x': {// custom fill color
15177
					if (empty($color_name)) {
15178
						// CMYK color
15179
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15180
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15181
					} else {
15182
						// Spot Color (CMYK + tint)
15183
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15184
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15185
						$color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15186
						$this->_out($color_cmd);
15187
					}
15188
					break;
15189
				}
15190
				case 'X': { // custom stroke color
15191
					if (empty($color_name)) {
15192
						// CMYK color
15193
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
15194
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15195
					} else {
15196
						// Spot Color (CMYK + tint)
15197
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15198
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15199
						$color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15200
						$this->_out($color_cmd);
15201
					}
15202
					break;
15203
				}
15204
				case 'Y':
15205
				case 'N':
15206
				case 'V':
15207
				case 'L':
15208
				case 'C': {
15209
					if ($skip) {
15210
						break;
15211
					}
15212
					$line[($len - 1)] = strtolower($cmd);
15213
					$this->_out($line);
15214
					break;
15215
				}
15216
				case 'b':
15217
				case 'B': {
15218
					$this->_out($cmd . '*');
15219
					break;
15220
				}
15221
				case 'f':
15222
				case 'F': {
15223
					if ($u > 0) {
15224
						$isU = false;
15225
						$max = min(($i + 5), $cnt);
15226
						for ($j = ($i + 1); $j < $max; ++$j) {
15227
							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15228
						}
15229
						if ($isU) {
15230
							$this->_out('f*');
15231
						}
15232
					} else {
15233
						$this->_out('f*');
15234
					}
15235
					break;
15236
				}
15237
				case '*u': {
15238
					++$u;
15239
					break;
15240
				}
15241
				case '*U': {
15242
					--$u;
15243
					break;
15244
				}
15245
			}
15246
		}
15247
		// restore previous graphic state
15248
		$this->_out($this->epsmarker.'Q');
15249
		if (!empty($border)) {
15250
			$bx = $this->x;
15251
			$by = $this->y;
15252
			$this->x = $ximg;
15253
			if ($this->rtl) {
15254
				$this->x += $w;
15255
			}
15256
			$this->y = $y;
15257
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15258
			$this->x = $bx;
15259
			$this->y = $by;
15260
		}
15261
		if ($link) {
15262
			$this->Link($ximg, $y, $w, $h, $link, 0);
15263
		}
15264
		// set pointer to align the next text/objects
15265
		switch($align) {
15266
			case 'T':{
15267
				$this->y = $y;
15268
				$this->x = $this->img_rb_x;
15269
				break;
15270
			}
15271
			case 'M':{
15272
				$this->y = $y + round($h/2);
15273
				$this->x = $this->img_rb_x;
15274
				break;
15275
			}
15276
			case 'B':{
15277
				$this->y = $this->img_rb_y;
15278
				$this->x = $this->img_rb_x;
15279
				break;
15280
			}
15281
			case 'N':{
15282
				$this->setY($this->img_rb_y);
15283
				break;
15284
			}
15285
			default:{
15286
				break;
15287
			}
15288
		}
15289
		$this->endlinex = $this->img_rb_x;
15290
	}
15291
 
15292
	/**
15293
	 * Set document barcode.
15294
	 * @param string $bc barcode
15295
	 * @public
15296
	 */
15297
	public function setBarcode($bc='') {
15298
		$this->barcode = $bc;
15299
	}
15300
 
15301
	/**
15302
	 * Get current barcode.
15303
	 * @return string
15304
	 * @public
15305
	 * @since 4.0.012 (2008-07-24)
15306
	 */
15307
	public function getBarcode() {
15308
		return $this->barcode;
15309
	}
15310
 
15311
	/**
15312
	 * Print a Linear Barcode.
15313
	 * @param string $code code to print
15314
	 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15315
	 * @param float|null $x x position in user units (null = current x position)
15316
	 * @param float|null $y y position in user units (null = current y position)
15317
	 * @param float|null $w width in user units (null = remaining page width)
15318
	 * @param float|null $h height in user units (null = remaining page height)
15319
	 * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm)
15320
	 * @param array $style array of options:<ul>
15321
	 * <li>boolean $style['border'] if true prints a border</li>
15322
	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15323
	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15324
	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15325
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15326
	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15327
	 * <li>boolean $style['text'] if true prints text below the barcode</li>
15328
	 * <li>string $style['label'] override default label</li>
15329
	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15330
	 * <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>
15331
	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15332
	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15333
	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15334
	 * <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>
15335
	 * <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>
15336
	 * @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>
15337
	 * @author Nicola Asuni
15338
	 * @since 3.1.000 (2008-06-09)
15339
	 * @public
15340
	 */
15341
	public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') {
15342
		if (TCPDF_STATIC::empty_string(trim($code))) {
15343
			return;
15344
		}
15345
		require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15346
		// save current graphic settings
15347
		$gvars = $this->getGraphicVars();
15348
		// create new barcode object
15349
		$barcodeobj = new TCPDFBarcode($code, $type);
15350
		$arrcode = $barcodeobj->getBarcodeArray();
15351
		if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15352
			$this->Error('Error in 1D barcode string');
15353
		}
15354
		if ($arrcode['maxh'] <= 0) {
15355
			$arrcode['maxh'] = 1;
15356
		}
15357
		// set default values
15358
		if (!isset($style['position'])) {
15359
			$style['position'] = '';
15360
		} elseif ($style['position'] == 'S') {
15361
			// keep this for backward compatibility
15362
			$style['position'] = '';
15363
			$style['stretch'] = true;
15364
		}
15365
		if (!isset($style['fitwidth'])) {
15366
			if (!isset($style['stretch'])) {
15367
				$style['fitwidth'] = true;
15368
			} else {
15369
				$style['fitwidth'] = false;
15370
			}
15371
		}
15372
		if ($style['fitwidth']) {
15373
			// disable stretch
15374
			$style['stretch'] = false;
15375
		}
15376
		if (!isset($style['stretch'])) {
15377
			if (($w === '') OR ($w <= 0)) {
15378
				$style['stretch'] = false;
15379
			} else {
15380
				$style['stretch'] = true;
15381
			}
15382
		}
15383
		if (!isset($style['fgcolor'])) {
15384
			$style['fgcolor'] = array(0,0,0); // default black
15385
		}
15386
		if (!isset($style['bgcolor'])) {
15387
			$style['bgcolor'] = false; // default transparent
15388
		}
15389
		if (!isset($style['border'])) {
15390
			$style['border'] = false;
15391
		}
15392
		$fontsize = 0;
15393
		if (!isset($style['text'])) {
15394
			$style['text'] = false;
15395
		}
15396
		if ($style['text'] AND isset($style['font'])) {
15397
			if (isset($style['fontsize'])) {
15398
				$fontsize = $style['fontsize'];
15399
			}
15400
			$this->setFont($style['font'], '', $fontsize);
15401
		}
15402
		if (!isset($style['stretchtext'])) {
15403
			$style['stretchtext'] = 4;
15404
		}
15405
		if (TCPDF_STATIC::empty_string($x)) {
15406
			$x = $this->x;
15407
		}
15408
		if (TCPDF_STATIC::empty_string($y)) {
15409
			$y = $this->y;
15410
		}
15411
		// check page for no-write regions and adapt page margins if necessary
15412
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15413
		if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
15414
			if ($this->rtl) {
15415
				$w = $x - $this->lMargin;
15416
			} else {
15417
				$w = $this->w - $this->rMargin - $x;
15418
			}
15419
		}
15420
		// padding
15421
		if (!isset($style['padding'])) {
15422
			$padding = 0;
15423
		} elseif ($style['padding'] === 'auto') {
15424
			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
15425
		} else {
15426
			$padding = floatval($style['padding']);
15427
		}
15428
		// horizontal padding
15429
		if (!isset($style['hpadding'])) {
15430
			$hpadding = $padding;
15431
		} elseif ($style['hpadding'] === 'auto') {
15432
			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15433
		} else {
15434
			$hpadding = floatval($style['hpadding']);
15435
		}
15436
		// vertical padding
15437
		if (!isset($style['vpadding'])) {
15438
			$vpadding = $padding;
15439
		} elseif ($style['vpadding'] === 'auto') {
15440
			$vpadding = ($hpadding / 2);
15441
		} else {
15442
			$vpadding = floatval($style['vpadding']);
15443
		}
15444
		// calculate xres (single bar width)
15445
		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15446
		if ($style['stretch']) {
15447
			$xres = $max_xres;
15448
		} else {
15449
			if (TCPDF_STATIC::empty_string($xres)) {
15450
				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
15451
			}
15452
			if ($xres > $max_xres) {
15453
				// correct xres to fit on $w
15454
				$xres = $max_xres;
15455
			}
15456
			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15457
				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15458
				$hpadding = 10 * $xres;
15459
				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15460
					$vpadding = ($hpadding / 2);
15461
				}
15462
			}
15463
		}
15464
		if ($style['fitwidth']) {
15465
			$wold = $w;
15466
			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15467
			if (isset($style['cellfitalign'])) {
15468
				switch ($style['cellfitalign']) {
15469
					case 'L': {
15470
						if ($this->rtl) {
15471
							$x -= ($wold - $w);
15472
						}
15473
						break;
15474
					}
15475
					case 'R': {
15476
						if (!$this->rtl) {
15477
							$x += ($wold - $w);
15478
						}
15479
						break;
15480
					}
15481
					case 'C': {
15482
						if ($this->rtl) {
15483
							$x -= (($wold - $w) / 2);
15484
						} else {
15485
							$x += (($wold - $w) / 2);
15486
						}
15487
						break;
15488
					}
15489
					default : {
15490
						break;
15491
					}
15492
				}
15493
			}
15494
		}
15495
		$text_height = $this->getCellHeight($fontsize / $this->k);
15496
		// height
15497
		if (TCPDF_STATIC::empty_string($h) OR ($h <= 0)) {
15498
			// set default height
15499
			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15500
		}
15501
		$barh = $h - $text_height - (2 * $vpadding);
15502
		if ($barh <=0) {
15503
			// try to reduce font or padding to fit barcode on available height
15504
			if ($text_height > $h) {
15505
				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15506
				$text_height = $this->getCellHeight($fontsize / $this->k);
15507
				$this->setFont($style['font'], '', $fontsize);
15508
			}
15509
			if ($vpadding > 0) {
15510
				$vpadding = (($h - $text_height) / 4);
15511
			}
15512
			$barh = $h - $text_height - (2 * $vpadding);
15513
		}
15514
		// fit the barcode on available space
15515
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15516
		// set alignment
15517
		$this->img_rb_y = $y + $h;
15518
		// set alignment
15519
		if ($this->rtl) {
15520
			if ($style['position'] == 'L') {
15521
				$xpos = $this->lMargin;
15522
			} elseif ($style['position'] == 'C') {
15523
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15524
			} elseif ($style['position'] == 'R') {
15525
				$xpos = $this->w - $this->rMargin - $w;
15526
			} else {
15527
				$xpos = $x - $w;
15528
			}
15529
			$this->img_rb_x = $xpos;
15530
		} else {
15531
			if ($style['position'] == 'L') {
15532
				$xpos = $this->lMargin;
15533
			} elseif ($style['position'] == 'C') {
15534
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15535
			} elseif ($style['position'] == 'R') {
15536
				$xpos = $this->w - $this->rMargin - $w;
15537
			} else {
15538
				$xpos = $x;
15539
			}
15540
			$this->img_rb_x = $xpos + $w;
15541
		}
15542
		$xpos_rect = $xpos;
15543
		if (!isset($style['align'])) {
15544
			$style['align'] = 'C';
15545
		}
15546
		switch ($style['align']) {
15547
			case 'L': {
15548
				$xpos = $xpos_rect + $hpadding;
15549
				break;
15550
			}
15551
			case 'R': {
15552
				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15553
				break;
15554
			}
15555
			case 'C':
15556
			default : {
15557
				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15558
				break;
15559
			}
15560
		}
15561
		$xpos_text = $xpos;
15562
		// barcode is always printed in LTR direction
15563
		$tempRTL = $this->rtl;
15564
		$this->rtl = false;
15565
		// print background color
15566
		if ($style['bgcolor']) {
15567
			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15568
		} elseif ($style['border']) {
15569
			$this->Rect($xpos_rect, $y, $w, $h, 'D');
15570
		}
15571
		// set foreground color
15572
		$this->setDrawColorArray($style['fgcolor']);
15573
		$this->setTextColorArray($style['fgcolor']);
15574
		// print bars
15575
		foreach ($arrcode['bcode'] as $k => $v) {
15576
			$bw = ($v['w'] * $xres);
15577
			if ($v['t']) {
15578
				// draw a vertical bar
15579
				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15580
				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15581
			}
15582
			$xpos += $bw;
15583
		}
15584
		// print text
15585
		if ($style['text']) {
15586
			if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15587
				$label = $style['label'];
15588
			} else {
15589
				$label = $code;
15590
			}
15591
			$txtwidth = ($arrcode['maxw'] * $xres);
15592
			if ($this->GetStringWidth($label) > $txtwidth) {
15593
				$style['stretchtext'] = 2;
15594
			}
15595
			// print text
15596
			$this->x = $xpos_text;
15597
			$this->y = $y + $vpadding + $barh;
15598
			$cellpadding = $this->cell_padding;
15599
			$this->setCellPadding(0);
15600
			$this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15601
			$this->cell_padding = $cellpadding;
15602
		}
15603
		// restore original direction
15604
		$this->rtl = $tempRTL;
15605
		// restore previous settings
15606
		$this->setGraphicVars($gvars);
15607
		// set pointer to align the next text/objects
15608
		switch($align) {
15609
			case 'T':{
15610
				$this->y = $y;
15611
				$this->x = $this->img_rb_x;
15612
				break;
15613
			}
15614
			case 'M':{
15615
				$this->y = $y + round($h / 2);
15616
				$this->x = $this->img_rb_x;
15617
				break;
15618
			}
15619
			case 'B':{
15620
				$this->y = $this->img_rb_y;
15621
				$this->x = $this->img_rb_x;
15622
				break;
15623
			}
15624
			case 'N':{
15625
				$this->setY($this->img_rb_y);
15626
				break;
15627
			}
15628
			default:{
15629
				break;
15630
			}
15631
		}
15632
		$this->endlinex = $this->img_rb_x;
15633
	}
15634
 
15635
	/**
15636
	 * Print 2D Barcode.
15637
	 * @param string $code code to print
15638
	 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15639
	 * @param float|null $x x position in user units
15640
	 * @param float|null $y y position in user units
15641
	 * @param float|null $w width in user units
15642
	 * @param float|null $h height in user units
15643
	 * @param array $style array of options:<ul>
15644
	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15645
	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15646
	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15647
	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15648
	 * <li>int $style['module_width'] width of a single module in points</li>
15649
	 * <li>int $style['module_height'] height of a single module in points</li>
15650
	 * <li>array $style['fgcolor'] color array for bars and text</li>
15651
	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15652
	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15653
	 * @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>
15654
	 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15655
	 * @author Nicola Asuni
15656
	 * @since 4.5.037 (2009-04-07)
15657
	 * @public
15658
	 */
15659
	public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) {
15660
		if (TCPDF_STATIC::empty_string(trim($code))) {
15661
			return;
15662
		}
15663
		require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15664
		// save current graphic settings
15665
		$gvars = $this->getGraphicVars();
15666
		// create new barcode object
15667
		$barcodeobj = new TCPDF2DBarcode($code, $type);
15668
		$arrcode = $barcodeobj->getBarcodeArray();
15669
		if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15670
			$this->Error('Error in 2D barcode string');
15671
		}
15672
		// set default values
15673
		if (!isset($style['position'])) {
15674
			$style['position'] = '';
15675
		}
15676
		if (!isset($style['fgcolor'])) {
15677
			$style['fgcolor'] = array(0,0,0); // default black
15678
		}
15679
		if (!isset($style['bgcolor'])) {
15680
			$style['bgcolor'] = false; // default transparent
15681
		}
15682
		if (!isset($style['border'])) {
15683
			$style['border'] = false;
15684
		}
15685
		// padding
15686
		if (!isset($style['padding'])) {
15687
			$style['padding'] = 0;
15688
		} elseif ($style['padding'] === 'auto') {
15689
			$style['padding'] = 4;
15690
		}
15691
		if (!isset($style['hpadding'])) {
15692
			$style['hpadding'] = $style['padding'];
15693
		} elseif ($style['hpadding'] === 'auto') {
15694
			$style['hpadding'] = 4;
15695
		}
15696
		if (!isset($style['vpadding'])) {
15697
			$style['vpadding'] = $style['padding'];
15698
		} elseif ($style['vpadding'] === 'auto') {
15699
			$style['vpadding'] = 4;
15700
		}
15701
		$hpad = (2 * $style['hpadding']);
15702
		$vpad = (2 * $style['vpadding']);
15703
		// cell (module) dimension
15704
		if (!isset($style['module_width'])) {
15705
			$style['module_width'] = 1; // width of a single module in points
15706
		}
15707
		if (!isset($style['module_height'])) {
15708
			$style['module_height'] = 1; // height of a single module in points
15709
		}
15710
		if (TCPDF_STATIC::empty_string($x)) {
15711
			$x = $this->x;
15712
		}
15713
		if (TCPDF_STATIC::empty_string($y)) {
15714
			$y = $this->y;
15715
		}
15716
		// check page for no-write regions and adapt page margins if necessary
15717
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
15718
		// number of barcode columns and rows
15719
		$rows = $arrcode['num_rows'];
15720
		$cols = $arrcode['num_cols'];
15721
		if (($rows <= 0) || ($cols <= 0)){
15722
			$this->Error('Error in 2D barcode string');
15723
		}
15724
		// module width and height
15725
		$mw = $style['module_width'];
15726
		$mh = $style['module_height'];
15727
		if (($mw <= 0) OR ($mh <= 0)) {
15728
			$this->Error('Error in 2D barcode string');
15729
		}
15730
		// get max dimensions
15731
		if ($this->rtl) {
15732
			$maxw = $x - $this->lMargin;
15733
		} else {
15734
			$maxw = $this->w - $this->rMargin - $x;
15735
		}
15736
		$maxh = ($this->h - $this->tMargin - $this->bMargin);
15737
		$ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15738
		$ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15739
		if (!$distort) {
15740
			if (($maxw * $ratioHW) > $maxh) {
15741
				$maxw = $maxh * $ratioWH;
15742
			}
15743
			if (($maxh * $ratioWH) > $maxw) {
15744
				$maxh = $maxw * $ratioHW;
15745
			}
15746
		}
15747
		// set maximum dimensions
15748
		if ($w > $maxw) {
15749
			$w = $maxw;
15750
		}
15751
		if ($h > $maxh) {
15752
			$h = $maxh;
15753
		}
15754
		// set dimensions
15755
		if ((TCPDF_STATIC::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC::empty_string($h) OR ($h <= 0))) {
15756
			$w = ($cols + $hpad) * ($mw / $this->k);
15757
			$h = ($rows + $vpad) * ($mh / $this->k);
15758
		} elseif (($w === '') OR ($w <= 0)) {
15759
			$w = $h * $ratioWH;
15760
		} elseif (($h === '') OR ($h <= 0)) {
15761
			$h = $w * $ratioHW;
15762
		}
15763
		// barcode size (excluding padding)
15764
		$bw = ($w * $cols) / ($cols + $hpad);
15765
		$bh = ($h * $rows) / ($rows + $vpad);
15766
		// dimension of single barcode cell unit
15767
		$cw = $bw / $cols;
15768
		$ch = $bh / $rows;
15769
		if (!$distort) {
15770
			if (($cw / $ch) > ($mw / $mh)) {
15771
				// correct horizontal distortion
15772
				$cw = $ch * $mw / $mh;
15773
				$bw = $cw * $cols;
15774
				$style['hpadding'] = ($w - $bw) / (2 * $cw);
15775
			} else {
15776
				// correct vertical distortion
15777
				$ch = $cw * $mh / $mw;
15778
				$bh = $ch * $rows;
15779
				$style['vpadding'] = ($h - $bh) / (2 * $ch);
15780
			}
15781
		}
15782
		// fit the barcode on available space
15783
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15784
		// set alignment
15785
		$this->img_rb_y = $y + $h;
15786
		// set alignment
15787
		if ($this->rtl) {
15788
			if ($style['position'] == 'L') {
15789
				$xpos = $this->lMargin;
15790
			} elseif ($style['position'] == 'C') {
15791
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15792
			} elseif ($style['position'] == 'R') {
15793
				$xpos = $this->w - $this->rMargin - $w;
15794
			} else {
15795
				$xpos = $x - $w;
15796
			}
15797
			$this->img_rb_x = $xpos;
15798
		} else {
15799
			if ($style['position'] == 'L') {
15800
				$xpos = $this->lMargin;
15801
			} elseif ($style['position'] == 'C') {
15802
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15803
			} elseif ($style['position'] == 'R') {
15804
				$xpos = $this->w - $this->rMargin - $w;
15805
			} else {
15806
				$xpos = $x;
15807
			}
15808
			$this->img_rb_x = $xpos + $w;
15809
		}
15810
		$xstart = $xpos + ($style['hpadding'] * $cw);
15811
		$ystart = $y + ($style['vpadding'] * $ch);
15812
		// barcode is always printed in LTR direction
15813
		$tempRTL = $this->rtl;
15814
		$this->rtl = false;
15815
		// print background color
15816
		if ($style['bgcolor']) {
15817
			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15818
		} elseif ($style['border']) {
15819
			$this->Rect($xpos, $y, $w, $h, 'D');
15820
		}
15821
		// set foreground color
15822
		$this->setDrawColorArray($style['fgcolor']);
15823
		// print barcode cells
15824
		// for each row
15825
		for ($r = 0; $r < $rows; ++$r) {
15826
			$xr = $xstart;
15827
			// for each column
15828
			for ($c = 0; $c < $cols; ++$c) {
15829
				if ($arrcode['bcode'][$r][$c] == 1) {
15830
					// draw a single barcode cell
15831
					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15832
				}
15833
				$xr += $cw;
15834
			}
15835
			$ystart += $ch;
15836
		}
15837
		// restore original direction
15838
		$this->rtl = $tempRTL;
15839
		// restore previous settings
15840
		$this->setGraphicVars($gvars);
15841
		// set pointer to align the next text/objects
15842
		switch($align) {
15843
			case 'T':{
15844
				$this->y = $y;
15845
				$this->x = $this->img_rb_x;
15846
				break;
15847
			}
15848
			case 'M':{
15849
				$this->y = $y + round($h/2);
15850
				$this->x = $this->img_rb_x;
15851
				break;
15852
			}
15853
			case 'B':{
15854
				$this->y = $this->img_rb_y;
15855
				$this->x = $this->img_rb_x;
15856
				break;
15857
			}
15858
			case 'N':{
15859
				$this->setY($this->img_rb_y);
15860
				break;
15861
			}
15862
			default:{
15863
				break;
15864
			}
15865
		}
15866
		$this->endlinex = $this->img_rb_x;
15867
	}
15868
 
15869
	/**
15870
	 * Returns an array containing current margins:
15871
	 * <ul>
15872
			<li>$ret['left'] = left margin</li>
15873
			<li>$ret['right'] = right margin</li>
15874
			<li>$ret['top'] = top margin</li>
15875
			<li>$ret['bottom'] = bottom margin</li>
15876
			<li>$ret['header'] = header margin</li>
15877
			<li>$ret['footer'] = footer margin</li>
15878
			<li>$ret['cell'] = cell padding array</li>
15879
			<li>$ret['padding_left'] = cell left padding</li>
15880
			<li>$ret['padding_top'] = cell top padding</li>
15881
			<li>$ret['padding_right'] = cell right padding</li>
15882
			<li>$ret['padding_bottom'] = cell bottom padding</li>
15883
	 * </ul>
15884
	 * @return array containing all margins measures
15885
	 * @public
15886
	 * @since 3.2.000 (2008-06-23)
15887
	 */
15888
	public function getMargins() {
15889
		$ret = array(
15890
			'left' => $this->lMargin,
15891
			'right' => $this->rMargin,
15892
			'top' => $this->tMargin,
15893
			'bottom' => $this->bMargin,
15894
			'header' => $this->header_margin,
15895
			'footer' => $this->footer_margin,
15896
			'cell' => $this->cell_padding,
15897
			'padding_left' => $this->cell_padding['L'],
15898
			'padding_top' => $this->cell_padding['T'],
15899
			'padding_right' => $this->cell_padding['R'],
15900
			'padding_bottom' => $this->cell_padding['B']
15901
		);
15902
		return $ret;
15903
	}
15904
 
15905
	/**
15906
	 * Returns an array containing original margins:
15907
	 * <ul>
15908
			<li>$ret['left'] = left margin</li>
15909
			<li>$ret['right'] = right margin</li>
15910
	 * </ul>
15911
	 * @return array containing all margins measures
15912
	 * @public
15913
	 * @since 4.0.012 (2008-07-24)
15914
	 */
15915
	public function getOriginalMargins() {
15916
		$ret = array(
15917
			'left' => $this->original_lMargin,
15918
			'right' => $this->original_rMargin
15919
		);
15920
		return $ret;
15921
	}
15922
 
15923
	/**
15924
	 * Returns the current font size.
15925
	 * @return float current font size
15926
	 * @public
15927
	 * @since 3.2.000 (2008-06-23)
15928
	 */
15929
	public function getFontSize() {
15930
		return $this->FontSize;
15931
	}
15932
 
15933
	/**
15934
	 * Returns the current font size in points unit.
15935
	 * @return int current font size in points unit
15936
	 * @public
15937
	 * @since 3.2.000 (2008-06-23)
15938
	 */
15939
	public function getFontSizePt() {
15940
		return $this->FontSizePt;
15941
	}
15942
 
15943
	/**
15944
	 * Returns the current font family name.
15945
	 * @return string current font family name
15946
	 * @public
15947
	 * @since 4.3.008 (2008-12-05)
15948
	 */
15949
	public function getFontFamily() {
15950
		return $this->FontFamily;
15951
	}
15952
 
15953
	/**
15954
	 * Returns the current font style.
15955
	 * @return string current font style
15956
	 * @public
15957
	 * @since 4.3.008 (2008-12-05)
15958
	 */
15959
	public function getFontStyle() {
15960
		return $this->FontStyle;
15961
	}
15962
 
15963
	/**
15964
	 * Cleanup HTML code (requires HTML Tidy library).
15965
	 * @param string $html htmlcode to fix
15966
	 * @param string $default_css CSS commands to add
15967
	 * @param array|null $tagvs parameters for setHtmlVSpace method
15968
	 * @param array|null $tidy_options options for tidy_parse_string function
15969
	 * @return string XHTML code cleaned up
15970
	 * @author Nicola Asuni
15971
	 * @public
15972
	 * @since 5.9.017 (2010-11-16)
15973
	 * @see setHtmlVSpace()
15974
	 */
15975
	public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) {
15976
		return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15977
	}
15978
 
15979
	/**
15980
	 * Returns the border width from CSS property
15981
	 * @param string $width border width
15982
	 * @return int with in user units
15983
	 * @protected
15984
	 * @since 5.7.000 (2010-08-02)
15985
	 */
15986
	protected function getCSSBorderWidth($width) {
15987
		if ($width == 'thin') {
15988
			$width = (2 / $this->k);
15989
		} elseif ($width == 'medium') {
15990
			$width = (4 / $this->k);
15991
		} elseif ($width == 'thick') {
15992
			$width = (6 / $this->k);
15993
		} else {
15994
			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15995
		}
15996
		return $width;
15997
	}
15998
 
15999
	/**
16000
	 * Returns the border dash style from CSS property
16001
	 * @param string $style border style to convert
16002
	 * @return int sash style (return -1 in case of none or hidden border)
16003
	 * @protected
16004
	 * @since 5.7.000 (2010-08-02)
16005
	 */
16006
	protected function getCSSBorderDashStyle($style) {
16007
		switch (strtolower($style)) {
16008
			case 'none':
16009
			case 'hidden': {
16010
				$dash = -1;
16011
				break;
16012
			}
16013
			case 'dotted': {
16014
				$dash = 1;
16015
				break;
16016
			}
16017
			case 'dashed': {
16018
				$dash = 3;
16019
				break;
16020
			}
16021
			case 'double':
16022
			case 'groove':
16023
			case 'ridge':
16024
			case 'inset':
16025
			case 'outset':
16026
			case 'solid':
16027
			default: {
16028
				$dash = 0;
16029
				break;
16030
			}
16031
		}
16032
		return $dash;
16033
	}
16034
 
16035
	/**
16036
	 * Returns the border style array from CSS border properties
16037
	 * @param string $cssborder border properties
16038
	 * @return array containing border properties
16039
	 * @protected
16040
	 * @since 5.7.000 (2010-08-02)
16041
	 */
16042
	protected function getCSSBorderStyle($cssborder) {
16043
		$bprop = preg_split('/[\s]+/', trim($cssborder));
16044
		$count = count($bprop);
16045
		if ($count > 0 && $bprop[$count - 1] === '!important') {
16046
			unset($bprop[$count - 1]);
16047
			--$count;
16048
		}
16049
 
16050
		$border = array(); // value to be returned
16051
		switch ($count) {
16052
			case 2: {
16053
				$width = 'medium';
16054
				$style = $bprop[0];
16055
				$color = $bprop[1];
16056
				break;
16057
			}
16058
			case 1: {
16059
				$width = 'medium';
16060
				$style = $bprop[0];
16061
				$color = 'black';
16062
				break;
16063
			}
16064
			case 0: {
16065
				$width = 'medium';
16066
				$style = 'solid';
16067
				$color = 'black';
16068
				break;
16069
			}
16070
			default: {
16071
				$width = $bprop[0];
16072
				$style = $bprop[1];
16073
				$color = $bprop[2];
16074
				break;
16075
			}
16076
		}
16077
		if ($style == 'none') {
16078
			return array();
16079
		}
16080
		$border['cap'] = 'square';
16081
		$border['join'] = 'miter';
16082
		$border['dash'] = $this->getCSSBorderDashStyle($style);
16083
		if ($border['dash'] < 0) {
16084
			return array();
16085
		}
16086
		$border['width'] = $this->getCSSBorderWidth($width);
16087
		$border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16088
		return $border;
16089
	}
16090
 
16091
	/**
16092
	 * Get the internal Cell padding from CSS attribute.
16093
	 * @param string $csspadding padding properties
16094
	 * @param float $width width of the containing element
16095
	 * @return array of cell paddings
16096
	 * @public
16097
	 * @since 5.9.000 (2010-10-04)
16098
	 */
16099
	public function getCSSPadding($csspadding, $width=0) {
16100
		$padding = preg_split('/[\s]+/', trim($csspadding));
16101
		$cell_padding = array(); // value to be returned
16102
		switch (count($padding)) {
16103
			case 4: {
16104
				$cell_padding['T'] = $padding[0];
16105
				$cell_padding['R'] = $padding[1];
16106
				$cell_padding['B'] = $padding[2];
16107
				$cell_padding['L'] = $padding[3];
16108
				break;
16109
			}
16110
			case 3: {
16111
				$cell_padding['T'] = $padding[0];
16112
				$cell_padding['R'] = $padding[1];
16113
				$cell_padding['B'] = $padding[2];
16114
				$cell_padding['L'] = $padding[1];
16115
				break;
16116
			}
16117
			case 2: {
16118
				$cell_padding['T'] = $padding[0];
16119
				$cell_padding['R'] = $padding[1];
16120
				$cell_padding['B'] = $padding[0];
16121
				$cell_padding['L'] = $padding[1];
16122
				break;
16123
			}
16124
			case 1: {
16125
				$cell_padding['T'] = $padding[0];
16126
				$cell_padding['R'] = $padding[0];
16127
				$cell_padding['B'] = $padding[0];
16128
				$cell_padding['L'] = $padding[0];
16129
				break;
16130
			}
16131
			default: {
16132
				return $this->cell_padding;
16133
			}
16134
		}
16135
		if ($width == 0) {
16136
			$width = $this->w - $this->lMargin - $this->rMargin;
16137
		}
16138
		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16139
		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16140
		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16141
		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16142
		return $cell_padding;
16143
	}
16144
 
16145
	/**
16146
	 * Get the internal Cell margin from CSS attribute.
16147
	 * @param string $cssmargin margin properties
16148
	 * @param float $width width of the containing element
16149
	 * @return array of cell margins
16150
	 * @public
16151
	 * @since 5.9.000 (2010-10-04)
16152
	 */
16153
	public function getCSSMargin($cssmargin, $width=0) {
16154
		$margin = preg_split('/[\s]+/', trim($cssmargin));
16155
		$cell_margin = array(); // value to be returned
16156
		switch (count($margin)) {
16157
			case 4: {
16158
				$cell_margin['T'] = $margin[0];
16159
				$cell_margin['R'] = $margin[1];
16160
				$cell_margin['B'] = $margin[2];
16161
				$cell_margin['L'] = $margin[3];
16162
				break;
16163
			}
16164
			case 3: {
16165
				$cell_margin['T'] = $margin[0];
16166
				$cell_margin['R'] = $margin[1];
16167
				$cell_margin['B'] = $margin[2];
16168
				$cell_margin['L'] = $margin[1];
16169
				break;
16170
			}
16171
			case 2: {
16172
				$cell_margin['T'] = $margin[0];
16173
				$cell_margin['R'] = $margin[1];
16174
				$cell_margin['B'] = $margin[0];
16175
				$cell_margin['L'] = $margin[1];
16176
				break;
16177
			}
16178
			case 1: {
16179
				$cell_margin['T'] = $margin[0];
16180
				$cell_margin['R'] = $margin[0];
16181
				$cell_margin['B'] = $margin[0];
16182
				$cell_margin['L'] = $margin[0];
16183
				break;
16184
			}
16185
			default: {
16186
				return $this->cell_margin;
16187
			}
16188
		}
16189
		if ($width == 0) {
16190
			$width = $this->w - $this->lMargin - $this->rMargin;
16191
		}
16192
		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16193
		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16194
		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16195
		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16196
		return $cell_margin;
16197
	}
16198
 
16199
	/**
16200
	 * Get the border-spacing from CSS attribute.
16201
	 * @param string $cssbspace border-spacing CSS properties
16202
	 * @param float $width width of the containing element
16203
	 * @return array of border spacings
16204
	 * @public
16205
	 * @since 5.9.010 (2010-10-27)
16206
	 */
16207
	public function getCSSBorderMargin($cssbspace, $width=0) {
16208
		$space = preg_split('/[\s]+/', trim($cssbspace));
16209
		$border_spacing = array(); // value to be returned
16210
		switch (count($space)) {
16211
			case 2: {
16212
				$border_spacing['H'] = $space[0];
16213
				$border_spacing['V'] = $space[1];
16214
				break;
16215
			}
16216
			case 1: {
16217
				$border_spacing['H'] = $space[0];
16218
				$border_spacing['V'] = $space[0];
16219
				break;
16220
			}
16221
			default: {
16222
				return array('H' => 0, 'V' => 0);
16223
			}
16224
		}
16225
		if ($width == 0) {
16226
			$width = $this->w - $this->lMargin - $this->rMargin;
16227
		}
16228
		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16229
		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16230
		return $border_spacing;
16231
	}
16232
 
16233
	/**
16234
	 * Returns the letter-spacing value from CSS value
16235
	 * @param string $spacing letter-spacing value
16236
	 * @param float $parent font spacing (tracking) value of the parent element
16237
	 * @return float quantity to increases or decreases the space between characters in a text.
16238
	 * @protected
16239
	 * @since 5.9.000 (2010-10-02)
16240
	 */
16241
	protected function getCSSFontSpacing($spacing, $parent=0) {
16242
		$val = 0; // value to be returned
16243
		$spacing = trim($spacing);
16244
		switch ($spacing) {
16245
			case 'normal': {
16246
				$val = 0;
16247
				break;
16248
			}
16249
			case 'inherit': {
16250
				if ($parent == 'normal') {
16251
					$val = 0;
16252
				} else {
16253
					$val = $parent;
16254
				}
16255
				break;
16256
			}
16257
			default: {
16258
				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16259
			}
16260
		}
16261
		return $val;
16262
	}
16263
 
16264
	/**
16265
	 * Returns the percentage of font stretching from CSS value
16266
	 * @param string $stretch stretch mode
16267
	 * @param float $parent stretch value of the parent element
16268
	 * @return float font stretching percentage
16269
	 * @protected
16270
	 * @since 5.9.000 (2010-10-02)
16271
	 */
16272
	protected function getCSSFontStretching($stretch, $parent=100) {
16273
		$val = 100; // value to be returned
16274
		$stretch = trim($stretch);
16275
		switch ($stretch) {
16276
			case 'ultra-condensed': {
16277
				$val = 40;
16278
				break;
16279
			}
16280
			case 'extra-condensed': {
16281
				$val = 55;
16282
				break;
16283
			}
16284
			case 'condensed': {
16285
				$val = 70;
16286
				break;
16287
			}
16288
			case 'semi-condensed': {
16289
				$val = 85;
16290
				break;
16291
			}
16292
			case 'normal': {
16293
				$val = 100;
16294
				break;
16295
			}
16296
			case 'semi-expanded': {
16297
				$val = 115;
16298
				break;
16299
			}
16300
			case 'expanded': {
16301
				$val = 130;
16302
				break;
16303
			}
16304
			case 'extra-expanded': {
16305
				$val = 145;
16306
				break;
16307
			}
16308
			case 'ultra-expanded': {
16309
				$val = 160;
16310
				break;
16311
			}
16312
			case 'wider': {
16313
				$val = ($parent + 10);
16314
				break;
16315
			}
16316
			case 'narrower': {
16317
				$val = ($parent - 10);
16318
				break;
16319
			}
16320
			case 'inherit': {
16321
				if ($parent == 'normal') {
16322
					$val = 100;
16323
				} else {
16324
					$val = $parent;
16325
				}
16326
				break;
16327
			}
16328
			default: {
16329
				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16330
			}
16331
		}
16332
		return $val;
16333
	}
16334
 
16335
	/**
16336
	 * Convert HTML string containing font size value to points
16337
	 * @param string $val String containing font size value and unit.
16338
	 * @param float $refsize Reference font size in points.
16339
	 * @param float $parent_size Parent font size in points.
16340
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16341
	 * @return float value in points
16342
	 * @public
16343
	 */
16344
	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16345
		$refsize = TCPDF_FONTS::getFontRefSize($refsize);
16346
		$parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16347
		switch ($val) {
16348
			case 'xx-small': {
16349
				$size = ($refsize - 4);
16350
				break;
16351
			}
16352
			case 'x-small': {
16353
				$size = ($refsize - 3);
16354
				break;
16355
			}
16356
			case 'small': {
16357
				$size = ($refsize - 2);
16358
				break;
16359
			}
16360
			case 'medium': {
16361
				$size = $refsize;
16362
				break;
16363
			}
16364
			case 'large': {
16365
				$size = ($refsize + 2);
16366
				break;
16367
			}
16368
			case 'x-large': {
16369
				$size = ($refsize + 4);
16370
				break;
16371
			}
16372
			case 'xx-large': {
16373
				$size = ($refsize + 6);
16374
				break;
16375
			}
16376
			case 'smaller': {
16377
				$size = ($parent_size - 3);
16378
				break;
16379
			}
16380
			case 'larger': {
16381
				$size = ($parent_size + 3);
16382
				break;
16383
			}
16384
			default: {
16385
				$parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16386
				$size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16387
			}
16388
		}
16389
		return $size;
16390
	}
16391
 
16392
	/**
16393
	 * Returns the HTML DOM array.
16394
	 * @param string $html html code
16395
	 * @return array
16396
	 * @protected
16397
	 * @since 3.2.000 (2008-06-20)
16398
	 */
16399
	protected function getHtmlDomArray($html) {
1441 ariadna 16400
		// set inheritable properties fot the first void element
16401
		// 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
16402
		$dom = array(
16403
			array(
16404
				'tag' => false,
16405
				'block' => false,
16406
				'value' => '',
16407
				'parent' => 0,
16408
				'hide' => false,
16409
				'fontname' => $this->FontFamily,
16410
				'fontstyle' => $this->FontStyle,
16411
				'fontsize' => $this->FontSizePt,
16412
				'font-stretch' => $this->font_stretching,
16413
				'letter-spacing' => $this->font_spacing,
16414
				'stroke' => $this->textstrokewidth,
16415
				'fill' => (($this->textrendermode % 2) == 0),
16416
				'clip' => ($this->textrendermode > 3),
16417
				'line-height' => $this->cell_height_ratio,
16418
				'bgcolor' => false,
16419
				'fgcolor' => $this->fgcolor, // color
16420
				'strokecolor' => $this->strokecolor,
16421
				'align' => '',
16422
				'listtype' => '',
16423
				'text-indent' => 0,
16424
				'text-transform' => '',
16425
				'border' => array(),
16426
				'dir' => $this->rtl?'rtl':'ltr',
16427
				'width' => 0,
16428
				'height' => 0,
16429
				'x' => 0,
16430
				'y' => 0,
16431
				'w' => 0,
16432
				'h' => 0,
16433
				'l' => 0,
16434
				't' => 0,
16435
				'r' => 0,
16436
				'b' => 0,
16437
				'padding' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16438
				'margin' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16439
				'border-spacing' => array('H' => 0, 'V' => 0),
16440
				'border-collapse' => 'separate',
16441
			)
16442
		);
16443
 
16444
		if($html === '' || $html === null) {
16445
			return $dom;
16446
		}
1 efrain 16447
		// array of CSS styles ( selector => properties).
16448
		$css = array();
16449
		// get CSS array defined at previous call
16450
		$matches = array();
16451
		if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16452
			if (isset($matches[1][0])) {
16453
				$css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16454
			}
16455
			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16456
		}
16457
		// extract external CSS files
16458
		$matches = array();
16459
		if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16460
			foreach ($matches[1] as $key => $link) {
16461
				$type = array();
16462
				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16463
					$type = array();
16464
					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16465
					// get 'all' and 'print' media, other media types are discarded
16466
					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16467
					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16468
						$type = array();
16469
						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16470
							// read CSS data file
16471
                            $cssdata = $this->getCachedFileContents(trim($type[1]));
16472
							if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16473
								$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16474
							}
16475
						}
16476
					}
16477
				}
16478
			}
16479
		}
16480
		// extract style tags
16481
		$matches = array();
16482
		if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16483
			foreach ($matches[1] as $key => $media) {
16484
				$type = array();
16485
				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16486
				// get 'all' and 'print' media, other media types are discarded
16487
				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16488
				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16489
					$cssdata = $matches[2][$key];
16490
					$css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16491
				}
16492
			}
16493
		}
16494
		// create a special tag to contain the CSS array (used for table content)
16495
		$csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16496
		// remove head and style blocks
16497
		$html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16498
		$html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16499
		// define block tags
16500
		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16501
		// define self-closing tags
16502
		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16503
		// remove all unsupported tags (the line below lists all supported tags)
16504
		$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>');
16505
		//replace some blank characters
16506
		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16507
		$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);
16508
		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
16509
		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16510
		$html = strtr($html, $repTable);
16511
		$offset = 0;
16512
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16513
			$html_a = substr($html, 0, $offset);
16514
			$html_b = substr($html, $offset, ($pos - $offset + 6));
16515
			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16516
				// preserve newlines on <pre> tag
16517
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16518
			}
16519
			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16520
				// preserve spaces on <pre> tag
16521
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16522
			}
16523
			$html = $html_a.$html_b.substr($html, $pos + 6);
16524
			$offset = strlen($html_a.$html_b);
16525
		}
16526
		$offset = 0;
16527
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16528
			$html_a = substr($html, 0, $offset);
16529
			$html_b = substr($html, $offset, ($pos - $offset + 11));
16530
			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16531
				// preserve newlines on <textarea> tag
16532
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16533
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16534
			}
16535
			$html = $html_a.$html_b.substr($html, $pos + 11);
16536
			$offset = strlen($html_a.$html_b);
16537
		}
16538
		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
16539
		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16540
		$offset = 0;
16541
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16542
			$html_a = substr($html, 0, $offset);
16543
			$html_b = substr($html, $offset, ($pos - $offset + 9));
16544
			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16545
				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16546
				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16547
			}
16548
			$html = $html_a.$html_b.substr($html, $pos + 9);
16549
			$offset = strlen($html_a.$html_b);
16550
		}
16551
		if (preg_match("'</select'si", $html)) {
16552
			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16553
			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16554
		}
16555
		$html = str_replace("\n", ' ', $html);
16556
		// restore textarea newlines
16557
		$html = str_replace('<TBR>', "\n", $html);
16558
		// remove extra spaces from code
16559
		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16560
		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16561
		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16562
		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16563
		$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);
16564
		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16565
		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16566
		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16567
		$html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16568
		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16569
		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16570
		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16571
		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16572
		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16573
		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16574
		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16575
		$html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16576
		$html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16577
		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16578
		// trim string
16579
		$html = $this->stringTrim($html);
16580
		// fix br tag after li
16581
		$html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16582
		// fix first image tag alignment
16583
		$html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16584
		// pattern for generic tag
16585
		$tagpattern = '/(<[^>]+>)/';
16586
		// explodes the string
16587
		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16588
		// count elements
16589
		$maxel = count($a);
16590
		$elkey = 0;
16591
		$thead = false; // true when we are inside the THEAD tag
1441 ariadna 16592
		$key = 1;
1 efrain 16593
		$level = array();
16594
		array_push($level, 0); // root
16595
		while ($elkey < $maxel) {
16596
			$dom[$key] = array();
16597
			$element = $a[$elkey];
16598
			$dom[$key]['elkey'] = $elkey;
16599
			if (preg_match($tagpattern, $element)) {
16600
				// html tag
16601
				$element = substr($element, 1, -1);
16602
				// get tag name
16603
				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16604
				$tagname = strtolower($tag[1]);
16605
				// check if we are inside a table header
16606
				if ($tagname == 'thead') {
16607
					if ($element[0] == '/') {
16608
						$thead = false;
16609
					} else {
16610
						$thead = true;
16611
					}
16612
					++$elkey;
16613
					continue;
16614
				}
16615
				$dom[$key]['tag'] = true;
16616
				$dom[$key]['value'] = $tagname;
16617
				if (in_array($dom[$key]['value'], $blocktags)) {
16618
					$dom[$key]['block'] = true;
16619
				} else {
16620
					$dom[$key]['block'] = false;
16621
				}
16622
				if ($element[0] == '/') {
16623
					// *** closing html tag
16624
					$dom[$key]['opening'] = false;
16625
					$dom[$key]['parent'] = end($level);
16626
					array_pop($level);
16627
					$dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16628
					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16629
					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16630
					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16631
					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16632
					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16633
					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16634
					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16635
					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16636
					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16637
					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16638
					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16639
					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16640
					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16641
					$dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16642
					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16643
					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16644
						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16645
					}
16646
					// set the number of columns in table tag
16647
					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16648
						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16649
					}
16650
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16651
						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16652
						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16653
							$dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16654
						}
16655
						$key = $i;
16656
						// mark nested tables
16657
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16658
						// remove thead sections from nested tables
16659
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16660
						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16661
					}
16662
					// store header rows on a new table
16663
					if (
16664
						($dom[$key]['value'] === 'tr')
16665
						&& !empty($dom[($dom[$key]['parent'])]['thead'])
16666
						&& ($dom[($dom[$key]['parent'])]['thead'] === true)
16667
					) {
16668
						if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16669
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16670
						}
16671
						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16672
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16673
						}
16674
						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16675
							$dom[($dom[$key]['parent'])]['attribute'] = array();
16676
						}
16677
						// header elements must be always contained in a single page
16678
						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16679
					}
16680
					if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16681
						// remove the nobr attributes from the table header
16682
						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16683
						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16684
					}
16685
				} else {
16686
					// *** opening or self-closing html tag
16687
					$dom[$key]['opening'] = true;
16688
					$dom[$key]['parent'] = end($level);
16689
					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16690
						// self-closing tag
16691
						$dom[$key]['self'] = true;
16692
					} else {
16693
						// opening tag
16694
						array_push($level, $key);
16695
						$dom[$key]['self'] = false;
16696
					}
16697
					// copy some values from parent
16698
					$parentkey = 0;
16699
					if ($key > 0) {
16700
						$parentkey = $dom[$key]['parent'];
16701
						$dom[$key]['hide'] = $dom[$parentkey]['hide'];
16702
						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16703
						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16704
						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16705
						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16706
						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16707
						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16708
						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
16709
						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
16710
						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16711
						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16712
						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16713
						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16714
						$dom[$key]['align'] = $dom[$parentkey]['align'];
16715
						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16716
						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16717
						$dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16718
						$dom[$key]['border'] = array();
16719
						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
16720
					}
16721
					// get attributes
16722
					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16723
					$dom[$key]['attribute'] = array(); // reset attribute array
16724
                    foreach($attr_array[1] as $id => $name) {
16725
                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16726
                    }
16727
					if (!empty($css)) {
16728
						// merge CSS style to current style
16729
						list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16730
						$dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16731
					}
16732
					// split style attributes
16733
					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16734
						// get style attributes
16735
						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16736
						$dom[$key]['style'] = array(); // reset style attribute array
16737
                        foreach($style_array[1] as $id => $name) {
16738
                            // in case of duplicate attribute the last replace the previous
16739
                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16740
                        }
16741
						// --- get some style attributes ---
16742
						// text direction
16743
						if (isset($dom[$key]['style']['direction'])) {
16744
							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
16745
						}
16746
						// display
16747
						if (isset($dom[$key]['style']['display'])) {
16748
							$dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16749
						}
16750
						// font family
16751
						if (isset($dom[$key]['style']['font-family'])) {
16752
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16753
						}
16754
						// list-style-type
16755
						if (isset($dom[$key]['style']['list-style-type'])) {
16756
							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16757
							if ($dom[$key]['listtype'] == 'inherit') {
16758
								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16759
							}
16760
						}
16761
						// text-indent
16762
						if (isset($dom[$key]['style']['text-indent'])) {
16763
							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16764
							if ($dom[$key]['text-indent'] == 'inherit') {
16765
								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16766
							}
16767
						}
16768
						// text-transform
16769
						if (isset($dom[$key]['style']['text-transform'])) {
16770
							$dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16771
						}
16772
						// font size
16773
						if (isset($dom[$key]['style']['font-size'])) {
16774
							$fsize = trim($dom[$key]['style']['font-size']);
16775
							$dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16776
						}
16777
						// font-stretch
16778
						if (isset($dom[$key]['style']['font-stretch'])) {
16779
							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16780
						}
16781
						// letter-spacing
16782
						if (isset($dom[$key]['style']['letter-spacing'])) {
16783
							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16784
						}
16785
						// line-height (internally is the cell height ratio)
16786
						if (isset($dom[$key]['style']['line-height'])) {
16787
							$lineheight = trim($dom[$key]['style']['line-height']);
16788
							switch ($lineheight) {
16789
								// A normal line height. This is default
16790
								case 'normal': {
16791
									$dom[$key]['line-height'] = $dom[0]['line-height'];
16792
									break;
16793
								}
16794
								case 'inherit': {
16795
									$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16796
								}
16797
								default: {
16798
									if (is_numeric($lineheight)) {
16799
										// convert to percentage of font height
16800
										$lineheight = ($lineheight * 100).'%';
16801
									}
16802
									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16803
									if (substr($lineheight, -1) !== '%') {
16804
										if ($dom[$key]['fontsize'] <= 0) {
16805
											$dom[$key]['line-height'] = 1;
16806
										} else {
16807
											$dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16808
										}
16809
									}
16810
								}
16811
							}
16812
						}
16813
						// font style
16814
						if (isset($dom[$key]['style']['font-weight'])) {
16815
							if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16816
								if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16817
									$dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16818
								}
16819
							} elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16820
								$dom[$key]['fontstyle'] .= 'B';
16821
							}
16822
						}
16823
						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16824
							$dom[$key]['fontstyle'] .= 'I';
16825
						}
16826
						// font color
16827
						if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16828
							$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16829
						} elseif ($dom[$key]['value'] == 'a') {
16830
							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16831
						}
16832
						// background color
16833
						if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16834
							$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16835
						}
16836
						// text-decoration
16837
						if (isset($dom[$key]['style']['text-decoration'])) {
16838
							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16839
							foreach ($decors as $dec) {
16840
								$dec = trim($dec);
16841
								if (!TCPDF_STATIC::empty_string($dec)) {
16842
									if ($dec[0] == 'u') {
16843
										// underline
16844
										$dom[$key]['fontstyle'] .= 'U';
16845
									} elseif ($dec[0] == 'l') {
16846
										// line-through
16847
										$dom[$key]['fontstyle'] .= 'D';
16848
									} elseif ($dec[0] == 'o') {
16849
										// overline
16850
										$dom[$key]['fontstyle'] .= 'O';
16851
									}
16852
								}
16853
							}
16854
						} elseif ($dom[$key]['value'] == 'a') {
16855
							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16856
						}
16857
						// check for width attribute
16858
						if (isset($dom[$key]['style']['width'])) {
16859
							$dom[$key]['width'] = $dom[$key]['style']['width'];
16860
						}
16861
						// check for height attribute
16862
						if (isset($dom[$key]['style']['height'])) {
16863
							$dom[$key]['height'] = $dom[$key]['style']['height'];
16864
						}
16865
						// check for text alignment
16866
						if (isset($dom[$key]['style']['text-align'])) {
16867
							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16868
						}
16869
						// check for CSS border properties
16870
						if (isset($dom[$key]['style']['border'])) {
16871
							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16872
							if (!empty($borderstyle)) {
16873
								$dom[$key]['border']['LTRB'] = $borderstyle;
16874
							}
16875
						}
16876
						if (isset($dom[$key]['style']['border-color'])) {
16877
							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16878
							if (isset($brd_colors[3])) {
16879
								$dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16880
							}
16881
							if (isset($brd_colors[1])) {
16882
								$dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16883
							}
16884
							if (isset($brd_colors[0])) {
16885
								$dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16886
							}
16887
							if (isset($brd_colors[2])) {
16888
								$dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16889
							}
16890
						}
16891
						if (isset($dom[$key]['style']['border-width'])) {
16892
							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16893
							if (isset($brd_widths[3])) {
16894
								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16895
							}
16896
							if (isset($brd_widths[1])) {
16897
								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16898
							}
16899
							if (isset($brd_widths[0])) {
16900
								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16901
							}
16902
							if (isset($brd_widths[2])) {
16903
								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16904
							}
16905
						}
16906
						if (isset($dom[$key]['style']['border-style'])) {
16907
							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16908
							if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16909
								$dom[$key]['border']['L']['cap'] = 'square';
16910
								$dom[$key]['border']['L']['join'] = 'miter';
16911
								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16912
								if ($dom[$key]['border']['L']['dash'] < 0) {
16913
									$dom[$key]['border']['L'] = array();
16914
								}
16915
							}
16916
							if (isset($brd_styles[1])) {
16917
								$dom[$key]['border']['R']['cap'] = 'square';
16918
								$dom[$key]['border']['R']['join'] = 'miter';
16919
								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16920
								if ($dom[$key]['border']['R']['dash'] < 0) {
16921
									$dom[$key]['border']['R'] = array();
16922
								}
16923
							}
16924
							if (isset($brd_styles[0])) {
16925
								$dom[$key]['border']['T']['cap'] = 'square';
16926
								$dom[$key]['border']['T']['join'] = 'miter';
16927
								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16928
								if ($dom[$key]['border']['T']['dash'] < 0) {
16929
									$dom[$key]['border']['T'] = array();
16930
								}
16931
							}
16932
							if (isset($brd_styles[2])) {
16933
								$dom[$key]['border']['B']['cap'] = 'square';
16934
								$dom[$key]['border']['B']['join'] = 'miter';
16935
								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16936
								if ($dom[$key]['border']['B']['dash'] < 0) {
16937
									$dom[$key]['border']['B'] = array();
16938
								}
16939
							}
16940
						}
16941
						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16942
						foreach ($cellside as $bsk => $bsv) {
16943
							if (isset($dom[$key]['style']['border-'.$bsv])) {
16944
								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16945
								if (!empty($borderstyle)) {
16946
									$dom[$key]['border'][$bsk] = $borderstyle;
16947
								}
16948
							}
16949
							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16950
								$dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16951
							}
16952
							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16953
								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16954
							}
16955
							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16956
								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16957
								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16958
									$dom[$key]['border'][$bsk] = array();
16959
								}
16960
							}
16961
						}
16962
						// check for CSS padding properties
16963
						if (isset($dom[$key]['style']['padding'])) {
16964
							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16965
						} else {
16966
							$dom[$key]['padding'] = $this->cell_padding;
16967
						}
16968
						foreach ($cellside as $psk => $psv) {
16969
							if (isset($dom[$key]['style']['padding-'.$psv])) {
16970
								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16971
							}
16972
						}
16973
						// check for CSS margin properties
16974
						if (isset($dom[$key]['style']['margin'])) {
16975
							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16976
						} else {
16977
							$dom[$key]['margin'] = $this->cell_margin;
16978
						}
16979
						foreach ($cellside as $psk => $psv) {
16980
							if (isset($dom[$key]['style']['margin-'.$psv])) {
16981
								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16982
							}
16983
						}
16984
						// check for CSS border-spacing properties
16985
						if (isset($dom[$key]['style']['border-spacing'])) {
16986
							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16987
						}
16988
						// page-break-inside
16989
						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16990
							$dom[$key]['attribute']['nobr'] = 'true';
16991
						}
16992
						// page-break-before
16993
						if (isset($dom[$key]['style']['page-break-before'])) {
16994
							if ($dom[$key]['style']['page-break-before'] == 'always') {
16995
								$dom[$key]['attribute']['pagebreak'] = 'true';
16996
							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16997
								$dom[$key]['attribute']['pagebreak'] = 'left';
16998
							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16999
								$dom[$key]['attribute']['pagebreak'] = 'right';
17000
							}
17001
						}
17002
						// page-break-after
17003
						if (isset($dom[$key]['style']['page-break-after'])) {
17004
							if ($dom[$key]['style']['page-break-after'] == 'always') {
17005
								$dom[$key]['attribute']['pagebreakafter'] = 'true';
17006
							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
17007
								$dom[$key]['attribute']['pagebreakafter'] = 'left';
17008
							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
17009
								$dom[$key]['attribute']['pagebreakafter'] = 'right';
17010
							}
17011
						}
17012
					}
17013
					if (isset($dom[$key]['attribute']['display'])) {
17014
						$dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
17015
					}
17016
					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
17017
						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
17018
						if (!empty($borderstyle)) {
17019
							$dom[$key]['border']['LTRB'] = $borderstyle;
17020
						}
17021
					}
17022
					// check for font tag
17023
					if ($dom[$key]['value'] == 'font') {
17024
						// font family
17025
						if (isset($dom[$key]['attribute']['face'])) {
17026
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17027
						}
17028
						// font size
17029
						if (isset($dom[$key]['attribute']['size'])) {
17030
							if ($key > 0) {
17031
								if ($dom[$key]['attribute']['size'][0] == '+') {
17032
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
17033
								} elseif ($dom[$key]['attribute']['size'][0] == '-') {
17034
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
17035
								} else {
17036
									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17037
								}
17038
							} else {
17039
								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17040
							}
17041
						}
17042
					}
17043
					// force natural alignment for lists
17044
					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
17045
						AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
17046
						if ($this->rtl) {
17047
							$dom[$key]['align'] = 'R';
17048
						} else {
17049
							$dom[$key]['align'] = 'L';
17050
						}
17051
					}
17052
					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17053
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17054
							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17055
						}
17056
					}
17057
					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17058
						$dom[$key]['fontstyle'] .= 'B';
17059
					}
17060
					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17061
						$dom[$key]['fontstyle'] .= 'I';
17062
					}
17063
					if ($dom[$key]['value'] == 'u') {
17064
						$dom[$key]['fontstyle'] .= 'U';
17065
					}
17066
					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17067
						$dom[$key]['fontstyle'] .= 'D';
17068
					}
17069
					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17070
						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17071
					}
17072
					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17073
						$dom[$key]['fontname'] = $this->default_monospaced_font;
17074
					}
17075
					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)) {
17076
						// headings h1, h2, h3, h4, h5, h6
17077
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17078
							$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17079
							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17080
						}
17081
						if (!isset($dom[$key]['style']['font-weight'])) {
17082
							$dom[$key]['fontstyle'] .= 'B';
17083
						}
17084
					}
17085
					if (($dom[$key]['value'] == 'table')) {
17086
						$dom[$key]['rows'] = 0; // number of rows
17087
						$dom[$key]['trids'] = array(); // IDs of TR elements
17088
						$dom[$key]['thead'] = ''; // table header rows
17089
					}
17090
					if (($dom[$key]['value'] == 'tr')) {
17091
						$dom[$key]['cols'] = 0;
17092
						if ($thead) {
17093
							$dom[$key]['thead'] = true;
17094
							// rows on thead block are printed as a separate table
17095
						} else {
17096
							$dom[$key]['thead'] = false;
17097
							$parent = $dom[$key]['parent'];
17098
 
17099
							if (!isset($dom[$parent]['rows'])) {
17100
								$dom[$parent]['rows'] = 0;
17101
							}
17102
							// store the number of rows on table element
17103
							++$dom[$parent]['rows'];
17104
 
17105
							if (!isset($dom[$parent]['trids'])) {
17106
								$dom[$parent]['trids'] = array();
17107
							}
17108
 
17109
							// store the TR elements IDs on table element
17110
							array_push($dom[$parent]['trids'], $key);
17111
						}
17112
					}
17113
					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17114
						if (isset($dom[$key]['attribute']['colspan'])) {
17115
							$colspan = intval($dom[$key]['attribute']['colspan']);
17116
						} else {
17117
							$colspan = 1;
17118
						}
17119
						$dom[$key]['attribute']['colspan'] = $colspan;
17120
						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
17121
					}
17122
					// text direction
17123
					if (isset($dom[$key]['attribute']['dir'])) {
17124
						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17125
					}
17126
					// set foreground color attribute
17127
					if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17128
						$dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17129
					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17130
						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17131
					}
17132
					// set background color attribute
17133
					if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17134
						$dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17135
					}
17136
					// set stroke color attribute
17137
					if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17138
						$dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17139
					}
17140
					// check for width attribute
17141
					if (isset($dom[$key]['attribute']['width'])) {
17142
						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
17143
					}
17144
					// check for height attribute
17145
					if (isset($dom[$key]['attribute']['height'])) {
17146
						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
17147
					}
17148
					// check for text alignment
17149
					if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17150
						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17151
					}
17152
					// check for text rendering mode (the following attributes do not exist in HTML)
17153
					if (isset($dom[$key]['attribute']['stroke'])) {
17154
						// font stroke width
17155
						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17156
					}
17157
					if (isset($dom[$key]['attribute']['fill'])) {
17158
						// font fill
17159
						if ($dom[$key]['attribute']['fill'] == 'true') {
17160
							$dom[$key]['fill'] = true;
17161
						} else {
17162
							$dom[$key]['fill'] = false;
17163
						}
17164
					}
17165
					if (isset($dom[$key]['attribute']['clip'])) {
17166
						// clipping mode
17167
						if ($dom[$key]['attribute']['clip'] == 'true') {
17168
							$dom[$key]['clip'] = true;
17169
						} else {
17170
							$dom[$key]['clip'] = false;
17171
						}
17172
					}
17173
				} // end opening tag
17174
			} else {
17175
				// text
17176
				$dom[$key]['tag'] = false;
17177
				$dom[$key]['block'] = false;
17178
				$dom[$key]['parent'] = end($level);
17179
				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17180
				if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17181
					// text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17182
					if (function_exists('mb_convert_case')) {
17183
						$ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17184
						if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17185
							$element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17186
						}
17187
					} elseif (!$this->isunicode) {
17188
						switch ($dom[$dom[$key]['parent']]['text-transform']) {
17189
							case 'capitalize': {
17190
								$element = ucwords(strtolower($element));
17191
								break;
17192
							}
17193
							case 'uppercase': {
17194
								$element = strtoupper($element);
17195
								break;
17196
							}
17197
							case 'lowercase': {
17198
								$element = strtolower($element);
17199
								break;
17200
							}
17201
						}
17202
					}
17203
					$element = preg_replace("/&NBSP;/i", "&nbsp;", $element);
17204
				}
17205
				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17206
			}
17207
			++$elkey;
17208
			++$key;
17209
		}
17210
		return $dom;
17211
	}
17212
 
17213
	/**
17214
	 * Returns the string used to find spaces
17215
	 * @return string
17216
	 * @protected
17217
	 * @author Nicola Asuni
17218
	 * @since 4.8.024 (2010-01-15)
17219
	 */
17220
	protected function getSpaceString() {
17221
		$spacestr = chr(32);
17222
		if ($this->isUnicodeFont()) {
17223
			$spacestr = chr(0).chr(32);
17224
		}
17225
		return $spacestr;
17226
	}
17227
 
17228
	/**
1441 ariadna 17229
	 * Calculates the hash value of the given data.
17230
	 *
17231
	 * @param string $data The data to be hashed.
17232
	 * @return string The hashed value of the data.
1 efrain 17233
	 */
1441 ariadna 17234
	protected function hashTCPDFtag($data) {
17235
		return hash_hmac('sha256', $data, $this->hash_key, false);
1 efrain 17236
	}
17237
 
17238
	/**
1441 ariadna 17239
	 * Serialize data to be used with TCPDF tag in HTML code.
17240
	 * @param string $method TCPDF method name
17241
	 * @param array $params Method parameters
17242
	 * @return string Serialized data
1 efrain 17243
	 * @public static
17244
	 */
1441 ariadna 17245
	public function serializeTCPDFtag($method, $params=array()) {
17246
		$data = array('m' => $method, 'p' => $params);
1 efrain 17247
		$encoded = urlencode(json_encode($data));
1441 ariadna 17248
		$hash = $this->hashTCPDFtag($encoded);
17249
		return strlen($hash).'+'.$hash.'+'.$encoded;
1 efrain 17250
	}
17251
 
17252
	/**
1441 ariadna 17253
	 * Unserialize data to be used with TCPDF tag in HTML code.
1 efrain 17254
	 * @param string $data serialized data
17255
	 * @return array containing unserialized data
17256
	 * @protected static
17257
	 */
1441 ariadna 17258
	protected function unserializeTCPDFtag($data) {
17259
		$hpos = strpos($data, '+');
17260
		$hlen = intval(substr($data, 0, $hpos));
17261
		$hash = substr($data, $hpos + 1, $hlen);
17262
		$encoded = substr($data, $hpos + 2 + $hlen);
17263
		if (!hash_equals( $this->hashTCPDFtag($encoded), $hash)) {
1 efrain 17264
			$this->Error('Invalid parameters');
17265
		}
17266
		return json_decode(urldecode($encoded), true);
17267
	}
17268
 
17269
	/**
1441 ariadna 17270
	 * Check if a TCPDF tag is allowed
17271
	 * @param string $method TCPDF method name
17272
	 * @return boolean
17273
	 * @protected
17274
	 */
17275
	protected function allowedTCPDFtag($method) {
17276
		if (defined('K_ALLOWED_TCPDF_TAGS')) {
17277
			return (strpos(K_ALLOWED_TCPDF_TAGS, '|'.$method.'|') !== false);
17278
		}
17279
		return false;
17280
	}
17281
 
17282
	/**
1 efrain 17283
	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17284
	 * 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 />
17285
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17286
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17287
	 * 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
17288
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17289
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17290
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
17291
	 * @param float|null $x upper-left corner X coordinate
17292
	 * @param float|null $y upper-left corner Y coordinate
17293
	 * @param string $html html text to print. Default value: empty string.
17294
	 * @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)))
1441 ariadna 17295
	 * @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.
1 efrain 17296
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17297
	 * @param boolean $reseth if true reset the last cell height (default true).
17298
	 * @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>
17299
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17300
	 * @see Multicell(), writeHTML()
17301
	 * @public
17302
	 */
17303
	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17304
		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17305
	}
17306
 
17307
	/**
17308
	 * Allows to preserve some HTML formatting (limited support).<br />
17309
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17310
	 * 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
17311
	 * NOTE: all the HTML attributes must be enclosed in double-quote.
17312
	 * @param string $html text to display
17313
	 * @param boolean $ln if true add a new line after text (default = true)
17314
	 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17315
	 * @param boolean $reseth if true reset the last cell height (default false).
17316
	 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17317
	 * @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>
17318
	 * @public
17319
	 */
17320
	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17321
		$gvars = $this->getGraphicVars();
17322
		// store current values
17323
		$prev_cell_margin = $this->cell_margin;
17324
		$prev_cell_padding = $this->cell_padding;
17325
		$prevPage = $this->page;
17326
		$prevlMargin = $this->lMargin;
17327
		$prevrMargin = $this->rMargin;
17328
		$curfontname = $this->FontFamily;
17329
		$curfontstyle = $this->FontStyle;
17330
		$curfontsize = $this->FontSizePt;
17331
		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17332
		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17333
		$curfontstretcing = $this->font_stretching;
17334
		$curfonttracking = $this->font_spacing;
17335
		$this->newline = true;
17336
		$newline = true;
17337
		$startlinepage = $this->page;
17338
		$minstartliney = $this->y;
17339
		$maxbottomliney = 0;
17340
		$startlinex = $this->x;
17341
		$startliney = $this->y;
17342
		$yshift = 0;
17343
		$loop = 0;
17344
		$curpos = 0;
17345
		$this_method_vars = array();
17346
		$undo = false;
17347
		$fontaligned = false;
17348
		$reverse_dir = false; // true when the text direction is reversed
17349
		$this->premode = false;
17350
		if ($this->inxobj) {
17351
			// we are inside an XObject template
17352
			$pask = count($this->xobjects[$this->xobjid]['annotations']);
17353
		} elseif (isset($this->PageAnnots[$this->page])) {
17354
			$pask = count($this->PageAnnots[$this->page]);
17355
		} else {
17356
			$pask = 0;
17357
		}
17358
		if ($this->inxobj) {
17359
			// we are inside an XObject template
17360
			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17361
		} elseif (!$this->InFooter) {
17362
			if (isset($this->footerlen[$this->page])) {
17363
				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17364
			} else {
17365
				$this->footerpos[$this->page] = $this->pagelen[$this->page];
17366
			}
17367
			$startlinepos = $this->footerpos[$this->page];
17368
		} else {
17369
			// we are inside the footer
17370
			$startlinepos = $this->pagelen[$this->page];
17371
		}
17372
		$lalign = $align;
17373
		$plalign = $align;
17374
		if ($this->rtl) {
17375
			$w = $this->x - $this->lMargin;
17376
		} else {
17377
			$w = $this->w - $this->rMargin - $this->x;
17378
		}
17379
		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17380
		if ($cell) {
17381
			if ($this->rtl) {
17382
				$this->x -= $this->cell_padding['R'];
17383
				$this->lMargin += $this->cell_padding['L'];
17384
			} else {
17385
				$this->x += $this->cell_padding['L'];
17386
				$this->rMargin += $this->cell_padding['R'];
17387
			}
17388
		}
17389
		if ($this->customlistindent >= 0) {
17390
			$this->listindent = $this->customlistindent;
17391
		} else {
17392
			$this->listindent = $this->GetStringWidth('000000');
17393
		}
17394
		$this->listindentlevel = 0;
17395
		// save previous states
17396
		$prev_cell_height_ratio = $this->cell_height_ratio;
17397
		$prev_listnum = $this->listnum;
17398
		$prev_listordered = $this->listordered;
17399
		$prev_listcount = $this->listcount;
17400
		$prev_lispacer = $this->lispacer;
17401
		$this->listnum = 0;
17402
		$this->listordered = array();
17403
		$this->listcount = array();
17404
		$this->lispacer = '';
17405
		if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17406
			// reset row height
17407
			$this->resetLastH();
17408
		}
17409
		$dom = $this->getHtmlDomArray($html);
17410
		$maxel = count($dom);
17411
		$key = 0;
17412
		while ($key < $maxel) {
17413
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17414
				// store the node key
17415
				$hidden_node_key = $key;
17416
				if ($dom[$key]['self']) {
17417
					// skip just this self-closing tag
17418
					++$key;
17419
				} else {
17420
					// skip this and all children tags
17421
					while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17422
						// skip hidden objects
17423
						++$key;
17424
					}
17425
					++$key;
17426
				}
17427
			}
17428
			if ($key == $maxel) break;
17429
			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17430
				// check for pagebreak
17431
				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17432
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17433
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17434
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17435
				}
17436
				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17437
					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17438
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
17439
					$this->checkPageBreak($this->PageBreakTrigger + 1);
17440
					$this->htmlvspace = ($this->PageBreakTrigger + 1);
17441
				}
17442
			}
17443
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17444
				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17445
					$dom[$key]['attribute']['nobr'] = false;
17446
				} else {
17447
					// store current object
17448
					$this->startTransaction();
17449
					// save this method vars
17450
					$this_method_vars['html'] = $html;
17451
					$this_method_vars['ln'] = $ln;
17452
					$this_method_vars['fill'] = $fill;
17453
					$this_method_vars['reseth'] = $reseth;
17454
					$this_method_vars['cell'] = $cell;
17455
					$this_method_vars['align'] = $align;
17456
					$this_method_vars['gvars'] = $gvars;
17457
					$this_method_vars['prevPage'] = $prevPage;
17458
					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17459
					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17460
					$this_method_vars['prevlMargin'] = $prevlMargin;
17461
					$this_method_vars['prevrMargin'] = $prevrMargin;
17462
					$this_method_vars['curfontname'] = $curfontname;
17463
					$this_method_vars['curfontstyle'] = $curfontstyle;
17464
					$this_method_vars['curfontsize'] = $curfontsize;
17465
					$this_method_vars['curfontascent'] = $curfontascent;
17466
					$this_method_vars['curfontdescent'] = $curfontdescent;
17467
					$this_method_vars['curfontstretcing'] = $curfontstretcing;
17468
					$this_method_vars['curfonttracking'] = $curfonttracking;
17469
					$this_method_vars['minstartliney'] = $minstartliney;
17470
					$this_method_vars['maxbottomliney'] = $maxbottomliney;
17471
					$this_method_vars['yshift'] = $yshift;
17472
					$this_method_vars['startlinepage'] = $startlinepage;
17473
					$this_method_vars['startlinepos'] = $startlinepos;
17474
					$this_method_vars['startlinex'] = $startlinex;
17475
					$this_method_vars['startliney'] = $startliney;
17476
					$this_method_vars['newline'] = $newline;
17477
					$this_method_vars['loop'] = $loop;
17478
					$this_method_vars['curpos'] = $curpos;
17479
					$this_method_vars['pask'] = $pask;
17480
					$this_method_vars['lalign'] = $lalign;
17481
					$this_method_vars['plalign'] = $plalign;
17482
					$this_method_vars['w'] = $w;
17483
					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17484
					$this_method_vars['prev_listnum'] = $prev_listnum;
17485
					$this_method_vars['prev_listordered'] = $prev_listordered;
17486
					$this_method_vars['prev_listcount'] = $prev_listcount;
17487
					$this_method_vars['prev_lispacer'] = $prev_lispacer;
17488
					$this_method_vars['fontaligned'] = $fontaligned;
17489
					$this_method_vars['key'] = $key;
17490
					$this_method_vars['dom'] = $dom;
17491
				}
17492
			}
17493
			// print THEAD block
17494
			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17495
				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17496
					$this->inthead = true;
17497
					// print table header (thead)
17498
					$this->writeHTML($this->thead, false, false, false, false, '');
17499
					// check if we are on a new page or on a new column
17500
					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17501
						// we are on a new page or on a new column and the total object height is less than the available vertical space.
17502
						// restore previous object
17503
						$this->rollbackTransaction(true);
17504
						// restore previous values
17505
						foreach ($this_method_vars as $vkey => $vval) {
17506
							$$vkey = $vval;
17507
						}
17508
						// disable table header
17509
						$tmp_thead = $this->thead;
17510
						$this->thead = '';
17511
						// add a page (or trig AcceptPageBreak() for multicolumn mode)
17512
						$pre_y = $this->y;
17513
						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17514
							// fix for multicolumn mode
17515
							$startliney = $this->y;
17516
						}
17517
						$this->start_transaction_page = $this->page;
17518
						$this->start_transaction_y = $this->y;
17519
						// restore table header
17520
						$this->thead = $tmp_thead;
17521
						// fix table border properties
17522
						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17523
							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17524
						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17525
							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17526
						} else {
17527
							$tmp_cellspacing = 0;
17528
						}
17529
						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17530
						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17531
						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17532
						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17533
						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17534
						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17535
						// print table header (thead)
17536
						$this->writeHTML($this->thead, false, false, false, false, '');
17537
					}
17538
				}
17539
				// move $key index forward to skip THEAD block
17540
				while ( ($key < $maxel) AND (!(
17541
					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17542
					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17543
					++$key;
17544
				}
17545
			}
17546
			if ($dom[$key]['tag'] OR ($key == 0)) {
17547
				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17548
					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17549
				}
17550
				// vertically align image in line
17551
				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17552
					// get image height
17553
					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17554
					$autolinebreak = false;
17555
					if (!empty($dom[$key]['width'])) {
17556
						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17557
						if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17558
							AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17559
							OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17560
							// add automatic line break
17561
							$autolinebreak = true;
17562
							$this->Ln('', $cell);
17563
							if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17564
								// go back to evaluate this line break
17565
								--$key;
17566
							}
17567
						}
17568
					}
17569
					if (!$autolinebreak) {
17570
						if ($this->inPageBody()) {
17571
							$pre_y = $this->y;
17572
							// check for page break
17573
							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17574
								// fix for multicolumn mode
17575
								$startliney = $this->y;
17576
							}
17577
						}
17578
						if ($this->page > $startlinepage) {
17579
							// fix line splitted over two pages
17580
							if (isset($this->footerlen[$startlinepage])) {
17581
								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17582
							}
17583
							// line to be moved one page forward
17584
							$pagebuff = $this->getPageBuffer($startlinepage);
17585
							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17586
							$tstart = substr($pagebuff, 0, $startlinepos);
17587
							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17588
							// remove line from previous page
17589
							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17590
							$pagebuff = $this->getPageBuffer($this->page);
17591
							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17592
							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17593
							// add line start to current page
17594
							$yshift = ($minstartliney - $this->y);
17595
							if ($fontaligned) {
17596
								$yshift += ($curfontsize / $this->k);
17597
							}
17598
							$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17599
							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17600
							// shift the annotations and links
17601
							if (isset($this->PageAnnots[$this->page])) {
17602
								$next_pask = count($this->PageAnnots[$this->page]);
17603
							} else {
17604
								$next_pask = 0;
17605
							}
17606
							if (isset($this->PageAnnots[$startlinepage])) {
17607
								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17608
									if ($pak >= $pask) {
17609
										$this->PageAnnots[$this->page][] = $pac;
17610
										unset($this->PageAnnots[$startlinepage][$pak]);
17611
										$npak = count($this->PageAnnots[$this->page]) - 1;
17612
										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17613
									}
17614
								}
17615
							}
17616
							$pask = $next_pask;
17617
							$startlinepos = $this->cntmrk[$this->page];
17618
							$startlinepage = $this->page;
17619
							$startliney = $this->y;
17620
							$this->newline = false;
17621
						}
17622
						$this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17623
						$minstartliney = min($this->y, $minstartliney);
17624
						$maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17625
					}
17626
				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17627
					// account for different font size
17628
					$pfontname = $curfontname;
17629
					$pfontstyle = $curfontstyle;
17630
					$pfontsize = $curfontsize;
17631
					$fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17632
					$fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17633
					$fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17634
					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17635
					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17636
					if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17637
						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17638
						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17639
						if (($key < ($maxel - 1)) AND (
17640
								($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17641
								OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17642
								OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17643
								AND ($fontsize >= 0) AND ($curfontsize >= 0)
17644
								AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17645
							)) {
17646
							if ($this->page > $startlinepage) {
17647
								// fix lines splitted over two pages
17648
								if (isset($this->footerlen[$startlinepage])) {
17649
									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17650
								}
17651
								// line to be moved one page forward
17652
								$pagebuff = $this->getPageBuffer($startlinepage);
17653
								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17654
								$tstart = substr($pagebuff, 0, $startlinepos);
17655
								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
17656
								// remove line start from previous page
17657
								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
17658
								$pagebuff = $this->getPageBuffer($this->page);
17659
								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17660
								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
17661
								// add line start to current page
17662
								$yshift = ($minstartliney - $this->y);
17663
								$try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17664
								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17665
								// shift the annotations and links
17666
								if (isset($this->PageAnnots[$this->page])) {
17667
									$next_pask = count($this->PageAnnots[$this->page]);
17668
								} else {
17669
									$next_pask = 0;
17670
								}
17671
								if (isset($this->PageAnnots[$startlinepage])) {
17672
									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17673
										if ($pak >= $pask) {
17674
											$this->PageAnnots[$this->page][] = $pac;
17675
											unset($this->PageAnnots[$startlinepage][$pak]);
17676
											$npak = count($this->PageAnnots[$this->page]) - 1;
17677
											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17678
										}
17679
									}
17680
								}
17681
								$pask = $next_pask;
17682
								$startlinepos = $this->cntmrk[$this->page];
17683
								$startlinepage = $this->page;
17684
								$startliney = $this->y;
17685
							}
17686
							if (!isset($dom[$key]['line-height'])) {
17687
								$dom[$key]['line-height'] = $this->cell_height_ratio;
17688
							}
17689
							if (!$dom[$key]['block']) {
17690
								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']))) {
17691
									$this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17692
								}
17693
								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17694
									$current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17695
									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)))) {
17696
										$minstartliney = min($this->y, $line_align_data[1]);
17697
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17698
									} else {
17699
										$minstartliney = min($this->y, $minstartliney);
17700
										$maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17701
									}
17702
									$line_align_data = $current_line_align_data;
17703
								}
17704
							}
17705
							$this->cell_height_ratio = $dom[$key]['line-height'];
17706
							$fontaligned = true;
17707
						}
17708
						$this->setFont($fontname, $fontstyle, $fontsize);
17709
						// reset row height
17710
						$this->resetLastH();
17711
						$curfontname = $fontname;
17712
						$curfontstyle = $fontstyle;
17713
						$curfontsize = $fontsize;
17714
						$curfontascent = $fontascent;
17715
						$curfontdescent = $fontdescent;
17716
					}
17717
				}
17718
				// set text rendering mode
17719
				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17720
				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17721
				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17722
				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
17723
				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17724
					$this->setFontStretching($dom[$key]['font-stretch']);
17725
				}
17726
				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17727
					$this->setFontSpacing($dom[$key]['letter-spacing']);
17728
				}
17729
				if (($plalign == 'J') AND $dom[$key]['block']) {
17730
					$plalign = '';
17731
				}
17732
				// get current position on page buffer
17733
				$curpos = $this->pagelen[$startlinepage];
17734
				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17735
					$this->setFillColorArray($dom[$key]['bgcolor']);
17736
					$wfill = true;
17737
				} else {
17738
					$wfill = $fill | false;
17739
				}
17740
				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17741
					$this->setTextColorArray($dom[$key]['fgcolor']);
17742
				}
17743
				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17744
					$this->setDrawColorArray($dom[$key]['strokecolor']);
17745
				}
17746
				if (isset($dom[$key]['align'])) {
17747
					$lalign = $dom[$key]['align'];
17748
				}
17749
				if (TCPDF_STATIC::empty_string($lalign)) {
17750
					$lalign = $align;
17751
				}
17752
			}
17753
			// align lines
17754
			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17755
				$newline = true;
17756
				$fontaligned = false;
17757
				// we are at the beginning of a new line
17758
				if (isset($startlinex)) {
17759
					$yshift = ($minstartliney - $startliney);
17760
					if (($yshift > 0) OR ($this->page > $startlinepage)) {
17761
						$yshift = 0;
17762
					}
17763
					$t_x = 0;
17764
					// the last line must be shifted to be aligned as requested
17765
					$linew = abs($this->endlinex - $startlinex);
17766
					if ($this->inxobj) {
17767
						// we are inside an XObject template
17768
						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17769
						if (isset($opentagpos)) {
17770
							$midpos = $opentagpos;
17771
						} else {
17772
							$midpos = 0;
17773
						}
17774
						if ($midpos > 0) {
17775
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17776
							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17777
						} else {
17778
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17779
							$pend = '';
17780
						}
17781
					} else {
17782
						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17783
						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17784
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17785
							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17786
						} elseif (isset($opentagpos)) {
17787
							$midpos = $opentagpos;
17788
						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17789
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17790
							$midpos = $this->footerpos[$startlinepage];
17791
						} else {
17792
							$midpos = 0;
17793
						}
17794
						if ($midpos > 0) {
17795
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17796
							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
17797
						} else {
17798
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17799
							$pend = '';
17800
						}
17801
					}
17802
					if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17803
						// calculate shifting amount
17804
						$tw = $w;
17805
						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17806
							$tw += $this->cell_padding['R'];
17807
						}
17808
						if ($this->lMargin != $prevlMargin) {
17809
							$tw += ($prevlMargin - $this->lMargin);
17810
						}
17811
						if ($this->rMargin != $prevrMargin) {
17812
							$tw += ($prevrMargin - $this->rMargin);
17813
						}
17814
						$one_space_width = $this->GetStringWidth(chr(32));
17815
						$no = 0; // number of spaces on a line contained on a single block
17816
						if ($this->isRTLTextDir()) { // RTL
17817
							// remove left space if exist
17818
							$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17819
							if ($pos1 > 0) {
17820
								$pos1 = intval($pos1);
17821
								if ($this->isUnicodeFont()) {
17822
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17823
									$spacelen = 2;
17824
								} else {
17825
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17826
									$spacelen = 1;
17827
								}
17828
								if ($pos1 == $pos2) {
17829
									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17830
									if (substr($pmid, $pos1, 4) == '[()]') {
17831
										$linew -= $one_space_width;
17832
									} elseif ($pos1 == strpos($pmid, '[(')) {
17833
										$no = 1;
17834
									}
17835
								}
17836
							}
17837
						} else { // LTR
17838
							// remove right space if exist
17839
							$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17840
							if ($pos1 > 0) {
17841
								$pos1 = intval($pos1);
17842
								if ($this->isUnicodeFont()) {
17843
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17844
									$spacelen = 2;
17845
								} else {
17846
									$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17847
									$spacelen = 1;
17848
								}
17849
								if ($pos1 == $pos2) {
17850
									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17851
									$linew -= $one_space_width;
17852
								}
17853
							}
17854
						}
17855
						$mdiff = ($tw - $linew);
17856
						if ($plalign == 'C') {
17857
							if ($this->rtl) {
17858
								$t_x = -($mdiff / 2);
17859
							} else {
17860
								$t_x = ($mdiff / 2);
17861
							}
17862
						} elseif ($plalign == 'R') {
17863
							// right alignment on LTR document
17864
							$t_x = $mdiff;
17865
						} elseif ($plalign == 'L') {
17866
							// left alignment on RTL document
17867
							$t_x = -$mdiff;
17868
						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17869
							// Justification
17870
							if ($this->isRTLTextDir()) {
17871
								// align text on the left
17872
								$t_x = -$mdiff;
17873
							}
17874
							$ns = 0; // number of spaces
17875
							$pmidtemp = $pmid;
17876
							// escape special characters
17877
							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17878
							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17879
							// search spaces
17880
							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17881
								$spacestr = $this->getSpaceString();
17882
								$maxkk = count($lnstring[1]) - 1;
17883
								for ($kk=0; $kk <= $maxkk; ++$kk) {
17884
									// restore special characters
17885
									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17886
									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17887
									// store number of spaces on the strings
17888
									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17889
									// count total spaces on line
17890
									$ns += $lnstring[2][$kk];
17891
									$lnstring[3][$kk] = $ns;
17892
								}
17893
								if ($ns == 0) {
17894
									$ns = 1;
17895
								}
17896
								// calculate additional space to add to each existing space
17897
								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17898
								if ($this->FontSize <= 0) {
17899
									$this->FontSize = 1;
17900
								}
17901
								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17902
								if ($this->font_spacing != 0) {
17903
									// fixed spacing mode
17904
									$osw = -1000 * $this->font_spacing / $this->FontSize;
17905
									$spacewidthu += $osw;
17906
								}
17907
								$nsmax = $ns;
17908
								$ns = 0;
17909
								reset($lnstring);
17910
								$offset = 0;
17911
								$strcount = 0;
17912
								$prev_epsposbeg = 0;
17913
								$textpos = 0;
17914
								if ($this->isRTLTextDir()) {
17915
									$textpos = $this->wPt;
17916
								}
17917
								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17918
									// check if we are inside a string section '[( ... )]'
17919
									$stroffset = strpos($pmid, '[(', $offset);
17920
									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17921
										// set offset to the end of string section
17922
										$offset = strpos($pmid, ')]', $stroffset);
17923
										while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17924
											$offset = strpos($pmid, ')]', ($offset + 1));
17925
										}
17926
										if ($offset === false) {
17927
											$this->Error('HTML Justification: malformed PDF code.');
17928
										}
17929
										continue;
17930
									}
17931
									if ($this->isRTLTextDir()) {
17932
										$spacew = ($spacewidth * ($nsmax - $ns));
17933
									} else {
17934
										$spacew = ($spacewidth * $ns);
17935
									}
17936
									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17937
									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17938
									if ($epsposend !== null) {
17939
										$epsposend += strlen($this->epsmarker.'Q');
17940
										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17941
										if ($epsposbeg === null) {
17942
											$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17943
											$prev_epsposbeg = $epsposbeg;
17944
										}
17945
										if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17946
											// shift EPS images
17947
											$trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17948
											$pmid_b = substr($pmid, 0, $epsposbeg);
17949
											$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17950
											$pmid_e = substr($pmid, $epsposend);
17951
											$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17952
											$offset = $epsposend;
17953
											continue;
17954
										}
17955
									}
17956
									$currentxpos = 0;
17957
									// shift blocks of code
17958
									switch ($strpiece[2][0]) {
17959
										case 'Td':
17960
										case 'cm':
17961
										case 'm':
17962
										case 'l': {
17963
											// get current X position
17964
											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17965
											if (!isset($xmatches[1])) {
17966
												break;
17967
											}
17968
											$currentxpos = $xmatches[1];
17969
											$textpos = $currentxpos;
17970
											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17971
												$ns = $lnstring[3][$strcount];
17972
												if ($this->isRTLTextDir()) {
17973
													$spacew = ($spacewidth * ($nsmax - $ns));
17974
												}
17975
												++$strcount;
17976
											}
17977
											// justify block
17978
											if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17979
												$newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17980
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
17981
												unset($pmatch, $newpmid);
17982
											}
17983
											break;
17984
										}
17985
										case 're': {
17986
											// justify block
17987
											if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17988
												$this->lispacer = '';
17989
												break;
17990
											}
17991
											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17992
											if (!isset($xmatches[1])) {
17993
												break;
17994
											}
17995
											$currentxpos = $xmatches[1];
17996
											$x_diff = 0;
17997
											$w_diff = 0;
17998
											if ($this->isRTLTextDir()) { // RTL
17999
												if ($currentxpos < $textpos) {
18000
													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
18001
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
18002
												} else {
18003
													if ($strcount > 0) {
18004
														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
18005
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18006
													}
18007
												}
18008
											} else { // LTR
18009
												if ($currentxpos > $textpos) {
18010
													if ($strcount > 0) {
18011
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
18012
													}
18013
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
18014
												} else {
18015
													if ($strcount > 1) {
18016
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
18017
													}
18018
													if ($strcount > 0) {
18019
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18020
													}
18021
												}
18022
											}
18023
											if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
18024
												$newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
18025
												$neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
18026
												$newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
18027
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18028
												unset($pmatch, $newpmid, $newx, $neww);
18029
											}
18030
											break;
18031
										}
18032
										case 'c': {
18033
											// get current X position
18034
											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);
18035
											if (!isset($xmatches[1])) {
18036
												break;
18037
											}
18038
											$currentxpos = $xmatches[1];
18039
											// justify block
18040
											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) {
18041
												$newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
18042
												$newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
18043
												$newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
18044
												$newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
18045
												$pmid = str_replace($pmatch[0], $newpmid, $pmid);
18046
												unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
18047
											}
18048
											break;
18049
										}
18050
									}
18051
									// shift the annotations and links
18052
									$cxpos = ($currentxpos / $this->k);
18053
									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
18054
									if ($this->inxobj) {
18055
										// we are inside an XObject template
18056
										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18057
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18058
												if ($cxpos > $lmpos) {
18059
													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
18060
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18061
												} else {
18062
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18063
												}
18064
												break;
18065
											}
18066
										}
18067
									} elseif (isset($this->PageAnnots[$this->page])) {
18068
										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18069
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18070
												if ($cxpos > $lmpos) {
18071
													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
18072
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18073
												} else {
18074
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18075
												}
18076
												break;
18077
											}
18078
										}
18079
									}
18080
								} // end of while
18081
								// remove markers
18082
								$pmid = str_replace('x*#!#*x', '', $pmid);
18083
								if ($this->isUnicodeFont()) {
18084
									// multibyte characters
18085
									$spacew = $spacewidthu;
18086
									if ($this->font_stretching != 100) {
18087
										// word spacing is affected by stretching
18088
										$spacew /= ($this->font_stretching / 100);
18089
									}
18090
									// escape special characters
18091
									$pos = 0;
18092
									$pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18093
									$pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18094
									if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18095
										foreach($pamatch[0] as $pk => $pmatch) {
18096
											$replace = $pamatch[1][$pk];
18097
											$replace = str_replace('#!#OP#!#', '(', $replace);
18098
											$replace = str_replace('#!#CP#!#', ')', $replace);
18099
											$newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18100
											$pos = strpos($pmid, $pmatch, $pos);
18101
											if ($pos !== FALSE) {
18102
												$pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18103
											}
18104
											++$pos;
18105
										}
18106
										unset($pamatch);
18107
									}
18108
									if ($this->inxobj) {
18109
										// we are inside an XObject template
18110
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18111
									} else {
18112
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18113
									}
18114
									$endlinepos = strlen($pstart."\n".$pmid."\n");
18115
								} else {
18116
									// non-unicode (single-byte characters)
18117
									if ($this->font_stretching != 100) {
18118
										// word spacing (Tw) is affected by stretching
18119
										$spacewidth /= ($this->font_stretching / 100);
18120
									}
18121
									$rs = sprintf('%F Tw', $spacewidth);
18122
									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18123
									if ($this->inxobj) {
18124
										// we are inside an XObject template
18125
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18126
									} else {
18127
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18128
									}
18129
									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18130
								}
18131
							}
18132
						} // end of J
18133
					} // end if $startlinex
18134
					if (($t_x != 0) OR ($yshift < 0)) {
18135
						// shift the line
18136
						$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18137
						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18138
						$endlinepos = strlen($pstart);
18139
						if ($this->inxobj) {
18140
							// we are inside an XObject template
18141
							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18142
							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18143
								if ($pak >= $pask) {
18144
									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18145
									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18146
								}
18147
							}
18148
						} else {
18149
							$this->setPageBuffer($startlinepage, $pstart.$pend);
18150
							// shift the annotations and links
18151
							if (isset($this->PageAnnots[$this->page])) {
18152
								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18153
									if ($pak >= $pask) {
18154
										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18155
										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18156
									}
18157
								}
18158
							}
18159
						}
18160
						$this->y -= $yshift;
18161
					}
18162
				}
18163
				$pbrk = $this->checkPageBreak($this->lasth);
18164
				$this->newline = false;
18165
				$startlinex = $this->x;
18166
				$startliney = $this->y;
18167
				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18168
					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18169
				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18170
					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
18171
				} else {
18172
					$minstartliney = $startliney;
18173
					$maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18174
				}
18175
				$startlinepage = $this->page;
18176
				if (isset($endlinepos) AND (!$pbrk)) {
18177
					$startlinepos = $endlinepos;
18178
				} else {
18179
					if ($this->inxobj) {
18180
						// we are inside an XObject template
18181
						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18182
					} elseif (!$this->InFooter) {
18183
						if (isset($this->footerlen[$this->page])) {
18184
							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18185
						} else {
18186
							$this->footerpos[$this->page] = $this->pagelen[$this->page];
18187
						}
18188
						$startlinepos = $this->footerpos[$this->page];
18189
					} else {
18190
						$startlinepos = $this->pagelen[$this->page];
18191
					}
18192
				}
18193
				unset($endlinepos);
18194
				$plalign = $lalign;
18195
				if (isset($this->PageAnnots[$this->page])) {
18196
					$pask = count($this->PageAnnots[$this->page]);
18197
				} else {
18198
					$pask = 0;
18199
				}
18200
				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18201
					AND (isset($this->emptypagemrk[$this->page]))
18202
					AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18203
					$this->setFont($fontname, $fontstyle, $fontsize);
18204
					if ($wfill) {
18205
						$this->setFillColorArray($this->bgcolor);
18206
					}
18207
				}
18208
			} // end newline
18209
			if (isset($opentagpos)) {
18210
				unset($opentagpos);
18211
			}
18212
			if ($dom[$key]['tag']) {
18213
				if ($dom[$key]['opening']) {
18214
					// get text indentation (if any)
18215
					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18216
						$this->textindent = $dom[$key]['text-indent'];
18217
						$this->newline = true;
18218
					}
18219
					// table
18220
					if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18221
						// available page width
18222
						if ($this->rtl) {
18223
							$wtmp = $this->x - $this->lMargin;
18224
						} else {
18225
							$wtmp = $this->w - $this->rMargin - $this->x;
18226
						}
18227
						// get cell spacing
18228
						if (isset($dom[$key]['attribute']['cellspacing'])) {
18229
							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18230
							$cellspacing = array('H' => $clsp, 'V' => $clsp);
18231
						} elseif (isset($dom[$key]['border-spacing'])) {
18232
							$cellspacing = $dom[$key]['border-spacing'];
18233
						} else {
18234
							$cellspacing = array('H' => 0, 'V' => 0);
18235
						}
18236
						// table width
18237
						if (isset($dom[$key]['width'])) {
18238
							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18239
						} else {
18240
							$table_width = $wtmp;
18241
						}
18242
						$table_width -= (2 * $cellspacing['H']);
18243
						if (!$this->inthead) {
18244
							$this->y += $cellspacing['V'];
18245
						}
18246
						if ($this->rtl) {
18247
							$cellspacingx = -$cellspacing['H'];
18248
						} else {
18249
							$cellspacingx = $cellspacing['H'];
18250
						}
18251
						// total table width without cellspaces
18252
						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18253
						// minimum column width
18254
						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18255
						// array of custom column widths
18256
						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18257
					}
18258
					// table row
18259
					if ($dom[$key]['value'] == 'tr') {
18260
						// reset column counter
18261
						$colid = 0;
18262
					}
18263
					// table cell
18264
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18265
						$trid = $dom[$key]['parent'];
18266
						$table_el = $dom[$trid]['parent'];
18267
						if (!isset($dom[$table_el]['cols'])) {
18268
							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
18269
						}
18270
						// store border info
18271
						$tdborder = 0;
18272
						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18273
							$tdborder = $dom[$key]['border'];
18274
						}
18275
						$colspan = intval($dom[$key]['attribute']['colspan']);
18276
						if ($colspan <= 0) {
18277
							$colspan = 1;
18278
						}
18279
						$old_cell_padding = $this->cell_padding;
18280
						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18281
							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18282
							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18283
						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18284
							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18285
						} else {
18286
							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18287
						}
18288
						$this->cell_padding = $current_cell_padding;
18289
						if (isset($dom[$key]['height'])) {
18290
							// minimum cell height
18291
							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18292
						} else {
18293
							$cellh = 0;
18294
						}
18295
						if (isset($dom[$key]['content'])) {
18296
							$cell_content = $dom[$key]['content'];
18297
						} else {
18298
							$cell_content = '&nbsp;';
18299
						}
18300
						$tagtype = $dom[$key]['value'];
18301
						$parentid = $key;
18302
						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18303
							// move $key index forward
18304
							++$key;
18305
						}
18306
						if (!isset($dom[$trid]['startpage'])) {
18307
							$dom[$trid]['startpage'] = $this->page;
18308
						} else {
18309
							$this->setPage($dom[$trid]['startpage']);
18310
						}
18311
						if (!isset($dom[$trid]['startcolumn'])) {
18312
							$dom[$trid]['startcolumn'] = $this->current_column;
18313
						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18314
							$tmpx = $this->x;
18315
							$this->selectColumn($dom[$trid]['startcolumn']);
18316
							$this->x = $tmpx;
18317
						}
18318
						if (!isset($dom[$trid]['starty'])) {
18319
							$dom[$trid]['starty'] = $this->y;
18320
						} else {
18321
							$this->y = $dom[$trid]['starty'];
18322
						}
18323
						if (!isset($dom[$trid]['startx'])) {
18324
							$dom[$trid]['startx'] = $this->x;
18325
							$this->x += $cellspacingx;
18326
						} else {
18327
							$this->x += ($cellspacingx / 2);
18328
						}
18329
						if (isset($dom[$parentid]['attribute']['rowspan'])) {
18330
							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18331
						} else {
18332
							$rowspan = 1;
18333
						}
18334
						// skip row-spanned cells started on the previous rows
18335
						if (isset($dom[$table_el]['rowspans'])) {
18336
							$rsk = 0;
18337
							$rskmax = count($dom[$table_el]['rowspans']);
18338
							while ($rsk < $rskmax) {
18339
								$trwsp = $dom[$table_el]['rowspans'][$rsk];
18340
								$rsstartx = $trwsp['startx'];
18341
								$rsendx = $trwsp['endx'];
18342
								// account for margin changes
18343
								if ($trwsp['startpage'] < $this->page) {
18344
									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18345
										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18346
										$rsstartx -= $dl;
18347
										$rsendx -= $dl;
18348
									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18349
										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18350
										$rsstartx += $dl;
18351
										$rsendx += $dl;
18352
									}
18353
								}
18354
								if (($trwsp['rowspan'] > 0)
18355
									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18356
									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18357
									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18358
									// set the starting X position of the current cell
18359
									$this->x = $rsendx + $cellspacingx;
18360
									// increment column indicator
18361
									$colid += $trwsp['colspan'];
18362
									if (($trwsp['rowspan'] == 1)
18363
										AND (isset($dom[$trid]['endy']))
18364
										AND (isset($dom[$trid]['endpage']))
18365
										AND (isset($dom[$trid]['endcolumn']))
18366
										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18367
										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18368
										// set ending Y position for row
18369
										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18370
										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18371
									}
18372
									$rsk = 0;
18373
								} else {
18374
									++$rsk;
18375
								}
18376
							}
18377
						}
18378
						if (isset($dom[$parentid]['width'])) {
18379
							// user specified width
18380
							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18381
							$tmpcw = ($cellw / $colspan);
18382
							for ($i = 0; $i < $colspan; ++$i) {
18383
								$table_colwidths[($colid + $i)] = $tmpcw;
18384
							}
18385
						} else {
18386
							// inherit column width
18387
							$cellw = 0;
18388
							for ($i = 0; $i < $colspan; ++$i) {
18389
								$cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18390
							}
18391
						}
18392
						$cellw += (($colspan - 1) * $cellspacing['H']);
18393
						// increment column indicator
18394
						$colid += $colspan;
18395
						// add rowspan information to table element
18396
						if ($rowspan > 1) {
18397
							$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));
18398
						}
18399
						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18400
						if ($rowspan > 1) {
18401
							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18402
						}
18403
						// push background colors
18404
						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18405
							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18406
						}
18407
						// store border info
18408
						if (!empty($tdborder)) {
18409
							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18410
						}
18411
						$prevLastH = $this->lasth;
18412
						// store some info for multicolumn mode
18413
						if ($this->rtl) {
18414
							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18415
						} else {
18416
							$this->colxshift['x'] = $this->x - $this->lMargin;
18417
						}
18418
						$this->colxshift['s'] = $cellspacing;
18419
						$this->colxshift['p'] = $current_cell_padding;
18420
						// ****** write the cell content ******
18421
						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18422
						// restore some values
18423
						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18424
						$this->lasth = $prevLastH;
18425
						$this->cell_padding = $old_cell_padding;
18426
						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18427
						// update the end of row position
18428
						if ($rowspan <= 1) {
18429
							if (isset($dom[$trid]['endy'])) {
18430
								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18431
									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18432
								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18433
									$dom[$trid]['endy'] = $this->y;
18434
								}
18435
							} else {
18436
								$dom[$trid]['endy'] = $this->y;
18437
							}
18438
							if (isset($dom[$trid]['endpage'])) {
18439
								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18440
							} else {
18441
								$dom[$trid]['endpage'] = $this->page;
18442
							}
18443
							if (isset($dom[$trid]['endcolumn'])) {
18444
								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18445
							} else {
18446
								$dom[$trid]['endcolumn'] = $this->current_column;
18447
							}
18448
						} else {
18449
							// account for row-spanned cells
18450
							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18451
							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18452
							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18453
							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18454
						}
18455
						if (isset($dom[$table_el]['rowspans'])) {
18456
							// update endy and endpage on rowspanned cells
18457
							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18458
								if ($trwsp['rowspan'] > 0) {
18459
									if (isset($dom[$trid]['endpage'])) {
18460
										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18461
											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18462
										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18463
											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18464
											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18465
											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18466
										} else {
18467
											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18468
										}
18469
									}
18470
								}
18471
							}
18472
						}
18473
						$this->x += ($cellspacingx / 2);
18474
					} else {
18475
						// opening tag (or self-closing tag)
18476
						if (!isset($opentagpos)) {
18477
							if ($this->inxobj) {
18478
								// we are inside an XObject template
18479
								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18480
							} elseif (!$this->InFooter) {
18481
								if (isset($this->footerlen[$this->page])) {
18482
									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18483
								} else {
18484
									$this->footerpos[$this->page] = $this->pagelen[$this->page];
18485
								}
18486
								$opentagpos = $this->footerpos[$this->page];
18487
							}
18488
						}
18489
						$dom = $this->openHTMLTagHandler($dom, $key, $cell);
18490
					}
18491
				} else { // closing tag
18492
					$prev_numpages = $this->numpages;
18493
					$old_bordermrk = $this->bordermrk[$this->page];
18494
					$dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18495
					if ($this->bordermrk[$this->page] > $old_bordermrk) {
18496
						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18497
					}
18498
					if ($prev_numpages > $this->numpages) {
18499
						$startlinepage = $this->page;
18500
					}
18501
				}
18502
			} elseif (strlen($dom[$key]['value']) > 0) {
18503
				// print list-item
18504
				if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18505
					$this->setFont($pfontname, $pfontstyle, $pfontsize);
18506
					$this->resetLastH();
18507
					$minstartliney = $this->y;
18508
					$maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18509
					if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18510
						$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18511
					}
18512
					$this->setFont($curfontname, $curfontstyle, $curfontsize);
18513
					$this->resetLastH();
18514
					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18515
						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18516
						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18517
						$this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18518
						$minstartliney = min($this->y, $minstartliney);
18519
						$maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18520
					}
18521
				}
18522
				// text
18523
				$this->htmlvspace = 0;
18524
				$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']);
18525
				if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18526
					// reverse spaces order
18527
					$lsp = ''; // left spaces
18528
					$rsp = ''; // right spaces
18529
					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18530
						$lsp = $matches[1];
18531
					}
18532
					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18533
						$rsp = $matches[1];
18534
					}
18535
					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18536
				}
18537
				if ($newline) {
18538
					if (!$this->premode) {
18539
						$prelen = strlen($dom[$key]['value']);
18540
						if ($this->isRTLTextDir() AND !$isRTLString) {
18541
							// right trim except non-breaking space
18542
							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18543
						} else {
18544
							// left trim except non-breaking space
18545
							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18546
						}
18547
						$postlen = strlen($dom[$key]['value']);
18548
						if (($postlen == 0) AND ($prelen > 0)) {
18549
							$dom[$key]['trimmed_space'] = true;
18550
						}
18551
					}
18552
					$newline = false;
18553
					$firstblock = true;
18554
				} else {
18555
					$firstblock = false;
18556
					// replace empty multiple spaces string with a single space
18557
					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18558
				}
18559
				$strrest = '';
18560
				if ($this->rtl) {
18561
					$this->x -= $this->textindent;
18562
				} else {
18563
					$this->x += $this->textindent;
18564
				}
18565
				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18566
					$strlinelen = $this->GetStringWidth($dom[$key]['value']);
18567
					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18568
						// HTML <a> Link
18569
						$hrefcolor = '';
18570
						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18571
							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18572
						}
18573
						$hrefstyle = -1;
18574
						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18575
							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18576
						}
18577
						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18578
					} else {
18579
						$wadj = 0; // space to leave for block continuity
18580
						if ($this->rtl) {
18581
							$cwa = ($this->x - $this->lMargin);
18582
						} else {
18583
							$cwa = ($this->w - $this->rMargin - $this->x);
18584
						}
18585
						if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18586
							// check the next text blocks for continuity
18587
							$nkey = ($key + 1);
18588
							$write_block = true;
18589
							$same_textdir = true;
18590
							$tmp_fontname = $this->FontFamily;
18591
							$tmp_fontstyle = $this->FontStyle;
18592
							$tmp_fontsize = $this->FontSizePt;
18593
							while ($write_block AND isset($dom[$nkey])) {
18594
								if ($dom[$nkey]['tag']) {
18595
									if ($dom[$nkey]['block']) {
18596
										// end of block
18597
										$write_block = false;
18598
									}
18599
									$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18600
									$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18601
									$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18602
									$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18603
								} else {
18604
									$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18605
									if (isset($nextstr[0]) AND $same_textdir) {
18606
										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18607
										if (isset($nextstr[1])) {
18608
											$write_block = false;
18609
										}
18610
									}
18611
								}
18612
								++$nkey;
18613
							}
18614
						}
18615
						if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18616
							$wadj = 0;
18617
							$nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18618
							$numblks = count($nextstr);
18619
							if ($numblks > 1) {
18620
								// try to split on blank spaces
18621
								$wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18622
							} else {
18623
								// set the entire block on new line
18624
								$wadj = $this->GetStringWidth($nextstr[0]);
18625
							}
18626
						}
18627
						// check for reversed text direction
18628
						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18629
							// LTR text on RTL direction or RTL text on LTR direction
18630
							$reverse_dir = true;
18631
							$this->rtl = !$this->rtl;
18632
							$revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18633
							if ($this->rtl) {
18634
								$this->x += $revshift;
18635
							} else {
18636
								$this->x -= $revshift;
18637
							}
18638
							$xws = $this->x;
18639
						}
18640
						// ****** write only until the end of the line and get the rest ******
18641
						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18642
						// restore default direction
18643
						if ($reverse_dir AND ($wadj == 0)) {
18644
							$this->x = $xws; // @phpstan-ignore-line
18645
							$this->rtl = !$this->rtl;
18646
							$reverse_dir = false;
18647
						}
18648
					}
18649
				}
18650
				$this->textindent = 0;
18651
				if (strlen($strrest) > 0) {
18652
					// store the remaining string on the previous $key position
18653
					$this->newline = true;
18654
					if ($strrest == $dom[$key]['value']) {
18655
						// used to avoid infinite loop
18656
						++$loop;
18657
					} else {
18658
						$loop = 0;
18659
					}
18660
					$dom[$key]['value'] = $strrest;
18661
					if ($cell) {
18662
						if ($this->rtl) {
18663
							$this->x -= $this->cell_padding['R'];
18664
						} else {
18665
							$this->x += $this->cell_padding['L'];
18666
						}
18667
					}
18668
					if ($loop < 3) {
18669
						--$key;
18670
					}
18671
				} else {
18672
					$loop = 0;
18673
					// add the positive font spacing of the last character (if any)
18674
					 if ($this->font_spacing > 0) {
18675
					 	if ($this->rtl) {
18676
							$this->x -= $this->font_spacing;
18677
						} else {
18678
							$this->x += $this->font_spacing;
18679
						}
18680
					}
18681
				}
18682
			}
18683
			++$key;
18684
			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')) {
18685
				// check if we are on a new page or on a new column
18686
				if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18687
					// we are on a new page or on a new column and the total object height is less than the available vertical space.
18688
					// restore previous object
18689
					$this->rollbackTransaction(true);
18690
					// restore previous values
18691
					foreach ($this_method_vars as $vkey => $vval) {
18692
						$$vkey = $vval;
18693
					}
18694
					if (!empty($dom[$key]['thead'])) {
18695
						$this->inthead = true;
18696
					}
18697
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18698
					$pre_y = $this->y;
18699
					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18700
						$startliney = $this->y;
18701
					}
18702
					$undo = true; // avoid infinite loop
18703
				} else {
18704
					$undo = false;
18705
				}
18706
			}
18707
		} // end for each $key
18708
		// align the last line
18709
		if (isset($startlinex)) {
18710
			$yshift = ($minstartliney - $startliney);
18711
			if (($yshift > 0) OR ($this->page > $startlinepage)) {
18712
				$yshift = 0;
18713
			}
18714
			$t_x = 0;
18715
			// the last line must be shifted to be aligned as requested
18716
			$linew = abs($this->endlinex - $startlinex);
18717
			if ($this->inxobj) {
18718
				// we are inside an XObject template
18719
				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18720
				if (isset($opentagpos)) {
18721
					$midpos = $opentagpos;
18722
				} else {
18723
					$midpos = 0;
18724
				}
18725
				if ($midpos > 0) {
18726
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18727
					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18728
				} else {
18729
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18730
					$pend = '';
18731
				}
18732
			} else {
18733
				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18734
				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18735
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18736
					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18737
				} elseif (isset($opentagpos)) {
18738
					$midpos = $opentagpos;
18739
				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18740
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18741
					$midpos = $this->footerpos[$startlinepage];
18742
				} else {
18743
					$midpos = 0;
18744
				}
18745
				if ($midpos > 0) {
18746
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18747
					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
18748
				} else {
18749
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18750
					$pend = '';
18751
				}
18752
			}
18753
			if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18754
				// calculate shifting amount
18755
				$tw = $w;
18756
				if ($this->lMargin != $prevlMargin) {
18757
					$tw += ($prevlMargin - $this->lMargin);
18758
				}
18759
				if ($this->rMargin != $prevrMargin) {
18760
					$tw += ($prevrMargin - $this->rMargin);
18761
				}
18762
				$one_space_width = $this->GetStringWidth(chr(32));
18763
				$no = 0; // number of spaces on a line contained on a single block
18764
				if ($this->isRTLTextDir()) { // RTL
18765
					// remove left space if exist
18766
					$pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18767
					if ($pos1 > 0) {
18768
						$pos1 = intval($pos1);
18769
						if ($this->isUnicodeFont()) {
18770
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18771
							$spacelen = 2;
18772
						} else {
18773
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18774
							$spacelen = 1;
18775
						}
18776
						if ($pos1 == $pos2) {
18777
							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18778
							if (substr($pmid, $pos1, 4) == '[()]') {
18779
								$linew -= $one_space_width;
18780
							} elseif ($pos1 == strpos($pmid, '[(')) {
18781
								$no = 1;
18782
							}
18783
						}
18784
					}
18785
				} else { // LTR
18786
					// remove right space if exist
18787
					$pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18788
					if ($pos1 > 0) {
18789
						$pos1 = intval($pos1);
18790
						if ($this->isUnicodeFont()) {
18791
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18792
							$spacelen = 2;
18793
						} else {
18794
							$pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18795
							$spacelen = 1;
18796
						}
18797
						if ($pos1 == $pos2) {
18798
							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18799
							$linew -= $one_space_width;
18800
						}
18801
					}
18802
				}
18803
				$mdiff = ($tw - $linew);
18804
				if ($plalign == 'C') {
18805
					if ($this->rtl) {
18806
						$t_x = -($mdiff / 2);
18807
					} else {
18808
						$t_x = ($mdiff / 2);
18809
					}
18810
				} elseif ($plalign == 'R') {
18811
					// right alignment on LTR document
18812
					$t_x = $mdiff;
18813
				} elseif ($plalign == 'L') {
18814
					// left alignment on RTL document
18815
					$t_x = -$mdiff;
18816
				}
18817
			} // end if startlinex
18818
			if (($t_x != 0) OR ($yshift < 0)) {
18819
				// shift the line
18820
				$trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18821
				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18822
				$endlinepos = strlen($pstart);
18823
				if ($this->inxobj) {
18824
					// we are inside an XObject template
18825
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18826
					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18827
						if ($pak >= $pask) {
18828
							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18829
							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18830
						}
18831
					}
18832
				} else {
18833
					$this->setPageBuffer($startlinepage, $pstart.$pend);
18834
					// shift the annotations and links
18835
					if (isset($this->PageAnnots[$this->page])) {
18836
						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18837
							if ($pak >= $pask) {
18838
								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18839
								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18840
							}
18841
						}
18842
					}
18843
				}
18844
				$this->y -= $yshift;
18845
				$yshift = 0;
18846
			}
18847
		}
18848
		// restore previous values
18849
		$this->setGraphicVars($gvars);
18850
		if ($this->num_columns > 1) {
18851
			$this->selectColumn();
18852
		} elseif ($this->page > $prevPage) {
18853
			$this->lMargin = $this->pagedim[$this->page]['olm'];
18854
			$this->rMargin = $this->pagedim[$this->page]['orm'];
18855
		}
18856
		// restore previous list state
18857
		$this->cell_height_ratio = $prev_cell_height_ratio;
18858
		$this->listnum = $prev_listnum;
18859
		$this->listordered = $prev_listordered;
18860
		$this->listcount = $prev_listcount;
18861
		$this->lispacer = $prev_lispacer;
18862
		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18863
			$this->Ln($this->lasth);
18864
			if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18865
				$this->y = $maxbottomliney;
18866
			}
18867
		}
18868
		unset($dom);
18869
	}
18870
 
18871
	/**
18872
	 * Process opening tags.
18873
	 * @param array $dom html dom array
18874
	 * @param int $key current element id
18875
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18876
	 * @return array $dom
18877
	 * @protected
18878
	 */
18879
	protected function openHTMLTagHandler($dom, $key, $cell) {
18880
		$tag = $dom[$key];
18881
		$parent = $dom[($dom[$key]['parent'])];
18882
		$firsttag = ($key == 1);
18883
		// check for text direction attribute
18884
		if (isset($tag['dir'])) {
18885
			$this->setTempRTL($tag['dir']);
18886
		} else {
18887
			$this->tmprtl = false;
18888
		}
18889
		if ($tag['block']) {
18890
			$hbz = 0; // distance from y to line bottom
18891
			$hb = 0; // vertical space between block tags
18892
			// calculate vertical space for block tags
18893
			if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18894
				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18895
			} elseif (isset($tag['fontsize'])) {
18896
				$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18897
			} else {
18898
				$cur_h = $this->getCellHeight($this->FontSize);
18899
			}
18900
			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18901
				$on = $this->tagvspaces[$tag['value']][0]['n'];
18902
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18903
				$on = 0.6;
18904
			} else {
18905
				$on = 1;
18906
			}
18907
			if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18908
				$hb = 0;
18909
			} else {
18910
				$hb = ($on * $cur_h);
18911
			}
18912
			if (($this->htmlvspace <= 0) AND ($on > 0)) {
18913
				if (isset($parent['fontsize'])) {
18914
					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18915
				} else {
18916
					$hbz = $this->getCellHeight($this->FontSize);
18917
				}
18918
			}
18919
			if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18920
				// fix vertical space after table
18921
				$hbz = 0;
18922
			}
18923
			// closing vertical space
18924
			$hbc = 0;
18925
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18926
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18927
			} elseif (isset($parent['fontsize'])) {
18928
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18929
			} else {
18930
				$pre_h = $this->getCellHeight($this->FontSize);
18931
			}
18932
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18933
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
18934
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18935
				$cn = 0.6;
18936
			} else {
18937
				$cn = 1;
18938
			}
18939
			if (isset($this->tagvspaces[$tag['value']][1])) {
18940
				$hbc = ($cn * $pre_h);
18941
			}
18942
		}
18943
		// Opening tag
18944
		switch($tag['value']) {
18945
			case 'table': {
18946
				$cp = 0;
18947
				$cs = 0;
18948
				$dom[$key]['rowspans'] = array();
18949
				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18950
					$this->htmlvspace = 0;
18951
					// set table header
18952
					if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18953
						// set table header
18954
						$this->thead = $dom[$key]['thead'];
18955
						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18956
							$this->theadMargins = array();
18957
							$this->theadMargins['cell_padding'] = $this->cell_padding;
18958
							$this->theadMargins['lmargin'] = $this->lMargin;
18959
							$this->theadMargins['rmargin'] = $this->rMargin;
18960
							$this->theadMargins['page'] = $this->page;
18961
							$this->theadMargins['cell'] = $cell;
18962
							$this->theadMargins['gvars'] = $this->getGraphicVars();
18963
						}
18964
					}
18965
				}
18966
				// store current margins and page
18967
				$dom[$key]['old_cell_padding'] = $this->cell_padding;
18968
				if (isset($tag['attribute']['cellpadding'])) {
18969
					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18970
					$this->setCellPadding($pad);
18971
				} elseif (isset($tag['padding'])) {
18972
					$this->cell_padding = $tag['padding'];
18973
				}
18974
				if (isset($tag['attribute']['cellspacing'])) {
18975
					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18976
				} elseif (isset($tag['border-spacing'])) {
18977
					$cs = $tag['border-spacing']['V'];
18978
				}
18979
				$prev_y = $this->y;
18980
				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18981
					$this->inthead = true;
18982
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
18983
					$this->checkPageBreak($this->PageBreakTrigger + 1);
18984
				}
18985
				break;
18986
			}
18987
			case 'tr': {
18988
				// array of columns positions
18989
				$dom[$key]['cellpos'] = array();
18990
				break;
18991
			}
18992
			case 'hr': {
18993
				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18994
					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18995
				} else {
18996
					$hrHeight = $this->GetLineWidth();
18997
				}
18998
				$this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18999
				$x = $this->GetX();
19000
				$y = $this->GetY();
19001
				$wtmp = $this->w - $this->lMargin - $this->rMargin;
19002
				if ($cell) {
19003
					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
19004
				}
19005
				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
19006
					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
19007
				} else {
19008
					$hrWidth = $wtmp;
19009
				}
19010
				$prevlinewidth = $this->GetLineWidth();
19011
				$this->setLineWidth($hrHeight);
19012
 
19013
				$lineStyle = array();
1441 ariadna 19014
				if (isset($tag['fgcolor'])) {
19015
					$lineStyle['color'] = $tag['fgcolor'];
19016
				}
1 efrain 19017
 
1441 ariadna 19018
				if (isset($tag['fgcolor'])) {
19019
					$lineStyle['color'] = $tag['fgcolor'];
19020
				}
1 efrain 19021
 
1441 ariadna 19022
				if (isset($tag['style']['cap'])) {
19023
					$lineStyle['cap'] = $tag['style']['cap'];
19024
				}
1 efrain 19025
 
1441 ariadna 19026
				if (isset($tag['style']['join'])) {
19027
					$lineStyle['join'] = $tag['style']['join'];
19028
				}
1 efrain 19029
 
1441 ariadna 19030
				if (isset($tag['style']['dash'])) {
19031
					$lineStyle['dash'] = $tag['style']['dash'];
19032
				}
1 efrain 19033
 
1441 ariadna 19034
				if (isset($tag['style']['phase'])) {
19035
					$lineStyle['phase'] = $tag['style']['phase'];
19036
				}
1 efrain 19037
 
19038
				$lineStyle = array_filter($lineStyle);
19039
 
19040
				$this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
19041
				$this->setLineWidth($prevlinewidth);
19042
				$this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
19043
				break;
19044
			}
19045
			case 'a': {
19046
				if (array_key_exists('href', $tag['attribute'])) {
19047
					$this->HREF['url'] = $tag['attribute']['href'];
19048
				}
19049
				break;
19050
			}
19051
			case 'img': {
19052
				if (empty($tag['attribute']['src'])) {
19053
					break;
19054
				}
19055
				$imgsrc = $tag['attribute']['src'];
19056
				if ($imgsrc[0] === '@') {
19057
					// data stream
19058
					$imgsrc = '@'.base64_decode(substr($imgsrc, 1));
1441 ariadna 19059
					$type = preg_match('/<svg\s+[^>]*[^>]*>.*<\/svg>/is', $imgsrc) ? 'svg' : '';
1 efrain 19060
				} else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19061
					$imgsrc = '@'.base64_decode($reg[2]);
19062
					$type = $reg[1];
1441 ariadna 19063
				} elseif (strpos($imgsrc, '../') !== false) {
19064
					// accessing parent folders is not allowed
19065
					break;
1 efrain 19066
				} elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
1441 ariadna 19067
					// get image type from a local file path
19068
					$imgsrc = substr($imgsrc, 7);
19069
					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
19070
				} else {
1 efrain 19071
					if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19072
						// fix image path
19073
						$findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
19074
						if (($findroot === false) OR ($findroot > 1)) {
19075
							if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
19076
								$imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
19077
							} else {
19078
								$imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
19079
							}
19080
						}
19081
						$imgsrc = urldecode($imgsrc);
19082
						$testscrtype = @parse_url($imgsrc);
19083
						if (empty($testscrtype['query'])) {
19084
							// convert URL to server path
19085
							$imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
19086
						} elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
19087
							// convert URL to server path
19088
							$imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
19089
						}
19090
					}
19091
					// get image type
19092
					$type = TCPDF_IMAGES::getImageFileType($imgsrc);
19093
				}
19094
				if (!isset($tag['width'])) {
19095
					$tag['width'] = 0;
19096
				}
19097
				if (!isset($tag['height'])) {
19098
					$tag['height'] = 0;
19099
				}
19100
				//if (!isset($tag['attribute']['align'])) {
19101
					// the only alignment supported is "bottom"
19102
					// further development is required for other modes.
19103
					$tag['attribute']['align'] = 'bottom';
19104
				//}
19105
				switch($tag['attribute']['align']) {
19106
					case 'top': {
19107
						$align = 'T';
19108
						break;
19109
					}
19110
					case 'middle': {
19111
						$align = 'M';
19112
						break;
19113
					}
19114
					case 'bottom': {
19115
						$align = 'B';
19116
						break;
19117
					}
19118
					default: {
19119
						$align = 'B';
19120
						break;
19121
					}
19122
				}
19123
				$prevy = $this->y;
19124
				$xpos = $this->x;
19125
				$imglink = '';
19126
				if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19127
					$imglink = $this->HREF['url'];
1441 ariadna 19128
					if ($imglink[0] == '#' AND is_numeric($imglink[1])) {
1 efrain 19129
						// convert url to internal link
19130
						$lnkdata = explode(',', $imglink);
19131
						if (isset($lnkdata[0])) {
19132
							$page = intval(substr($lnkdata[0], 1));
19133
							if (empty($page) OR ($page <= 0)) {
19134
								$page = $this->page;
19135
							}
19136
							if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19137
								$lnky = floatval($lnkdata[1]);
19138
							} else {
19139
								$lnky = 0;
19140
							}
19141
							$imglink = $this->AddLink();
19142
							$this->setLink($imglink, $lnky, $page);
19143
						}
19144
					}
19145
				}
19146
				$border = 0;
19147
				if (isset($tag['border']) AND !empty($tag['border'])) {
19148
					// currently only support 1 (frame) or a combination of 'LTRB'
19149
					$border = $tag['border'];
19150
				}
19151
				$iw = '';
19152
				if (isset($tag['width'])) {
19153
					$iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19154
				}
19155
				$ih = '';
19156
				if (isset($tag['height'])) {
19157
					$ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19158
				}
19159
				if (($type == 'eps') OR ($type == 'ai')) {
19160
					$this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19161
				} elseif ($type == 'svg') {
19162
					$this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19163
				} else {
19164
					$this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19165
				}
19166
				switch($align) {
19167
					case 'T': {
19168
						$this->y = $prevy;
19169
						break;
19170
					}
19171
					case 'M': {
19172
						$this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19173
						break;
19174
					}
19175
					case 'B': {
19176
						$this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19177
						break;
19178
					}
19179
				}
19180
				break;
19181
			}
19182
			case 'dl': {
19183
				++$this->listnum;
19184
				if ($this->listnum == 1) {
19185
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19186
				} else {
19187
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19188
				}
19189
				break;
19190
			}
19191
			case 'dt': {
19192
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19193
				break;
19194
			}
19195
			case 'dd': {
19196
				if ($this->rtl) {
19197
					$this->rMargin += $this->listindent;
19198
				} else {
19199
					$this->lMargin += $this->listindent;
19200
				}
19201
				++$this->listindentlevel;
19202
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19203
				break;
19204
			}
19205
			case 'ul':
19206
			case 'ol': {
19207
				++$this->listnum;
19208
				if ($tag['value'] == 'ol') {
19209
					$this->listordered[$this->listnum] = true;
19210
				} else {
19211
					$this->listordered[$this->listnum] = false;
19212
				}
19213
				if (isset($tag['attribute']['start'])) {
19214
					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19215
				} else {
19216
					$this->listcount[$this->listnum] = 0;
19217
				}
19218
				if ($this->rtl) {
19219
					$this->rMargin += $this->listindent;
19220
					$this->x -= $this->listindent;
19221
				} else {
19222
					$this->lMargin += $this->listindent;
19223
					$this->x += $this->listindent;
19224
				}
19225
				++$this->listindentlevel;
19226
				if ($this->listnum == 1) {
19227
					if ($key > 1) {
19228
						$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19229
					}
19230
				} else {
19231
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19232
				}
19233
				break;
19234
			}
19235
			case 'li': {
19236
				if ($key > 2) {
19237
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19238
				}
19239
				if ($this->listordered[$this->listnum]) {
19240
					// ordered item
19241
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19242
						$this->lispacer = $parent['attribute']['type'];
19243
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19244
						$this->lispacer = $parent['listtype'];
19245
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19246
						$this->lispacer = $this->lisymbol;
19247
					} else {
19248
						$this->lispacer = '#';
19249
					}
19250
					++$this->listcount[$this->listnum];
19251
					if (isset($tag['attribute']['value'])) {
19252
						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19253
					}
19254
				} else {
19255
					// unordered item
19256
					if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19257
						$this->lispacer = $parent['attribute']['type'];
19258
					} elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19259
						$this->lispacer = $parent['listtype'];
19260
					} elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19261
						$this->lispacer = $this->lisymbol;
19262
					} else {
19263
						$this->lispacer = '!';
19264
					}
19265
				}
19266
				break;
19267
			}
19268
			case 'blockquote': {
19269
				if ($this->rtl) {
19270
					$this->rMargin += $this->listindent;
19271
				} else {
19272
					$this->lMargin += $this->listindent;
19273
				}
19274
				++$this->listindentlevel;
19275
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19276
				break;
19277
			}
19278
			case 'br': {
19279
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19280
				break;
19281
			}
19282
			case 'div': {
19283
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19284
				break;
19285
			}
19286
			case 'p': {
19287
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19288
				break;
19289
			}
19290
			case 'pre': {
19291
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19292
				$this->premode = true;
19293
				break;
19294
			}
19295
			case 'sup': {
19296
				$this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19297
				break;
19298
			}
19299
			case 'sub': {
19300
				$this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19301
				break;
19302
			}
19303
			case 'h1':
19304
			case 'h2':
19305
			case 'h3':
19306
			case 'h4':
19307
			case 'h5':
19308
			case 'h6': {
19309
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19310
				break;
19311
			}
19312
			// Form fields (since 4.8.000 - 2009-09-07)
19313
			case 'form': {
19314
				if (isset($tag['attribute']['action'])) {
19315
					$this->form_action = $tag['attribute']['action'];
19316
				} else {
19317
					$this->Error('Please explicitly set action attribute path!');
19318
				}
19319
				if (isset($tag['attribute']['enctype'])) {
19320
					$this->form_enctype = $tag['attribute']['enctype'];
19321
				} else {
19322
					$this->form_enctype = 'application/x-www-form-urlencoded';
19323
				}
19324
				if (isset($tag['attribute']['method'])) {
19325
					$this->form_mode = $tag['attribute']['method'];
19326
				} else {
19327
					$this->form_mode = 'post';
19328
				}
19329
				break;
19330
			}
19331
			case 'input': {
19332
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19333
					$name = $tag['attribute']['name'];
19334
				} else {
19335
					break;
19336
				}
19337
				$prop = array();
19338
				$opt = array();
19339
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19340
					$prop['readonly'] = true;
19341
				}
19342
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19343
					$value = $tag['attribute']['value'];
19344
				}
19345
				if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19346
					$opt['maxlen'] = intval($tag['attribute']['maxlength']);
19347
				}
19348
				$h = $this->getCellHeight($this->FontSize);
19349
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19350
					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19351
				} else {
19352
					$w = $h;
19353
				}
19354
				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19355
					$checked = true;
19356
				} else {
19357
					$checked = false;
19358
				}
19359
				if (isset($tag['align'])) {
19360
					switch ($tag['align']) {
19361
						case 'C': {
19362
							$opt['q'] = 1;
19363
							break;
19364
						}
19365
						case 'R': {
19366
							$opt['q'] = 2;
19367
							break;
19368
						}
19369
						case 'L':
19370
						default: {
19371
							break;
19372
						}
19373
					}
19374
				}
19375
				switch ($tag['attribute']['type']) {
19376
					case 'text': {
19377
						if (isset($value)) {
19378
							$opt['v'] = $value;
19379
						}
19380
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19381
						break;
19382
					}
19383
					case 'password': {
19384
						if (isset($value)) {
19385
							$opt['v'] = $value;
19386
						}
19387
						$prop['password'] = 'true';
19388
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19389
						break;
19390
					}
19391
					case 'checkbox': {
19392
						if (!isset($value)) {
19393
							break;
19394
						}
19395
						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19396
						break;
19397
					}
19398
					case 'radio': {
19399
						if (!isset($value)) {
19400
							break;
19401
						}
19402
						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19403
						break;
19404
					}
19405
					case 'submit': {
19406
						if (!isset($value)) {
19407
							$value = 'submit';
19408
						}
19409
						$w = $this->GetStringWidth($value) * 1.5;
19410
						$h *= 1.6;
19411
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19412
						$action = array();
19413
						$action['S'] = 'SubmitForm';
19414
						$action['F'] = $this->form_action;
19415
						if ($this->form_enctype != 'FDF') {
19416
							$action['Flags'] = array('ExportFormat');
19417
						}
19418
						if ($this->form_mode == 'get') {
19419
							$action['Flags'] = array('GetMethod');
19420
						}
19421
						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19422
						break;
19423
					}
19424
					case 'reset': {
19425
						if (!isset($value)) {
19426
							$value = 'reset';
19427
						}
19428
						$w = $this->GetStringWidth($value) * 1.5;
19429
						$h *= 1.6;
19430
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19431
						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19432
						break;
19433
					}
19434
					case 'file': {
19435
						$prop['fileSelect'] = 'true';
19436
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19437
						if (!isset($value)) {
19438
							$value = '*';
19439
						}
19440
						$w = $this->GetStringWidth($value) * 2;
19441
						$h *= 1.2;
19442
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19443
						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19444
						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19445
						break;
19446
					}
19447
					case 'hidden': {
19448
						if (isset($value)) {
19449
							$opt['v'] = $value;
19450
						}
19451
						$opt['f'] = array('invisible', 'hidden');
19452
						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19453
						break;
19454
					}
19455
					case 'image': {
19456
						// THIS TYPE MUST BE FIXED
19457
						if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19458
							$img = $tag['attribute']['src'];
19459
						} else {
19460
							break;
19461
						}
19462
						$value = 'img';
19463
						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19464
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19465
							$jsaction = $tag['attribute']['onclick'];
19466
						} else {
19467
							$jsaction = '';
19468
						}
19469
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19470
						break;
19471
					}
19472
					case 'button': {
19473
						if (!isset($value)) {
19474
							$value = ' ';
19475
						}
19476
						$w = $this->GetStringWidth($value) * 1.5;
19477
						$h *= 1.6;
19478
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19479
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19480
							$jsaction = $tag['attribute']['onclick'];
19481
						} else {
19482
							$jsaction = '';
19483
						}
19484
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19485
						break;
19486
					}
19487
				}
19488
				break;
19489
			}
19490
			case 'textarea': {
19491
				$prop = array();
19492
				$opt = array();
19493
				if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19494
					$prop['readonly'] = true;
19495
				}
19496
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19497
					$name = $tag['attribute']['name'];
19498
				} else {
19499
					break;
19500
				}
19501
				if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19502
					$opt['v'] = $tag['attribute']['value'];
19503
				}
19504
				if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19505
					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19506
				} else {
19507
					$w = 40;
19508
				}
19509
				if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19510
					$h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19511
				} else {
19512
					$h = 10;
19513
				}
19514
				$prop['multiline'] = 'true';
19515
				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19516
				break;
19517
			}
19518
			case 'select': {
19519
				$h = $this->getCellHeight($this->FontSize);
19520
				if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19521
					$h *= ($tag['attribute']['size'] + 1);
19522
				}
19523
				$prop = array();
19524
				$opt = array();
19525
				if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19526
					$name = $tag['attribute']['name'];
19527
				} else {
19528
					break;
19529
				}
19530
				$w = 0;
19531
				if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19532
					$options = explode('#!NwL!#', $tag['attribute']['opt']);
19533
					$values = array();
19534
					foreach ($options as $val) {
19535
						if (strpos($val, '#!TaB!#') !== false) {
19536
							$opts = explode('#!TaB!#', $val);
19537
							$values[] = $opts;
19538
							$w = max($w, $this->GetStringWidth($opts[1]));
19539
						} else {
19540
							$values[] = $val;
19541
							$w = max($w, $this->GetStringWidth($val));
19542
						}
19543
					}
19544
				} else {
19545
					break;
19546
				}
19547
				$w *= 2;
19548
				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19549
					$prop['multipleSelection'] = 'true';
19550
					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19551
				} else {
19552
					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19553
				}
19554
				break;
19555
			}
19556
			case 'tcpdf': {
19557
				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19558
					// Special tag used to call TCPDF methods
1441 ariadna 19559
					// This tag is disabled by default by the K_TCPDF_CALLS_IN_HTML constant on TCPDF configuration file.
19560
					// 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.
19561
					if (!empty($tag['attribute']['data'])) {
19562
						$tcpdf_tag_data = $this->unserializeTCPDFtag($tag['attribute']['data']);
19563
						if ($this->allowedTCPDFtag($tcpdf_tag_data['m'])) {
19564
							call_user_func_array(array($this, $tcpdf_tag_data['m']), $tcpdf_tag_data['p']);
1 efrain 19565
						}
1441 ariadna 19566
						$this->newline = true;
1 efrain 19567
					}
19568
				}
19569
				break;
19570
			}
19571
			default: {
19572
				break;
19573
			}
19574
		}
19575
		// define tags that support borders and background colors
19576
		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19577
		if (in_array($tag['value'], $bordertags)) {
19578
			// set border
19579
			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
19580
		}
19581
		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19582
			$pba = $dom[$key]['attribute']['pagebreakafter'];
19583
			// check for pagebreak
19584
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19585
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19586
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19587
			}
19588
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19589
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19590
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
19591
				$this->checkPageBreak($this->PageBreakTrigger + 1);
19592
			}
19593
		}
19594
		return $dom;
19595
	}
19596
 
19597
	/**
19598
	 * Process closing tags.
19599
	 * @param array $dom html dom array
19600
	 * @param int $key current element id
19601
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19602
	 * @param int $maxbottomliney maximum y value of current line
19603
	 * @return array $dom
19604
	 * @protected
19605
	 */
19606
	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19607
		$tag = $dom[$key];
19608
		$parent = $dom[($dom[$key]['parent'])];
19609
		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19610
		$in_table_head = false;
19611
		// maximum x position (used to draw borders)
19612
		if ($this->rtl) {
19613
			$xmax = $this->w;
19614
		} else {
19615
			$xmax = 0;
19616
		}
19617
		if ($tag['block']) {
19618
			$hbz = 0; // distance from y to line bottom
19619
			$hb = 0; // vertical space between block tags
19620
			// calculate vertical space for block tags
19621
			if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19622
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19623
			} elseif (isset($parent['fontsize'])) {
19624
				$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19625
			} else {
19626
				$pre_h = $this->getCellHeight($this->FontSize);
19627
			}
19628
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19629
				$cn = $this->tagvspaces[$tag['value']][1]['n'];
19630
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19631
				$cn = 0.6;
19632
			} else {
19633
				$cn = 1;
19634
			}
19635
			if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19636
				$hb = 0;
19637
			} else {
19638
				$hb = ($cn * $pre_h);
19639
			}
19640
			if ($maxbottomliney > $this->PageBreakTrigger) {
19641
				$hbz = $this->getCellHeight($this->FontSize);
19642
			} elseif ($this->y < $maxbottomliney) {
19643
				$hbz = ($maxbottomliney - $this->y);
19644
			}
19645
		}
19646
		// Closing tag
19647
		switch($tag['value']) {
19648
			case 'tr': {
19649
				$table_el = $dom[($dom[$key]['parent'])]['parent'];
19650
				if (!isset($parent['endy'])) {
19651
					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19652
					$parent['endy'] = $this->y;
19653
				}
19654
				if (!isset($parent['endpage'])) {
19655
					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19656
					$parent['endpage'] = $this->page;
19657
				}
19658
				if (!isset($parent['endcolumn'])) {
19659
					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19660
					$parent['endcolumn'] = $this->current_column;
19661
				}
19662
				// update row-spanned cells
19663
				if (isset($dom[$table_el]['rowspans'])) {
19664
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19665
						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19666
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19667
							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19668
								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19669
							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19670
								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19671
								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19672
								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19673
							}
19674
						}
19675
					}
19676
					// report new endy and endpage to the rowspanned cells
19677
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19678
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19679
							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19680
							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19681
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19682
							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19683
							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19684
							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19685
						}
19686
					}
19687
					// update remaining rowspanned cells
19688
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19689
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19690
							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19691
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19692
							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19693
						}
19694
					}
19695
				}
19696
				$prev_page = $this->page;
19697
				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19698
				if ($this->num_columns > 1) {
19699
					if (($prev_page < $this->page)
19700
						AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19701
							OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19702
						// page jump
19703
						$this->selectColumn(0);
19704
						$dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19705
						$dom[($dom[$key]['parent'])]['endy'] = $this->y;
19706
					} else {
19707
						$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19708
						$this->y = $dom[($dom[$key]['parent'])]['endy'];
19709
					}
19710
				} else {
19711
					$this->y = $dom[($dom[$key]['parent'])]['endy'];
19712
				}
19713
				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19714
					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19715
				} elseif (isset($dom[$table_el]['border-spacing'])) {
19716
					$this->y += $dom[$table_el]['border-spacing']['V'];
19717
				}
19718
				$this->Ln(0, $cell);
19719
				if ($this->current_column == $parent['startcolumn']) {
19720
					$this->x = $parent['startx'];
19721
				}
19722
				// account for booklet mode
19723
				if ($this->page > $parent['startpage']) {
19724
					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19725
						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19726
					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19727
						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19728
					}
19729
				}
19730
				break;
19731
			}
19732
			case 'tablehead':
19733
				// closing tag used for the thead part
19734
				$in_table_head = true;
19735
				$this->inthead = false;
19736
			case 'table': {
19737
				$table_el = $parent;
19738
				// set default border
19739
				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19740
					// set default border
19741
					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19742
				} else {
19743
					$border = 0;
19744
				}
19745
				$default_border = $border;
19746
				// fix bottom line alignment of last line before page break
19747
				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19748
					// update row-spanned cells
19749
					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19750
						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19751
							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19752
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19753
							}
19754
							if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19755
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19756
							}
19757
						}
19758
					}
19759
					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19760
						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19761
						$dom[$prevtrkey]['endy'] = $pgendy;
19762
						// update row-spanned cells
19763
						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19764
							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19765
								if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19766
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19767
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19768
								}
19769
							}
19770
						}
19771
					}
19772
					$prevtrkey = $trkey;
19773
					$table_el = $dom[($dom[$key]['parent'])];
19774
				}
19775
				// for each row
19776
				if (!empty($table_el['trids'])) {
19777
					unset($xmax);
19778
				}
19779
				foreach ($table_el['trids'] as $j => $trkey) {
19780
					$parent = $dom[$trkey];
19781
					if (!isset($xmax)) {
19782
						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19783
					}
19784
					// for each cell on the row
19785
					foreach ($parent['cellpos'] as $k => $cellpos) {
19786
						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19787
							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19788
							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19789
							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19790
							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19791
							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19792
							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19793
							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19794
						} else {
19795
							$endy = $parent['endy'];
19796
							$startpage = $parent['startpage'];
19797
							$endpage = $parent['endpage'];
19798
							$startcolumn = $parent['startcolumn'];
19799
							$endcolumn = $parent['endcolumn'];
19800
						}
19801
						if ($this->num_columns == 0) {
19802
							$this->num_columns = 1;
19803
						}
19804
						if (isset($cellpos['border'])) {
19805
							$border = $cellpos['border'];
19806
						}
19807
						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19808
							$this->setFillColorArray($cellpos['bgcolor']);
19809
							$fill = true;
19810
						} else {
19811
							$fill = false;
19812
						}
19813
						$x = $cellpos['startx'];
19814
						$y = $parent['starty'];
19815
						$starty = $y;
19816
						$w = abs($cellpos['endx'] - $cellpos['startx']);
19817
						// get border modes
19818
						$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19819
						$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19820
						$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19821
						// design borders around HTML cells.
19822
						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19823
							$ccode = '';
19824
							$this->setPage($page);
19825
							if ($this->num_columns < 2) {
19826
								// single-column mode
19827
								$this->x = $x;
19828
								$this->y = $this->tMargin;
19829
							}
19830
							// account for margin changes
19831
							if ($page > $startpage) {
19832
								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19833
									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19834
								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19835
									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19836
								}
19837
							}
19838
							if ($startpage == $endpage) { // single page
19839
								$deltacol = 0;
19840
								$deltath = 0;
19841
								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19842
									$this->selectColumn($column);
19843
									if ($startcolumn == $endcolumn) { // single column
19844
										$cborder = $border;
19845
										$h = $endy - $parent['starty'];
19846
										$this->y = $y;
19847
										$this->x = $x;
19848
									} elseif ($column == $startcolumn) { // first column
19849
										$cborder = $border_start;
19850
										$this->y = $starty;
19851
										$this->x = $x;
19852
										$h = $this->h - $this->y - $this->bMargin;
19853
										if ($this->rtl) {
19854
											$deltacol = $this->x + $this->rMargin - $this->w;
19855
										} else {
19856
											$deltacol = $this->x - $this->lMargin;
19857
										}
19858
									} elseif ($column == $endcolumn) { // end column
19859
										$cborder = $border_end;
19860
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19861
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19862
										}
19863
										$this->x += $deltacol;
19864
										$h = $endy - $this->y;
19865
									} else { // middle column
19866
										$cborder = $border_middle;
19867
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19868
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19869
										}
19870
										$this->x += $deltacol;
19871
										$h = $this->h - $this->y - $this->bMargin;
19872
									}
19873
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19874
								} // end for each column
19875
							} elseif ($page == $startpage) { // first page
19876
								$deltacol = 0;
19877
								$deltath = 0;
19878
								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19879
									$this->selectColumn($column);
19880
									if ($column == $startcolumn) { // first column
19881
										$cborder = $border_start;
19882
										$this->y = $starty;
19883
										$this->x = $x;
19884
										$h = $this->h - $this->y - $this->bMargin;
19885
										if ($this->rtl) {
19886
											$deltacol = $this->x + $this->rMargin - $this->w;
19887
										} else {
19888
											$deltacol = $this->x - $this->lMargin;
19889
										}
19890
									} else { // middle column
19891
										$cborder = $border_middle;
19892
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19893
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19894
										}
19895
										$this->x += $deltacol;
19896
										$h = $this->h - $this->y - $this->bMargin;
19897
									}
19898
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19899
								} // end for each column
19900
							} elseif ($page == $endpage) { // last page
19901
								$deltacol = 0;
19902
								$deltath = 0;
19903
								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19904
									$this->selectColumn($column);
19905
									if ($column == $endcolumn) { // end column
19906
										$cborder = $border_end;
19907
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19908
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19909
										}
19910
										$this->x += $deltacol;
19911
										$h = $endy - $this->y;
19912
									} else { // middle column
19913
										$cborder = $border_middle;
19914
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19915
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19916
										}
19917
										$this->x += $deltacol;
19918
										$h = $this->h - $this->y - $this->bMargin;
19919
									}
19920
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19921
								} // end for each column
19922
							} else { // middle page
19923
								$deltacol = 0;
19924
								$deltath = 0;
19925
								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19926
									$this->selectColumn($column);
19927
									$cborder = $border_middle;
19928
									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19929
										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
19930
									}
19931
									$this->x += $deltacol;
19932
									$h = $this->h - $this->y - $this->bMargin;
19933
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19934
								} // end for each column
19935
							}
19936
							if (!empty($cborder) OR !empty($fill)) {
19937
								$offsetlen = strlen($ccode);
19938
								// draw border and fill
19939
								if ($this->inxobj) {
19940
									// we are inside an XObject template
19941
									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19942
										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19943
										$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19944
										$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19945
									} else {
19946
										$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19947
										$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19948
									}
19949
									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19950
									$pstart = substr($pagebuff, 0, $pagemark);
19951
									$pend = substr($pagebuff, $pagemark);
19952
									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19953
								} else {
19954
									// draw border and fill
19955
									if (end($this->transfmrk[$this->page]) !== false) {
19956
										$pagemarkkey = key($this->transfmrk[$this->page]);
19957
										$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19958
									} elseif ($this->InFooter) {
19959
										$pagemark = $this->footerpos[$this->page];
19960
									} else {
19961
										$pagemark = $this->intmrk[$this->page];
19962
									}
19963
									$pagebuff = $this->getPageBuffer($this->page);
19964
									$pstart = substr($pagebuff, 0, $pagemark);
19965
									$pend = substr($pagebuff, $pagemark);
19966
									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19967
								}
19968
							}
19969
						} // end for each page
19970
						// restore default border
19971
						$border = $default_border;
19972
					} // end for each cell on the row
19973
					if (isset($table_el['attribute']['cellspacing'])) {
19974
						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19975
					} elseif (isset($table_el['border-spacing'])) {
19976
						$this->y += $table_el['border-spacing']['V'];
19977
					}
19978
					$this->Ln(0, $cell);
19979
					$this->x = $parent['startx'];
19980
					if ($endpage > $startpage) {
19981
						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19982
							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19983
						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19984
							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19985
						}
19986
					}
19987
				}
19988
				if (!$in_table_head) { // we are not inside a thead section
19989
					$this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null;
19990
					// reset row height
19991
					$this->resetLastH();
19992
					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19993
						$plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19994
						if (($plendiff > 0) AND ($plendiff < 60)) {
19995
							$pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19996
							if (substr($pagediff, 0, 5) == 'BT /F') {
19997
								// the difference is only a font setting
19998
								$plendiff = 0;
19999
							}
20000
						}
20001
						if ($plendiff == 0) {
20002
							// remove last blank page
20003
							$this->deletePage($this->numpages);
20004
						}
20005
					}
20006
					if (isset($this->theadMargins['top'])) {
20007
						// restore top margin
20008
						$this->tMargin = $this->theadMargins['top'];
20009
					}
20010
					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
20011
						// reset main table header
20012
						$this->thead = '';
20013
						$this->theadMargins = array();
20014
						$this->pagedim[$this->page]['tm'] = $this->tMargin;
20015
					}
20016
				}
20017
				$parent = $table_el;
20018
				break;
20019
			}
20020
			case 'a': {
20021
				$this->HREF = array();
20022
				break;
20023
			}
20024
			case 'sup': {
20025
				$this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
20026
				break;
20027
			}
20028
			case 'sub': {
20029
				$this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
20030
				break;
20031
			}
20032
			case 'div': {
20033
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20034
				break;
20035
			}
20036
			case 'blockquote': {
20037
				if ($this->rtl) {
20038
					$this->rMargin -= $this->listindent;
20039
				} else {
20040
					$this->lMargin -= $this->listindent;
20041
				}
20042
				--$this->listindentlevel;
20043
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20044
				break;
20045
			}
20046
			case 'p': {
20047
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20048
				break;
20049
			}
20050
			case 'pre': {
20051
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20052
				$this->premode = false;
20053
				break;
20054
			}
20055
			case 'dl': {
20056
				--$this->listnum;
20057
				if ($this->listnum <= 0) {
20058
					$this->listnum = 0;
20059
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20060
				} else {
20061
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20062
				}
20063
				$this->resetLastH();
20064
				break;
20065
			}
20066
			case 'dt': {
20067
				$this->lispacer = '';
20068
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20069
				break;
20070
			}
20071
			case 'dd': {
20072
				$this->lispacer = '';
20073
				if ($this->rtl) {
20074
					$this->rMargin -= $this->listindent;
20075
				} else {
20076
					$this->lMargin -= $this->listindent;
20077
				}
20078
				--$this->listindentlevel;
20079
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20080
				break;
20081
			}
20082
			case 'ul':
20083
			case 'ol': {
20084
				--$this->listnum;
20085
				$this->lispacer = '';
20086
				if ($this->rtl) {
20087
					$this->rMargin -= $this->listindent;
20088
				} else {
20089
					$this->lMargin -= $this->listindent;
20090
				}
20091
				--$this->listindentlevel;
20092
				if ($this->listnum <= 0) {
20093
					$this->listnum = 0;
20094
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20095
				} else {
20096
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20097
				}
20098
				$this->resetLastH();
20099
				break;
20100
			}
20101
			case 'li': {
20102
				$this->lispacer = '';
20103
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20104
				break;
20105
			}
20106
			case 'h1':
20107
			case 'h2':
20108
			case 'h3':
20109
			case 'h4':
20110
			case 'h5':
20111
			case 'h6': {
20112
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20113
				break;
20114
			}
20115
			// Form fields (since 4.8.000 - 2009-09-07)
20116
			case 'form': {
20117
				$this->form_action = '';
20118
				$this->form_enctype = 'application/x-www-form-urlencoded';
20119
				break;
20120
			}
20121
			default : {
20122
				break;
20123
			}
20124
		}
20125
		// draw border and background (if any)
20126
		$this->drawHTMLTagBorder($parent, $xmax);
20127
		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20128
			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20129
			// check for pagebreak
20130
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20131
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20132
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20133
			}
20134
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20135
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20136
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
20137
				$this->checkPageBreak($this->PageBreakTrigger + 1);
20138
			}
20139
		}
20140
		$this->tmprtl = false;
20141
		return $dom;
20142
	}
20143
 
20144
	/**
20145
	 * Add vertical spaces if needed.
20146
	 * @param string $hbz Distance between current y and line bottom.
20147
	 * @param string $hb The height of the break.
20148
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20149
	 * @param boolean $firsttag set to true when the tag is the first.
20150
	 * @param boolean $lasttag set to true when the tag is the last.
20151
	 * @protected
20152
	 */
20153
	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20154
		if ($firsttag) {
20155
			$this->Ln(0, $cell);
20156
			$this->htmlvspace = 0;
20157
			return;
20158
		}
20159
		if ($lasttag) {
20160
			$this->Ln($hbz, $cell);
20161
			$this->htmlvspace = 0;
20162
			return;
20163
		}
20164
		if ($hb < $this->htmlvspace) {
20165
			$hd = 0;
20166
		} else {
20167
			$hd = $hb - $this->htmlvspace;
20168
			$this->htmlvspace = $hb;
20169
		}
20170
		$this->Ln(($hbz + $hd), $cell);
20171
	}
20172
 
20173
	/**
20174
	 * Return the starting coordinates to draw an html border
20175
	 * @return array containing top-left border coordinates
20176
	 * @protected
20177
	 * @since 5.7.000 (2010-08-03)
20178
	 */
20179
	protected function getBorderStartPosition() {
20180
		if ($this->rtl) {
20181
			$xmax = $this->lMargin;
20182
		} else {
20183
			$xmax = $this->w - $this->rMargin;
20184
		}
20185
		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20186
	}
20187
 
20188
	/**
20189
	 * Draw an HTML block border and fill
20190
	 * @param array $tag array of tag properties.
20191
	 * @param int $xmax end X coordinate for border.
20192
	 * @protected
20193
	 * @since 5.7.000 (2010-08-03)
20194
	 */
20195
	protected function drawHTMLTagBorder($tag, $xmax) {
20196
		if (!isset($tag['borderposition'])) {
20197
			// nothing to draw
20198
			return;
20199
		}
20200
		$prev_x = $this->x;
20201
		$prev_y = $this->y;
20202
		$prev_lasth = $this->lasth;
20203
		$border = 0;
20204
		$fill = false;
20205
		$this->lasth = 0;
20206
		if (isset($tag['border']) AND !empty($tag['border'])) {
20207
			// get border style
20208
			$border = $tag['border'];
20209
			if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20210
				// border for table header
20211
				$border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20212
			}
20213
		}
20214
		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20215
			// get background color
20216
			$old_bgcolor = $this->bgcolor;
20217
			$this->setFillColorArray($tag['bgcolor']);
20218
			$fill = true;
20219
		}
20220
		if (!$border AND !$fill) {
20221
			// nothing to draw
20222
			return;
20223
		}
20224
		if (isset($tag['attribute']['cellspacing'])) {
20225
			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20226
			$cellspacing = array('H' => $clsp, 'V' => $clsp);
20227
		} elseif (isset($tag['border-spacing'])) {
20228
			$cellspacing = $tag['border-spacing'];
20229
		} else {
20230
			$cellspacing = array('H' => 0, 'V' => 0);
20231
		}
20232
		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20233
			// draw the border externally respect the sqare edge.
20234
			$border['mode'] = 'ext';
20235
		}
20236
		if ($this->rtl) {
20237
			if ($xmax >= $tag['borderposition']['x']) {
20238
				$xmax = $tag['borderposition']['xmax'];
20239
			}
20240
			$w = ($tag['borderposition']['x'] - $xmax);
20241
		} else {
20242
			if ($xmax <= $tag['borderposition']['x']) {
20243
				$xmax = $tag['borderposition']['xmax'];
20244
			}
20245
			$w = ($xmax - $tag['borderposition']['x']);
20246
		}
20247
		if ($w <= 0) {
20248
			return;
20249
		}
20250
		$w += $cellspacing['H'];
20251
		$startpage = $tag['borderposition']['page'];
20252
		$startcolumn = $tag['borderposition']['column'];
20253
		$x = $tag['borderposition']['x'];
20254
		$y = $tag['borderposition']['y'];
20255
		$endpage = $this->page;
20256
		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
20257
		$currentY = $this->y;
20258
		$this->x = $x;
20259
		// get latest column
20260
		$endcolumn = $this->current_column;
20261
		if ($this->num_columns == 0) {
20262
			$this->num_columns = 1;
20263
		}
20264
		// get border modes
20265
		$border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20266
		$border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20267
		$border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20268
		// temporary disable page regions
20269
		$temp_page_regions = $this->page_regions;
20270
		$this->page_regions = array();
20271
		// design borders around HTML cells.
20272
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20273
			$ccode = '';
20274
			$this->setPage($page);
20275
			if ($this->num_columns < 2) {
20276
				// single-column mode
20277
				$this->x = $x;
20278
				$this->y = $this->tMargin;
20279
			}
20280
			// account for margin changes
20281
			if ($page > $startpage) {
20282
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20283
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20284
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20285
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20286
				}
20287
			}
20288
			if ($startpage == $endpage) {
20289
				// single page
20290
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20291
					$this->selectColumn($column);
20292
					if ($startcolumn == $endcolumn) { // single column
20293
						$cborder = $border;
20294
						$h = ($currentY - $y) + $cellspacing['V'];
20295
						$this->y = $starty;
20296
					} elseif ($column == $startcolumn) { // first column
20297
						$cborder = $border_start;
20298
						$this->y = $starty;
20299
						$h = $this->h - $this->y - $this->bMargin;
20300
					} elseif ($column == $endcolumn) { // end column
20301
						$cborder = $border_end;
20302
						$h = $currentY - $this->y;
20303
					} else { // middle column
20304
						$cborder = $border_middle;
20305
						$h = $this->h - $this->y - $this->bMargin;
20306
					}
20307
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20308
				} // end for each column
20309
			} elseif ($page == $startpage) { // first page
20310
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20311
					$this->selectColumn($column);
20312
					if ($column == $startcolumn) { // first column
20313
						$cborder = $border_start;
20314
						$this->y = $starty;
20315
						$h = $this->h - $this->y - $this->bMargin;
20316
					} else { // middle column
20317
						$cborder = $border_middle;
20318
						$h = $this->h - $this->y - $this->bMargin;
20319
					}
20320
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20321
				} // end for each column
20322
			} elseif ($page == $endpage) { // last page
20323
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20324
					$this->selectColumn($column);
20325
					if ($column == $endcolumn) {
20326
						// end column
20327
						$cborder = $border_end;
20328
						$h = $currentY - $this->y;
20329
					} else {
20330
						// middle column
20331
						$cborder = $border_middle;
20332
						$h = $this->h - $this->y - $this->bMargin;
20333
					}
20334
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20335
				} // end for each column
20336
			} else { // middle page
20337
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20338
					$this->selectColumn($column);
20339
					$cborder = $border_middle;
20340
					$h = $this->h - $this->y - $this->bMargin;
20341
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20342
				} // end for each column
20343
			}
20344
			if ($cborder OR $fill) {
20345
				$offsetlen = strlen($ccode);
20346
				// draw border and fill
20347
				if ($this->inxobj) {
20348
					// we are inside an XObject template
20349
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20350
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20351
						$pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20352
						$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20353
					} else {
20354
						$pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20355
						$this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20356
					}
20357
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20358
					$pstart = substr($pagebuff, 0, $pagemark);
20359
					$pend = substr($pagebuff, $pagemark);
20360
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20361
				} else {
20362
					if (end($this->transfmrk[$this->page]) !== false) {
20363
						$pagemarkkey = key($this->transfmrk[$this->page]);
20364
						$pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20365
					} elseif ($this->InFooter) {
20366
						$pagemark = $this->footerpos[$this->page];
20367
					} else {
20368
						$pagemark = $this->intmrk[$this->page];
20369
					}
20370
					$pagebuff = $this->getPageBuffer($this->page);
20371
					$pstart = substr($pagebuff, 0, $pagemark);
20372
					$pend = substr($pagebuff, $pagemark);
20373
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20374
					$this->bordermrk[$this->page] += $offsetlen;
20375
					$this->cntmrk[$this->page] += $offsetlen;
20376
				}
20377
			}
20378
		} // end for each page
20379
		// restore page regions
20380
		$this->page_regions = $temp_page_regions;
20381
		if (isset($old_bgcolor)) {
20382
			// restore background color
20383
			$this->setFillColorArray($old_bgcolor);
20384
		}
20385
		// restore pointer position
20386
		$this->x = $prev_x;
20387
		$this->y = $prev_y;
20388
		$this->lasth = $prev_lasth;
20389
	}
20390
 
20391
	/**
20392
	 * Set the default bullet to be used as LI bullet symbol
20393
	 * @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')
20394
	 * @public
20395
	 * @since 4.0.028 (2008-09-26)
20396
	 */
20397
	public function setLIsymbol($symbol='!') {
20398
		// check for custom image symbol
20399
		if (substr($symbol, 0, 4) == 'img|') {
20400
			$this->lisymbol = $symbol;
20401
			return;
20402
		}
20403
		$symbol = strtolower($symbol);
20404
		$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');
20405
		if (in_array($symbol, $valid_symbols)) {
20406
			$this->lisymbol = $symbol;
20407
		} else {
20408
			$this->lisymbol = '';
20409
		}
20410
	}
20411
 
20412
	/**
20413
	 * Set the booklet mode for double-sided pages.
20414
	 * @param boolean $booklet true set the booklet mode on, false otherwise.
20415
	 * @param float $inner Inner page margin.
20416
	 * @param float $outer Outer page margin.
20417
	 * @public
20418
	 * @since 4.2.000 (2008-10-29)
20419
	 */
20420
	public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20421
		$this->booklet = $booklet;
20422
		if ($inner >= 0) {
20423
			$this->lMargin = $inner;
20424
		}
20425
		if ($outer >= 0) {
20426
			$this->rMargin = $outer;
20427
		}
20428
	}
20429
 
20430
	/**
20431
	 * Swap the left and right margins.
20432
	 * @param boolean $reverse if true swap left and right margins.
20433
	 * @protected
20434
	 * @since 4.2.000 (2008-10-29)
20435
	 */
20436
	protected function swapMargins($reverse=true) {
20437
		if ($reverse) {
20438
			// swap left and right margins
20439
			$mtemp = $this->original_lMargin;
20440
			$this->original_lMargin = $this->original_rMargin;
20441
			$this->original_rMargin = $mtemp;
20442
			$deltam = $this->original_lMargin - $this->original_rMargin;
20443
			$this->lMargin += $deltam;
20444
			$this->rMargin -= $deltam;
20445
		}
20446
	}
20447
 
20448
	/**
20449
	 * Set the vertical spaces for HTML tags.
20450
	 * The array must have the following structure (example):
20451
	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20452
	 * The first array level contains the tag names,
20453
	 * the second level contains 0 for opening tags or 1 for closing tags,
20454
	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20455
	 * If the h parameter is not specified, default values are used.
20456
	 * @param array $tagvs array of tags and relative vertical spaces.
20457
	 * @public
20458
	 * @since 4.2.001 (2008-10-30)
20459
	 */
20460
	public function setHtmlVSpace($tagvs) {
20461
		$this->tagvspaces = $tagvs;
20462
	}
20463
 
20464
	/**
20465
	 * Set custom width for list indentation.
20466
	 * @param float $width width of the indentation. Use negative value to disable it.
20467
	 * @public
20468
	 * @since 4.2.007 (2008-11-12)
20469
	 */
20470
	public function setListIndentWidth($width) {
20471
		return $this->customlistindent = floatval($width);
20472
	}
20473
 
20474
	/**
20475
	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20476
	 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20477
	 * @public
20478
	 * @since 4.2.010 (2008-11-14)
20479
	 */
20480
	public function setOpenCell($isopen) {
20481
		$this->opencell = $isopen;
20482
	}
20483
 
20484
	/**
20485
	 * Set the color and font style for HTML links.
20486
	 * @param array $color RGB array of colors
20487
	 * @param string $fontstyle additional font styles to add
20488
	 * @public
20489
	 * @since 4.4.003 (2008-12-09)
20490
	 */
20491
	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20492
		$this->htmlLinkColorArray = $color;
20493
		$this->htmlLinkFontStyle = $fontstyle;
20494
	}
20495
 
20496
	/**
20497
	 * Convert HTML string containing value and unit of measure to user's units or points.
20498
	 * @param string $htmlval String containing values and unit.
20499
	 * @param string $refsize Reference value in points.
20500
	 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20501
	 * @param boolean $points If true returns points, otherwise returns value in user's units.
20502
	 * @return float value in user's unit or point if $points=true
20503
	 * @public
20504
	 * @since 4.4.004 (2008-12-10)
20505
	 */
20506
	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20507
		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20508
		$retval = 0;
20509
		$value = 0;
20510
		$unit = 'px';
20511
		if ($points) {
20512
			$k = 1;
20513
		} else {
20514
			$k = $this->k;
20515
		}
20516
		if (in_array($defaultunit, $supportedunits)) {
20517
			$unit = $defaultunit;
20518
		}
20519
		if (is_numeric($htmlval)) {
20520
			$value = floatval($htmlval);
20521
		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20522
			$value = floatval($mnum[1]);
20523
			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20524
				if (in_array($munit[1], $supportedunits)) {
20525
					$unit = $munit[1];
20526
				}
20527
			}
20528
		}
20529
		switch ($unit) {
20530
			// percentage
20531
			case '%': {
20532
				$retval = (($value * $refsize) / 100);
20533
				break;
20534
			}
20535
			// relative-size
20536
			case 'em': {
20537
				$retval = ($value * $refsize);
20538
				break;
20539
			}
20540
			// height of lower case 'x' (about half the font-size)
20541
			case 'ex': {
20542
				$retval = ($value * ($refsize / 2));
20543
				break;
20544
			}
20545
			// absolute-size
20546
			case 'in': {
20547
				$retval = (($value * $this->dpi) / $k);
20548
				break;
20549
			}
20550
			// centimeters
20551
			case 'cm': {
20552
				$retval = (($value / 2.54 * $this->dpi) / $k);
20553
				break;
20554
			}
20555
			// millimeters
20556
			case 'mm': {
20557
				$retval = (($value / 25.4 * $this->dpi) / $k);
20558
				break;
20559
			}
20560
			// one pica is 12 points
20561
			case 'pc': {
20562
				$retval = (($value * 12) / $k);
20563
				break;
20564
			}
20565
			// points
20566
			case 'pt': {
20567
				$retval = ($value / $k);
20568
				break;
20569
			}
20570
			// pixels
20571
			case 'px': {
20572
				$retval = $this->pixelsToUnits($value);
20573
				if ($points) {
20574
					$retval *= $this->k;
20575
				}
20576
				break;
20577
			}
20578
		}
20579
		return $retval;
20580
	}
20581
 
20582
	/**
20583
	 * Output an HTML list bullet or ordered item symbol
20584
	 * @param int $listdepth list nesting level
20585
	 * @param string $listtype type of list
20586
	 * @param float $size current font size
20587
	 * @protected
20588
	 * @since 4.4.004 (2008-12-10)
20589
	 */
20590
	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20591
		if ($this->state != 2) {
20592
			return;
20593
		}
20594
		$size /= $this->k;
20595
		$fill = '';
20596
		$bgcolor = $this->bgcolor;
20597
		$color = $this->fgcolor;
20598
		$strokecolor = $this->strokecolor;
20599
		$width = 0;
20600
		$textitem = '';
20601
		$tmpx = $this->x;
20602
		$lspace = $this->GetStringWidth('  ');
20603
		if ($listtype == '^') {
20604
			// special symbol used for avoid justification of rect bullet
20605
			$this->lispacer = '';
20606
			return;
20607
		} elseif ($listtype == '!') {
20608
			// set default list type for unordered list
20609
			$deftypes = array('disc', 'circle', 'square');
20610
			$listtype = $deftypes[($listdepth - 1) % 3];
20611
		} elseif ($listtype == '#') {
20612
			// set default list type for ordered list
20613
			$listtype = 'decimal';
20614
		} elseif (substr($listtype, 0, 4) == 'img|') {
20615
			// custom image type ('img|type|width|height|image.ext')
20616
			$img = explode('|', $listtype);
20617
			$listtype = 'img';
20618
		}
20619
		switch ($listtype) {
20620
			// unordered types
20621
			case 'none': {
20622
				break;
20623
			}
20624
			case 'disc': {
20625
				$r = $size / 6;
20626
				$lspace += (2 * $r);
20627
				if ($this->rtl) {
20628
					$this->x += $lspace;
20629
				} else {
20630
					$this->x -= $lspace;
20631
				}
20632
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20633
				break;
20634
			}
20635
			case 'circle': {
20636
				$r = $size / 6;
20637
				$lspace += (2 * $r);
20638
				if ($this->rtl) {
20639
					$this->x += $lspace;
20640
				} else {
20641
					$this->x -= $lspace;
20642
				}
20643
				$prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20644
				$new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20645
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20646
				$this->_out($prev_line_style); // restore line settings
20647
				break;
20648
			}
20649
			case 'square': {
20650
				$l = $size / 3;
20651
				$lspace += $l;
20652
				if ($this->rtl) {;
20653
					$this->x += $lspace;
20654
				} else {
20655
					$this->x -= $lspace;
20656
				}
20657
				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20658
				break;
20659
			}
20660
			case 'img': {
20661
				// 1=>type, 2=>width, 3=>height, 4=>image.ext
20662
				$lspace += $img[2];
20663
				if ($this->rtl) {;
20664
					$this->x += $lspace;
20665
				} else {
20666
					$this->x -= $lspace;
20667
				}
20668
				$imgtype = strtolower($img[1]);
20669
				$prev_y = $this->y;
20670
				switch ($imgtype) {
20671
					case 'svg': {
20672
						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20673
						break;
20674
					}
20675
					case 'ai':
20676
					case 'eps': {
20677
						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20678
						break;
20679
					}
20680
					default: {
20681
						$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);
20682
						break;
20683
					}
20684
				}
20685
				$this->y = $prev_y;
20686
				break;
20687
			}
20688
			// ordered types
20689
			// $this->listcount[$this->listnum];
20690
			// $textitem
20691
			case '1':
20692
			case 'decimal': {
20693
				$textitem = $this->listcount[$this->listnum];
20694
				break;
20695
			}
20696
			case 'decimal-leading-zero': {
20697
				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20698
				break;
20699
			}
20700
			case 'i':
20701
			case 'lower-roman': {
20702
				$textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20703
				break;
20704
			}
20705
			case 'I':
20706
			case 'upper-roman': {
20707
				$textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20708
				break;
20709
			}
20710
			case 'a':
20711
			case 'lower-alpha':
20712
			case 'lower-latin': {
20713
				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20714
				break;
20715
			}
20716
			case 'A':
20717
			case 'upper-alpha':
20718
			case 'upper-latin': {
20719
				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20720
				break;
20721
			}
20722
			case 'lower-greek': {
20723
				$textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20724
				break;
20725
			}
20726
			/*
20727
			// Types to be implemented (special handling)
20728
			case 'hebrew': {
20729
				break;
20730
			}
20731
			case 'armenian': {
20732
				break;
20733
			}
20734
			case 'georgian': {
20735
				break;
20736
			}
20737
			case 'cjk-ideographic': {
20738
				break;
20739
			}
20740
			case 'hiragana': {
20741
				break;
20742
			}
20743
			case 'katakana': {
20744
				break;
20745
			}
20746
			case 'hiragana-iroha': {
20747
				break;
20748
			}
20749
			case 'katakana-iroha': {
20750
				break;
20751
			}
20752
			*/
20753
			default: {
20754
				$textitem = $this->listcount[$this->listnum];
20755
			}
20756
		}
20757
		if (!TCPDF_STATIC::empty_string($textitem)) {
20758
			// Check whether we need a new page or new column
20759
			$prev_y = $this->y;
20760
			$h = $this->getCellHeight($this->FontSize);
20761
			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20762
				$tmpx = $this->x;
20763
			}
20764
			// print ordered item
20765
			if ($this->rtl) {
20766
				$textitem = '.'.$textitem;
20767
			} else {
20768
				$textitem = $textitem.'.';
20769
			}
20770
			$lspace += $this->GetStringWidth($textitem);
20771
			if ($this->rtl) {
20772
				$this->x += $lspace;
20773
			} else {
20774
				$this->x -= $lspace;
20775
			}
20776
			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20777
		}
20778
		$this->x = $tmpx;
20779
		$this->lispacer = '^';
20780
		// restore colors
20781
		$this->setFillColorArray($bgcolor);
20782
		$this->setDrawColorArray($strokecolor);
20783
		$this->settextColorArray($color);
20784
	}
20785
 
20786
	/**
20787
	 * Returns current graphic variables as array.
20788
	 * @return array of graphic variables
20789
	 * @protected
20790
	 * @since 4.2.010 (2008-11-14)
20791
	 */
20792
	protected function getGraphicVars() {
20793
		$grapvars = array(
20794
			'FontFamily' => $this->FontFamily,
20795
			'FontStyle' => $this->FontStyle,
20796
			'FontSizePt' => $this->FontSizePt,
20797
			'rMargin' => $this->rMargin,
20798
			'lMargin' => $this->lMargin,
20799
			'cell_padding' => $this->cell_padding,
20800
			'cell_margin' => $this->cell_margin,
20801
			'LineWidth' => $this->LineWidth,
20802
			'linestyleWidth' => $this->linestyleWidth,
20803
			'linestyleCap' => $this->linestyleCap,
20804
			'linestyleJoin' => $this->linestyleJoin,
20805
			'linestyleDash' => $this->linestyleDash,
20806
			'textrendermode' => $this->textrendermode,
20807
			'textstrokewidth' => $this->textstrokewidth,
20808
			'DrawColor' => $this->DrawColor,
20809
			'FillColor' => $this->FillColor,
20810
			'TextColor' => $this->TextColor,
20811
			'ColorFlag' => $this->ColorFlag,
20812
			'bgcolor' => $this->bgcolor,
20813
			'fgcolor' => $this->fgcolor,
20814
			'htmlvspace' => $this->htmlvspace,
20815
			'listindent' => $this->listindent,
20816
			'listindentlevel' => $this->listindentlevel,
20817
			'listnum' => $this->listnum,
20818
			'listordered' => $this->listordered,
20819
			'listcount' => $this->listcount,
20820
			'lispacer' => $this->lispacer,
20821
			'cell_height_ratio' => $this->cell_height_ratio,
20822
			'font_stretching' => $this->font_stretching,
20823
			'font_spacing' => $this->font_spacing,
20824
			'alpha' => $this->alpha,
20825
			// extended
20826
			'lasth' => $this->lasth,
20827
			'tMargin' => $this->tMargin,
20828
			'bMargin' => $this->bMargin,
20829
			'AutoPageBreak' => $this->AutoPageBreak,
20830
			'PageBreakTrigger' => $this->PageBreakTrigger,
20831
			'x' => $this->x,
20832
			'y' => $this->y,
20833
			'w' => $this->w,
20834
			'h' => $this->h,
20835
			'wPt' => $this->wPt,
20836
			'hPt' => $this->hPt,
20837
			'fwPt' => $this->fwPt,
20838
			'fhPt' => $this->fhPt,
20839
			'page' => $this->page,
20840
			'current_column' => $this->current_column,
20841
			'num_columns' => $this->num_columns
20842
			);
20843
		return $grapvars;
20844
	}
20845
 
20846
	/**
20847
	 * Set graphic variables.
20848
	 * @param array $gvars array of graphic variablesto restore
20849
	 * @param boolean $extended if true restore extended graphic variables
20850
	 * @protected
20851
	 * @since 4.2.010 (2008-11-14)
20852
	 */
20853
	protected function setGraphicVars($gvars, $extended=false) {
20854
		if ($this->state != 2) {
20855
			 return;
20856
		}
20857
		$this->FontFamily = $gvars['FontFamily'];
20858
		$this->FontStyle = $gvars['FontStyle'];
20859
		$this->FontSizePt = $gvars['FontSizePt'];
20860
		$this->rMargin = $gvars['rMargin'];
20861
		$this->lMargin = $gvars['lMargin'];
20862
		$this->cell_padding = $gvars['cell_padding'];
20863
		$this->cell_margin = $gvars['cell_margin'];
20864
		$this->LineWidth = $gvars['LineWidth'];
20865
		$this->linestyleWidth = $gvars['linestyleWidth'];
20866
		$this->linestyleCap = $gvars['linestyleCap'];
20867
		$this->linestyleJoin = $gvars['linestyleJoin'];
20868
		$this->linestyleDash = $gvars['linestyleDash'];
20869
		$this->textrendermode = $gvars['textrendermode'];
20870
		$this->textstrokewidth = $gvars['textstrokewidth'];
20871
		$this->DrawColor = $gvars['DrawColor'];
20872
		$this->FillColor = $gvars['FillColor'];
20873
		$this->TextColor = $gvars['TextColor'];
20874
		$this->ColorFlag = $gvars['ColorFlag'];
20875
		$this->bgcolor = $gvars['bgcolor'];
20876
		$this->fgcolor = $gvars['fgcolor'];
20877
		$this->htmlvspace = $gvars['htmlvspace'];
20878
		$this->listindent = $gvars['listindent'];
20879
		$this->listindentlevel = $gvars['listindentlevel'];
20880
		$this->listnum = $gvars['listnum'];
20881
		$this->listordered = $gvars['listordered'];
20882
		$this->listcount = $gvars['listcount'];
20883
		$this->lispacer = $gvars['lispacer'];
20884
		$this->cell_height_ratio = $gvars['cell_height_ratio'];
20885
		$this->font_stretching = $gvars['font_stretching'];
20886
		$this->font_spacing = $gvars['font_spacing'];
20887
		$this->alpha = $gvars['alpha'];
20888
		if ($extended) {
20889
			// restore extended values
20890
			$this->lasth = $gvars['lasth'];
20891
			$this->tMargin = $gvars['tMargin'];
20892
			$this->bMargin = $gvars['bMargin'];
20893
			$this->AutoPageBreak = $gvars['AutoPageBreak'];
20894
			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20895
			$this->x = $gvars['x'];
20896
			$this->y = $gvars['y'];
20897
			$this->w = $gvars['w'];
20898
			$this->h = $gvars['h'];
20899
			$this->wPt = $gvars['wPt'];
20900
			$this->hPt = $gvars['hPt'];
20901
			$this->fwPt = $gvars['fwPt'];
20902
			$this->fhPt = $gvars['fhPt'];
20903
			$this->page = $gvars['page'];
20904
			$this->current_column = $gvars['current_column'];
20905
			$this->num_columns = $gvars['num_columns'];
20906
		}
20907
		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20908
		if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20909
			$this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20910
		}
20911
	}
20912
 
20913
	/**
20914
	 * Outputs the "save graphics state" operator 'q'
20915
	 * @protected
20916
	 */
20917
	protected function _outSaveGraphicsState() {
20918
		$this->_out('q');
20919
	}
20920
 
20921
	/**
20922
	 * Outputs the "restore graphics state" operator 'Q'
20923
	 * @protected
20924
	 */
20925
	protected function _outRestoreGraphicsState() {
20926
		$this->_out('Q');
20927
	}
20928
 
20929
	/**
20930
	 * Set buffer content (always append data).
20931
	 * @param string $data data
20932
	 * @protected
20933
	 * @since 4.5.000 (2009-01-02)
20934
	 */
20935
	protected function setBuffer($data) {
20936
		$this->bufferlen += strlen($data);
20937
		$this->buffer .= $data;
20938
	}
20939
 
20940
	/**
20941
	 * Replace the buffer content
20942
	 * @param string $data data
20943
	 * @protected
20944
	 * @since 5.5.000 (2010-06-22)
20945
	 */
20946
	protected function replaceBuffer($data) {
20947
		$this->bufferlen = strlen($data);
20948
		$this->buffer = $data;
20949
	}
20950
 
20951
	/**
20952
	 * Get buffer content.
20953
	 * @return string buffer content
20954
	 * @protected
20955
	 * @since 4.5.000 (2009-01-02)
20956
	 */
20957
	protected function getBuffer() {
20958
		return $this->buffer;
20959
	}
20960
 
20961
	/**
20962
	 * Set page buffer content.
20963
	 * @param int $page page number
20964
	 * @param string $data page data
20965
	 * @param boolean $append if true append data, false replace.
20966
	 * @protected
20967
	 * @since 4.5.000 (2008-12-31)
20968
	 */
20969
	protected function setPageBuffer($page, $data, $append=false) {
20970
		if ($append) {
20971
			$this->pages[$page] .= $data;
20972
		} else {
20973
			$this->pages[$page] = $data;
20974
		}
20975
		if ($append AND isset($this->pagelen[$page])) {
20976
			$this->pagelen[$page] += strlen($data);
20977
		} else {
20978
			$this->pagelen[$page] = strlen($data);
20979
		}
20980
	}
20981
 
20982
	/**
20983
	 * Get page buffer content.
20984
	 * @param int $page page number
20985
	 * @return string page buffer content or false in case of error
20986
	 * @protected
20987
	 * @since 4.5.000 (2008-12-31)
20988
	 */
20989
	protected function getPageBuffer($page) {
20990
		if (isset($this->pages[$page])) {
20991
			return $this->pages[$page];
20992
		}
20993
		return false;
20994
	}
20995
 
20996
	/**
20997
	 * Set image buffer content.
20998
	 * @param string $image image key
20999
	 * @param array $data image data
21000
	 * @return int image index number
21001
	 * @protected
21002
	 * @since 4.5.000 (2008-12-31)
21003
	 */
21004
	protected function setImageBuffer($image, $data) {
21005
		if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
21006
			$this->imagekeys[$this->numimages] = $image;
21007
			$data['i'] = $this->numimages;
21008
			++$this->numimages;
21009
		}
21010
		$this->images[$image] = $data;
21011
		return $data['i'];
21012
	}
21013
 
21014
	/**
21015
	 * Set image buffer content for a specified sub-key.
21016
	 * @param string $image image key
21017
	 * @param string $key image sub-key
21018
	 * @param array $data image data
21019
	 * @protected
21020
	 * @since 4.5.000 (2008-12-31)
21021
	 */
21022
	protected function setImageSubBuffer($image, $key, $data) {
21023
		if (!isset($this->images[$image])) {
21024
			$this->setImageBuffer($image, array());
21025
		}
21026
		$this->images[$image][$key] = $data;
21027
	}
21028
 
21029
	/**
21030
	 * Get image buffer content.
21031
	 * @param string $image image key
21032
	 * @return string|false image buffer content or false in case of error
21033
	 * @protected
21034
	 * @since 4.5.000 (2008-12-31)
21035
	 */
21036
	protected function getImageBuffer($image) {
21037
		if (isset($this->images[$image])) {
21038
			return $this->images[$image];
21039
		}
21040
		return false;
21041
	}
21042
 
21043
	/**
21044
	 * Set font buffer content.
21045
	 * @param string $font font key
21046
	 * @param array $data font data
21047
	 * @protected
21048
	 * @since 4.5.000 (2009-01-02)
21049
	 */
21050
	protected function setFontBuffer($font, $data) {
21051
		$this->fonts[$font] = $data;
21052
		if (!in_array($font, $this->fontkeys)) {
21053
			$this->fontkeys[] = $font;
21054
			// store object ID for current font
21055
			++$this->n;
21056
			$this->font_obj_ids[$font] = $this->n;
21057
			$this->setFontSubBuffer($font, 'n', $this->n);
21058
		}
21059
	}
21060
 
21061
	/**
21062
	 * Set font buffer content.
21063
	 * @param string $font font key
21064
	 * @param string $key font sub-key
21065
	 * @param mixed $data font data
21066
	 * @protected
21067
	 * @since 4.5.000 (2009-01-02)
21068
	 */
21069
	protected function setFontSubBuffer($font, $key, $data) {
21070
		if (!isset($this->fonts[$font])) {
21071
			$this->setFontBuffer($font, array());
21072
		}
21073
		$this->fonts[$font][$key] = $data;
21074
	}
21075
 
21076
	/**
21077
	 * Get font buffer content.
21078
	 * @param string $font font key
21079
	 * @return string|false font buffer content or false in case of error
21080
	 * @protected
21081
	 * @since 4.5.000 (2009-01-02)
21082
	 */
21083
	protected function getFontBuffer($font) {
21084
		if (isset($this->fonts[$font])) {
21085
			return $this->fonts[$font];
21086
		}
21087
		return false;
21088
	}
21089
 
21090
	/**
21091
	 * Move a page to a previous position.
21092
	 * @param int $frompage number of the source page
21093
	 * @param int $topage number of the destination page (must be less than $frompage)
21094
	 * @return bool true in case of success, false in case of error.
21095
	 * @public
21096
	 * @since 4.5.000 (2009-01-02)
21097
	 */
21098
	public function movePage($frompage, $topage) {
21099
		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21100
			return false;
21101
		}
21102
		if ($frompage == $this->page) {
21103
			// close the page before moving it
21104
			$this->endPage();
21105
		}
21106
		// move all page-related states
21107
		$tmppage = $this->getPageBuffer($frompage);
21108
		$tmppagedim = $this->pagedim[$frompage];
21109
		$tmppagelen = $this->pagelen[$frompage];
21110
		$tmpintmrk = $this->intmrk[$frompage];
21111
		$tmpbordermrk = $this->bordermrk[$frompage];
21112
		$tmpcntmrk = $this->cntmrk[$frompage];
21113
		$tmppageobjects = $this->pageobjects[$frompage];
21114
		if (isset($this->footerpos[$frompage])) {
21115
			$tmpfooterpos = $this->footerpos[$frompage];
21116
		}
21117
		if (isset($this->footerlen[$frompage])) {
21118
			$tmpfooterlen = $this->footerlen[$frompage];
21119
		}
21120
		if (isset($this->transfmrk[$frompage])) {
21121
			$tmptransfmrk = $this->transfmrk[$frompage];
21122
		}
21123
		if (isset($this->PageAnnots[$frompage])) {
21124
			$tmpannots = $this->PageAnnots[$frompage];
21125
		}
21126
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21127
			for ($i = $frompage; $i > $topage; --$i) {
21128
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21129
					--$this->pagegroups[$this->newpagegroup[$i]];
21130
					break;
21131
				}
21132
			}
21133
			for ($i = $topage; $i > 0; --$i) {
21134
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21135
					++$this->pagegroups[$this->newpagegroup[$i]];
21136
					break;
21137
				}
21138
			}
21139
		}
21140
		for ($i = $frompage; $i > $topage; --$i) {
21141
			$j = $i - 1;
21142
			// shift pages down
21143
			$this->setPageBuffer($i, $this->getPageBuffer($j));
21144
			$this->pagedim[$i] = $this->pagedim[$j];
21145
			$this->pagelen[$i] = $this->pagelen[$j];
21146
			$this->intmrk[$i] = $this->intmrk[$j];
21147
			$this->bordermrk[$i] = $this->bordermrk[$j];
21148
			$this->cntmrk[$i] = $this->cntmrk[$j];
21149
			$this->pageobjects[$i] = $this->pageobjects[$j];
21150
			if (isset($this->footerpos[$j])) {
21151
				$this->footerpos[$i] = $this->footerpos[$j];
21152
			} elseif (isset($this->footerpos[$i])) {
21153
				unset($this->footerpos[$i]);
21154
			}
21155
			if (isset($this->footerlen[$j])) {
21156
				$this->footerlen[$i] = $this->footerlen[$j];
21157
			} elseif (isset($this->footerlen[$i])) {
21158
				unset($this->footerlen[$i]);
21159
			}
21160
			if (isset($this->transfmrk[$j])) {
21161
				$this->transfmrk[$i] = $this->transfmrk[$j];
21162
			} elseif (isset($this->transfmrk[$i])) {
21163
				unset($this->transfmrk[$i]);
21164
			}
21165
			if (isset($this->PageAnnots[$j])) {
21166
				$this->PageAnnots[$i] = $this->PageAnnots[$j];
21167
			} elseif (isset($this->PageAnnots[$i])) {
21168
				unset($this->PageAnnots[$i]);
21169
			}
21170
			if (isset($this->newpagegroup[$j])) {
21171
				$this->newpagegroup[$i] = $this->newpagegroup[$j];
21172
				unset($this->newpagegroup[$j]);
21173
			}
21174
			if ($this->currpagegroup == $j) {
21175
				$this->currpagegroup = $i;
21176
			}
21177
		}
21178
		$this->setPageBuffer($topage, $tmppage);
21179
		$this->pagedim[$topage] = $tmppagedim;
21180
		$this->pagelen[$topage] = $tmppagelen;
21181
		$this->intmrk[$topage] = $tmpintmrk;
21182
		$this->bordermrk[$topage] = $tmpbordermrk;
21183
		$this->cntmrk[$topage] = $tmpcntmrk;
21184
		$this->pageobjects[$topage] = $tmppageobjects;
21185
		if (isset($tmpfooterpos)) {
21186
			$this->footerpos[$topage] = $tmpfooterpos;
21187
		} elseif (isset($this->footerpos[$topage])) {
21188
			unset($this->footerpos[$topage]);
21189
		}
21190
		if (isset($tmpfooterlen)) {
21191
			$this->footerlen[$topage] = $tmpfooterlen;
21192
		} elseif (isset($this->footerlen[$topage])) {
21193
			unset($this->footerlen[$topage]);
21194
		}
21195
		if (isset($tmptransfmrk)) {
21196
			$this->transfmrk[$topage] = $tmptransfmrk;
21197
		} elseif (isset($this->transfmrk[$topage])) {
21198
			unset($this->transfmrk[$topage]);
21199
		}
21200
		if (isset($tmpannots)) {
21201
			$this->PageAnnots[$topage] = $tmpannots;
21202
		} elseif (isset($this->PageAnnots[$topage])) {
21203
			unset($this->PageAnnots[$topage]);
21204
		}
21205
		// adjust outlines
21206
		$tmpoutlines = $this->outlines;
21207
		foreach ($tmpoutlines as $key => $outline) {
21208
			if (!$outline['f']) {
21209
				if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21210
					$this->outlines[$key]['p'] = ($outline['p'] + 1);
21211
				} elseif ($outline['p'] == $frompage) {
21212
					$this->outlines[$key]['p'] = $topage;
21213
				}
21214
			}
21215
		}
21216
		// adjust dests
21217
		$tmpdests = $this->dests;
21218
		foreach ($tmpdests as $key => $dest) {
21219
			if (!$dest['f']) {
21220
				if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21221
					$this->dests[$key]['p'] = ($dest['p'] + 1);
21222
				} elseif ($dest['p'] == $frompage) {
21223
					$this->dests[$key]['p'] = $topage;
21224
				}
21225
			}
21226
		}
21227
		// adjust links
21228
		$tmplinks = $this->links;
21229
		foreach ($tmplinks as $key => $link) {
21230
			if (!$link['f']) {
21231
				if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21232
					$this->links[$key]['p'] = ($link['p'] + 1);
21233
				} elseif ($link['p'] == $frompage) {
21234
					$this->links[$key]['p'] = $topage;
21235
				}
21236
			}
21237
		}
21238
		// adjust javascript
21239
		$jfrompage = $frompage;
21240
		$jtopage = $topage;
21241
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21242
			foreach($pamatch[0] as $pk => $pmatch) {
21243
				$pagenum = intval($pamatch[3][$pk]) + 1;
21244
				if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21245
					$newpage = ($pagenum + 1);
21246
				} elseif ($pagenum == $jfrompage) {
21247
					$newpage = $jtopage;
21248
				} else {
21249
					$newpage = $pagenum;
21250
				}
21251
				--$newpage;
21252
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21253
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21254
			}
21255
			unset($pamatch);
21256
		}
21257
		// return to last page
21258
		$this->lastPage(true);
21259
		return true;
21260
	}
21261
 
21262
	/**
21263
	 * Remove the specified page.
21264
	 * @param int $page page to remove
21265
	 * @return bool true in case of success, false in case of error.
21266
	 * @public
21267
	 * @since 4.6.004 (2009-04-23)
21268
	 */
21269
	public function deletePage($page) {
21270
		if (($page < 1) OR ($page > $this->numpages)) {
21271
			return false;
21272
		}
21273
		// delete current page
21274
		unset($this->pages[$page]);
21275
		unset($this->pagedim[$page]);
21276
		unset($this->pagelen[$page]);
21277
		unset($this->intmrk[$page]);
21278
		unset($this->bordermrk[$page]);
21279
		unset($this->cntmrk[$page]);
21280
		foreach ($this->pageobjects[$page] as $oid) {
21281
			if (isset($this->offsets[$oid])){
21282
				unset($this->offsets[$oid]);
21283
			}
21284
		}
21285
		unset($this->pageobjects[$page]);
21286
		if (isset($this->footerpos[$page])) {
21287
			unset($this->footerpos[$page]);
21288
		}
21289
		if (isset($this->footerlen[$page])) {
21290
			unset($this->footerlen[$page]);
21291
		}
21292
		if (isset($this->transfmrk[$page])) {
21293
			unset($this->transfmrk[$page]);
21294
		}
21295
		if (isset($this->PageAnnots[$page])) {
21296
			unset($this->PageAnnots[$page]);
21297
		}
21298
		if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21299
			for ($i = $page; $i > 0; --$i) {
21300
				if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21301
					--$this->pagegroups[$this->newpagegroup[$i]];
21302
					break;
21303
				}
21304
			}
21305
		}
21306
		if (isset($this->pageopen[$page])) {
21307
			unset($this->pageopen[$page]);
21308
		}
21309
		if ($page < $this->numpages) {
21310
			// update remaining pages
21311
			for ($i = $page; $i < $this->numpages; ++$i) {
21312
				$j = $i + 1;
21313
				// shift pages
21314
				$this->setPageBuffer($i, $this->getPageBuffer($j));
21315
				$this->pagedim[$i] = $this->pagedim[$j];
21316
				$this->pagelen[$i] = $this->pagelen[$j];
21317
				$this->intmrk[$i] = $this->intmrk[$j];
21318
				$this->bordermrk[$i] = $this->bordermrk[$j];
21319
				$this->cntmrk[$i] = $this->cntmrk[$j];
21320
				$this->pageobjects[$i] = $this->pageobjects[$j];
21321
				if (isset($this->footerpos[$j])) {
21322
					$this->footerpos[$i] = $this->footerpos[$j];
21323
				} elseif (isset($this->footerpos[$i])) {
21324
					unset($this->footerpos[$i]);
21325
				}
21326
				if (isset($this->footerlen[$j])) {
21327
					$this->footerlen[$i] = $this->footerlen[$j];
21328
				} elseif (isset($this->footerlen[$i])) {
21329
					unset($this->footerlen[$i]);
21330
				}
21331
				if (isset($this->transfmrk[$j])) {
21332
					$this->transfmrk[$i] = $this->transfmrk[$j];
21333
				} elseif (isset($this->transfmrk[$i])) {
21334
					unset($this->transfmrk[$i]);
21335
				}
21336
				if (isset($this->PageAnnots[$j])) {
21337
					$this->PageAnnots[$i] = $this->PageAnnots[$j];
21338
				} elseif (isset($this->PageAnnots[$i])) {
21339
					unset($this->PageAnnots[$i]);
21340
				}
21341
				if (isset($this->newpagegroup[$j])) {
21342
					$this->newpagegroup[$i] = $this->newpagegroup[$j];
21343
					unset($this->newpagegroup[$j]);
21344
				}
21345
				if ($this->currpagegroup == $j) {
21346
					$this->currpagegroup = $i;
21347
				}
21348
				if (isset($this->pageopen[$j])) {
21349
					$this->pageopen[$i] = $this->pageopen[$j];
21350
				} elseif (isset($this->pageopen[$i])) {
21351
					unset($this->pageopen[$i]);
21352
				}
21353
			}
21354
			// remove last page
21355
			unset($this->pages[$this->numpages]);
21356
			unset($this->pagedim[$this->numpages]);
21357
			unset($this->pagelen[$this->numpages]);
21358
			unset($this->intmrk[$this->numpages]);
21359
			unset($this->bordermrk[$this->numpages]);
21360
			unset($this->cntmrk[$this->numpages]);
21361
			foreach ($this->pageobjects[$this->numpages] as $oid) {
21362
				if (isset($this->offsets[$oid])){
21363
					unset($this->offsets[$oid]);
21364
				}
21365
			}
21366
			unset($this->pageobjects[$this->numpages]);
21367
			if (isset($this->footerpos[$this->numpages])) {
21368
				unset($this->footerpos[$this->numpages]);
21369
			}
21370
			if (isset($this->footerlen[$this->numpages])) {
21371
				unset($this->footerlen[$this->numpages]);
21372
			}
21373
			if (isset($this->transfmrk[$this->numpages])) {
21374
				unset($this->transfmrk[$this->numpages]);
21375
			}
21376
			if (isset($this->PageAnnots[$this->numpages])) {
21377
				unset($this->PageAnnots[$this->numpages]);
21378
			}
21379
			if (isset($this->newpagegroup[$this->numpages])) {
21380
				unset($this->newpagegroup[$this->numpages]);
21381
			}
21382
			if ($this->currpagegroup == $this->numpages) {
21383
				$this->currpagegroup = ($this->numpages - 1);
21384
			}
21385
			if (isset($this->pagegroups[$this->numpages])) {
21386
				unset($this->pagegroups[$this->numpages]);
21387
			}
21388
			if (isset($this->pageopen[$this->numpages])) {
21389
				unset($this->pageopen[$this->numpages]);
21390
			}
21391
		}
21392
		--$this->numpages;
21393
		$this->page = $this->numpages;
21394
		// adjust outlines
21395
		$tmpoutlines = $this->outlines;
21396
		foreach ($tmpoutlines as $key => $outline) {
21397
			if (!$outline['f']) {
21398
				if ($outline['p'] > $page) {
21399
					$this->outlines[$key]['p'] = $outline['p'] - 1;
21400
				} elseif ($outline['p'] == $page) {
21401
					unset($this->outlines[$key]);
21402
				}
21403
			}
21404
		}
21405
		// adjust dests
21406
		$tmpdests = $this->dests;
21407
		foreach ($tmpdests as $key => $dest) {
21408
			if (!$dest['f']) {
21409
				if ($dest['p'] > $page) {
21410
					$this->dests[$key]['p'] = $dest['p'] - 1;
21411
				} elseif ($dest['p'] == $page) {
21412
					unset($this->dests[$key]);
21413
				}
21414
			}
21415
		}
21416
		// adjust links
21417
		$tmplinks = $this->links;
21418
		foreach ($tmplinks as $key => $link) {
21419
			if (!$link['f']) {
21420
				if ($link['p'] > $page) {
21421
					$this->links[$key]['p'] = $link['p'] - 1;
21422
				} elseif ($link['p'] == $page) {
21423
					unset($this->links[$key]);
21424
				}
21425
			}
21426
		}
21427
		// adjust javascript
21428
		$jpage = $page;
21429
		if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21430
			foreach($pamatch[0] as $pk => $pmatch) {
21431
				$pagenum = intval($pamatch[3][$pk]) + 1;
21432
				if ($pagenum >= $jpage) {
21433
					$newpage = ($pagenum - 1);
21434
				} elseif ($pagenum == $jpage) {
21435
					$newpage = 1;
21436
				} else {
21437
					$newpage = $pagenum;
21438
				}
21439
				--$newpage;
21440
				$newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21441
				$this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21442
			}
21443
			unset($pamatch);
21444
		}
21445
		// return to last page
21446
		if ($this->numpages > 0) {
21447
			$this->lastPage(true);
21448
		}
21449
		return true;
21450
	}
21451
 
21452
	/**
21453
	 * Clone the specified page to a new page.
21454
	 * @param int $page number of page to copy (0 = current page)
21455
	 * @return bool true in case of success, false in case of error.
21456
	 * @public
21457
	 * @since 4.9.015 (2010-04-20)
21458
	 */
21459
	public function copyPage($page=0) {
21460
		if ($page == 0) {
21461
			// default value
21462
			$page = $this->page;
21463
		}
21464
		if (($page < 1) OR ($page > $this->numpages)) {
21465
			return false;
21466
		}
21467
		// close the last page
21468
		$this->endPage();
21469
		// copy all page-related states
21470
		++$this->numpages;
21471
		$this->page = $this->numpages;
21472
		$this->setPageBuffer($this->page, $this->getPageBuffer($page));
21473
		$this->pagedim[$this->page] = $this->pagedim[$page];
21474
		$this->pagelen[$this->page] = $this->pagelen[$page];
21475
		$this->intmrk[$this->page] = $this->intmrk[$page];
21476
		$this->bordermrk[$this->page] = $this->bordermrk[$page];
21477
		$this->cntmrk[$this->page] = $this->cntmrk[$page];
21478
		$this->pageobjects[$this->page] = $this->pageobjects[$page];
21479
		$this->pageopen[$this->page] = false;
21480
		if (isset($this->footerpos[$page])) {
21481
			$this->footerpos[$this->page] = $this->footerpos[$page];
21482
		}
21483
		if (isset($this->footerlen[$page])) {
21484
			$this->footerlen[$this->page] = $this->footerlen[$page];
21485
		}
21486
		if (isset($this->transfmrk[$page])) {
21487
			$this->transfmrk[$this->page] = $this->transfmrk[$page];
21488
		}
21489
		if (isset($this->PageAnnots[$page])) {
21490
			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21491
		}
21492
		if (isset($this->newpagegroup[$page])) {
21493
			// start a new group
21494
			$this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21495
			$this->currpagegroup = $this->newpagegroup[$this->page];
21496
			$this->pagegroups[$this->currpagegroup] = 1;
21497
		} elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21498
			++$this->pagegroups[$this->currpagegroup];
21499
		}
21500
		// copy outlines
21501
		$tmpoutlines = $this->outlines;
21502
		foreach ($tmpoutlines as $key => $outline) {
21503
			if ($outline['p'] == $page) {
21504
				$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']);
21505
			}
21506
		}
21507
		// copy links
21508
		$tmplinks = $this->links;
21509
		foreach ($tmplinks as $key => $link) {
21510
			if ($link['p'] == $page) {
21511
				$this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21512
			}
21513
		}
21514
		// return to last page
21515
		$this->lastPage(true);
21516
		return true;
21517
	}
21518
 
21519
	/**
21520
	 * Output a Table of Content Index (TOC).
21521
	 * This method must be called after all Bookmarks were set.
21522
	 * Before calling this method you have to open the page using the addTOCPage() method.
21523
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21524
	 * You can override this method to achieve different styles.
21525
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21526
	 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21527
	 * @param string $filler string used to fill the space between text and page number.
21528
	 * @param string $toc_name name to use for TOC bookmark.
21529
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21530
	 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21531
	 * @public
21532
	 * @author Nicola Asuni
21533
	 * @since 4.5.000 (2009-01-02)
21534
	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21535
	 */
21536
	public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21537
		$fontsize = $this->FontSizePt;
21538
		$fontfamily = $this->FontFamily;
21539
		$fontstyle = $this->FontStyle;
21540
		$w = $this->w - $this->lMargin - $this->rMargin;
21541
		$spacer = $this->GetStringWidth(chr(32)) * 4;
21542
		$lmargin = $this->lMargin;
21543
		$rmargin = $this->rMargin;
21544
		$x_start = $this->GetX();
21545
		$page_first = $this->page;
21546
		$current_page = $this->page;
21547
		$page_fill_start = false;
21548
		$page_fill_end = false;
21549
		$current_column = $this->current_column;
21550
		if (TCPDF_STATIC::empty_string($numbersfont)) {
21551
			$numbersfont = $this->default_monospaced_font;
21552
		}
21553
		if (TCPDF_STATIC::empty_string($filler)) {
21554
			$filler = ' ';
21555
		}
21556
		if (TCPDF_STATIC::empty_string($page)) {
21557
			$gap = ' ';
21558
		} else {
21559
			$gap = '';
21560
			if ($page < 1) {
21561
				$page = 1;
21562
			}
21563
		}
21564
		$this->setFont($numbersfont, $fontstyle, $fontsize);
21565
		$numwidth = $this->GetStringWidth('00000');
21566
		$maxpage = 0; //used for pages on attached documents
21567
		foreach ($this->outlines as $key => $outline) {
21568
			// check for extra pages (used for attachments)
21569
			if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21570
				$outline['p'] += ($this->page - $page_first);
21571
			}
21572
			if ($this->rtl) {
21573
				$aligntext = 'R';
21574
				$alignnum = 'L';
21575
			} else {
21576
				$aligntext = 'L';
21577
				$alignnum = 'R';
21578
			}
21579
			if ($outline['l'] == 0) {
21580
				$this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21581
			} else {
21582
				$this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21583
			}
21584
			$this->setTextColorArray($outline['c']);
21585
			// check for page break
21586
			$this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21587
			// set margins and X position
21588
			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21589
				$this->lMargin = $lmargin;
21590
				$this->rMargin = $rmargin;
21591
			} else {
21592
				if ($this->current_column != $current_column) {
21593
					if ($this->rtl) {
21594
						$x_start = $this->w - $this->columns[$this->current_column]['x'];
21595
					} else {
21596
						$x_start = $this->columns[$this->current_column]['x'];
21597
					}
21598
				}
21599
				$lmargin = $this->lMargin;
21600
				$rmargin = $this->rMargin;
21601
				$current_page = $this->page;
21602
				$current_column = $this->current_column;
21603
			}
21604
			$this->setX($x_start);
21605
			$indent = ($spacer * $outline['l']);
21606
			if ($this->rtl) {
21607
				$this->x -= $indent;
21608
				$this->rMargin = $this->w - $this->x;
21609
			} else {
21610
				$this->x += $indent;
21611
				$this->lMargin = $this->x;
21612
			}
21613
			$link = $this->AddLink();
21614
			$this->setLink($link, $outline['y'], $outline['p']);
21615
			// write the text
21616
			if ($this->rtl) {
21617
				$txt = ' '.$outline['t'];
21618
			} else {
21619
				$txt = $outline['t'].' ';
21620
			}
21621
			$this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21622
			if ($this->rtl) {
21623
				$tw = $this->x - $this->lMargin;
21624
			} else {
21625
				$tw = $this->w - $this->rMargin - $this->x;
21626
			}
21627
			$this->setFont($numbersfont, $fontstyle, $fontsize);
21628
			if (TCPDF_STATIC::empty_string($page)) {
21629
				$pagenum = $outline['p'];
21630
			} else {
21631
				// placemark to be replaced with the correct number
21632
				$pagenum = '{#'.($outline['p']).'}';
21633
				if ($this->isUnicodeFont()) {
21634
					$pagenum = '{'.$pagenum.'}';
21635
				}
21636
				$maxpage = max($maxpage, $outline['p']);
21637
			}
21638
			$fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21639
			$wfiller = $this->GetStringWidth($filler);
21640
			if ($wfiller > 0) {
21641
				$numfills = floor($fw / $wfiller);
21642
			} else {
21643
				$numfills = 0;
21644
			}
21645
			if ($numfills > 0) {
21646
				$rowfill = str_repeat($filler, $numfills);
21647
			} else {
21648
				$rowfill = '';
21649
			}
21650
			if ($this->rtl) {
21651
				$pagenum = $pagenum.$gap.$rowfill;
21652
			} else {
21653
				$pagenum = $rowfill.$gap.$pagenum;
21654
			}
21655
			// write the number
21656
			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21657
		}
21658
		$page_last = $this->getPage();
21659
		$numpages = ($page_last - $page_first + 1);
21660
		// account for booklet mode
21661
		if ($this->booklet) {
21662
			// check if a blank page is required before TOC
21663
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21664
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21665
			if ($page_fill_start) {
21666
				// add a page at the end (to be moved before TOC)
21667
				$this->addPage();
21668
				++$page_last;
21669
				++$numpages;
21670
			}
21671
			if ($page_fill_end) {
21672
				// add a page at the end
21673
				$this->addPage();
21674
				++$page_last;
21675
				++$numpages;
21676
			}
21677
		}
21678
		$maxpage = max($maxpage, $page_last);
21679
		if (!TCPDF_STATIC::empty_string($page)) {
21680
			for ($p = $page_first; $p <= $page_last; ++$p) {
21681
				// get page data
21682
				$temppage = $this->getPageBuffer($p);
21683
				for ($n = 1; $n <= $maxpage; ++$n) {
21684
					// update page numbers
21685
					$a = '{#'.$n.'}';
21686
					// get page number aliases
21687
					$pnalias = $this->getInternalPageNumberAliases($a);
21688
					// calculate replacement number
21689
					if (($n >= $page) AND ($n <= $this->numpages)) {
21690
						$np = $n + $numpages;
21691
					} else {
21692
						$np = $n;
21693
					}
21694
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21695
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21696
					// replace aliases with numbers
21697
					foreach ($pnalias['u'] as $u) {
21698
						$sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21699
						if ($this->rtl) {
21700
							$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21701
						} else {
21702
							$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21703
						}
21704
						$temppage = str_replace($u, $nr, $temppage);
21705
					}
21706
					foreach ($pnalias['a'] as $a) {
21707
						$sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21708
						if ($this->rtl) {
21709
							$nr = $na.' '.$sfill;
21710
						} else {
21711
							$nr = $sfill.' '.$na;
21712
						}
21713
						$temppage = str_replace($a, $nr, $temppage);
21714
					}
21715
				}
21716
				// save changes
21717
				$this->setPageBuffer($p, $temppage);
21718
			}
21719
			// move pages
21720
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21721
			if ($page_fill_start) {
21722
				$this->movePage($page_last, $page_first);
21723
			}
21724
			for ($i = 0; $i < $numpages; ++$i) {
21725
				$this->movePage($page_last, $page);
21726
			}
21727
		}
21728
	}
21729
 
21730
	/**
21731
	 * Output a Table Of Content Index (TOC) using HTML templates.
21732
	 * This method must be called after all Bookmarks were set.
21733
	 * Before calling this method you have to open the page using the addTOCPage() method.
21734
	 * After calling this method you have to call endTOCPage() to close the TOC page.
21735
	 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21736
	 * @param string $toc_name name to use for TOC bookmark.
21737
	 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21738
	 * @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)
21739
	 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21740
	 * @param array $color RGB color array for title (values from 0 to 255).
21741
	 * @public
21742
	 * @author Nicola Asuni
21743
	 * @since 5.0.001 (2010-05-06)
21744
	 * @see addTOCPage(), endTOCPage(), addTOC()
21745
	 */
21746
	public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21747
		$filler = ' ';
21748
		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21749
		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21750
		// set new style for link
21751
		$this->htmlLinkColorArray = array();
21752
		$this->htmlLinkFontStyle = '';
21753
		$page_first = $this->getPage();
21754
		$page_fill_start = false;
21755
		$page_fill_end = false;
21756
		// get the font type used for numbers in each template
21757
		$current_font = $this->FontFamily;
21758
		foreach ($templates as $level => $html) {
21759
			$dom = $this->getHtmlDomArray($html);
21760
			foreach ($dom as $key => $value) {
21761
				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21762
					$this->setFont($dom[($key - 1)]['fontname']);
21763
					$templates['F'.$level] = $this->isUnicodeFont();
21764
				}
21765
			}
21766
		}
21767
		$this->setFont($current_font);
21768
		$maxpage = 0; //used for pages on attached documents
21769
		foreach ($this->outlines as $key => $outline) {
21770
			// get HTML template
21771
			$row = $templates[$outline['l']];
21772
			if (TCPDF_STATIC::empty_string($page)) {
21773
				$pagenum = $outline['p'];
21774
			} else {
21775
				// placemark to be replaced with the correct number
21776
				$pagenum = '{#'.($outline['p']).'}';
21777
				if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21778
					$pagenum = '{'.$pagenum.'}';
21779
				}
21780
				$maxpage = max($maxpage, $outline['p']);
21781
			}
21782
			// replace templates with current values
21783
			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21784
			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21785
			// add link to page
21786
			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21787
			// write bookmark entry
21788
			$this->writeHTML($row, false, false, true, false, '');
21789
		}
21790
		// restore link styles
21791
		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21792
		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21793
		// move TOC page and replace numbers
21794
		$page_last = $this->getPage();
21795
		$numpages = ($page_last - $page_first + 1);
21796
		// account for booklet mode
21797
		if ($this->booklet) {
21798
			// check if a blank page is required before TOC
21799
			$page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21800
			$page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21801
			if ($page_fill_start) {
21802
				// add a page at the end (to be moved before TOC)
21803
				$this->addPage();
21804
				++$page_last;
21805
				++$numpages;
21806
			}
21807
			if ($page_fill_end) {
21808
				// add a page at the end
21809
				$this->addPage();
21810
				++$page_last;
21811
				++$numpages;
21812
			}
21813
		}
21814
		$maxpage = max($maxpage, $page_last);
21815
		if (!TCPDF_STATIC::empty_string($page)) {
21816
			for ($p = $page_first; $p <= $page_last; ++$p) {
21817
				// get page data
21818
				$temppage = $this->getPageBuffer($p);
21819
				for ($n = 1; $n <= $maxpage; ++$n) {
21820
					// update page numbers
21821
					$a = '{#'.$n.'}';
21822
					// get page number aliases
21823
					$pnalias = $this->getInternalPageNumberAliases($a);
21824
					// calculate replacement number
21825
					if ($n >= $page) {
21826
						$np = $n + $numpages;
21827
					} else {
21828
						$np = $n;
21829
					}
21830
					$na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21831
					$nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21832
					// replace aliases with numbers
21833
					foreach ($pnalias['u'] as $u) {
21834
						if ($correct_align) {
21835
							$sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21836
							if ($this->rtl) {
21837
								$nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21838
							} else {
21839
								$nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21840
							}
21841
						} else {
21842
							$nr = $nu;
21843
						}
21844
						$temppage = str_replace($u, $nr, $temppage);
21845
					}
21846
					foreach ($pnalias['a'] as $a) {
21847
						if ($correct_align) {
21848
							$sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21849
							if ($this->rtl) {
21850
								$nr = $na.' '.$sfill;
21851
							} else {
21852
								$nr = $sfill.' '.$na;
21853
							}
21854
						} else {
21855
							$nr = $na;
21856
						}
21857
						$temppage = str_replace($a, $nr, $temppage);
21858
					}
21859
				}
21860
				// save changes
21861
				$this->setPageBuffer($p, $temppage);
21862
			}
21863
			// move pages
21864
			$this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21865
			if ($page_fill_start) {
21866
				$this->movePage($page_last, $page_first);
21867
			}
21868
			for ($i = 0; $i < $numpages; ++$i) {
21869
				$this->movePage($page_last, $page);
21870
			}
21871
		}
21872
	}
21873
 
21874
	/**
21875
	 * Stores a copy of the current TCPDF object used for undo operation.
21876
	 * @public
21877
	 * @since 4.5.029 (2009-03-19)
21878
	 */
21879
	public function startTransaction() {
21880
		if (isset($this->objcopy)) {
21881
			// remove previous copy
21882
			$this->commitTransaction();
21883
		}
21884
		// record current page number and Y position
21885
		$this->start_transaction_page = $this->page;
21886
		$this->start_transaction_y = $this->y;
21887
		// clone current object
21888
		$this->objcopy = TCPDF_STATIC::objclone($this);
21889
	}
21890
 
21891
	/**
21892
	 * Delete the copy of the current TCPDF object used for undo operation.
21893
	 * @public
21894
	 * @since 4.5.029 (2009-03-19)
21895
	 */
21896
	public function commitTransaction() {
21897
		if (isset($this->objcopy)) {
21898
			$this->objcopy->_destroy(true, true);
21899
			/* The unique file_id should not be used during cleanup again */
21900
			$this->objcopy->file_id = NULL;
21901
			unset($this->objcopy);
21902
		}
21903
	}
21904
 
21905
	/**
21906
	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21907
	 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21908
	 * @return TCPDF object.
21909
	 * @public
21910
	 * @since 4.5.029 (2009-03-19)
21911
	 */
21912
	public function rollbackTransaction($self=false) {
1441 ariadna 21913
		if (!isset($this->objcopy)) {
21914
			return $this;
21915
		}
21916
		$file_id = $this->file_id;
21917
		$objcopy = $this->objcopy;
21918
		$this->_destroy(true, true);
21919
		if ($self) {
21920
			$objvars = get_object_vars($objcopy);
21921
			foreach ($objvars as $key => $value) {
21922
				$this->$key = $value;
1 efrain 21923
			}
1441 ariadna 21924
			$objcopy->_destroy(true, true);
21925
			unset($objcopy);
21926
			return $this;
1 efrain 21927
		}
1441 ariadna 21928
		$this->file_id = $file_id;
21929
		return $objcopy;
1 efrain 21930
	}
21931
 
21932
	// --- MULTI COLUMNS METHODS -----------------------
21933
 
21934
	/**
21935
	 * Set multiple columns of the same size
21936
	 * @param int $numcols number of columns (set to zero to disable columns mode)
21937
	 * @param int $width column width
21938
	 * @param int|null $y column starting Y position (leave empty for current Y position)
21939
	 * @public
21940
	 * @since 4.9.001 (2010-03-28)
21941
	 */
21942
	public function setEqualColumns($numcols=0, $width=0, $y=null) {
21943
		$this->columns = array();
21944
		if ($numcols < 2) {
21945
			$numcols = 0;
21946
			$this->columns = array();
21947
		} else {
21948
			// maximum column width
21949
			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21950
			if (($width == 0) OR ($width > $maxwidth)) {
21951
				$width = $maxwidth;
21952
			}
21953
			if (TCPDF_STATIC::empty_string($y)) {
21954
				$y = $this->y;
21955
			}
21956
			// space between columns
21957
			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21958
			// fill the columns array (with, space, starting Y position)
21959
			for ($i = 0; $i < $numcols; ++$i) {
21960
				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21961
			}
21962
		}
21963
		$this->num_columns = $numcols;
21964
		$this->current_column = 0;
21965
		$this->column_start_page = $this->page;
21966
		$this->selectColumn(0);
21967
	}
21968
 
21969
	/**
21970
	 * Remove columns and reset page margins.
21971
	 * @public
21972
	 * @since 5.9.072 (2011-04-26)
21973
	 */
21974
	public function resetColumns() {
21975
		$this->lMargin = $this->original_lMargin;
21976
		$this->rMargin = $this->original_rMargin;
21977
		$this->setEqualColumns();
21978
	}
21979
 
21980
	/**
21981
	 * Set columns array.
21982
	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21983
	 * @param array $columns
21984
	 * @public
21985
	 * @since 4.9.001 (2010-03-28)
21986
	 */
21987
	public function setColumnsArray($columns) {
21988
		$this->columns = $columns;
21989
		$this->num_columns = count($columns);
21990
		$this->current_column = 0;
21991
		$this->column_start_page = $this->page;
21992
		$this->selectColumn(0);
21993
	}
21994
 
21995
	/**
21996
	 * Set position at a given column
21997
	 * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21998
	 * @public
21999
	 * @since 4.9.001 (2010-03-28)
22000
	 */
22001
	public function selectColumn($col=null) {
22002
		if (TCPDF_STATIC::empty_string($col)) {
22003
			$col = $this->current_column;
22004
		} elseif ($col >= $this->num_columns) {
22005
			$col = 0;
22006
		}
22007
		$xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
22008
		$enable_thead = false;
22009
		if ($this->num_columns > 1) {
22010
			if ($col != $this->current_column) {
22011
				// move Y pointer at the top of the column
22012
				if ($this->column_start_page == $this->page) {
22013
					$this->y = $this->columns[$col]['y'];
22014
				} else {
22015
					$this->y = $this->tMargin;
22016
				}
22017
				// Avoid to write table headers more than once
22018
				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
22019
					$enable_thead = true;
22020
					$this->maxselcol['page'] = $this->page;
22021
					$this->maxselcol['column'] = $col;
22022
				}
22023
			}
22024
			$xshift = $this->colxshift;
22025
			// set X position of the current column by case
22026
			$listindent = ($this->listindentlevel * $this->listindent);
22027
			// calculate column X position
22028
			$colpos = 0;
22029
			for ($i = 0; $i < $col; ++$i) {
22030
				$colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
22031
			}
22032
			if ($this->rtl) {
22033
				$x = $this->w - $this->original_rMargin - $colpos;
22034
				$this->rMargin = ($this->w - $x + $listindent);
22035
				$this->lMargin = ($x - $this->columns[$col]['w']);
22036
				$this->x = $x - $listindent;
22037
			} else {
22038
				$x = $this->original_lMargin + $colpos;
22039
				$this->lMargin = ($x + $listindent);
22040
				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
22041
				$this->x = $x + $listindent;
22042
			}
22043
			$this->columns[$col]['x'] = $x;
22044
		}
22045
		$this->current_column = $col;
22046
		// fix for HTML mode
22047
		$this->newline = true;
22048
		// print HTML table header (if any)
22049
		if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
22050
			if ($enable_thead) {
22051
				// print table header
22052
				$this->writeHTML($this->thead, false, false, false, false, '');
22053
				$this->y += $xshift['s']['V'];
22054
				// store end of header position
22055
				if (!isset($this->columns[$col]['th'])) {
22056
					$this->columns[$col]['th'] = array();
22057
				}
22058
				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22059
				$this->lasth = 0;
22060
			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22061
				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22062
			}
22063
		}
22064
		// account for an html table cell over multiple columns
22065
		if ($this->rtl) {
22066
			$this->rMargin += $xshift['x'];
22067
			$this->x -= ($xshift['x'] + $xshift['p']['R']);
22068
		} else {
22069
			$this->lMargin += $xshift['x'];
22070
			$this->x += $xshift['x'] + $xshift['p']['L'];
22071
		}
22072
	}
22073
 
22074
	/**
22075
	 * Return the current column number
22076
	 * @return int current column number
22077
	 * @public
22078
	 * @since 5.5.011 (2010-07-08)
22079
	 */
22080
	public function getColumn() {
22081
		return $this->current_column;
22082
	}
22083
 
22084
	/**
22085
	 * Return the current number of columns.
22086
	 * @return int number of columns
22087
	 * @public
22088
	 * @since 5.8.018 (2010-08-25)
22089
	 */
22090
	public function getNumberOfColumns() {
22091
		return $this->num_columns;
22092
	}
22093
 
22094
	/**
22095
	 * Set Text rendering mode.
22096
	 * @param int $stroke outline size in user units (0 = disable).
22097
	 * @param boolean $fill if true fills the text (default).
22098
	 * @param boolean $clip if true activate clipping mode
22099
	 * @public
22100
	 * @since 4.9.008 (2009-04-02)
22101
	 */
22102
	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22103
		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22104
		// convert text rendering parameters
22105
		if ($stroke < 0 || !is_numeric($stroke)) {
22106
			$stroke = 0;
22107
		}
22108
		if ($fill === true) {
22109
			if ($stroke > 0) {
22110
				if ($clip === true) {
22111
					// Fill, then stroke text and add to path for clipping
22112
					$textrendermode = 6;
22113
				} else {
22114
					// Fill, then stroke text
22115
					$textrendermode = 2;
22116
				}
22117
				$textstrokewidth = $stroke;
22118
			} else {
22119
				if ($clip === true) {
22120
					// Fill text and add to path for clipping
22121
					$textrendermode = 4;
22122
				} else {
22123
					// Fill text
22124
					$textrendermode = 0;
22125
				}
22126
			}
22127
		} else {
22128
			if ($stroke > 0) {
22129
				if ($clip === true) {
22130
					// Stroke text and add to path for clipping
22131
					$textrendermode = 5;
22132
				} else {
22133
					// Stroke text
22134
					$textrendermode = 1;
22135
				}
22136
				$textstrokewidth = $stroke;
22137
			} else {
22138
				if ($clip === true) {
22139
					// Add text to path for clipping
22140
					$textrendermode = 7;
22141
				} else {
22142
					// Neither fill nor stroke text (invisible)
22143
					$textrendermode = 3;
22144
				}
22145
			}
22146
		}
22147
		$this->textrendermode = $textrendermode;
22148
		$this->textstrokewidth = $stroke;
22149
	}
22150
 
22151
	/**
22152
	 * Set parameters for drop shadow effect for text.
22153
	 * @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.
22154
	 * @since 5.9.174 (2012-07-25)
22155
	 * @public
22156
	*/
22157
	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22158
		if (isset($params['enabled'])) {
22159
			$this->txtshadow['enabled'] = $params['enabled']?true:false;
22160
		} else {
22161
			$this->txtshadow['enabled'] = false;
22162
		}
22163
		if (isset($params['depth_w'])) {
22164
			$this->txtshadow['depth_w'] = floatval($params['depth_w']);
22165
		} else {
22166
			$this->txtshadow['depth_w'] = 0;
22167
		}
22168
		if (isset($params['depth_h'])) {
22169
			$this->txtshadow['depth_h'] = floatval($params['depth_h']);
22170
		} else {
22171
			$this->txtshadow['depth_h'] = 0;
22172
		}
22173
		if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22174
			$this->txtshadow['color'] = $params['color'];
22175
		} else {
22176
			$this->txtshadow['color'] = $this->strokecolor;
22177
		}
22178
		if (isset($params['opacity'])) {
22179
			$this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22180
		} else {
22181
			$this->txtshadow['opacity'] = 1;
22182
		}
22183
		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'))) {
22184
			$this->txtshadow['blend_mode'] = $params['blend_mode'];
22185
		} else {
22186
			$this->txtshadow['blend_mode'] = 'Normal';
22187
		}
22188
		if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22189
			$this->txtshadow['enabled'] = false;
22190
		}
22191
	}
22192
 
22193
	/**
22194
	 * Return the text shadow parameters array.
22195
	 * @return array array of parameters.
22196
	 * @since 5.9.174 (2012-07-25)
22197
	 * @public
22198
	 */
22199
	public function getTextShadow() {
22200
		return $this->txtshadow;
22201
	}
22202
 
22203
	/**
22204
	 * Returns an array of chars containing soft hyphens.
22205
	 * @param array $word array of chars
22206
	 * @param array $patterns Array of hypenation patterns.
22207
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22208
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22209
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22210
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22211
	 * @param int $charmax Maximum length of broken piece of word.
22212
	 * @return array text with soft hyphens
22213
	 * @author Nicola Asuni
22214
	 * @since 4.9.012 (2010-04-12)
22215
	 * @protected
22216
	 */
22217
	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22218
		$hyphenword = array(); // hyphens positions
22219
		$numchars = count($word);
22220
		if ($numchars <= $charmin) {
22221
			return $word;
22222
		}
22223
		$word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22224
		// some words will be returned as-is
22225
		$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})(\]?)$/';
22226
		if (preg_match($pattern, $word_string) > 0) {
22227
			// email
22228
			return $word;
22229
		}
22230
		$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})(\]?)$/';
22231
		if (preg_match($pattern, $word_string) > 0) {
22232
			// URL
22233
			return $word;
22234
		}
22235
		if (isset($dictionary[$word_string])) {
22236
			return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22237
		}
22238
		// surround word with '_' characters
22239
		$tmpword = array_merge(array(46), $word, array(46));
22240
		$tmpnumchars = $numchars + 2;
22241
		$maxpos = $tmpnumchars - 1;
22242
		for ($pos = 0; $pos < $maxpos; ++$pos) {
22243
			$imax = min(($tmpnumchars - $pos), $charmax);
22244
			for ($i = 1; $i <= $imax; ++$i) {
22245
				$subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22246
				if (isset($patterns[$subword])) {
22247
					$pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22248
					$pattern_length = count($pattern);
22249
					$digits = 1;
22250
					for ($j = 0; $j < $pattern_length; ++$j) {
22251
						// check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22252
						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22253
							if ($j == 0) {
22254
								$zero = $pos - 1;
22255
							} else {
22256
								$zero = $pos + $j - $digits;
22257
							}
22258
							// get hyphenation level
22259
							$level = ($pattern[$j] - 48);
22260
							// if two levels from two different patterns match at the same point, the higher one is selected.
22261
							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22262
								$hyphenword[$zero] = $level;
22263
							}
22264
							++$digits;
22265
						}
22266
					}
22267
				}
22268
			}
22269
		}
22270
		$inserted = 0;
22271
		$maxpos = $numchars - $rightmin;
22272
		for ($i = $leftmin; $i <= $maxpos; ++$i) {
22273
			// only odd levels indicate allowed hyphenation points
22274
			if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22275
				// 173 = soft hyphen character
22276
				array_splice($word, $i + $inserted, 0, 173);
22277
				++$inserted;
22278
			}
22279
		}
22280
		return $word;
22281
	}
22282
 
22283
	/**
22284
	 * Returns text with soft hyphens.
22285
	 * @param string $text text to process
22286
	 * @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/
22287
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22288
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22289
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22290
	 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22291
	 * @param int $charmax Maximum length of broken piece of word.
22292
	 * @return string text with soft hyphens
22293
	 * @author Nicola Asuni
22294
	 * @since 4.9.012 (2010-04-12)
22295
	 * @public
22296
	 */
22297
	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22298
		$text = $this->unhtmlentities($text);
22299
		$word = array(); // last word
22300
		$txtarr = array(); // text to be returned
22301
		$intag = false; // true if we are inside an HTML tag
22302
		$skip = false; // true to skip hyphenation
22303
		if (!is_array($patterns)) {
22304
			$patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22305
		}
22306
		// get array of characters
22307
		$unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22308
		// for each char
22309
		foreach ($unichars as $char) {
22310
			if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22311
				// letter character
22312
				$word[] = $char;
22313
			} else {
22314
				// other type of character
22315
				if (!TCPDF_STATIC::empty_string($word)) {
22316
					// hypenate the word
22317
					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22318
					$word = array();
22319
				}
22320
				$txtarr[] = $char;
22321
				if (chr($char) == '<') {
22322
					// we are inside an HTML tag
22323
					$intag = true;
22324
				} elseif ($intag AND (chr($char) == '>')) {
22325
					// end of HTML tag
22326
					$intag = false;
22327
					// check for style tag
22328
					$expected = array(115, 116, 121, 108, 101); // = 'style'
22329
					$current = array_slice($txtarr, -6, 5); // last 5 chars
22330
					$compare = array_diff($expected, $current);
22331
					if (empty($compare)) {
22332
						// check if it is a closing tag
22333
						$expected = array(47); // = '/'
22334
						$current = array_slice($txtarr, -7, 1);
22335
						$compare = array_diff($expected, $current);
22336
						if (empty($compare)) {
22337
							// closing style tag
22338
							$skip = false;
22339
						} else {
22340
							// opening style tag
22341
							$skip = true;
22342
						}
22343
					}
22344
				}
22345
			}
22346
		}
22347
		if (!TCPDF_STATIC::empty_string($word)) {
22348
			// hypenate the word
22349
			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22350
		}
22351
		// convert char array to string and return
22352
		return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22353
	}
22354
 
22355
	/**
22356
	 * Enable/disable rasterization of vector images using ImageMagick library.
22357
	 * @param boolean $mode if true enable rasterization, false otherwise.
22358
	 * @public
22359
	 * @since 5.0.000 (2010-04-27)
22360
	 */
22361
	public function setRasterizeVectorImages($mode) {
22362
		$this->rasterize_vector_images = $mode;
22363
	}
22364
 
22365
	/**
22366
	 * Enable or disable default option for font subsetting.
22367
	 * @param boolean $enable if true enable font subsetting by default.
22368
	 * @author Nicola Asuni
22369
	 * @public
22370
	 * @since 5.3.002 (2010-06-07)
22371
	 */
22372
	public function setFontSubsetting($enable=true) {
22373
		if ($this->pdfa_mode) {
22374
			$this->font_subsetting = false;
22375
		} else {
22376
			$this->font_subsetting = $enable ? true : false;
22377
		}
22378
	}
22379
 
22380
	/**
22381
	 * Return the default option for font subsetting.
22382
	 * @return bool default font subsetting state.
22383
	 * @author Nicola Asuni
22384
	 * @public
22385
	 * @since 5.3.002 (2010-06-07)
22386
	 */
22387
	public function getFontSubsetting() {
22388
		return $this->font_subsetting;
22389
	}
22390
 
22391
	/**
22392
	 * Left trim the input string
22393
	 * @param string $str string to trim
22394
	 * @param string $replace string that replace spaces.
22395
	 * @return string left trimmed string
22396
	 * @author Nicola Asuni
22397
	 * @public
22398
	 * @since 5.8.000 (2010-08-11)
22399
	 */
22400
	public function stringLeftTrim($str, $replace='') {
22401
		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22402
	}
22403
 
22404
	/**
22405
	 * Right trim the input string
22406
	 * @param string $str string to trim
22407
	 * @param string $replace string that replace spaces.
22408
	 * @return string right trimmed string
22409
	 * @author Nicola Asuni
22410
	 * @public
22411
	 * @since 5.8.000 (2010-08-11)
22412
	 */
22413
	public function stringRightTrim($str, $replace='') {
22414
		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22415
	}
22416
 
22417
	/**
22418
	 * Trim the input string
22419
	 * @param string $str string to trim
22420
	 * @param string $replace string that replace spaces.
22421
	 * @return string trimmed string
22422
	 * @author Nicola Asuni
22423
	 * @public
22424
	 * @since 5.8.000 (2010-08-11)
22425
	 */
22426
	public function stringTrim($str, $replace='') {
22427
		$str = $this->stringLeftTrim($str, $replace);
22428
		$str = $this->stringRightTrim($str, $replace);
22429
		return $str;
22430
	}
22431
 
22432
	/**
22433
	 * Return true if the current font is unicode type.
22434
	 * @return bool true for unicode font, false otherwise.
22435
	 * @author Nicola Asuni
22436
	 * @public
22437
	 * @since 5.8.002 (2010-08-14)
22438
	 */
22439
	public function isUnicodeFont() {
22440
		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22441
	}
22442
 
22443
	/**
22444
	 * Return normalized font name
22445
	 * @param string $fontfamily property string containing font family names
22446
	 * @return string normalized font name
22447
	 * @author Nicola Asuni
22448
	 * @public
22449
	 * @since 5.8.004 (2010-08-17)
22450
	 */
22451
	public function getFontFamilyName($fontfamily) {
22452
		// remove spaces and symbols
22453
		$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22454
		// extract all font names
22455
		$fontslist = preg_split('/[,]/', $fontfamily);
22456
		// find first valid font name
22457
		foreach ($fontslist as $font) {
22458
			// replace font variations
22459
			$font = preg_replace('/regular$/', '', $font);
22460
			$font = preg_replace('/italic$/', 'I', $font);
22461
			$font = preg_replace('/oblique$/', 'I', $font);
22462
			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22463
			// replace common family names and core fonts
22464
			$pattern = array();
22465
			$replacement = array();
22466
			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22467
			$replacement[] = 'times';
22468
			$pattern[] = '/^sansserif/';
22469
			$replacement[] = 'helvetica';
22470
			$pattern[] = '/^monospace/';
22471
			$replacement[] = 'courier';
22472
			$font = preg_replace($pattern, $replacement, $font);
22473
			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22474
				return $font;
22475
			}
22476
		}
22477
		// return current font as default
22478
		return $this->CurrentFont['fontkey'];
22479
	}
22480
 
22481
	/**
22482
	 * Start a new XObject Template.
22483
	 * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
22484
	 * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
22485
	 * Note: X,Y coordinates will be reset to 0,0.
22486
	 * @param int $w Template width in user units (empty string or zero = page width less margins).
22487
	 * @param int $h Template height in user units (empty string or zero = page height less margins).
22488
	 * @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).
22489
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22490
	 * @author Nicola Asuni
22491
	 * @public
22492
	 * @since 5.8.017 (2010-08-24)
22493
	 * @see endTemplate(), printTemplate()
22494
	 */
22495
	public function startTemplate($w=0, $h=0, $group=false) {
22496
		if ($this->inxobj) {
22497
			// we are already inside an XObject template
22498
			return false;
22499
		}
22500
		$this->inxobj = true;
22501
		++$this->n;
22502
		// XObject ID
22503
		$this->xobjid = 'XT'.$this->n;
22504
		// object ID
22505
		$this->xobjects[$this->xobjid] = array('n' => $this->n);
22506
		// store current graphic state
22507
		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22508
		// initialize data
22509
		$this->xobjects[$this->xobjid]['intmrk'] = 0;
22510
		$this->xobjects[$this->xobjid]['transfmrk'] = array();
22511
		$this->xobjects[$this->xobjid]['outdata'] = '';
22512
		$this->xobjects[$this->xobjid]['xobjects'] = array();
22513
		$this->xobjects[$this->xobjid]['images'] = array();
22514
		$this->xobjects[$this->xobjid]['fonts'] = array();
22515
		$this->xobjects[$this->xobjid]['annotations'] = array();
22516
		$this->xobjects[$this->xobjid]['extgstates'] = array();
22517
		$this->xobjects[$this->xobjid]['gradients'] = array();
22518
		$this->xobjects[$this->xobjid]['spot_colors'] = array();
22519
		// set new environment
22520
		$this->num_columns = 1;
22521
		$this->current_column = 0;
22522
		$this->setAutoPageBreak(false);
22523
		if (($w === '') OR ($w <= 0)) {
22524
			$w = $this->w - $this->lMargin - $this->rMargin;
22525
		}
22526
		if (($h === '') OR ($h <= 0)) {
22527
			$h = $this->h - $this->tMargin - $this->bMargin;
22528
		}
22529
		$this->xobjects[$this->xobjid]['x'] = 0;
22530
		$this->xobjects[$this->xobjid]['y'] = 0;
22531
		$this->xobjects[$this->xobjid]['w'] = $w;
22532
		$this->xobjects[$this->xobjid]['h'] = $h;
22533
		$this->w = $w;
22534
		$this->h = $h;
22535
		$this->wPt = $this->w * $this->k;
22536
		$this->hPt = $this->h * $this->k;
22537
		$this->fwPt = $this->wPt;
22538
		$this->fhPt = $this->hPt;
22539
		$this->x = 0;
22540
		$this->y = 0;
22541
		$this->lMargin = 0;
22542
		$this->rMargin = 0;
22543
		$this->tMargin = 0;
22544
		$this->bMargin = 0;
22545
		// set group mode
22546
		$this->xobjects[$this->xobjid]['group'] = $group;
22547
		return $this->xobjid;
22548
	}
22549
 
22550
	/**
22551
	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22552
	 * 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).
22553
	 * 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.
22554
	 * @return string|false the XObject Template ID in case of success or false in case of error.
22555
	 * @author Nicola Asuni
22556
	 * @public
22557
	 * @since 5.8.017 (2010-08-24)
22558
	 * @see startTemplate(), printTemplate()
22559
	 */
22560
	public function endTemplate() {
22561
		if (!$this->inxobj) {
22562
			// we are not inside a template
22563
			return false;
22564
		}
22565
		$this->inxobj = false;
22566
		// restore previous graphic state
22567
		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22568
		return $this->xobjid;
22569
	}
22570
 
22571
	/**
22572
	 * Print an XObject Template.
22573
	 * You can print an XObject Template inside the currently opened Template.
22574
	 * 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).
22575
	 * 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.
22576
	 * @param string $id The ID of XObject Template to print.
22577
	 * @param float|null $x X position in user units (empty string = current x position)
22578
	 * @param float|null $y Y position in user units (empty string = current y position)
22579
	 * @param float $w Width in user units (zero = remaining page width)
22580
	 * @param float $h Height in user units (zero = remaining page height)
22581
	 * @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>
22582
	 * @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>
22583
	 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22584
	 * @author Nicola Asuni
22585
	 * @public
22586
	 * @since 5.8.017 (2010-08-24)
22587
	 * @see startTemplate(), endTemplate()
22588
	 */
22589
	public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22590
		if ($this->state != 2) {
22591
			 return;
22592
		}
22593
		if (!isset($this->xobjects[$id])) {
22594
			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22595
		}
22596
		if ($this->inxobj) {
22597
			if ($id == $this->xobjid) {
22598
				// close current template
22599
				$this->endTemplate();
22600
			} else {
22601
				// use the template as resource for the template currently opened
22602
				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22603
			}
22604
		}
22605
		// set default values
22606
		if (TCPDF_STATIC::empty_string($x)) {
22607
			$x = $this->x;
22608
		}
22609
		if (TCPDF_STATIC::empty_string($y)) {
22610
			$y = $this->y;
22611
		}
22612
		// check page for no-write regions and adapt page margins if necessary
22613
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22614
		$ow = $this->xobjects[$id]['w'];
22615
		if ($ow <= 0) {
22616
			$ow = 1;
22617
		}
22618
		$oh = $this->xobjects[$id]['h'];
22619
		if ($oh <= 0) {
22620
			$oh = 1;
22621
		}
22622
		// calculate template width and height on document
22623
		if (($w <= 0) AND ($h <= 0)) {
22624
			$w = $ow;
22625
			$h = $oh;
22626
		} elseif ($w <= 0) {
22627
			$w = $h * $ow / $oh;
22628
		} elseif ($h <= 0) {
22629
			$h = $w * $oh / $ow;
22630
		}
22631
		// fit the template on available space
22632
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22633
		// set page alignment
22634
		$rb_y = $y + $h;
22635
		// set alignment
22636
		if ($this->rtl) {
22637
			if ($palign == 'L') {
22638
				$xt = $this->lMargin;
22639
			} elseif ($palign == 'C') {
22640
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22641
			} elseif ($palign == 'R') {
22642
				$xt = $this->w - $this->rMargin - $w;
22643
			} else {
22644
				$xt = $x - $w;
22645
			}
22646
			$rb_x = $xt;
22647
		} else {
22648
			if ($palign == 'L') {
22649
				$xt = $this->lMargin;
22650
			} elseif ($palign == 'C') {
22651
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22652
			} elseif ($palign == 'R') {
22653
				$xt = $this->w - $this->rMargin - $w;
22654
			} else {
22655
				$xt = $x;
22656
			}
22657
			$rb_x = $xt + $w;
22658
		}
22659
		// print XObject Template + Transformation matrix
22660
		$this->StartTransform();
22661
		// translate and scale
22662
		$sx = ($w / $ow);
22663
		$sy = ($h / $oh);
22664
		$tm = array();
22665
		$tm[0] = $sx;
22666
		$tm[1] = 0;
22667
		$tm[2] = 0;
22668
		$tm[3] = $sy;
22669
		$tm[4] = $xt * $this->k;
22670
		$tm[5] = ($this->h - $h - $y) * $this->k;
22671
		$this->Transform($tm);
22672
		// set object
22673
		$this->_out('/'.$id.' Do');
22674
		$this->StopTransform();
22675
		// add annotations
22676
		if (!empty($this->xobjects[$id]['annotations'])) {
22677
			foreach ($this->xobjects[$id]['annotations'] as $annot) {
22678
				// transform original coordinates
22679
				$coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22680
				$ax = ($coordlt[4] / $this->k);
22681
				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
22682
				$coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22683
				$aw = ($coordrb[4] / $this->k) - $ax;
22684
				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22685
				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22686
			}
22687
		}
22688
		// set pointer to align the next text/objects
22689
		switch($align) {
22690
			case 'T': {
22691
				$this->y = $y;
22692
				$this->x = $rb_x;
22693
				break;
22694
			}
22695
			case 'M': {
22696
				$this->y = $y + round($h/2);
22697
				$this->x = $rb_x;
22698
				break;
22699
			}
22700
			case 'B': {
22701
				$this->y = $rb_y;
22702
				$this->x = $rb_x;
22703
				break;
22704
			}
22705
			case 'N': {
22706
				$this->setY($rb_y);
22707
				break;
22708
			}
22709
			default:{
22710
				break;
22711
			}
22712
		}
22713
	}
22714
 
22715
	/**
22716
	 * Set the percentage of character stretching.
22717
	 * @param int $perc percentage of stretching (100 = no stretching)
22718
	 * @author Nicola Asuni
22719
	 * @public
22720
	 * @since 5.9.000 (2010-09-29)
22721
	 */
22722
	public function setFontStretching($perc=100) {
22723
		$this->font_stretching = $perc;
22724
	}
22725
 
22726
	/**
22727
	 * Get the percentage of character stretching.
22728
	 * @return float stretching value
22729
	 * @author Nicola Asuni
22730
	 * @public
22731
	 * @since 5.9.000 (2010-09-29)
22732
	 */
22733
	public function getFontStretching() {
22734
		return $this->font_stretching;
22735
	}
22736
 
22737
	/**
22738
	 * Set the amount to increase or decrease the space between characters in a text.
22739
	 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22740
	 * @author Nicola Asuni
22741
	 * @public
22742
	 * @since 5.9.000 (2010-09-29)
22743
	 */
22744
	public function setFontSpacing($spacing=0) {
22745
		$this->font_spacing = $spacing;
22746
	}
22747
 
22748
	/**
22749
	 * Get the amount to increase or decrease the space between characters in a text.
22750
	 * @return int font spacing (tracking) value
22751
	 * @author Nicola Asuni
22752
	 * @public
22753
	 * @since 5.9.000 (2010-09-29)
22754
	 */
22755
	public function getFontSpacing() {
22756
		return $this->font_spacing;
22757
	}
22758
 
22759
	/**
22760
	 * Return an array of no-write page regions
22761
	 * @return array of no-write page regions
22762
	 * @author Nicola Asuni
22763
	 * @public
22764
	 * @since 5.9.003 (2010-10-13)
22765
	 * @see setPageRegions(), addPageRegion()
22766
	 */
22767
	public function getPageRegions() {
22768
		return $this->page_regions;
22769
	}
22770
 
22771
	/**
22772
	 * Set no-write regions on page.
22773
	 * 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.
22774
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22775
	 * You can set multiple regions for the same page.
22776
	 * @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.
22777
	 * @author Nicola Asuni
22778
	 * @public
22779
	 * @since 5.9.003 (2010-10-13)
22780
	 * @see addPageRegion(), getPageRegions()
22781
	 */
22782
	public function setPageRegions($regions=array()) {
22783
		// empty current regions array
22784
		$this->page_regions = array();
22785
		// add regions
22786
		foreach ($regions as $data) {
22787
			$this->addPageRegion($data);
22788
		}
22789
	}
22790
 
22791
	/**
22792
	 * Add a single no-write region on selected page.
22793
	 * 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.
22794
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22795
	 * You can set multiple regions for the same page.
22796
	 * @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).
22797
	 * @author Nicola Asuni
22798
	 * @public
22799
	 * @since 5.9.003 (2010-10-13)
22800
	 * @see setPageRegions(), getPageRegions()
22801
	 */
22802
	public function addPageRegion($region) {
22803
		if (!isset($region['page']) OR empty($region['page'])) {
22804
			$region['page'] = $this->page;
22805
		}
22806
		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22807
			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22808
			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22809
			$this->page_regions[] = $region;
22810
		}
22811
	}
22812
 
22813
	/**
22814
	 * Remove a single no-write region.
22815
	 * @param int $key region key
22816
	 * @author Nicola Asuni
22817
	 * @public
22818
	 * @since 5.9.003 (2010-10-13)
22819
	 * @see setPageRegions(), getPageRegions()
22820
	 */
22821
	public function removePageRegion($key) {
22822
		if (isset($this->page_regions[$key])) {
22823
			unset($this->page_regions[$key]);
22824
		}
22825
	}
22826
 
22827
	/**
22828
	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22829
	 * 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.
22830
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22831
	 * @param float $h height of the text/image/object to print in user units
22832
	 * @param float $x current X coordinate in user units
22833
	 * @param float $y current Y coordinate in user units
22834
	 * @return float[] array($x, $y)
22835
	 * @author Nicola Asuni
22836
	 * @protected
22837
	 * @since 5.9.003 (2010-10-13)
22838
	 */
22839
	protected function checkPageRegions($h, $x, $y) {
22840
		// set default values
22841
		if ($x === '') {
22842
			$x = $this->x;
22843
		}
22844
		if ($y === '') {
22845
			$y = $this->y;
22846
		}
22847
		if (!$this->check_page_regions OR empty($this->page_regions)) {
22848
			// no page regions defined
22849
			return array($x, $y);
22850
		}
22851
		if (empty($h)) {
22852
			$h = $this->getCellHeight($this->FontSize);
22853
		}
22854
		// check for page break
22855
		if ($this->checkPageBreak($h, $y)) {
22856
			// the content will be printed on a new page
22857
			$x = $this->x;
22858
			$y = $this->y;
22859
		}
22860
		if ($this->num_columns > 1) {
22861
			if ($this->rtl) {
22862
				$this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22863
			} else {
22864
				$this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22865
			}
22866
		} else {
22867
			if ($this->rtl) {
22868
				$this->lMargin = max($this->clMargin, $this->original_lMargin);
22869
			} else {
22870
				$this->rMargin = max($this->crMargin, $this->original_rMargin);
22871
			}
22872
		}
22873
		// adjust coordinates and page margins
22874
		foreach ($this->page_regions as $regid => $regdata) {
22875
			if ($regdata['page'] == $this->page) {
22876
				// check region boundaries
22877
				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22878
					// Y is inside the region
22879
					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22880
					$yt = max($y, $regdata['yt']);
22881
					$yb = min(($yt + $h), $regdata['yb']);
22882
					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22883
					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22884
					if ($regdata['side'] == 'L') { // left side
22885
						$new_margin = max($xt, $xb);
22886
						if ($this->lMargin < $new_margin) {
22887
							if ($this->rtl) {
22888
								// adjust left page margin
22889
								$this->lMargin = max(0, $new_margin);
22890
							}
22891
							if ($x < $new_margin) {
22892
								// adjust x position
22893
								$x = $new_margin;
22894
								if ($new_margin > ($this->w - $this->rMargin)) {
22895
									// adjust y position
22896
									$y = $regdata['yb'] - $h;
22897
								}
22898
							}
22899
						}
22900
					} elseif ($regdata['side'] == 'R') { // right side
22901
						$new_margin = min($xt, $xb);
22902
						if (($this->w - $this->rMargin) > $new_margin) {
22903
							if (!$this->rtl) {
22904
								// adjust right page margin
22905
								$this->rMargin = max(0, ($this->w - $new_margin));
22906
							}
22907
							if ($x > $new_margin) {
22908
								// adjust x position
22909
								$x = $new_margin;
22910
								if ($new_margin > $this->lMargin) {
22911
									// adjust y position
22912
									$y = $regdata['yb'] - $h;
22913
								}
22914
							}
22915
						}
22916
					}
22917
				}
22918
			}
22919
		}
22920
		return array($x, $y);
22921
	}
22922
 
22923
	// --- SVG METHODS ---------------------------------------------------------
22924
 
22925
	/**
22926
	 * Embedd a Scalable Vector Graphics (SVG) image.
22927
	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22928
	 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
22929
	 * @param float|null $x Abscissa of the upper-left corner.
22930
	 * @param float|null $y Ordinate of the upper-left corner.
22931
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22932
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22933
	 * @param mixed $link URL or identifier returned by AddLink().
22934
	 * @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.
22935
	 * @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>
22936
	 * @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)))
22937
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
22938
	 * @author Nicola Asuni
22939
	 * @since 5.0.000 (2010-05-02)
22940
	 * @public
22941
	 */
22942
	public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22943
		if ($this->state != 2) {
22944
			 return;
22945
		}
22946
		// reset SVG vars
22947
		$this->svggradients = array();
22948
		$this->svggradientid = 0;
22949
		$this->svgdefsmode = false;
22950
		$this->svgdefs = array();
22951
		$this->svgclipmode = false;
22952
		$this->svgclippaths = array();
22953
		$this->svgcliptm = array();
22954
		$this->svgclipid = 0;
22955
		$this->svgtext = '';
22956
		$this->svgtextmode = array();
22957
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22958
			// convert SVG to raster image using GD or ImageMagick libraries
22959
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22960
		}
22961
		if ($file[0] === '@') { // image from string
22962
			$this->svgdir = '';
22963
			$svgdata = substr($file, 1);
22964
		} else { // SVG file
22965
			$this->svgdir = dirname($file);
22966
            $svgdata = $this->getCachedFileContents($file);
22967
		}
22968
		if ($svgdata === FALSE) {
22969
			$this->Error('SVG file not found: '.$file);
22970
		}
22971
		if (TCPDF_STATIC::empty_string($x)) {
22972
			$x = $this->x;
22973
		}
22974
		if (TCPDF_STATIC::empty_string($y)) {
22975
			$y = $this->y;
22976
		}
22977
		// check page for no-write regions and adapt page margins if necessary
22978
		list($x, $y) = $this->checkPageRegions($h, $x, $y);
22979
		$k = $this->k;
22980
		$ox = 0;
22981
		$oy = 0;
22982
		$ow = $w;
22983
		$oh = $h;
22984
		$aspect_ratio_align = 'xMidYMid';
22985
		$aspect_ratio_ms = 'meet';
22986
		$regs = array();
22987
		// get original image width and height
22988
		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22989
		if (isset($regs[1]) AND !empty($regs[1])) {
22990
			$tmp = array();
22991
			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22992
				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22993
			}
22994
			$tmp = array();
22995
			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22996
				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22997
			}
22998
			$tmp = array();
22999
			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23000
				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23001
			}
23002
			$tmp = array();
23003
			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23004
				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23005
			}
23006
			$tmp = array();
23007
			$view_box = array();
23008
			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
23009
				if (count($tmp) == 5) {
23010
					array_shift($tmp);
23011
					foreach ($tmp as $key => $val) {
23012
						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
23013
					}
23014
					$ox = $view_box[0];
23015
					$oy = $view_box[1];
23016
				}
23017
				// get aspect ratio
23018
				$tmp = array();
23019
				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23020
					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
23021
					switch (count($aspect_ratio)) {
23022
						case 3: {
23023
							$aspect_ratio_align = $aspect_ratio[1];
23024
							$aspect_ratio_ms = $aspect_ratio[2];
23025
							break;
23026
						}
23027
						case 2: {
23028
							$aspect_ratio_align = $aspect_ratio[0];
23029
							$aspect_ratio_ms = $aspect_ratio[1];
23030
							break;
23031
						}
23032
						case 1: {
23033
							$aspect_ratio_align = $aspect_ratio[0];
23034
							$aspect_ratio_ms = 'meet';
23035
							break;
23036
						}
23037
					}
23038
				}
23039
			}
23040
		}
23041
		if ($ow <= 0) {
23042
			$ow = 1;
23043
		}
23044
		if ($oh <= 0) {
23045
			$oh = 1;
23046
		}
23047
		// calculate image width and height on document
23048
		if (($w <= 0) AND ($h <= 0)) {
23049
			// convert image size to document unit
23050
			$w = $ow;
23051
			$h = $oh;
23052
		} elseif ($w <= 0) {
23053
			$w = $h * $ow / $oh;
23054
		} elseif ($h <= 0) {
23055
			$h = $w * $oh / $ow;
23056
		}
23057
		// fit the image on available space
23058
		list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23059
		if ($this->rasterize_vector_images) {
23060
			// convert SVG to raster image using GD or ImageMagick libraries
23061
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23062
		}
23063
		// set alignment
23064
		$this->img_rb_y = $y + $h;
23065
		// set alignment
23066
		if ($this->rtl) {
23067
			if ($palign == 'L') {
23068
				$ximg = $this->lMargin;
23069
			} elseif ($palign == 'C') {
23070
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23071
			} elseif ($palign == 'R') {
23072
				$ximg = $this->w - $this->rMargin - $w;
23073
			} else {
23074
				$ximg = $x - $w;
23075
			}
23076
			$this->img_rb_x = $ximg;
23077
		} else {
23078
			if ($palign == 'L') {
23079
				$ximg = $this->lMargin;
23080
			} elseif ($palign == 'C') {
23081
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23082
			} elseif ($palign == 'R') {
23083
				$ximg = $this->w - $this->rMargin - $w;
23084
			} else {
23085
				$ximg = $x;
23086
			}
23087
			$this->img_rb_x = $ximg + $w;
23088
		}
23089
		// store current graphic vars
23090
		$gvars = $this->getGraphicVars();
23091
		// store SVG position and scale factors
23092
		$svgoffset_x = ($ximg - $ox) * $this->k;
23093
		$svgoffset_y = -($y - $oy) * $this->k;
23094
		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23095
			$ow = $view_box[2];
23096
			$oh = $view_box[3];
23097
		} else {
23098
			if ($ow <= 0) {
23099
				$ow = $w;
23100
			}
23101
			if ($oh <= 0) {
23102
				$oh = $h;
23103
			}
23104
		}
23105
		$svgscale_x = $w / $ow;
23106
		$svgscale_y = $h / $oh;
23107
		// scaling and alignment
23108
		if ($aspect_ratio_align != 'none') {
23109
			// store current scaling values
23110
			$svgscale_old_x = $svgscale_x;
23111
			$svgscale_old_y = $svgscale_y;
23112
			// force uniform scaling
23113
			if ($aspect_ratio_ms == 'slice') {
23114
				// the entire viewport is covered by the viewBox
23115
				if ($svgscale_x > $svgscale_y) {
23116
					$svgscale_y = $svgscale_x;
23117
				} elseif ($svgscale_x < $svgscale_y) {
23118
					$svgscale_x = $svgscale_y;
23119
				}
23120
			} else { // meet
23121
				// the entire viewBox is visible within the viewport
23122
				if ($svgscale_x < $svgscale_y) {
23123
					$svgscale_y = $svgscale_x;
23124
				} elseif ($svgscale_x > $svgscale_y) {
23125
					$svgscale_x = $svgscale_y;
23126
				}
23127
			}
23128
			// correct X alignment
23129
			switch (substr($aspect_ratio_align, 1, 3)) {
23130
				case 'Min': {
23131
					// do nothing
23132
					break;
23133
				}
23134
				case 'Max': {
23135
					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23136
					break;
23137
				}
23138
				default:
23139
				case 'Mid': {
23140
					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23141
					break;
23142
				}
23143
			}
23144
			// correct Y alignment
23145
			switch (substr($aspect_ratio_align, 5)) {
23146
				case 'Min': {
23147
					// do nothing
23148
					break;
23149
				}
23150
				case 'Max': {
23151
					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23152
					break;
23153
				}
23154
				default:
23155
				case 'Mid': {
23156
					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23157
					break;
23158
				}
23159
			}
23160
		}
23161
		// store current page break mode
23162
		$page_break_mode = $this->AutoPageBreak;
23163
		$page_break_margin = $this->getBreakMargin();
23164
		$cell_padding = $this->cell_padding;
23165
		$this->setCellPadding(0);
23166
		$this->setAutoPageBreak(false);
23167
		// save the current graphic state
23168
		$this->_out('q'.$this->epsmarker);
23169
		// set initial clipping mask
23170
		$this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23171
		// scale and translate
23172
		$e = $ox * $this->k * (1 - $svgscale_x);
23173
		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23174
		$this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23175
		// creates a new XML parser to be used by the other XML functions
23176
		$parser = xml_parser_create('UTF-8');
23177
		// disable case-folding for this XML parser
23178
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23179
		// sets the element handler functions for the XML parser
1441 ariadna 23180
		xml_set_element_handler($parser, [$this, 'startSVGElementHandler'], [$this, 'endSVGElementHandler']);
1 efrain 23181
		// sets the character data handler function for the XML parser
1441 ariadna 23182
		xml_set_character_data_handler($parser, [$this, 'segSVGContentHandler']);
1 efrain 23183
		// start parsing an XML document
23184
		if (!xml_parse($parser, $svgdata)) {
23185
			$error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23186
			$this->Error($error_message);
23187
		}
23188
		// free this XML parser
23189
		xml_parser_free($parser);
23190
 
23191
		// >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23192
		unset($parser);
23193
 
23194
		// restore previous graphic state
23195
		$this->_out($this->epsmarker.'Q');
23196
		// restore graphic vars
23197
		$this->setGraphicVars($gvars);
23198
		$this->lasth = $gvars['lasth'];
23199
		if (!empty($border)) {
23200
			$bx = $this->x;
23201
			$by = $this->y;
23202
			$this->x = $ximg;
23203
			if ($this->rtl) {
23204
				$this->x += $w;
23205
			}
23206
			$this->y = $y;
23207
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23208
			$this->x = $bx;
23209
			$this->y = $by;
23210
		}
23211
		if ($link) {
23212
			$this->Link($ximg, $y, $w, $h, $link, 0);
23213
		}
23214
		// set pointer to align the next text/objects
23215
		switch($align) {
23216
			case 'T':{
23217
				$this->y = $y;
23218
				$this->x = $this->img_rb_x;
23219
				break;
23220
			}
23221
			case 'M':{
23222
				$this->y = $y + round($h/2);
23223
				$this->x = $this->img_rb_x;
23224
				break;
23225
			}
23226
			case 'B':{
23227
				$this->y = $this->img_rb_y;
23228
				$this->x = $this->img_rb_x;
23229
				break;
23230
			}
23231
			case 'N':{
23232
				$this->setY($this->img_rb_y);
23233
				break;
23234
			}
23235
			default:{
23236
				// restore pointer to starting position
23237
				$this->x = $gvars['x'];
23238
				$this->y = $gvars['y'];
23239
				$this->page = $gvars['page'];
23240
				$this->current_column = $gvars['current_column'];
23241
				$this->tMargin = $gvars['tMargin'];
23242
				$this->bMargin = $gvars['bMargin'];
23243
				$this->w = $gvars['w'];
23244
				$this->h = $gvars['h'];
23245
				$this->wPt = $gvars['wPt'];
23246
				$this->hPt = $gvars['hPt'];
23247
				$this->fwPt = $gvars['fwPt'];
23248
				$this->fhPt = $gvars['fhPt'];
23249
				break;
23250
			}
23251
		}
23252
		$this->endlinex = $this->img_rb_x;
23253
		// restore page break
23254
		$this->setAutoPageBreak($page_break_mode, $page_break_margin);
23255
		$this->cell_padding = $cell_padding;
23256
	}
23257
 
23258
	/**
23259
	 * Convert SVG transformation matrix to PDF.
23260
	 * @param array $tm original SVG transformation matrix
23261
	 * @return array transformation matrix
23262
	 * @protected
23263
	 * @since 5.0.000 (2010-05-02)
23264
	 */
23265
	protected function convertSVGtMatrix($tm) {
23266
		$a = $tm[0];
23267
		$b = -$tm[1];
23268
		$c = -$tm[2];
23269
		$d = $tm[3];
23270
		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23271
		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23272
		$x = 0;
23273
		$y = $this->h * $this->k;
23274
		$e = ($x * (1 - $a)) - ($y * $c) + $e;
23275
		$f = ($y * (1 - $d)) - ($x * $b) + $f;
23276
		return array($a, $b, $c, $d, $e, $f);
23277
	}
23278
 
23279
	/**
23280
	 * Apply SVG graphic transformation matrix.
23281
	 * @param array $tm original SVG transformation matrix
23282
	 * @protected
23283
	 * @since 5.0.000 (2010-05-02)
23284
	 */
23285
	protected function SVGTransform($tm) {
23286
		$this->Transform($this->convertSVGtMatrix($tm));
23287
	}
23288
 
23289
	/**
23290
	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23291
	 * @param array $svgstyle array of SVG styles to apply
23292
	 * @param array $prevsvgstyle array of previous SVG style
23293
	 * @param int $x X origin of the bounding box
23294
	 * @param int $y Y origin of the bounding box
23295
	 * @param int $w width of the bounding box
23296
	 * @param int $h height of the bounding box
23297
	 * @param string $clip_function clip function
23298
	 * @param array $clip_params array of parameters for clipping function
23299
	 * @return string style
23300
	 * @author Nicola Asuni
23301
	 * @since 5.0.000 (2010-05-02)
23302
	 * @protected
23303
	 */
23304
	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23305
		if ($this->state != 2) {
23306
			 return;
23307
		}
23308
		$objstyle = '';
23309
		$minlen = (0.01 / $this->k); // minimum acceptable length
23310
		if (!isset($svgstyle['opacity'])) {
23311
			return $objstyle;
23312
		}
23313
		// clip-path
23314
		$regs = array();
23315
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23316
			$clip_path = $this->svgclippaths[$regs[1]];
23317
			foreach ($clip_path as $cp) {
23318
				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23319
			}
23320
		}
23321
		// opacity
23322
		if ($svgstyle['opacity'] != 1) {
23323
			$this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23324
		}
23325
		// color
23326
		$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23327
		$this->setFillColorArray($fill_color);
23328
		// text color
23329
		$text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23330
		$this->setTextColorArray($text_color);
23331
		// clip
1441 ariadna 23332
		if (preg_match('/rect\(([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)\)/si', $svgstyle['clip'], $regs)) {
1 efrain 23333
			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23334
			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23335
			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23336
			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23337
			$cx = $x + $left;
23338
			$cy = $y + $top;
23339
			$cw = $w - $left - $right;
23340
			$ch = $h - $top - $bottom;
23341
			if ($svgstyle['clip-rule'] == 'evenodd') {
23342
				$clip_rule = 'CNZ';
23343
			} else {
23344
				$clip_rule = 'CEO';
23345
			}
23346
			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23347
		}
23348
		// fill
23349
		$regs = array();
23350
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23351
			// gradient
23352
			$gradient = $this->svggradients[$regs[1]];
23353
			if (isset($gradient['xref'])) {
23354
				// reference to another gradient definition
23355
				$newgradient = $this->svggradients[$gradient['xref']];
23356
				$newgradient['coords'] = $gradient['coords'];
23357
				$newgradient['mode'] = $gradient['mode'];
23358
				$newgradient['type'] = $gradient['type'];
23359
				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
23360
				if (isset($gradient['gradientTransform'])) {
23361
					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
23362
				}
23363
				$gradient = $newgradient;
23364
			}
23365
			//save current Graphic State
23366
			$this->_outSaveGraphicsState();
23367
			//set clipping area
23368
			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23369
				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23370
				if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23371
					list($x, $y, $w, $h) = $bbox;
23372
				}
23373
			}
23374
			if ($gradient['mode'] == 'measure') {
23375
				if (!isset($gradient['coords'][4])) {
23376
					$gradient['coords'][4] = 0.5;
23377
				}
23378
				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23379
					$gtm = $gradient['gradientTransform'];
23380
					// apply transformation matrix
23381
					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23382
					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23383
					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23384
					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23385
					$r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23386
					$gradient['coords'][0] = $xa;
23387
					$gradient['coords'][1] = $ya;
23388
					$gradient['coords'][2] = $xb;
23389
					$gradient['coords'][3] = $yb;
23390
					$gradient['coords'][4] = $r;
23391
				}
23392
				// convert SVG coordinates to user units
23393
				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23394
				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23395
				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23396
				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23397
				$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23398
				if ($w <= $minlen) {
23399
					$w = $minlen;
23400
				}
23401
				if ($h <= $minlen) {
23402
					$h = $minlen;
23403
				}
23404
				// shift units
23405
				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23406
					// convert to SVG coordinate system
23407
					$gradient['coords'][0] += $x;
23408
					$gradient['coords'][1] += $y;
23409
					$gradient['coords'][2] += $x;
23410
					$gradient['coords'][3] += $y;
23411
				}
23412
				// calculate percentages
23413
				$gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23414
				$gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23415
				$gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23416
				$gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23417
				$gradient['coords'][4] /= $w;
23418
			} elseif ($gradient['mode'] == 'percentage') {
23419
				foreach($gradient['coords'] as $key => $val) {
23420
					$gradient['coords'][$key] = (intval($val) / 100);
23421
					if ($val < 0) {
23422
						$gradient['coords'][$key] = 0;
23423
					} elseif ($val > 1) {
23424
						$gradient['coords'][$key] = 1;
23425
					}
23426
				}
23427
			}
23428
			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23429
				// single color (no shading)
23430
				$gradient['coords'][0] = 1;
23431
				$gradient['coords'][1] = 0;
23432
				$gradient['coords'][2] = 0.999;
23433
				$gradient['coords'][3] = 0;
23434
			}
23435
			// swap Y coordinates
23436
			$tmp = $gradient['coords'][1];
23437
			$gradient['coords'][1] = $gradient['coords'][3];
23438
			$gradient['coords'][3] = $tmp;
23439
			// set transformation map for gradient
23440
			$cy = ($this->h - $y);
23441
			if ($gradient['type'] == 3) {
23442
				// circular gradient
23443
				$cy -= ($gradient['coords'][1] * ($w + $h));
23444
				$h = $w = max($w, $h);
23445
			} else {
23446
				$cy -= $h;
23447
			}
23448
			$this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
1441 ariadna 23449
			if ((is_array($gradient['stops']) || $gradient['stops'] instanceof Countable) && count($gradient['stops']) > 1) {
23450
				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops']);
1 efrain 23451
			}
23452
		} elseif ($svgstyle['fill'] != 'none') {
23453
			$fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23454
			if ($svgstyle['fill-opacity'] != 1) {
23455
				$this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23456
			}
23457
			$this->setFillColorArray($fill_color);
23458
			if ($svgstyle['fill-rule'] == 'evenodd') {
23459
				$objstyle .= 'F*';
23460
			} else {
23461
				$objstyle .= 'F';
23462
			}
23463
		}
23464
		// stroke
23465
		if ($svgstyle['stroke'] != 'none') {
23466
			if ($svgstyle['stroke-opacity'] != 1) {
23467
				$this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23468
			} elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23469
				$this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23470
			}
23471
			$stroke_style = array(
23472
				'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23473
				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23474
				'cap' => $svgstyle['stroke-linecap'],
23475
				'join' => $svgstyle['stroke-linejoin']
23476
				);
23477
			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23478
				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23479
			}
23480
			$this->setLineStyle($stroke_style);
23481
			$objstyle .= 'D';
23482
		}
23483
		// font
23484
		$regs = array();
23485
		if (!empty($svgstyle['font'])) {
23486
			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23487
				$font_family = $this->getFontFamilyName($regs[1]);
23488
			} else {
1441 ariadna 23489
				$font_family = $this->getFontFamilyName($svgstyle['font-family']);
1 efrain 23490
			}
23491
			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23492
				$font_size = trim($regs[1]);
23493
			} else {
23494
				$font_size = $svgstyle['font-size'];
23495
			}
23496
			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23497
				$font_style = trim($regs[1]);
23498
			} else {
23499
				$font_style = $svgstyle['font-style'];
23500
			}
23501
			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23502
				$font_weight = trim($regs[1]);
23503
			} else {
23504
				$font_weight = $svgstyle['font-weight'];
23505
			}
23506
			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23507
				$font_stretch = trim($regs[1]);
23508
			} else {
23509
				$font_stretch = $svgstyle['font-stretch'];
23510
			}
23511
			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23512
				$font_spacing = trim($regs[1]);
23513
			} else {
23514
				$font_spacing = $svgstyle['letter-spacing'];
23515
			}
23516
		} else {
23517
			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
23518
			$font_size = $svgstyle['font-size'];
23519
			$font_style = $svgstyle['font-style'];
23520
			$font_weight = $svgstyle['font-weight'];
23521
			$font_stretch = $svgstyle['font-stretch'];
23522
			$font_spacing = $svgstyle['letter-spacing'];
23523
		}
23524
		$font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23525
		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23526
		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23527
		switch ($font_style) {
23528
			case 'italic': {
23529
				$font_style = 'I';
23530
				break;
23531
			}
23532
			case 'oblique': {
23533
				$font_style = 'I';
23534
				break;
23535
			}
23536
			default:
23537
			case 'normal': {
23538
				$font_style = '';
23539
				break;
23540
			}
23541
		}
23542
		switch ($font_weight) {
23543
			case 'bold':
23544
			case 'bolder': {
23545
				$font_style .= 'B';
23546
				break;
23547
			}
23548
			case 'normal': {
23549
				if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23550
					$font_family = substr($font_family, 0, -2).'I';
23551
				} elseif (substr($font_family, -1) == 'B') {
23552
					$font_family = substr($font_family, 0, -1);
23553
				}
23554
				break;
23555
			}
23556
		}
23557
		switch ($svgstyle['text-decoration']) {
23558
			case 'underline': {
23559
				$font_style .= 'U';
23560
				break;
23561
			}
23562
			case 'overline': {
23563
				$font_style .= 'O';
23564
				break;
23565
			}
23566
			case 'line-through': {
23567
				$font_style .= 'D';
23568
				break;
23569
			}
23570
			default:
23571
			case 'none': {
23572
				break;
23573
			}
23574
		}
23575
		$this->setFont($font_family, $font_style, $font_size);
23576
		$this->setFontStretching($font_stretch);
23577
		$this->setFontSpacing($font_spacing);
23578
		return $objstyle;
23579
	}
23580
 
23581
	/**
23582
	 * Draws an SVG path
23583
	 * @param string $d attribute d of the path SVG element
23584
	 * @param string $style Style of rendering. Possible values are:
23585
	 * <ul>
23586
	 *	 <li>D or empty string: Draw (default).</li>
23587
	 *	 <li>F: Fill.</li>
23588
	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23589
	 *	 <li>DF or FD: Draw and fill.</li>
23590
	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23591
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23592
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23593
	 * </ul>
23594
	 * @return array of container box measures (x, y, w, h)
23595
	 * @author Nicola Asuni
23596
	 * @since 5.0.000 (2010-05-02)
23597
	 * @protected
23598
	 */
23599
	protected function SVGPath($d, $style='') {
23600
		if ($this->state != 2) {
23601
			return;
23602
		}
23603
		// set fill/stroke style
23604
		$op = TCPDF_STATIC::getPathPaintOperator($style, '');
23605
		if (empty($op)) {
23606
			return;
23607
		}
23608
		$paths = array();
23609
		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23610
		$d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23611
		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23612
		$x = 0;
23613
		$y = 0;
23614
		$x1 = 0;
23615
		$y1 = 0;
23616
		$x2 = 0;
23617
		$y2 = 0;
23618
		$xmin = 2147483647;
23619
		$xmax = 0;
23620
		$ymin = 2147483647;
23621
		$ymax = 0;
23622
		$xinitial = 0;
23623
		$yinitial = 0;
23624
		$relcoord = false;
23625
		$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23626
		$firstcmd = true; // used to print first point
23627
		// draw curve pieces
23628
		foreach ($paths as $key => $val) {
23629
			// get curve type
23630
			$cmd = trim($val[1]);
23631
			if (strtolower($cmd) == $cmd) {
23632
				// use relative coordinated instead of absolute
23633
				$relcoord = true;
23634
				$xoffset = $x;
23635
				$yoffset = $y;
23636
			} else {
23637
				$relcoord = false;
23638
				$xoffset = 0;
23639
				$yoffset = 0;
23640
			}
23641
			$params = array();
23642
			if (isset($val[2])) {
23643
				// get curve parameters
1441 ariadna 23644
				preg_match_all('/-?\d*\.?\d+/', trim($val[2]), $matches);
23645
				$rawparams = $matches[0];
1 efrain 23646
				$params = array();
23647
				foreach ($rawparams as $ck => $cp) {
23648
					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23649
					if (abs($params[$ck]) < $minlen) {
23650
						// approximate little values to zero
23651
						$params[$ck] = 0;
23652
					}
23653
				}
23654
			}
23655
			// store current origin point
23656
			$x0 = $x;
23657
			$y0 = $y;
23658
			switch (strtoupper($cmd)) {
23659
				case 'M': { // moveto
23660
					foreach ($params as $ck => $cp) {
23661
						if (($ck % 2) == 0) {
23662
							$x = $cp + $xoffset;
23663
						} else {
23664
							$y = $cp + $yoffset;
23665
							if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23666
								if ($ck == 1) {
23667
									$this->_outPoint($x, $y);
23668
									$firstcmd = false;
23669
									$xinitial = $x;
23670
									$yinitial = $y;
23671
								} else {
23672
									$this->_outLine($x, $y);
23673
								}
23674
								$x0 = $x;
23675
								$y0 = $y;
23676
							}
23677
							$xmin = min($xmin, $x);
23678
							$ymin = min($ymin, $y);
23679
							$xmax = max($xmax, $x);
23680
							$ymax = max($ymax, $y);
23681
							if ($relcoord) {
23682
								$xoffset = $x;
23683
								$yoffset = $y;
23684
							}
23685
						}
23686
					}
23687
					break;
23688
				}
23689
				case 'L': { // lineto
23690
					foreach ($params as $ck => $cp) {
23691
						if (($ck % 2) == 0) {
23692
							$x = $cp + $xoffset;
23693
						} else {
23694
							$y = $cp + $yoffset;
23695
							if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23696
								$this->_outLine($x, $y);
23697
								$x0 = $x;
23698
								$y0 = $y;
23699
							}
23700
							$xmin = min($xmin, $x);
23701
							$ymin = min($ymin, $y);
23702
							$xmax = max($xmax, $x);
23703
							$ymax = max($ymax, $y);
23704
							if ($relcoord) {
23705
								$xoffset = $x;
23706
								$yoffset = $y;
23707
							}
23708
						}
23709
					}
23710
					break;
23711
				}
23712
				case 'H': { // horizontal lineto
23713
					foreach ($params as $ck => $cp) {
23714
						$x = $cp + $xoffset;
23715
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23716
							$this->_outLine($x, $y);
23717
							$x0 = $x;
23718
							$y0 = $y;
23719
						}
23720
						$xmin = min($xmin, $x);
23721
						$xmax = max($xmax, $x);
23722
						if ($relcoord) {
23723
							$xoffset = $x;
23724
						}
23725
					}
23726
					break;
23727
				}
23728
				case 'V': { // vertical lineto
23729
					foreach ($params as $ck => $cp) {
23730
						$y = $cp + $yoffset;
23731
						if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23732
							$this->_outLine($x, $y);
23733
							$x0 = $x;
23734
							$y0 = $y;
23735
						}
23736
						$ymin = min($ymin, $y);
23737
						$ymax = max($ymax, $y);
23738
						if ($relcoord) {
23739
							$yoffset = $y;
23740
						}
23741
					}
23742
					break;
23743
				}
23744
				case 'C': { // curveto
23745
					foreach ($params as $ck => $cp) {
23746
						$params[$ck] = $cp;
23747
						if ((($ck + 1) % 6) == 0) {
23748
							$x1 = $params[($ck - 5)] + $xoffset;
23749
							$y1 = $params[($ck - 4)] + $yoffset;
23750
							$x2 = $params[($ck - 3)] + $xoffset;
23751
							$y2 = $params[($ck - 2)] + $yoffset;
23752
							$x = $params[($ck - 1)] + $xoffset;
23753
							$y = $params[($ck)] + $yoffset;
23754
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23755
							$xmin = min($xmin, $x, $x1, $x2);
23756
							$ymin = min($ymin, $y, $y1, $y2);
23757
							$xmax = max($xmax, $x, $x1, $x2);
23758
							$ymax = max($ymax, $y, $y1, $y2);
23759
							if ($relcoord) {
23760
								$xoffset = $x;
23761
								$yoffset = $y;
23762
							}
23763
						}
23764
					}
23765
					break;
23766
				}
23767
				case 'S': { // shorthand/smooth curveto
23768
					foreach ($params as $ck => $cp) {
23769
						$params[$ck] = $cp;
23770
						if ((($ck + 1) % 4) == 0) {
23771
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23772
								$x1 = (2 * $x) - $x2;
23773
								$y1 = (2 * $y) - $y2;
23774
							} else {
23775
								$x1 = $x;
23776
								$y1 = $y;
23777
							}
23778
							$x2 = $params[($ck - 3)] + $xoffset;
23779
							$y2 = $params[($ck - 2)] + $yoffset;
23780
							$x = $params[($ck - 1)] + $xoffset;
23781
							$y = $params[($ck)] + $yoffset;
23782
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23783
							$xmin = min($xmin, $x, $x1, $x2);
23784
							$ymin = min($ymin, $y, $y1, $y2);
23785
							$xmax = max($xmax, $x, $x1, $x2);
23786
							$ymax = max($ymax, $y, $y1, $y2);
23787
							if ($relcoord) {
23788
								$xoffset = $x;
23789
								$yoffset = $y;
23790
							}
23791
						}
23792
					}
23793
					break;
23794
				}
23795
				case 'Q': { // quadratic Bezier curveto
23796
					foreach ($params as $ck => $cp) {
23797
						$params[$ck] = $cp;
23798
						if ((($ck + 1) % 4) == 0) {
23799
							// convert quadratic points to cubic points
23800
							$x1 = $params[($ck - 3)] + $xoffset;
23801
							$y1 = $params[($ck - 2)] + $yoffset;
23802
							$xa = ($x + (2 * $x1)) / 3;
23803
							$ya = ($y + (2 * $y1)) / 3;
23804
							$x = $params[($ck - 1)] + $xoffset;
23805
							$y = $params[($ck)] + $yoffset;
23806
							$xb = ($x + (2 * $x1)) / 3;
23807
							$yb = ($y + (2 * $y1)) / 3;
23808
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23809
							$xmin = min($xmin, $x, $xa, $xb);
23810
							$ymin = min($ymin, $y, $ya, $yb);
23811
							$xmax = max($xmax, $x, $xa, $xb);
23812
							$ymax = max($ymax, $y, $ya, $yb);
23813
							if ($relcoord) {
23814
								$xoffset = $x;
23815
								$yoffset = $y;
23816
							}
23817
						}
23818
					}
23819
					break;
23820
				}
23821
				case 'T': { // shorthand/smooth quadratic Bezier curveto
23822
					foreach ($params as $ck => $cp) {
23823
						$params[$ck] = $cp;
23824
						if (($ck % 2) != 0) {
23825
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23826
								$x1 = (2 * $x) - $x1;
23827
								$y1 = (2 * $y) - $y1;
23828
							} else {
23829
								$x1 = $x;
23830
								$y1 = $y;
23831
							}
23832
							// convert quadratic points to cubic points
23833
							$xa = ($x + (2 * $x1)) / 3;
23834
							$ya = ($y + (2 * $y1)) / 3;
23835
							$x = $params[($ck - 1)] + $xoffset;
23836
							$y = $params[($ck)] + $yoffset;
23837
							$xb = ($x + (2 * $x1)) / 3;
23838
							$yb = ($y + (2 * $y1)) / 3;
23839
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23840
							$xmin = min($xmin, $x, $xa, $xb);
23841
							$ymin = min($ymin, $y, $ya, $yb);
23842
							$xmax = max($xmax, $x, $xa, $xb);
23843
							$ymax = max($ymax, $y, $ya, $yb);
23844
							if ($relcoord) {
23845
								$xoffset = $x;
23846
								$yoffset = $y;
23847
							}
23848
						}
23849
					}
23850
					break;
23851
				}
23852
				case 'A': { // elliptical arc
23853
					foreach ($params as $ck => $cp) {
23854
						$params[$ck] = $cp;
23855
						if ((($ck + 1) % 7) == 0) {
23856
							$x0 = $x;
23857
							$y0 = $y;
23858
							$rx = max(abs($params[($ck - 6)]), .000000001);
23859
							$ry = max(abs($params[($ck - 5)]), .000000001);
23860
							$ang = -$rawparams[($ck - 4)];
23861
							$angle = deg2rad($ang);
23862
							$fa = $rawparams[($ck - 3)]; // large-arc-flag
23863
							$fs = $rawparams[($ck - 2)]; // sweep-flag
23864
							$x = $params[($ck - 1)] + $xoffset;
23865
							$y = $params[$ck] + $yoffset;
23866
							if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23867
								// endpoints are almost identical
23868
								$xmin = min($xmin, $x);
23869
								$ymin = min($ymin, $y);
23870
								$xmax = max($xmax, $x);
23871
								$ymax = max($ymax, $y);
23872
							} else {
23873
								$cos_ang = cos($angle);
23874
								$sin_ang = sin($angle);
23875
								$a = (($x0 - $x) / 2);
23876
								$b = (($y0 - $y) / 2);
23877
								$xa = ($a * $cos_ang) - ($b * $sin_ang);
23878
								$ya = ($a * $sin_ang) + ($b * $cos_ang);
23879
								$rx2 = $rx * $rx;
23880
								$ry2 = $ry * $ry;
23881
								$xa2 = $xa * $xa;
23882
								$ya2 = $ya * $ya;
23883
								$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23884
								if ($delta > 1) {
23885
									$rx *= sqrt($delta);
23886
									$ry *= sqrt($delta);
23887
									$rx2 = $rx * $rx;
23888
									$ry2 = $ry * $ry;
23889
								}
23890
								$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23891
								if ($numerator < 0) {
23892
									$root = 0;
23893
								} else {
23894
									$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23895
								}
23896
								if ($fa == $fs){
23897
									$root *= -1;
23898
								}
23899
								$cax = $root * (($rx * $ya) / $ry);
23900
								$cay = -$root * (($ry * $xa) / $rx);
23901
								// coordinates of ellipse center
23902
								$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23903
								$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23904
								// get angles
23905
								$angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23906
								$dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23907
								if (($fs == 0) AND ($dang > 0)) {
23908
									$dang -= (2 * M_PI);
23909
								} elseif (($fs == 1) AND ($dang < 0)) {
23910
									$dang += (2 * M_PI);
23911
								}
23912
								$angf = $angs - $dang;
23913
								if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23914
									// reverse angles
23915
									$tmp = $angs;
23916
									$angs = $angf;
23917
									$angf = $tmp;
23918
								}
23919
								$angs = round(rad2deg($angs), 6);
23920
								$angf = round(rad2deg($angf), 6);
23921
								// covent angles to positive values
23922
								if (($angs < 0) AND ($angf < 0)) {
23923
									$angs += 360;
23924
									$angf += 360;
23925
								}
23926
								$pie = false;
23927
								if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23928
									$pie = true;
23929
								}
23930
								list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23931
								$xmin = min($xmin, $x, $axmin);
23932
								$ymin = min($ymin, $y, $aymin);
23933
								$xmax = max($xmax, $x, $axmax);
23934
								$ymax = max($ymax, $y, $aymax);
23935
							}
23936
							if ($relcoord) {
23937
								$xoffset = $x;
23938
								$yoffset = $y;
23939
							}
23940
						}
23941
					}
23942
					break;
23943
				}
23944
				case 'Z': {
23945
					$this->_out('h');
23946
					$x = $x0 = $xinitial;
23947
					$y = $y0 = $yinitial;
23948
					break;
23949
				}
23950
			}
23951
			$firstcmd = false;
23952
		} // end foreach
23953
		$this->_out($op);
23954
		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23955
	}
23956
 
23957
	/**
23958
	 * Return the tag name without the namespace
23959
	 * @param string $name Tag name
23960
	 * @protected
23961
	 */
23962
	protected function removeTagNamespace($name) {
23963
		if(strpos($name, ':') !== false) {
23964
			$parts = explode(':', $name);
23965
			return $parts[(sizeof($parts) - 1)];
23966
		}
23967
		return $name;
23968
	}
23969
 
23970
	/**
23971
	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23972
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
23973
	 * @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.
23974
	 * @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.
23975
	 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
23976
	 * @author Nicola Asuni
23977
	 * @since 5.0.000 (2010-05-02)
23978
	 * @protected
23979
	 */
23980
	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23981
		$name = $this->removeTagNamespace($name);
23982
		// check if we are in clip mode
23983
		if ($this->svgclipmode) {
23984
			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23985
			return;
23986
		}
23987
		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23988
			if (isset($attribs['id'])) {
23989
				$attribs['child_elements'] = array();
23990
				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23991
				return;
23992
			}
23993
			if (end($this->svgdefs) !== FALSE) {
23994
				$last_svgdefs_id = key($this->svgdefs);
23995
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23996
					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23997
					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23998
					return;
23999
				}
24000
			}
24001
			return;
24002
		}
24003
		$clipping = false;
24004
		if ($parser == 'clip-path') {
24005
			// set clipping mode
24006
			$clipping = true;
24007
		}
24008
		// get styling properties
24009
		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
24010
		$svgstyle = $this->svgstyles[0]; // set default style
24011
		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
24012
			// default fill attribute for clipping
24013
			$attribs['fill'] = 'none';
24014
		}
24015
		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
24016
			// fix style for regular expression
24017
			$attribs['style'] = ';'.$attribs['style'];
24018
		}
24019
		foreach ($prev_svgstyle as $key => $val) {
24020
			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
24021
				// inherit previous value
24022
				$svgstyle[$key] = $val;
24023
			}
24024
			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
24025
				// specific attribute settings
24026
				if ($attribs[$key] == 'inherit') {
24027
					$svgstyle[$key] = $val;
24028
				} else {
24029
					$svgstyle[$key] = $attribs[$key];
24030
				}
24031
			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
24032
				// CSS style syntax
24033
				$attrval = array();
24034
				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
24035
					if ($attrval[1] == 'inherit') {
24036
						$svgstyle[$key] = $val;
24037
					} else {
24038
						$svgstyle[$key] = $attrval[1];
24039
					}
24040
				}
24041
			}
24042
		}
24043
		// transformation matrix
24044
		if (!empty($ctm)) {
24045
			$tm = $ctm;
24046
		} else {
24047
			$tm = array(1,0,0,1,0,0);
24048
		}
24049
		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
24050
			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
24051
		}
24052
		$svgstyle['transfmatrix'] = $tm;
24053
		$invisible = false;
24054
		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
24055
			// the current graphics element is invisible (nothing is painted)
24056
			$invisible = true;
24057
		}
24058
		// process tag
24059
		switch($name) {
24060
			case 'defs': {
24061
				$this->svgdefsmode = true;
24062
				break;
24063
			}
24064
			// clipPath
24065
			case 'clipPath': {
24066
				if ($invisible) {
24067
					break;
24068
				}
24069
				$this->svgclipmode = true;
24070
				if (!isset($attribs['id'])) {
24071
					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
24072
				}
24073
				$this->svgclipid = $attribs['id'];
24074
				$this->svgclippaths[$this->svgclipid] = array();
24075
				$this->svgcliptm[$this->svgclipid] = $tm;
24076
				break;
24077
			}
24078
			case 'svg': {
24079
				// start of SVG object
24080
				if(++$this->svg_tag_depth <= 1) {
24081
					break;
24082
				}
24083
				// inner SVG
24084
				array_push($this->svgstyles, $svgstyle);
24085
				$this->StartTransform();
24086
				$svgX = (isset($attribs['x'])?$attribs['x']:0);
24087
				$svgY = (isset($attribs['y'])?$attribs['y']:0);
24088
				$svgW = (isset($attribs['width'])?$attribs['width']:0);
24089
				$svgH = (isset($attribs['height'])?$attribs['height']:0);
24090
				// set x, y position using transform matrix
24091
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24092
				$this->SVGTransform($tm);
24093
				// set clipping for width and height
24094
				$x = 0;
24095
				$y = 0;
24096
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
24097
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
24098
				// draw clipping rect
24099
				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24100
				// parse viewbox, calculate extra transformation matrix
24101
				if (isset($attribs['viewBox'])) {
24102
					$tmp = array();
24103
					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24104
					$tmp = $tmp[0];
24105
					if (sizeof($tmp) == 4) {
24106
						$vx = $tmp[0];
24107
						$vy = $tmp[1];
24108
						$vw = $tmp[2];
24109
						$vh = $tmp[3];
24110
						// get aspect ratio
24111
						$tmp = array();
24112
						$aspectX = 'xMid';
24113
						$aspectY = 'YMid';
24114
						$fit = 'meet';
24115
						if (isset($attribs['preserveAspectRatio'])) {
24116
							if($attribs['preserveAspectRatio'] == 'none') {
24117
								$fit = 'none';
24118
							} else {
24119
								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24120
								$tmp = $tmp[0];
24121
								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24122
									$aspectX = substr($tmp[0], 0, 4);
24123
									$aspectY = substr($tmp[0], 4, 4);
24124
									$fit = $tmp[1];
24125
								}
24126
							}
24127
						}
24128
						$wr = ($svgW / $vw);
24129
						$hr = ($svgH / $vh);
24130
						$ax = $ay = 0;
24131
						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24132
							if ($aspectX == 'xMax') {
24133
								$ax = (($vw * ($wr / $hr)) - $vw);
24134
							}
24135
							if ($aspectX == 'xMid') {
24136
								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24137
							}
24138
							$wr = $hr;
24139
						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24140
							if ($aspectY == 'YMax') {
24141
								$ay = (($vh * ($hr / $wr)) - $vh);
24142
							}
24143
							if ($aspectY == 'YMid') {
24144
								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24145
							}
24146
							$hr = $wr;
24147
						}
24148
						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24149
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24150
						$this->SVGTransform($tm);
24151
					}
24152
				}
24153
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24154
				break;
24155
			}
24156
			case 'g': {
24157
				// group together related graphics elements
24158
				array_push($this->svgstyles, $svgstyle);
24159
				$this->StartTransform();
24160
				$x = (isset($attribs['x'])?$attribs['x']:0);
24161
				$y = (isset($attribs['y'])?$attribs['y']:0);
24162
				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24163
				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24164
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24165
				$this->SVGTransform($tm);
24166
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
24167
				break;
24168
			}
24169
			case 'linearGradient': {
24170
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24171
					break;
24172
				}
24173
				if (!isset($attribs['id'])) {
24174
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24175
				}
24176
				$this->svggradientid = $attribs['id'];
24177
				$this->svggradients[$this->svggradientid] = array();
24178
				$this->svggradients[$this->svggradientid]['type'] = 2;
24179
				$this->svggradients[$this->svggradientid]['stops'] = array();
24180
				if (isset($attribs['gradientUnits'])) {
24181
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24182
				} else {
24183
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24184
				}
24185
				//$attribs['spreadMethod']
24186
				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24187
					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24188
						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24189
						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24190
						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24191
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24192
				} else {
24193
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24194
				}
24195
				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24196
				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24197
				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24198
				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24199
				if (isset($attribs['gradientTransform'])) {
24200
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24201
				}
24202
				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24203
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24204
					// gradient is defined on another place
24205
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24206
				}
24207
				break;
24208
			}
24209
			case 'radialGradient': {
24210
				if ($this->pdfa_mode && $this->pdfa_version < 2) {
24211
					break;
24212
				}
24213
				if (!isset($attribs['id'])) {
24214
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24215
				}
24216
				$this->svggradientid = $attribs['id'];
24217
				$this->svggradients[$this->svggradientid] = array();
24218
				$this->svggradients[$this->svggradientid]['type'] = 3;
24219
				$this->svggradients[$this->svggradientid]['stops'] = array();
24220
				if (isset($attribs['gradientUnits'])) {
24221
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24222
				} else {
24223
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24224
				}
24225
				//$attribs['spreadMethod']
24226
				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24227
					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24228
					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24229
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24230
				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24231
					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24232
				} else {
24233
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
24234
				}
24235
				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24236
				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24237
				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24238
				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24239
				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24240
				if (isset($attribs['gradientTransform'])) {
24241
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24242
				}
24243
				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24244
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24245
					// gradient is defined on another place
24246
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24247
				}
24248
				break;
24249
			}
24250
			case 'stop': {
24251
				// gradient stops
24252
				if (substr($attribs['offset'], -1) == '%') {
24253
					$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24254
				} else {
24255
					$offset = floatval($attribs['offset']);
24256
					if ($offset > 1) {
24257
						$offset /= 100;
24258
					}
24259
				}
24260
				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24261
				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24262
				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24263
				break;
24264
			}
24265
			// paths
24266
			case 'path': {
24267
				if ($invisible) {
24268
					break;
24269
				}
24270
				if (isset($attribs['d'])) {
24271
					$d = trim($attribs['d']);
24272
					if (!empty($d)) {
24273
						$x = (isset($attribs['x'])?$attribs['x']:0);
24274
						$y = (isset($attribs['y'])?$attribs['y']:0);
24275
						$w = (isset($attribs['width'])?$attribs['width']:1);
24276
						$h = (isset($attribs['height'])?$attribs['height']:1);
24277
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24278
						if ($clipping) {
24279
							$this->SVGTransform($tm);
24280
							$this->SVGPath($d, 'CNZ');
24281
						} else {
24282
							$this->StartTransform();
24283
							$this->SVGTransform($tm);
24284
							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24285
							if (!empty($obstyle)) {
24286
								$this->SVGPath($d, $obstyle);
24287
							}
24288
							$this->StopTransform();
24289
						}
24290
					}
24291
				}
24292
				break;
24293
			}
24294
			// shapes
24295
			case 'rect': {
24296
				if ($invisible) {
24297
					break;
24298
				}
24299
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24300
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24301
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24302
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24303
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24304
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24305
				if ($clipping) {
24306
					$this->SVGTransform($tm);
24307
					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24308
				} else {
24309
					$this->StartTransform();
24310
					$this->SVGTransform($tm);
24311
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24312
					if (!empty($obstyle)) {
24313
						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24314
					}
24315
					$this->StopTransform();
24316
				}
24317
				break;
24318
			}
24319
			case 'circle': {
24320
				if ($invisible) {
24321
					break;
24322
				}
24323
				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24324
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24325
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24326
				$x = ($cx - $r);
24327
				$y = ($cy - $r);
24328
				$w = (2 * $r);
24329
				$h = $w;
24330
				if ($clipping) {
24331
					$this->SVGTransform($tm);
24332
					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24333
				} else {
24334
					$this->StartTransform();
24335
					$this->SVGTransform($tm);
24336
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24337
					if (!empty($obstyle)) {
24338
						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24339
					}
24340
					$this->StopTransform();
24341
				}
24342
				break;
24343
			}
24344
			case 'ellipse': {
24345
				if ($invisible) {
24346
					break;
24347
				}
24348
				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24349
				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24350
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24351
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24352
				$x = ($cx - $rx);
24353
				$y = ($cy - $ry);
24354
				$w = (2 * $rx);
24355
				$h = (2 * $ry);
24356
				if ($clipping) {
24357
					$this->SVGTransform($tm);
24358
					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24359
				} else {
24360
					$this->StartTransform();
24361
					$this->SVGTransform($tm);
24362
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24363
					if (!empty($obstyle)) {
24364
						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24365
					}
24366
					$this->StopTransform();
24367
				}
24368
				break;
24369
			}
24370
			case 'line': {
24371
				if ($invisible) {
24372
					break;
24373
				}
24374
				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24375
				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24376
				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24377
				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24378
				$x = $x1;
24379
				$y = $y1;
24380
				$w = abs($x2 - $x1);
24381
				$h = abs($y2 - $y1);
24382
				if (!$clipping) {
24383
					$this->StartTransform();
24384
					$this->SVGTransform($tm);
24385
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24386
					$this->Line($x1, $y1, $x2, $y2);
24387
					$this->StopTransform();
24388
				}
24389
				break;
24390
			}
24391
			case 'polyline':
24392
			case 'polygon': {
24393
				if ($invisible) {
24394
					break;
24395
				}
24396
				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
24397
				$points = trim($points);
24398
				// note that point may use a complex syntax not covered here
24399
				$points = preg_split('/[\,\s]+/si', $points);
24400
				if (count($points) < 4) {
24401
					break;
24402
				}
24403
				$p = array();
24404
				$xmin = 2147483647;
24405
				$xmax = 0;
24406
				$ymin = 2147483647;
24407
				$ymax = 0;
24408
				foreach ($points as $key => $val) {
24409
					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24410
					if (($key % 2) == 0) {
24411
						// X coordinate
24412
						$xmin = min($xmin, $p[$key]);
24413
						$xmax = max($xmax, $p[$key]);
24414
					} else {
24415
						// Y coordinate
24416
						$ymin = min($ymin, $p[$key]);
24417
						$ymax = max($ymax, $p[$key]);
24418
					}
24419
				}
24420
				$x = $xmin;
24421
				$y = $ymin;
24422
				$w = ($xmax - $xmin);
24423
				$h = ($ymax - $ymin);
24424
				if ($name == 'polyline') {
24425
					$this->StartTransform();
24426
					$this->SVGTransform($tm);
24427
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24428
					if (!empty($obstyle)) {
24429
						$this->PolyLine($p, $obstyle, array(), array());
24430
					}
24431
					$this->StopTransform();
24432
				} else { // polygon
24433
					if ($clipping) {
24434
						$this->SVGTransform($tm);
24435
						$this->Polygon($p, 'CNZ', array(), array(), true);
24436
					} else {
24437
						$this->StartTransform();
24438
						$this->SVGTransform($tm);
24439
						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24440
						if (!empty($obstyle)) {
24441
							$this->Polygon($p, $obstyle, array(), array(), true);
24442
						}
24443
						$this->StopTransform();
24444
					}
24445
				}
24446
				break;
24447
			}
24448
			// image
24449
			case 'image': {
24450
				if ($invisible) {
24451
					break;
24452
				}
24453
				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24454
					break;
24455
				}
24456
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24457
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24458
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24459
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24460
				$img = $attribs['xlink:href'];
24461
				if (!$clipping) {
24462
					$this->StartTransform();
24463
					$this->SVGTransform($tm);
24464
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24465
					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24466
						// embedded image encoded as base64
24467
						$img = '@'.base64_decode(substr($img, strlen($m[0])));
24468
					} else {
24469
						// fix image path
1441 ariadna 24470
						if (strpos($img, '../') !== false) {
24471
							// accessing parent folders is not allowed
24472
							break;
24473
						}
1 efrain 24474
						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24475
							// replace relative path with full server path
24476
							$img = $this->svgdir.'/'.$img;
24477
						}
24478
						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24479
							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24480
							if (($findroot === false) OR ($findroot > 1)) {
24481
								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24482
									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24483
								} else {
24484
									$img = $_SERVER['DOCUMENT_ROOT'].$img;
24485
								}
24486
							}
24487
						}
24488
						$img = urldecode($img);
24489
						$testscrtype = @parse_url($img);
24490
						if (empty($testscrtype['query'])) {
24491
							// convert URL to server path
24492
							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24493
						} elseif (preg_match('|^https?://|', $img) !== 1) {
24494
							// convert server path to URL
24495
							$img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24496
						}
24497
					}
24498
					// get image type
24499
					$imgtype = TCPDF_IMAGES::getImageFileType($img);
24500
					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24501
						$this->ImageEps($img, $x, $y, $w, $h);
24502
					} elseif ($imgtype == 'svg') {
24503
						// store SVG vars
24504
						$svggradients = $this->svggradients;
24505
						$svggradientid = $this->svggradientid;
24506
						$svgdefsmode = $this->svgdefsmode;
24507
						$svgdefs = $this->svgdefs;
24508
						$svgclipmode = $this->svgclipmode;
24509
						$svgclippaths = $this->svgclippaths;
24510
						$svgcliptm = $this->svgcliptm;
24511
						$svgclipid = $this->svgclipid;
24512
						$svgtext = $this->svgtext;
24513
						$svgtextmode = $this->svgtextmode;
24514
						$this->ImageSVG($img, $x, $y, $w, $h);
24515
						// restore SVG vars
24516
						$this->svggradients = $svggradients;
24517
						$this->svggradientid = $svggradientid;
24518
						$this->svgdefsmode = $svgdefsmode;
24519
						$this->svgdefs = $svgdefs;
24520
						$this->svgclipmode = $svgclipmode;
24521
						$this->svgclippaths = $svgclippaths;
24522
						$this->svgcliptm = $svgcliptm;
24523
						$this->svgclipid = $svgclipid;
24524
						$this->svgtext = $svgtext;
24525
						$this->svgtextmode = $svgtextmode;
24526
					} else {
24527
						$this->Image($img, $x, $y, $w, $h);
24528
					}
24529
					$this->StopTransform();
24530
				}
24531
				break;
24532
			}
24533
			// text
24534
			case 'text':
24535
			case 'tspan': {
24536
				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24537
					// @TODO: unsupported feature
24538
				}
24539
				// only basic support - advanced features must be implemented
24540
				$this->svgtextmode['invisible'] = $invisible;
24541
				if ($invisible) {
24542
					break;
24543
				}
24544
				array_push($this->svgstyles, $svgstyle);
24545
				if (isset($attribs['x'])) {
24546
					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24547
				} elseif ($name == 'tspan') {
24548
					$x = $this->x;
24549
				} else {
24550
					$x = 0;
24551
				}
24552
				if (isset($attribs['dx'])) {
24553
					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24554
				}
24555
				if (isset($attribs['y'])) {
24556
					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24557
				} elseif ($name == 'tspan') {
24558
					$y = $this->y;
24559
				} else {
24560
					$y = 0;
24561
				}
24562
				if (isset($attribs['dy'])) {
24563
					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24564
				}
24565
				$svgstyle['text-color'] = $svgstyle['fill'];
24566
				$this->svgtext = '';
24567
				if (isset($svgstyle['text-anchor'])) {
24568
					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24569
				} else {
24570
					$this->svgtextmode['text-anchor'] = 'start';
24571
				}
24572
				if (isset($svgstyle['direction'])) {
24573
					if ($svgstyle['direction'] == 'rtl') {
24574
						$this->svgtextmode['rtl'] = true;
24575
					} else {
24576
						$this->svgtextmode['rtl'] = false;
24577
					}
24578
				} else {
24579
					$this->svgtextmode['rtl'] = false;
24580
				}
24581
				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24582
					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24583
				} else {
24584
					$this->svgtextmode['stroke'] = false;
24585
				}
24586
				$this->StartTransform();
24587
				$this->SVGTransform($tm);
24588
				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24589
				$this->x = $x;
24590
				$this->y = $y;
24591
				break;
24592
			}
24593
			// use
24594
			case 'use': {
24595
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24596
					$svgdefid = substr($attribs['xlink:href'], 1);
24597
					if (isset($this->svgdefs[$svgdefid])) {
24598
						$use = $this->svgdefs[$svgdefid];
24599
						if (isset($attribs['xlink:href'])) {
24600
							unset($attribs['xlink:href']);
24601
						}
24602
						if (isset($attribs['id'])) {
24603
							unset($attribs['id']);
24604
						}
24605
						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24606
							$attribs['x'] += $use['attribs']['x'];
24607
						}
24608
						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24609
							$attribs['y'] += $use['attribs']['y'];
24610
						}
24611
						if (empty($attribs['style'])) {
24612
							$attribs['style'] = '';
24613
						}
24614
						if (!empty($use['attribs']['style'])) {
24615
							// merge styles
24616
							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24617
						}
24618
						$attribs = array_merge($use['attribs'], $attribs);
24619
						$this->startSVGElementHandler($parser, $use['name'], $attribs);
24620
						return;
24621
					}
24622
				}
24623
				break;
24624
			}
24625
			default: {
24626
				break;
24627
			}
24628
		} // end of switch
24629
		// process child elements
24630
		if (!empty($attribs['child_elements'])) {
24631
			$child_elements = $attribs['child_elements'];
24632
			unset($attribs['child_elements']);
24633
			foreach($child_elements as $child_element) {
24634
				if (empty($child_element['attribs']['closing_tag'])) {
24635
					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24636
				} else {
24637
					if (isset($child_element['attribs']['content'])) {
24638
						$this->svgtext = $child_element['attribs']['content'];
24639
					}
24640
					$this->endSVGElementHandler('child-tag', $child_element['name']);
24641
				}
24642
			}
24643
		}
24644
	}
24645
 
24646
	/**
24647
	 * Sets the closing SVG element handler function for the XML parser.
24648
	 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24649
	 * @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.
24650
	 * @author Nicola Asuni
24651
	 * @since 5.0.000 (2010-05-02)
24652
	 * @protected
24653
	 */
24654
	protected function endSVGElementHandler($parser, $name) {
24655
		$name = $this->removeTagNamespace($name);
24656
		if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24657
			if (end($this->svgdefs) !== FALSE) {
24658
				$last_svgdefs_id = key($this->svgdefs);
24659
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24660
					foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24661
						if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24662
							$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24663
							return;
24664
						}
24665
					}
24666
					if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24667
						$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24668
						return;
24669
					}
24670
				}
24671
			}
24672
			return;
24673
		}
24674
		switch($name) {
24675
			case 'defs': {
24676
				$this->svgdefsmode = false;
24677
				break;
24678
			}
24679
			// clipPath
24680
			case 'clipPath': {
24681
				$this->svgclipmode = false;
24682
				break;
24683
			}
24684
			case 'svg': {
24685
				if (--$this->svg_tag_depth <= 0) {
24686
					break;
24687
				}
24688
			}
24689
			case 'g': {
24690
				// ungroup: remove last style from array
24691
				array_pop($this->svgstyles);
24692
				$this->StopTransform();
24693
				break;
24694
			}
24695
			case 'text':
24696
			case 'tspan': {
24697
				if ($this->svgtextmode['invisible']) {
24698
					// This implementation must be fixed to following the rule:
24699
					// 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.
24700
					break;
24701
				}
24702
				// print text
24703
				$text = $this->svgtext;
24704
				//$text = $this->stringTrim($text);
24705
				$textlen = $this->GetStringWidth($text);
24706
				if ($this->svgtextmode['text-anchor'] != 'start') {
24707
					// check if string is RTL text
24708
					if ($this->svgtextmode['text-anchor'] == 'end') {
24709
						if ($this->svgtextmode['rtl']) {
24710
							$this->x += $textlen;
24711
						} else {
24712
							$this->x -= $textlen;
24713
						}
24714
					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24715
						if ($this->svgtextmode['rtl']) {
24716
							$this->x += ($textlen / 2);
24717
						} else {
24718
							$this->x -= ($textlen / 2);
24719
						}
24720
					}
24721
				}
24722
				$textrendermode = $this->textrendermode;
24723
				$textstrokewidth = $this->textstrokewidth;
24724
				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24725
				if ($name == 'text') {
24726
					// store current coordinates
24727
					$tmpx = $this->x;
24728
					$tmpy = $this->y;
24729
				}
24730
				// print the text
24731
				$this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24732
				if ($name == 'text') {
24733
					// restore coordinates
24734
					$this->x = $tmpx;
24735
					$this->y = $tmpy;
24736
				}
24737
				// restore previous rendering mode
24738
				$this->textrendermode = $textrendermode;
24739
				$this->textstrokewidth = $textstrokewidth;
24740
				$this->svgtext = '';
24741
				$this->StopTransform();
24742
				if (!$this->svgdefsmode) {
24743
					array_pop($this->svgstyles);
24744
				}
24745
				break;
24746
			}
24747
			default: {
24748
				break;
24749
			}
24750
		}
24751
	}
24752
 
24753
	/**
24754
	 * Sets the character data handler function for the XML parser.
24755
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24756
	 * @param string $data The second parameter, data, contains the character data as a string.
24757
	 * @author Nicola Asuni
24758
	 * @since 5.0.000 (2010-05-02)
24759
	 * @protected
24760
	 */
24761
	protected function segSVGContentHandler($parser, $data) {
24762
		$this->svgtext .= $data;
24763
	}
24764
 
24765
	// --- END SVG METHODS -----------------------------------------------------
24766
 
24767
    /**
24768
     * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24769
     * @param string $file
24770
     * @return string
24771
     */
24772
    protected function getCachedFileContents($file)
24773
    {
24774
        if (!isset($this->fileContentCache[$file])) {
24775
            $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24776
        }
24777
        return $this->fileContentCache[$file];
24778
    }
24779
 
24780
    /**
24781
     * Avoid multiple calls to an external server to see if a file exists
24782
     * @param string $file
24783
     * @return bool
24784
     */
24785
    protected function fileExists($file)
24786
    {
24787
        if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24788
            return true;
24789
        }
24790
 
24791
        return TCPDF_STATIC::file_exists($file);
24792
    }
24793
 
24794
} // END OF TCPDF CLASS
24795
 
24796
//============================================================+
24797
// END OF FILE
24798
//============================================================+