Subversion Repositories Applications.annuaire

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
296 aurelien 1
<?php
2
//============================================================+
3
// File name   : tcpdf.php
4
// Version     : 5.9.029
5
// Begin       : 2002-08-03
6
// Last Update : 2010-12-04
7
// Author      : Nicola Asuni - Tecnick.com S.r.l - Via Della Pace, 11 - 09044 - Quartucciu (CA) - ITALY - www.tecnick.com - info@tecnick.com
8
// License     : http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3 + YOU CAN'T REMOVE ANY TCPDF COPYRIGHT NOTICE OR LINK FROM THE GENERATED PDF DOCUMENTS.
9
// -------------------------------------------------------------------
10
// Copyright (C) 2002-2010  Nicola Asuni - Tecnick.com S.r.l.
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. Additionally,
18
// YOU CAN'T REMOVE ANY TCPDF COPYRIGHT NOTICE OR LINK FROM THE
19
// GENERATED PDF DOCUMENTS.
20
//
21
// TCPDF is distributed in the hope that it will be useful, but
22
// WITHOUT ANY WARRANTY; without even the implied warranty of
23
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24
// See the GNU Lesser General Public License for more details.
25
//
26
// You should have received a copy of the License
27
// along with TCPDF. If not, see
28
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
29
//
30
// See LICENSE.TXT file for more information.
31
// -------------------------------------------------------------------
32
//
33
// Description : This is a PHP class for generating PDF documents without
34
//               requiring external extensions.
35
//
36
// NOTE:
37
//   This class was originally derived in 2002 from the Public
38
//   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
39
//   but now is almost entirely rewritten and contains thousands of
40
//   new lines of code and hundreds new features.
41
//
42
// Main features:
43
//  * no external libraries are required for the basic functions;
44
//  * all standard page formats, custom page formats, custom margins and units of measure;
45
//  * UTF-8 Unicode and Right-To-Left languages;
46
//  * TrueTypeUnicode, OpenTypeUnicode, TrueType, OpenType, Type1 and CID-0 fonts;
47
//  * font subsetting;
48
//  * methods to publish some XHTML + CSS code, Javascript and Forms;
49
//  * images, graphic (geometric figures) and transformation methods;
50
//  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
51
//  * 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 Extention, 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, QR-Code, PDF417;
52
//  * Grayscale, RGB, CMYK, Spot Colors and Transparencies;
53
//  * automatic page header and footer management;
54
//  * document encryption up to 256 bit and digital signature certifications;
55
//  * transactions to UNDO commands;
56
//  * PDF annotations, including links, text and file attachments;
57
//  * text rendering modes (fill, stroke and clipping);
58
//  * multiple columns mode;
59
//  * no-write page regions;
60
//  * bookmarks and table of content;
61
//  * text hyphenation;
62
//  * text stretching and spacing (tracking/kerning);
63
//  * automatic page break, line break and text alignments including justification;
64
//  * automatic page numbering and page groups;
65
//  * move and delete pages;
66
//  * page compression (requires php-zlib extension);
67
//  * XOBject Templates;
68
//
69
// -----------------------------------------------------------
70
// THANKS TO:
71
//
72
// Olivier Plathey (http://www.fpdf.org) for original FPDF.
73
// Efthimios Mavrogeorgiadis (emavro@yahoo.com) for suggestions on RTL language support.
74
// Klemen Vodopivec (http://www.fpdf.de/downloads/addons/37/) for Encryption algorithm.
75
// Warren Sherliker (wsherliker@gmail.com) for better image handling.
76
// dullus for text Justification.
77
// Bob Vincent (pillarsdotnet@users.sourceforge.net) for <li> value attribute.
78
// Patrick Benny for text stretch suggestion on Cell().
79
// Johannes G�ntert for JavaScript support.
80
// Denis Van Nuffelen for Dynamic Form.
81
// Jacek Czekaj for multibyte justification
82
// Anthony Ferrara for the reintroduction of legacy image methods.
83
// Sourceforge user 1707880 (hucste) for line-trough mode.
84
// Larry Stanbery for page groups.
85
// Martin Hall-May for transparency.
86
// Aaron C. Spike for Polycurve method.
87
// Mohamad Ali Golkar, Saleh AlMatrafe, Charles Abbott for Arabic and Persian support.
88
// Moritz Wagner and Andreas Wurmser for graphic functions.
89
// Andrew Whitehead for core fonts support.
90
// Esteban Jo�l Mar�n for OpenType font conversion.
91
// Teus Hagen for several suggestions and fixes.
92
// Yukihiro Nakadaira for CID-0 CJK fonts fixes.
93
// Kosmas Papachristos for some CSS improvements.
94
// Marcel Partap for some fixes.
95
// Won Kyu Park for several suggestions, fixes and patches.
96
// Dominik Dzienia for QR-code support.
97
// Laurent Minguet for some suggestions.
98
// Christian Deligant for some suggestions and fixes.
99
// Anyone that has reported a bug or sent a suggestion.
100
//============================================================+
101
 
102
/**
103
 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
104
 * 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>
105
 * <h3>TCPDF main features are:</h3>
106
 * <ul>
107
 * <li>no external libraries are required for the basic functions;</li>
108
 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
109
 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
110
 * <li>TrueTypeUnicode, OpenTypeUnicode, TrueType, OpenType, Type1 and CID-0 fonts;</li>
111
 * <li>font subsetting;</li>
112
 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
113
 * <li>images, graphic (geometric figures) and transformation methods;
114
 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
115
 * <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 Extention, 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, QR-Code, PDF417;</li>
116
 * <li>Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
117
 * <li>automatic page header and footer management;</li>
118
 * <li>document encryption up to 256 bit and digital signature certifications;</li>
119
 * <li>transactions to UNDO commands;</li>
120
 * <li>PDF annotations, including links, text and file attachments;</li>
121
 * <li>text rendering modes (fill, stroke and clipping);</li>
122
 * <li>multiple columns mode;</li>
123
 * <li>no-write page regions;</li>
124
 * <li>bookmarks and table of content;</li>
125
 * <li>text hyphenation;</li>
126
 * <li>text stretching and spacing (tracking/kerning);</li>
127
 * <li>automatic page break, line break and text alignments including justification;</li>
128
 * <li>automatic page numbering and page groups;</li>
129
 * <li>move and delete pages;</li>
130
 * <li>page compression (requires php-zlib extension);</li>
131
 * <li>XOBject Templates;</li>
132
 * </ul>
133
 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
134
 * @package com.tecnick.tcpdf
135
 * @abstract Class for generating PDF files on-the-fly without requiring external extensions.
136
 * @author Nicola Asuni
137
 * @copyright 2002-2010 Nicola Asuni - Tecnick.com S.r.l (www.tecnick.com) Via Della Pace, 11 - 09044 - Quartucciu (CA) - ITALY - www.tecnick.com - info@tecnick.com
138
 * @link http://www.tcpdf.org
139
 * @license http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3 + YOU CAN'T REMOVE ANY TCPDF COPYRIGHT NOTICE OR LINK FROM THE GENERATED PDF DOCUMENTS.
140
 * @version 5.9.029
141
 */
142
 
143
/**
144
 * main configuration file
145
 * (define the K_TCPDF_EXTERNAL_CONFIG constant to skip this file)
146
 */
147
require_once(dirname(__FILE__).'/config/tcpdf_config.php');
148
 
149
/**
150
* This is a PHP class for generating PDF documents without requiring external extensions.<br>
151
* 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>
152
* @name TCPDF
153
* @package com.tecnick.tcpdf
154
* @version 5.9.029
155
* @author Nicola Asuni - info@tecnick.com
156
* @link http://www.tcpdf.org
157
* @license http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3 + YOU CAN'T REMOVE ANY TCPDF COPYRIGHT NOTICE OR LINK FROM THE GENERATED PDF DOCUMENTS.
158
*/
159
class TCPDF {
160
 
161
	// private properties
162
 
163
	/**
164
	 * @var current TCPDF version
165
	 * @access private
166
	 */
167
	private $tcpdf_version = '5.9.029';
168
 
169
	// Protected properties
170
 
171
	/**
172
	 * @var current page number
173
	 * @access protected
174
	 */
175
	protected $page;
176
 
177
	/**
178
	 * @var current object number
179
	 * @access protected
180
	 */
181
	protected $n;
182
 
183
	/**
184
	 * @var array of object offsets
185
	 * @access protected
186
	 */
187
	protected $offsets;
188
 
189
	/**
190
	 * @var buffer holding in-memory PDF
191
	 * @access protected
192
	 */
193
	protected $buffer;
194
 
195
	/**
196
	 * @var array containing pages
197
	 * @access protected
198
	 */
199
	protected $pages = array();
200
 
201
	/**
202
	 * @var current document state
203
	 * @access protected
204
	 */
205
	protected $state;
206
 
207
	/**
208
	 * @var compression flag
209
	 * @access protected
210
	 */
211
	protected $compress;
212
 
213
	/**
214
	 * @var current page orientation (P = Portrait, L = Landscape)
215
	 * @access protected
216
	 */
217
	protected $CurOrientation;
218
 
219
	/**
220
	 * @var Page dimensions
221
	 * @access protected
222
	 */
223
	protected $pagedim = array();
224
 
225
	/**
226
	 * @var scale factor (number of points in user unit)
227
	 * @access protected
228
	 */
229
	protected $k;
230
 
231
	/**
232
	 * @var width of page format in points
233
	 * @access protected
234
	 */
235
	protected $fwPt;
236
 
237
	/**
238
	 * @var height of page format in points
239
	 * @access protected
240
	 */
241
	protected $fhPt;
242
 
243
	/**
244
	 * @var current width of page in points
245
	 * @access protected
246
	 */
247
	protected $wPt;
248
 
249
	/**
250
	 * @var current height of page in points
251
	 * @access protected
252
	 */
253
	protected $hPt;
254
 
255
	/**
256
	 * @var current width of page in user unit
257
	 * @access protected
258
	 */
259
	protected $w;
260
 
261
	/**
262
	 * @var current height of page in user unit
263
	 * @access protected
264
	 */
265
	protected $h;
266
 
267
	/**
268
	 * @var left margin
269
	 * @access protected
270
	 */
271
	protected $lMargin;
272
 
273
	/**
274
	 * @var top margin
275
	 * @access protected
276
	 */
277
	protected $tMargin;
278
 
279
	/**
280
	 * @var right margin
281
	 * @access protected
282
	 */
283
	protected $rMargin;
284
 
285
	/**
286
	 * @var page break margin
287
	 * @access protected
288
	 */
289
	protected $bMargin;
290
 
291
	/**
292
	 * @var array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left)
293
	 * @since 5.9.000 (2010-10-03)
294
	 * @access protected
295
	 */
296
	protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
297
 
298
	/**
299
	 * @var array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left)
300
	 * @since 5.9.000 (2010-10-04)
301
	 * @access protected
302
	 */
303
	protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
304
 
305
	/**
306
	 * @var current horizontal position in user unit for cell positioning
307
	 * @access protected
308
	 */
309
	protected $x;
310
 
311
	/**
312
	 * @var current vertical position in user unit for cell positioning
313
	 * @access protected
314
	 */
315
	protected $y;
316
 
317
	/**
318
	 * @var height of last cell printed
319
	 * @access protected
320
	 */
321
	protected $lasth;
322
 
323
	/**
324
	 * @var line width in user unit
325
	 * @access protected
326
	 */
327
	protected $LineWidth;
328
 
329
	/**
330
	 * @var array of standard font names
331
	 * @access protected
332
	 */
333
	protected $CoreFonts;
334
 
335
	/**
336
	 * @var array of used fonts
337
	 * @access protected
338
	 */
339
	protected $fonts = array();
340
 
341
	/**
342
	 * @var array of font files
343
	 * @access protected
344
	 */
345
	protected $FontFiles = array();
346
 
347
	/**
348
	 * @var array of encoding differences
349
	 * @access protected
350
	 */
351
	protected $diffs = array();
352
 
353
	/**
354
	 * @var array of used images
355
	 * @access protected
356
	 */
357
	protected $images = array();
358
 
359
	/**
360
	 * @var array of Annotations in pages
361
	 * @access protected
362
	 */
363
	protected $PageAnnots = array();
364
 
365
	/**
366
	 * @var array of internal links
367
	 * @access protected
368
	 */
369
	protected $links = array();
370
 
371
	/**
372
	 * @var current font family
373
	 * @access protected
374
	 */
375
	protected $FontFamily;
376
 
377
	/**
378
	 * @var current font style
379
	 * @access protected
380
	 */
381
	protected $FontStyle;
382
 
383
	/**
384
	 * @var current font ascent (distance between font top and baseline)
385
	 * @access protected
386
	 * @since 2.8.000 (2007-03-29)
387
	 */
388
	protected $FontAscent;
389
 
390
	/**
391
	 * @var current font descent (distance between font bottom and baseline)
392
	 * @access protected
393
	 * @since 2.8.000 (2007-03-29)
394
	 */
395
	protected $FontDescent;
396
 
397
	/**
398
	 * @var underlining flag
399
	 * @access protected
400
	 */
401
	protected $underline;
402
 
403
	/**
404
	 * @var overlining flag
405
	 * @access protected
406
	 */
407
	protected $overline;
408
 
409
	/**
410
	 * @var current font info
411
	 * @access protected
412
	 */
413
	protected $CurrentFont;
414
 
415
	/**
416
	 * @var current font size in points
417
	 * @access protected
418
	 */
419
	protected $FontSizePt;
420
 
421
	/**
422
	 * @var current font size in user unit
423
	 * @access protected
424
	 */
425
	protected $FontSize;
426
 
427
	/**
428
	 * @var commands for drawing color
429
	 * @access protected
430
	 */
431
	protected $DrawColor;
432
 
433
	/**
434
	 * @var commands for filling color
435
	 * @access protected
436
	 */
437
	protected $FillColor;
438
 
439
	/**
440
	 * @var commands for text color
441
	 * @access protected
442
	 */
443
	protected $TextColor;
444
 
445
	/**
446
	 * @var indicates whether fill and text colors are different
447
	 * @access protected
448
	 */
449
	protected $ColorFlag;
450
 
451
	/**
452
	 * @var automatic page breaking
453
	 * @access protected
454
	 */
455
	protected $AutoPageBreak;
456
 
457
	/**
458
	 * @var threshold used to trigger page breaks
459
	 * @access protected
460
	 */
461
	protected $PageBreakTrigger;
462
 
463
	/**
464
	 * @var flag set when processing footer
465
	 * @access protected
466
	 */
467
	protected $InFooter = false;
468
 
469
	/**
470
	 * @var zoom display mode
471
	 * @access protected
472
	 */
473
	protected $ZoomMode;
474
 
475
	/**
476
	 * @var layout display mode
477
	 * @access protected
478
	 */
479
	protected $LayoutMode;
480
 
481
	/**
482
	 * @var if true set the document information dictionary in Unicode.
483
	 * @access protected
484
	 */
485
	protected $docinfounicode = true;
486
 
487
	/**
488
	 * @var title
489
	 * @access protected
490
	 */
491
	protected $title = '';
492
 
493
	/**
494
	 * @var subject
495
	 * @access protected
496
	 */
497
	protected $subject = '';
498
 
499
	/**
500
	 * @var author
501
	 * @access protected
502
	 */
503
	protected $author = '';
504
 
505
	/**
506
	 * @var keywords
507
	 * @access protected
508
	 */
509
	protected $keywords = '';
510
 
511
	/**
512
	 * @var creator
513
	 * @access protected
514
	 */
515
	protected $creator = '';
516
 
517
	/**
518
	 * @var alias for total number of pages
519
	 * @access protected
520
	 */
521
	protected $AliasNbPages = '{nb}';
522
 
523
	/**
524
	 * @var alias for page number
525
	 * @access protected
526
	 */
527
	protected $AliasNumPage = '{pnb}';
528
 
529
	/**
530
	 * @var right-bottom corner X coordinate of inserted image
531
	 * @since 2002-07-31
532
	 * @author Nicola Asuni
533
	 * @access protected
534
	 */
535
	protected $img_rb_x;
536
 
537
	/**
538
	 * @var right-bottom corner Y coordinate of inserted image
539
	 * @since 2002-07-31
540
	 * @author Nicola Asuni
541
	 * @access protected
542
	 */
543
	protected $img_rb_y;
544
 
545
	/**
546
	 * @var adjusting factor to convert pixels to user units.
547
	 * @since 2004-06-14
548
	 * @author Nicola Asuni
549
	 * @access protected
550
	 */
551
	protected $imgscale = 1;
552
 
553
	/**
554
	 * @var boolean set to true when the input text is unicode (require unicode fonts)
555
	 * @since 2005-01-02
556
	 * @author Nicola Asuni
557
	 * @access protected
558
	 */
559
	protected $isunicode = false;
560
 
561
	/**
562
	 * @var object containing unicode data
563
	 * @since 5.9.004 (2010-10-18)
564
	 * @author Nicola Asuni
565
	 * @access protected
566
	 */
567
	protected $unicode;
568
 
569
	/**
570
	 * @var PDF version
571
	 * @since 1.5.3
572
	 * @access protected
573
	 */
574
	protected $PDFVersion = '1.7';
575
 
576
	/**
577
	 * @var Minimum distance between header and top page margin.
578
	 * @access protected
579
	 */
580
	protected $header_margin;
581
 
582
	/**
583
	 * @var Minimum distance between footer and bottom page margin.
584
	 * @access protected
585
	 */
586
	protected $footer_margin;
587
 
588
	/**
589
	 * @var original left margin value
590
	 * @access protected
591
	 * @since 1.53.0.TC013
592
	 */
593
	protected $original_lMargin;
594
 
595
	/**
596
	 * @var original right margin value
597
	 * @access protected
598
	 * @since 1.53.0.TC013
599
	 */
600
	protected $original_rMargin;
601
 
602
	/**
603
	 * @var Header font.
604
	 * @access protected
605
	 */
606
	protected $header_font;
607
 
608
	/**
609
	 * @var Footer font.
610
	 * @access protected
611
	 */
612
	protected $footer_font;
613
 
614
	/**
615
	 * @var Language templates.
616
	 * @access protected
617
	 */
618
	protected $l;
619
 
620
	/**
621
	 * @var Barcode to print on page footer (only if set).
622
	 * @access protected
623
	 */
624
	protected $barcode = false;
625
 
626
	/**
627
	 * @var If true prints header
628
	 * @access protected
629
	 */
630
	protected $print_header = true;
631
 
632
	/**
633
	 * @var If true prints footer.
634
	 * @access protected
635
	 */
636
	protected $print_footer = true;
637
 
638
	/**
639
	 * @var Header image logo.
640
	 * @access protected
641
	 */
642
	protected $header_logo = '';
643
 
644
	/**
645
	 * @var Header image logo width in mm.
646
	 * @access protected
647
	 */
648
	protected $header_logo_width = 30;
649
 
650
	/**
651
	 * @var String to print as title on document header.
652
	 * @access protected
653
	 */
654
	protected $header_title = '';
655
 
656
	/**
657
	 * @var String to print on document header.
658
	 * @access protected
659
	 */
660
	protected $header_string = '';
661
 
662
	/**
663
	 * @var Default number of columns for html table.
664
	 * @access protected
665
	 */
666
	protected $default_table_columns = 4;
667
 
668
	// variables for html parser
669
 
670
	/**
671
	 * @var HTML PARSER: array to store current link and rendering styles.
672
	 * @access protected
673
	 */
674
	protected $HREF = array();
675
 
676
	/**
677
	 * @var store a list of available fonts on filesystem.
678
	 * @access protected
679
	 */
680
	protected $fontlist = array();
681
 
682
	/**
683
	 * @var current foreground color
684
	 * @access protected
685
	 */
686
	protected $fgcolor;
687
 
688
	/**
689
	 * @var HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
690
	 * @access protected
691
	 */
692
	protected $listordered = array();
693
 
694
	/**
695
	 * @var HTML PARSER: array count list items on nested lists.
696
	 * @access protected
697
	 */
698
	protected $listcount = array();
699
 
700
	/**
701
	 * @var HTML PARSER: current list nesting level.
702
	 * @access protected
703
	 */
704
	protected $listnum = 0;
705
 
706
	/**
707
	 * @var HTML PARSER: indent amount for lists.
708
	 * @access protected
709
	 */
710
	protected $listindent = 0;
711
 
712
	/**
713
	 * @var HTML PARSER: current list indententation level.
714
	 * @access protected
715
	 */
716
	protected $listindentlevel = 0;
717
 
718
	/**
719
	 * @var current background color
720
	 * @access protected
721
	 */
722
	protected $bgcolor;
723
 
724
	/**
725
	 * @var Store temporary font size in points.
726
	 * @access protected
727
	 */
728
	protected $tempfontsize = 10;
729
 
730
	/**
731
	 * @var spacer for LI tags.
732
	 * @access protected
733
	 */
734
	protected $lispacer = '';
735
 
736
	/**
737
	 * @var default encoding
738
	 * @access protected
739
	 * @since 1.53.0.TC010
740
	 */
741
	protected $encoding = 'UTF-8';
742
 
743
	/**
744
	 * @var PHP internal encoding
745
	 * @access protected
746
	 * @since 1.53.0.TC016
747
	 */
748
	protected $internal_encoding;
749
 
750
	/**
751
	 * @var indicates if the document language is Right-To-Left
752
	 * @access protected
753
	 * @since 2.0.000
754
	 */
755
	protected $rtl = false;
756
 
757
	/**
758
	 * @var used to force RTL or LTR string inversion
759
	 * @access protected
760
	 * @since 2.0.000
761
	 */
762
	protected $tmprtl = false;
763
 
764
	// --- Variables used for document encryption:
765
 
766
	/**
767
	 * Indicates whether document is protected
768
	 * @access protected
769
	 * @since 2.0.000 (2008-01-02)
770
	 */
771
	protected $encrypted;
772
 
773
	/**
774
	 * Array containing encryption settings
775
	 * @access protected
776
	 * @since 5.0.005 (2010-05-11)
777
	 */
778
	protected $encryptdata = array();
779
 
780
	/**
781
	 * last RC4 key encrypted (cached for optimisation)
782
	 * @access protected
783
	 * @since 2.0.000 (2008-01-02)
784
	 */
785
	protected $last_enc_key;
786
 
787
	/**
788
	 * last RC4 computed key
789
	 * @access protected
790
	 * @since 2.0.000 (2008-01-02)
791
	 */
792
	protected $last_enc_key_c;
793
 
794
	/**
795
	 * Encryption padding
796
	 * @access protected
797
	 */
798
	protected $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
799
 
800
	/**
801
	 * File ID (used on trailer)
802
	 * @access protected
803
	 * @since 5.0.005 (2010-05-12)
804
	 */
805
	protected $file_id;
806
 
807
	// --- bookmark ---
808
 
809
	/**
810
	 * Outlines for bookmark
811
	 * @access protected
812
	 * @since 2.1.002 (2008-02-12)
813
	 */
814
	protected $outlines = array();
815
 
816
	/**
817
	 * Outline root for bookmark
818
	 * @access protected
819
	 * @since 2.1.002 (2008-02-12)
820
	 */
821
	protected $OutlineRoot;
822
 
823
	// --- javascript and form ---
824
 
825
	/**
826
	 * javascript code
827
	 * @access protected
828
	 * @since 2.1.002 (2008-02-12)
829
	 */
830
	protected $javascript = '';
831
 
832
	/**
833
	 * javascript counter
834
	 * @access protected
835
	 * @since 2.1.002 (2008-02-12)
836
	 */
837
	protected $n_js;
838
 
839
	/**
840
	 * line trough state
841
	 * @access protected
842
	 * @since 2.8.000 (2008-03-19)
843
	 */
844
	protected $linethrough;
845
 
846
	/**
847
	 * Array with additional document-wide usage rights for the document.
848
	 * @access protected
849
	 * @since 5.8.014 (2010-08-23)
850
	 */
851
	protected $ur = array();
852
 
853
	/**
854
	 * Dot Per Inch Document Resolution (do not change)
855
	 * @access protected
856
	 * @since 3.0.000 (2008-03-27)
857
	 */
858
	protected $dpi = 72;
859
 
860
	/**
861
	 * Array of page numbers were a new page group was started
862
	 * @access protected
863
	 * @since 3.0.000 (2008-03-27)
864
	 */
865
	protected $newpagegroup = array();
866
 
867
	/**
868
	 * Contains the number of pages of the groups
869
	 * @access protected
870
	 * @since 3.0.000 (2008-03-27)
871
	 */
872
	protected $pagegroups;
873
 
874
	/**
875
	 * Contains the alias of the current page group
876
	 * @access protected
877
	 * @since 3.0.000 (2008-03-27)
878
	 */
879
	protected $currpagegroup;
880
 
881
	/**
882
	 * Restrict the rendering of some elements to screen or printout.
883
	 * @access protected
884
	 * @since 3.0.000 (2008-03-27)
885
	 */
886
	protected $visibility = 'all';
887
 
888
	/**
889
	 * Print visibility.
890
	 * @access protected
891
	 * @since 3.0.000 (2008-03-27)
892
	 */
893
	protected $n_ocg_print;
894
 
895
	/**
896
	 * View visibility.
897
	 * @access protected
898
	 * @since 3.0.000 (2008-03-27)
899
	 */
900
	protected $n_ocg_view;
901
 
902
	/**
903
	 * Array of transparency objects and parameters.
904
	 * @access protected
905
	 * @since 3.0.000 (2008-03-27)
906
	 */
907
	protected $extgstates;
908
 
909
	/**
910
	 * Set the default JPEG compression quality (1-100)
911
	 * @access protected
912
	 * @since 3.0.000 (2008-03-27)
913
	 */
914
	protected $jpeg_quality;
915
 
916
	/**
917
	 * Default cell height ratio.
918
	 * @access protected
919
	 * @since 3.0.014 (2008-05-23)
920
	 */
921
	protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
922
 
923
	/**
924
	 * PDF viewer preferences.
925
	 * @access protected
926
	 * @since 3.1.000 (2008-06-09)
927
	 */
928
	protected $viewer_preferences;
929
 
930
	/**
931
	 * A name object specifying how the document should be displayed when opened.
932
	 * @access protected
933
	 * @since 3.1.000 (2008-06-09)
934
	 */
935
	protected $PageMode;
936
 
937
	/**
938
	 * Array for storing gradient information.
939
	 * @access protected
940
	 * @since 3.1.000 (2008-06-09)
941
	 */
942
	protected $gradients = array();
943
 
944
	/**
945
	 * Array used to store positions inside the pages buffer.
946
	 * keys are the page numbers
947
	 * @access protected
948
	 * @since 3.2.000 (2008-06-26)
949
	 */
950
	protected $intmrk = array();
951
 
952
	/**
953
	 * Array used to store positions inside the pages buffer.
954
	 * keys are the page numbers
955
	 * @access protected
956
	 * @since 5.7.000 (2010-08-03)
957
	 */
958
	protected $bordermrk = array();
959
 
960
	/**
961
	 * Array used to store page positions to track empty pages.
962
	 * keys are the page numbers
963
	 * @access protected
964
	 * @since 5.8.007 (2010-08-18)
965
	 */
966
	protected $emptypagemrk = array();
967
 
968
	/**
969
	 * Array used to store content positions inside the pages buffer.
970
	 * keys are the page numbers
971
	 * @access protected
972
	 * @since 4.6.021 (2009-07-20)
973
	 */
974
	protected $cntmrk = array();
975
 
976
	/**
977
	 * Array used to store footer positions of each page.
978
	 * @access protected
979
	 * @since 3.2.000 (2008-07-01)
980
	 */
981
	protected $footerpos = array();
982
 
983
	/**
984
	 * Array used to store footer length of each page.
985
	 * @access protected
986
	 * @since 4.0.014 (2008-07-29)
987
	 */
988
	protected $footerlen = array();
989
 
990
	/**
991
	 * True if a newline is created.
992
	 * @access protected
993
	 * @since 3.2.000 (2008-07-01)
994
	 */
995
	protected $newline = true;
996
 
997
	/**
998
	 * End position of the latest inserted line
999
	 * @access protected
1000
	 * @since 3.2.000 (2008-07-01)
1001
	 */
1002
	protected $endlinex = 0;
1003
 
1004
	/**
1005
	 * PDF string for last line width
1006
	 * @access protected
1007
	 * @since 4.0.006 (2008-07-16)
1008
	 */
1009
	protected $linestyleWidth = '';
1010
 
1011
	/**
1012
	 * PDF string for last line width
1013
	 * @access protected
1014
	 * @since 4.0.006 (2008-07-16)
1015
	 */
1016
	protected $linestyleCap = '0 J';
1017
 
1018
	/**
1019
	 * PDF string for last line width
1020
	 * @access protected
1021
	 * @since 4.0.006 (2008-07-16)
1022
	 */
1023
	protected $linestyleJoin = '0 j';
1024
 
1025
	/**
1026
	 * PDF string for last line width
1027
	 * @access protected
1028
	 * @since 4.0.006 (2008-07-16)
1029
	 */
1030
	protected $linestyleDash = '[] 0 d';
1031
 
1032
	/**
1033
	 * True if marked-content sequence is open
1034
	 * @access protected
1035
	 * @since 4.0.013 (2008-07-28)
1036
	 */
1037
	protected $openMarkedContent = false;
1038
 
1039
	/**
1040
	 * Count the latest inserted vertical spaces on HTML
1041
	 * @access protected
1042
	 * @since 4.0.021 (2008-08-24)
1043
	 */
1044
	protected $htmlvspace = 0;
1045
 
1046
	/**
1047
	 * Array of Spot colors
1048
	 * @access protected
1049
	 * @since 4.0.024 (2008-09-12)
1050
	 */
1051
	protected $spot_colors = array();
1052
 
1053
	/**
1054
	 * Symbol used for HTML unordered list items
1055
	 * @access protected
1056
	 * @since 4.0.028 (2008-09-26)
1057
	 */
1058
	protected $lisymbol = '';
1059
 
1060
	/**
1061
	 * String used to mark the beginning and end of EPS image blocks
1062
	 * @access protected
1063
	 * @since 4.1.000 (2008-10-18)
1064
	 */
1065
	protected $epsmarker = 'x#!#EPS#!#x';
1066
 
1067
	/**
1068
	 * Array of transformation matrix
1069
	 * @access protected
1070
	 * @since 4.2.000 (2008-10-29)
1071
	 */
1072
	protected $transfmatrix = array();
1073
 
1074
	/**
1075
	 * Current key for transformation matrix
1076
	 * @access protected
1077
	 * @since 4.8.005 (2009-09-17)
1078
	 */
1079
	protected $transfmatrix_key = 0;
1080
 
1081
	/**
1082
	 * Booklet mode for double-sided pages
1083
	 * @access protected
1084
	 * @since 4.2.000 (2008-10-29)
1085
	 */
1086
	protected $booklet = false;
1087
 
1088
	/**
1089
	 * Epsilon value used for float calculations
1090
	 * @access protected
1091
	 * @since 4.2.000 (2008-10-29)
1092
	 */
1093
	protected $feps = 0.005;
1094
 
1095
	/**
1096
	 * Array used for custom vertical spaces for HTML tags
1097
	 * @access protected
1098
	 * @since 4.2.001 (2008-10-30)
1099
	 */
1100
	protected $tagvspaces = array();
1101
 
1102
	/**
1103
	 * @var HTML PARSER: custom indent amount for lists.
1104
	 * Negative value means disabled.
1105
	 * @access protected
1106
	 * @since 4.2.007 (2008-11-12)
1107
	 */
1108
	protected $customlistindent = -1;
1109
 
1110
	/**
1111
	 * @var if true keeps the border open for the cell sides that cross the page.
1112
	 * @access protected
1113
	 * @since 4.2.010 (2008-11-14)
1114
	 */
1115
	protected $opencell = true;
1116
 
1117
	/**
1118
	 * @var array of files to embedd
1119
	 * @access protected
1120
	 * @since 4.4.000 (2008-12-07)
1121
	 */
1122
	protected $embeddedfiles = array();
1123
 
1124
	/**
1125
	 * @var boolean true when inside html pre tag
1126
	 * @access protected
1127
	 * @since 4.4.001 (2008-12-08)
1128
	 */
1129
	protected $premode = false;
1130
 
1131
	/**
1132
	 * Array used to store positions of graphics transformation blocks inside the page buffer.
1133
	 * keys are the page numbers
1134
	 * @access protected
1135
	 * @since 4.4.002 (2008-12-09)
1136
	 */
1137
	protected $transfmrk = array();
1138
 
1139
	/**
1140
	 * Default color for html links
1141
	 * @access protected
1142
	 * @since 4.4.003 (2008-12-09)
1143
	 */
1144
	protected $htmlLinkColorArray = array(0, 0, 255);
1145
 
1146
	/**
1147
	 * Default font style to add to html links
1148
	 * @access protected
1149
	 * @since 4.4.003 (2008-12-09)
1150
	 */
1151
	protected $htmlLinkFontStyle = 'U';
1152
 
1153
	/**
1154
	 * Counts the number of pages.
1155
	 * @access protected
1156
	 * @since 4.5.000 (2008-12-31)
1157
	 */
1158
	protected $numpages = 0;
1159
 
1160
	/**
1161
	 * Array containing page lengths in bytes.
1162
	 * @access protected
1163
	 * @since 4.5.000 (2008-12-31)
1164
	 */
1165
	protected $pagelen = array();
1166
 
1167
	/**
1168
	 * Counts the number of pages.
1169
	 * @access protected
1170
	 * @since 4.5.000 (2008-12-31)
1171
	 */
1172
	protected $numimages = 0;
1173
 
1174
	/**
1175
	 * Store the image keys.
1176
	 * @access protected
1177
	 * @since 4.5.000 (2008-12-31)
1178
	 */
1179
	protected $imagekeys = array();
1180
 
1181
	/**
1182
	 * Length of the buffer in bytes.
1183
	 * @access protected
1184
	 * @since 4.5.000 (2008-12-31)
1185
	 */
1186
	protected $bufferlen = 0;
1187
 
1188
	/**
1189
	 * If true enables disk caching.
1190
	 * @access protected
1191
	 * @since 4.5.000 (2008-12-31)
1192
	 */
1193
	protected $diskcache = false;
1194
 
1195
	/**
1196
	 * Counts the number of fonts.
1197
	 * @access protected
1198
	 * @since 4.5.000 (2009-01-02)
1199
	 */
1200
	protected $numfonts = 0;
1201
 
1202
	/**
1203
	 * Store the font keys.
1204
	 * @access protected
1205
	 * @since 4.5.000 (2009-01-02)
1206
	 */
1207
	protected $fontkeys = array();
1208
 
1209
	/**
1210
	 * Store the font object IDs.
1211
	 * @access protected
1212
	 * @since 4.8.001 (2009-09-09)
1213
	 */
1214
	protected $font_obj_ids = array();
1215
 
1216
	/**
1217
	 * Store the fage status (true when opened, false when closed).
1218
	 * @access protected
1219
	 * @since 4.5.000 (2009-01-02)
1220
	 */
1221
	protected $pageopen = array();
1222
 
1223
	/**
1224
	 * Default monospaced font
1225
	 * @access protected
1226
	 * @since 4.5.025 (2009-03-10)
1227
	 */
1228
	protected $default_monospaced_font = 'courier';
1229
 
1230
	/**
1231
	 * Used to store a cloned copy of the current class object
1232
	 * @access protected
1233
	 * @since 4.5.029 (2009-03-19)
1234
	 */
1235
	protected $objcopy;
1236
 
1237
	/**
1238
	 * Array used to store the lengths of cache files
1239
	 * @access protected
1240
	 * @since 4.5.029 (2009-03-19)
1241
	 */
1242
	protected $cache_file_length = array();
1243
 
1244
	/**
1245
	 * Table header content to be repeated on each new page
1246
	 * @access protected
1247
	 * @since 4.5.030 (2009-03-20)
1248
	 */
1249
	protected $thead = '';
1250
 
1251
	/**
1252
	 * Margins used for table header.
1253
	 * @access protected
1254
	 * @since 4.5.030 (2009-03-20)
1255
	 */
1256
	protected $theadMargins = array();
1257
 
1258
	/**
1259
	 * Cache array for UTF8StringToArray() method.
1260
	 * @access protected
1261
	 * @since 4.5.037 (2009-04-07)
1262
	 */
1263
	protected $cache_UTF8StringToArray = array();
1264
 
1265
	/**
1266
	 * Maximum size of cache array used for UTF8StringToArray() method.
1267
	 * @access protected
1268
	 * @since 4.5.037 (2009-04-07)
1269
	 */
1270
	protected $cache_maxsize_UTF8StringToArray = 8;
1271
 
1272
	/**
1273
	 * Current size of cache array used for UTF8StringToArray() method.
1274
	 * @access protected
1275
	 * @since 4.5.037 (2009-04-07)
1276
	 */
1277
	protected $cache_size_UTF8StringToArray = 0;
1278
 
1279
	/**
1280
	 * If true enables document signing
1281
	 * @access protected
1282
	 * @since 4.6.005 (2009-04-24)
1283
	 */
1284
	protected $sign = false;
1285
 
1286
	/**
1287
	 * Signature data
1288
	 * @access protected
1289
	 * @since 4.6.005 (2009-04-24)
1290
	 */
1291
	protected $signature_data = array();
1292
 
1293
	/**
1294
	 * Signature max length
1295
	 * @access protected
1296
	 * @since 4.6.005 (2009-04-24)
1297
	 */
1298
	protected $signature_max_length = 11742;
1299
 
1300
	/**
1301
	 * data for signature appearance
1302
	 * @access protected
1303
	 * @since 5.3.011 (2010-06-16)
1304
	 */
1305
	protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1306
 
1307
	/**
1308
	 * Regular expression used to find blank characters used for word-wrapping.
1309
	 * @access protected
1310
	 * @since 4.6.006 (2009-04-28)
1311
	 */
1312
	protected $re_spaces = '/[^\S\xa0]/';
1313
 
1314
	/**
1315
	 * Array of parts $re_spaces
1316
	 * @access protected
1317
	 * @since 5.5.011 (2010-07-09)
1318
	 */
1319
	protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1320
 
1321
	/**
1322
	 * Signature object ID
1323
	 * @access protected
1324
	 * @since 4.6.022 (2009-06-23)
1325
	 */
1326
	protected $sig_obj_id = 0;
1327
 
1328
	/**
1329
	 * ByteRange placemark used during signature process.
1330
	 * @access protected
1331
	 * @since 4.6.028 (2009-08-25)
1332
	 */
1333
	protected $byterange_string = '/ByteRange[0 ********** ********** **********]';
1334
 
1335
	/**
1336
	 * Placemark used during signature process.
1337
	 * @access protected
1338
	 * @since 4.6.028 (2009-08-25)
1339
	 */
1340
	protected $sig_annot_ref = '***SIGANNREF*** 0 R';
1341
 
1342
	/**
1343
	 * ID of page objects
1344
	 * @access protected
1345
	 * @since 4.7.000 (2009-08-29)
1346
	 */
1347
	protected $page_obj_id = array();
1348
 
1349
	/**
1350
	 * List of form annotations IDs
1351
	 * @access protected
1352
	 * @since 4.8.000 (2009-09-07)
1353
	 */
1354
	protected $form_obj_id = array();
1355
 
1356
	/**
1357
	 * 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.
1358
	 * @access protected
1359
	 * @since 4.8.000 (2009-09-07)
1360
	 */
1361
	protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1362
 
1363
	/**
1364
	 * Javascript objects array
1365
	 * @access protected
1366
	 * @since 4.8.000 (2009-09-07)
1367
	 */
1368
	protected $js_objects = array();
1369
 
1370
	/**
1371
	 * Current form action (used during XHTML rendering)
1372
	 * @access protected
1373
	 * @since 4.8.000 (2009-09-07)
1374
	 */
1375
	protected $form_action = '';
1376
 
1377
	/**
1378
	 * Current form encryption type (used during XHTML rendering)
1379
	 * @access protected
1380
	 * @since 4.8.000 (2009-09-07)
1381
	 */
1382
	protected $form_enctype = 'application/x-www-form-urlencoded';
1383
 
1384
	/**
1385
	 * Current method to submit forms.
1386
	 * @access protected
1387
	 * @since 4.8.000 (2009-09-07)
1388
	 */
1389
	protected $form_mode = 'post';
1390
 
1391
	/**
1392
	 * List of fonts used on form fields (fontname => fontkey).
1393
	 * @access protected
1394
	 * @since 4.8.001 (2009-09-09)
1395
	 */
1396
	protected $annotation_fonts = array();
1397
 
1398
	/**
1399
	 * List of radio buttons parent objects.
1400
	 * @access protected
1401
	 * @since 4.8.001 (2009-09-09)
1402
	 */
1403
	protected $radiobutton_groups = array();
1404
 
1405
	/**
1406
	 * List of radio group objects IDs
1407
	 * @access protected
1408
	 * @since 4.8.001 (2009-09-09)
1409
	 */
1410
	protected $radio_groups = array();
1411
 
1412
	/**
1413
	 * Text indentation value (used for text-indent CSS attribute)
1414
	 * @access protected
1415
	 * @since 4.8.006 (2009-09-23)
1416
	 */
1417
	protected $textindent = 0;
1418
 
1419
	/**
1420
	 * Store page number when startTransaction() is called.
1421
	 * @access protected
1422
	 * @since 4.8.006 (2009-09-23)
1423
	 */
1424
	protected $start_transaction_page = 0;
1425
 
1426
	/**
1427
	 * Store Y position when startTransaction() is called.
1428
	 * @access protected
1429
	 * @since 4.9.001 (2010-03-28)
1430
	 */
1431
	protected $start_transaction_y = 0;
1432
 
1433
	/**
1434
	 * True when we are printing the thead section on a new page
1435
	 * @access protected
1436
	 * @since 4.8.027 (2010-01-25)
1437
	 */
1438
	protected $inthead = false;
1439
 
1440
	/**
1441
	 * Array of column measures (width, space, starting Y position)
1442
	 * @access protected
1443
	 * @since 4.9.001 (2010-03-28)
1444
	 */
1445
	protected $columns = array();
1446
 
1447
	/**
1448
	 * Number of colums
1449
	 * @access protected
1450
	 * @since 4.9.001 (2010-03-28)
1451
	 */
1452
	protected $num_columns = 1;
1453
 
1454
	/**
1455
	 * Current column number
1456
	 * @access protected
1457
	 * @since 4.9.001 (2010-03-28)
1458
	 */
1459
	protected $current_column = 0;
1460
 
1461
	/**
1462
	 * Starting page for columns
1463
	 * @access protected
1464
	 * @since 4.9.001 (2010-03-28)
1465
	 */
1466
	protected $column_start_page = 0;
1467
 
1468
	/**
1469
	 * Maximum page and column selected
1470
	 * @access protected
1471
	 * @since 5.8.000 (2010-08-11)
1472
	 */
1473
	protected $maxselcol = array('page' => 0, 'column' => 0);
1474
 
1475
	/**
1476
	 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding
1477
	 * @access protected
1478
	 * @since 5.8.000 (2010-08-11)
1479
	 */
1480
	protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1481
 
1482
	/**
1483
	 * 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.
1484
	 * @access protected
1485
	 * @since 4.9.008 (2010-04-03)
1486
	 */
1487
	protected $textrendermode = 0;
1488
 
1489
	/**
1490
	 * Text stroke width in doc units
1491
	 * @access protected
1492
	 * @since 4.9.008 (2010-04-03)
1493
	 */
1494
	protected $textstrokewidth = 0;
1495
 
1496
	/**
1497
	 * @var current stroke color
1498
	 * @access protected
1499
	 * @since 4.9.008 (2010-04-03)
1500
	 */
1501
	protected $strokecolor;
1502
 
1503
	/**
1504
	 * @var default unit of measure for document
1505
	 * @access protected
1506
	 * @since 5.0.000 (2010-04-22)
1507
	 */
1508
	protected $pdfunit = 'mm';
1509
 
1510
	/**
1511
	 * @var true when we are on TOC (Table Of Content) page
1512
	 * @access protected
1513
	 */
1514
	protected $tocpage = false;
1515
 
1516
	/**
1517
	 * @var If true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1518
	 * @access protected
1519
	 * @since 5.0.000 (2010-04-26)
1520
	 */
1521
	protected $rasterize_vector_images = false;
1522
 
1523
	/**
1524
	 * @var If true enables font subsetting by default
1525
	 * @access protected
1526
	 * @since 5.3.002 (2010-06-07)
1527
	 */
1528
	protected $font_subsetting = true;
1529
 
1530
	/**
1531
	 * @var Array of default graphic settings
1532
	 * @access protected
1533
	 * @since 5.5.008 (2010-07-02)
1534
	 */
1535
	protected $default_graphic_vars = array();
1536
 
1537
	/**
1538
	 * @var Array of XObjects
1539
	 * @access protected
1540
	 * @since 5.8.014 (2010-08-23)
1541
	 */
1542
	protected $xobjects = array();
1543
 
1544
	/**
1545
	 * @var boolean true when we are inside an XObject
1546
	 * @access protected
1547
	 * @since 5.8.017 (2010-08-24)
1548
	 */
1549
	protected $inxobj = false;
1550
 
1551
	/**
1552
	 * @var current XObject ID
1553
	 * @access protected
1554
	 * @since 5.8.017 (2010-08-24)
1555
	 */
1556
	protected $xobjid = '';
1557
 
1558
	/**
1559
	 * @var percentage of character stretching
1560
	 * @access protected
1561
	 * @since 5.9.000 (2010-09-29)
1562
	 */
1563
	protected $font_stretching = 100;
1564
 
1565
	/**
1566
	 * @var increases or decreases the space between characters in a text by the specified amount (tracking/kerning).
1567
	 * @access protected
1568
	 * @since 5.9.000 (2010-09-29)
1569
	 */
1570
	protected $font_spacing = 0;
1571
 
1572
	/**
1573
	 * @var array of no-write regions
1574
	 * ('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)
1575
	 * @access protected
1576
	 * @since 5.9.003 (2010-10-14)
1577
	 */
1578
	protected $page_regions = array();
1579
 
1580
	/**
1581
	 * @var array containing HTML color names and values
1582
	 * @access protected
1583
	 * @since 5.9.004 (2010-10-18)
1584
	 */
1585
	protected $webcolor = array();
1586
 
1587
	/**
1588
	 * @var array containing spot color names and values
1589
	 * @access protected
1590
	 * @since 5.9.012 (2010-11-11)
1591
	 */
1592
	protected $spotcolor = array();
1593
 
1594
	/**
1595
	 * @var directory used for the last SVG image
1596
	 * @access protected
1597
	 * @since 5.0.000 (2010-05-05)
1598
	 */
1599
	protected $svgdir = '';
1600
 
1601
	/**
1602
	 * @var Deafult unit of measure for SVG
1603
	 * @access protected
1604
	 * @since 5.0.000 (2010-05-02)
1605
	 */
1606
	protected $svgunit = 'px';
1607
 
1608
	/**
1609
	 * @var array of SVG gradients
1610
	 * @access protected
1611
	 * @since 5.0.000 (2010-05-02)
1612
	 */
1613
	protected $svggradients = array();
1614
 
1615
	/**
1616
	 * @var ID of last SVG gradient
1617
	 * @access protected
1618
	 * @since 5.0.000 (2010-05-02)
1619
	 */
1620
	protected $svggradientid = 0;
1621
 
1622
	/**
1623
	 * @var true when in SVG defs group
1624
	 * @access protected
1625
	 * @since 5.0.000 (2010-05-02)
1626
	 */
1627
	protected $svgdefsmode = false;
1628
 
1629
	/**
1630
	 * @var array of SVG defs
1631
	 * @access protected
1632
	 * @since 5.0.000 (2010-05-02)
1633
	 */
1634
	protected $svgdefs = array();
1635
 
1636
	/**
1637
	 * @var true when in SVG clipPath tag
1638
	 * @access protected
1639
	 * @since 5.0.000 (2010-04-26)
1640
	 */
1641
	protected $svgclipmode = false;
1642
 
1643
	/**
1644
	 * @var array of SVG clipPath commands
1645
	 * @access protected
1646
	 * @since 5.0.000 (2010-05-02)
1647
	 */
1648
	protected $svgclippaths = array();
1649
 
1650
	/**
1651
	 * @var array of SVG clipPath tranformation matrix
1652
	 * @access protected
1653
	 * @since 5.8.022 (2010-08-31)
1654
	 */
1655
	protected $svgcliptm = array();
1656
 
1657
	/**
1658
	 * @var ID of last SVG clipPath
1659
	 * @access protected
1660
	 * @since 5.0.000 (2010-05-02)
1661
	 */
1662
	protected $svgclipid = 0;
1663
 
1664
	/**
1665
	 * @var svg text
1666
	 * @access protected
1667
	 * @since 5.0.000 (2010-05-02)
1668
	 */
1669
	protected $svgtext = '';
1670
 
1671
	/**
1672
	 * @var svg text properties
1673
	 * @access protected
1674
	 * @since 5.8.013 (2010-08-23)
1675
	 */
1676
	protected $svgtextmode = array();
1677
 
1678
	/**
1679
	 * @var array of hinheritable SVG properties
1680
	 * @access protected
1681
	 * @since 5.0.000 (2010-05-02)
1682
	 */
1683
	protected $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode');
1684
 
1685
	/**
1686
	 * @var array of SVG properties
1687
	 * @access protected
1688
	 * @since 5.0.000 (2010-05-02)
1689
	 */
1690
	protected $svgstyles = array(array(
1691
		'alignment-baseline' => 'auto',
1692
		'baseline-shift' => 'baseline',
1693
		'clip' => 'auto',
1694
		'clip-path' => 'none',
1695
		'clip-rule' => 'nonzero',
1696
		'color' => 'black',
1697
		'color-interpolation' => 'sRGB',
1698
		'color-interpolation-filters' => 'linearRGB',
1699
		'color-profile' => 'auto',
1700
		'color-rendering' => 'auto',
1701
		'cursor' => 'auto',
1702
		'direction' => 'ltr',
1703
		'display' => 'inline',
1704
		'dominant-baseline' => 'auto',
1705
		'enable-background' => 'accumulate',
1706
		'fill' => 'black',
1707
		'fill-opacity' => 1,
1708
		'fill-rule' => 'nonzero',
1709
		'filter' => 'none',
1710
		'flood-color' => 'black',
1711
		'flood-opacity' => 1,
1712
		'font' => '',
1713
		'font-family' => 'helvetica',
1714
		'font-size' => 'medium',
1715
		'font-size-adjust' => 'none',
1716
		'font-stretch' => 'normal',
1717
		'font-style' => 'normal',
1718
		'font-variant' => 'normal',
1719
		'font-weight' => 'normal',
1720
		'glyph-orientation-horizontal' => '0deg',
1721
		'glyph-orientation-vertical' => 'auto',
1722
		'image-rendering' => 'auto',
1723
		'kerning' => 'auto',
1724
		'letter-spacing' => 'normal',
1725
		'lighting-color' => 'white',
1726
		'marker' => '',
1727
		'marker-end' => 'none',
1728
		'marker-mid' => 'none',
1729
		'marker-start' => 'none',
1730
		'mask' => 'none',
1731
		'opacity' => 1,
1732
		'overflow' => 'auto',
1733
		'pointer-events' => 'visiblePainted',
1734
		'shape-rendering' => 'auto',
1735
		'stop-color' => 'black',
1736
		'stop-opacity' => 1,
1737
		'stroke' => 'none',
1738
		'stroke-dasharray' => 'none',
1739
		'stroke-dashoffset' => 0,
1740
		'stroke-linecap' => 'butt',
1741
		'stroke-linejoin' => 'miter',
1742
		'stroke-miterlimit' => 4,
1743
		'stroke-opacity' => 1,
1744
		'stroke-width' => 1,
1745
		'text-anchor' => 'start',
1746
		'text-decoration' => 'none',
1747
		'text-rendering' => 'auto',
1748
		'unicode-bidi' => 'normal',
1749
		'visibility' => 'visible',
1750
		'word-spacing' => 'normal',
1751
		'writing-mode' => 'lr-tb',
1752
		'text-color' => 'black',
1753
		'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1754
		));
1755
 
1756
	//------------------------------------------------------------
1757
	// METHODS
1758
	//------------------------------------------------------------
1759
 
1760
	/**
1761
	 * This is the class constructor.
1762
	 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1763
	 * @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>
1764
	 * @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.
1765
	 * @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().
1766
	 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1767
	 * @param boolean $diskcache if TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1768
	 * @param String $encoding charset encoding; default is UTF-8
1769
	 * @access public
1770
	 * @see getPageSizeFromFormat(), setPageFormat()
1771
	 */
1772
	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false) {
1773
		/* Set internal character encoding to ASCII */
1774
		if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1775
			$this->internal_encoding = mb_internal_encoding();
1776
			mb_internal_encoding('ASCII');
1777
		}
1778
		// get array of HTML colors
1779
		require(dirname(__FILE__).'/htmlcolors.php');
1780
		$this->webcolor = $webcolor;
1781
		// get array of custom spot colors
1782
		if (file_exists(dirname(__FILE__).'/spotcolors.php')) {
1783
			require(dirname(__FILE__).'/spotcolors.php');
1784
			$this->spotcolor = $spotcolor;
1785
		} else {
1786
			$this->spotcolor = array();
1787
		}
1788
		require_once(dirname(__FILE__).'/unicode_data.php');
1789
		$this->unicode = new TCPDF_UNICODE_DATA();
1790
		$this->font_obj_ids = array();
1791
		$this->page_obj_id = array();
1792
		$this->form_obj_id = array();
1793
		// set disk caching
1794
		$this->diskcache = $diskcache ? true : false;
1795
		// set language direction
1796
		$this->rtl = false;
1797
		$this->tmprtl = false;
1798
		// some checks
1799
		$this->_dochecks();
1800
		// initialization of properties
1801
		$this->isunicode = $unicode;
1802
		$this->page = 0;
1803
		$this->transfmrk[0] = array();
1804
		$this->pagedim = array();
1805
		$this->n = 2;
1806
		$this->buffer = '';
1807
		$this->pages = array();
1808
		$this->state = 0;
1809
		$this->fonts = array();
1810
		$this->FontFiles = array();
1811
		$this->diffs = array();
1812
		$this->images = array();
1813
		$this->links = array();
1814
		$this->gradients = array();
1815
		$this->InFooter = false;
1816
		$this->lasth = 0;
1817
		$this->FontFamily = 'helvetica';
1818
		$this->FontStyle = '';
1819
		$this->FontSizePt = 12;
1820
		$this->underline = false;
1821
		$this->overline = false;
1822
		$this->linethrough = false;
1823
		$this->DrawColor = '0 G';
1824
		$this->FillColor = '0 g';
1825
		$this->TextColor = '0 g';
1826
		$this->ColorFlag = false;
1827
		// encryption values
1828
		$this->encrypted = false;
1829
		$this->last_enc_key = '';
1830
		// standard Unicode fonts
1831
		$this->CoreFonts = array(
1832
			'courier'=>'Courier',
1833
			'courierB'=>'Courier-Bold',
1834
			'courierI'=>'Courier-Oblique',
1835
			'courierBI'=>'Courier-BoldOblique',
1836
			'helvetica'=>'Helvetica',
1837
			'helveticaB'=>'Helvetica-Bold',
1838
			'helveticaI'=>'Helvetica-Oblique',
1839
			'helveticaBI'=>'Helvetica-BoldOblique',
1840
			'times'=>'Times-Roman',
1841
			'timesB'=>'Times-Bold',
1842
			'timesI'=>'Times-Italic',
1843
			'timesBI'=>'Times-BoldItalic',
1844
			'symbol'=>'Symbol',
1845
			'zapfdingbats'=>'ZapfDingbats'
1846
		);
1847
		// set scale factor
1848
		$this->setPageUnit($unit);
1849
		// set page format and orientation
1850
		$this->setPageFormat($format, $orientation);
1851
		// page margins (1 cm)
1852
		$margin = 28.35 / $this->k;
1853
		$this->SetMargins($margin, $margin);
1854
		// internal cell padding
1855
		$cpadding = $margin / 10;
1856
		$this->setCellPaddings($cpadding, 0, $cpadding, 0);
1857
		// cell margins
1858
		$this->setCellMargins(0, 0, 0, 0);
1859
		// line width (0.2 mm)
1860
		$this->LineWidth = 0.57 / $this->k;
1861
		$this->linestyleWidth = sprintf('%.2F w', ($this->LineWidth * $this->k));
1862
		$this->linestyleCap = '0 J';
1863
		$this->linestyleJoin = '0 j';
1864
		$this->linestyleDash = '[] 0 d';
1865
		// automatic page break
1866
		$this->SetAutoPageBreak(true, (2 * $margin));
1867
		// full width display mode
1868
		$this->SetDisplayMode('fullwidth');
1869
		// compression
1870
		$this->SetCompression(true);
1871
		// set default PDF version number
1872
		$this->PDFVersion = '1.7';
1873
		$this->encoding = $encoding;
1874
		$this->HREF = array();
1875
		$this->getFontsList();
1876
		$this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1877
		$this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1878
		$this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1879
		$this->extgstates = array();
1880
		// user's rights
1881
		$this->sign = false;
1882
		$this->ur['enabled'] = false;
1883
		$this->ur['document'] = '/FullSave';
1884
		$this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1885
		$this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1886
		$this->ur['signature'] = '/Modify';
1887
		$this->ur['ef'] = '/Create/Delete/Modify/Import';
1888
		$this->ur['formex'] = '';
1889
		$this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1890
		// set default JPEG quality
1891
		$this->jpeg_quality = 75;
1892
		// initialize some settings
1893
		$this->utf8Bidi(array(''), '');
1894
		// set default font
1895
		$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1896
		// check if PCRE Unicode support is enabled
1897
		if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1898
			// PCRE unicode support is turned ON
1899
			// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
1900
			// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
1901
			// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
1902
			//$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
1903
			$this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
1904
		} else {
1905
			// PCRE unicode support is turned OFF
1906
			$this->setSpacesRE('/[^\S\xa0]/');
1907
		}
1908
		$this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1909
		// set file ID for trailer
1910
		$this->file_id = md5($this->getRandomSeed('TCPDF'.$orientation.$unit.$format.$encoding));
1911
		// get default graphic vars
1912
		$this->default_graphic_vars = $this->getGraphicVars();
1913
	}
1914
 
1915
	/**
1916
	 * Default destructor.
1917
	 * @access public
1918
	 * @since 1.53.0.TC016
1919
	 */
1920
	public function __destruct() {
1921
		// restore internal encoding
1922
		if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1923
			mb_internal_encoding($this->internal_encoding);
1924
		}
1925
		// unset all class variables
1926
		$this->_destroy(true);
1927
	}
1928
 
1929
	/**
1930
	 * Return the current TCPDF version.
1931
	 * @return TCPDF version string
1932
	 * @access public
1933
	 * @since 5.9.012 (2010-11-10)
1934
	 */
1935
	public function getTCPDFVersion() {
1936
		return $this->tcpdf_version;
1937
	}
1938
 
1939
	/**
1940
	 * Set the units of measure for the document.
1941
	 * @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.
1942
	 * @access public
1943
	 * @since 3.0.015 (2008-06-06)
1944
	 */
1945
	public function setPageUnit($unit) {
1946
		$unit = strtolower($unit);
1947
		//Set scale factor
1948
		switch ($unit) {
1949
			// points
1950
			case 'px':
1951
			case 'pt': {
1952
				$this->k = 1;
1953
				break;
1954
			}
1955
			// millimeters
1956
			case 'mm': {
1957
				$this->k = $this->dpi / 25.4;
1958
				break;
1959
			}
1960
			// centimeters
1961
			case 'cm': {
1962
				$this->k = $this->dpi / 2.54;
1963
				break;
1964
			}
1965
			// inches
1966
			case 'in': {
1967
				$this->k = $this->dpi;
1968
				break;
1969
			}
1970
			// unsupported unit
1971
			default : {
1972
				$this->Error('Incorrect unit: '.$unit);
1973
				break;
1974
			}
1975
		}
1976
		$this->pdfunit = $unit;
1977
		if (isset($this->CurOrientation)) {
1978
			$this->setPageOrientation($this->CurOrientation);
1979
		}
1980
	}
1981
 
1982
	/**
1983
	 * Get page dimensions from format name.
1984
	 * @param mixed $format The format name. It can be: <ul>
1985
	 * <li><b>ISO 216 A Series + 2 SIS 014711 extensions</b></li>
1986
	 * <li>A0 (841x1189 mm ; 33.11x46.81 in)</li>
1987
	 * <li>A1 (594x841 mm ; 23.39x33.11 in)</li>
1988
	 * <li>A2 (420x594 mm ; 16.54x23.39 in)</li>
1989
	 * <li>A3 (297x420 mm ; 11.69x16.54 in)</li>
1990
	 * <li>A4 (210x297 mm ; 8.27x11.69 in)</li>
1991
	 * <li>A5 (148x210 mm ; 5.83x8.27 in)</li>
1992
	 * <li>A6 (105x148 mm ; 4.13x5.83 in)</li>
1993
	 * <li>A7 (74x105 mm ; 2.91x4.13 in)</li>
1994
	 * <li>A8 (52x74 mm ; 2.05x2.91 in)</li>
1995
	 * <li>A9 (37x52 mm ; 1.46x2.05 in)</li>
1996
	 * <li>A10 (26x37 mm ; 1.02x1.46 in)</li>
1997
	 * <li>A11 (18x26 mm ; 0.71x1.02 in)</li>
1998
	 * <li>A12 (13x18 mm ; 0.51x0.71 in)</li>
1999
	 * <li><b>ISO 216 B Series + 2 SIS 014711 extensions</b></li>
2000
	 * <li>B0 (1000x1414 mm ; 39.37x55.67 in)</li>
2001
	 * <li>B1 (707x1000 mm ; 27.83x39.37 in)</li>
2002
	 * <li>B2 (500x707 mm ; 19.69x27.83 in)</li>
2003
	 * <li>B3 (353x500 mm ; 13.90x19.69 in)</li>
2004
	 * <li>B4 (250x353 mm ; 9.84x13.90 in)</li>
2005
	 * <li>B5 (176x250 mm ; 6.93x9.84 in)</li>
2006
	 * <li>B6 (125x176 mm ; 4.92x6.93 in)</li>
2007
	 * <li>B7 (88x125 mm ; 3.46x4.92 in)</li>
2008
	 * <li>B8 (62x88 mm ; 2.44x3.46 in)</li>
2009
	 * <li>B9 (44x62 mm ; 1.73x2.44 in)</li>
2010
	 * <li>B10 (31x44 mm ; 1.22x1.73 in)</li>
2011
	 * <li>B11 (22x31 mm ; 0.87x1.22 in)</li>
2012
	 * <li>B12 (15x22 mm ; 0.59x0.87 in)</li>
2013
	 * <li><b>ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION</b></li>
2014
	 * <li>C0 (917x1297 mm ; 36.10x51.06 in)</li>
2015
	 * <li>C1 (648x917 mm ; 25.51x36.10 in)</li>
2016
	 * <li>C2 (458x648 mm ; 18.03x25.51 in)</li>
2017
	 * <li>C3 (324x458 mm ; 12.76x18.03 in)</li>
2018
	 * <li>C4 (229x324 mm ; 9.02x12.76 in)</li>
2019
	 * <li>C5 (162x229 mm ; 6.38x9.02 in)</li>
2020
	 * <li>C6 (114x162 mm ; 4.49x6.38 in)</li>
2021
	 * <li>C7 (81x114 mm ; 3.19x4.49 in)</li>
2022
	 * <li>C8 (57x81 mm ; 2.24x3.19 in)</li>
2023
	 * <li>C9 (40x57 mm ; 1.57x2.24 in)</li>
2024
	 * <li>C10 (28x40 mm ; 1.10x1.57 in)</li>
2025
	 * <li>C11 (20x28 mm ; 0.79x1.10 in)</li>
2026
	 * <li>C12 (14x20 mm ; 0.55x0.79 in)</li>
2027
	 * <li>C76 (81x162 mm ; 3.19x6.38 in)</li>
2028
	 * <li>DL (110x220 mm ; 4.33x8.66 in)</li>
2029
	 * <li><b>SIS 014711 E Series</b></li>
2030
	 * <li>E0 (879x1241 mm ; 34.61x48.86 in)</li>
2031
	 * <li>E1 (620x879 mm ; 24.41x34.61 in)</li>
2032
	 * <li>E2 (440x620 mm ; 17.32x24.41 in)</li>
2033
	 * <li>E3 (310x440 mm ; 12.20x17.32 in)</li>
2034
	 * <li>E4 (220x310 mm ; 8.66x12.20 in)</li>
2035
	 * <li>E5 (155x220 mm ; 6.10x8.66 in)</li>
2036
	 * <li>E6 (110x155 mm ; 4.33x6.10 in)</li>
2037
	 * <li>E7 (78x110 mm ; 3.07x4.33 in)</li>
2038
	 * <li>E8 (55x78 mm ; 2.17x3.07 in)</li>
2039
	 * <li>E9 (39x55 mm ; 1.54x2.17 in)</li>
2040
	 * <li>E10 (27x39 mm ; 1.06x1.54 in)</li>
2041
	 * <li>E11 (19x27 mm ; 0.75x1.06 in)</li>
2042
	 * <li>E12 (13x19 mm ; 0.51x0.75 in)</li>
2043
	 * <li><b>SIS 014711 G Series</b></li>
2044
	 * <li>G0 (958x1354 mm ; 37.72x53.31 in)</li>
2045
	 * <li>G1 (677x958 mm ; 26.65x37.72 in)</li>
2046
	 * <li>G2 (479x677 mm ; 18.86x26.65 in)</li>
2047
	 * <li>G3 (338x479 mm ; 13.31x18.86 in)</li>
2048
	 * <li>G4 (239x338 mm ; 9.41x13.31 in)</li>
2049
	 * <li>G5 (169x239 mm ; 6.65x9.41 in)</li>
2050
	 * <li>G6 (119x169 mm ; 4.69x6.65 in)</li>
2051
	 * <li>G7 (84x119 mm ; 3.31x4.69 in)</li>
2052
	 * <li>G8 (59x84 mm ; 2.32x3.31 in)</li>
2053
	 * <li>G9 (42x59 mm ; 1.65x2.32 in)</li>
2054
	 * <li>G10 (29x42 mm ; 1.14x1.65 in)</li>
2055
	 * <li>G11 (21x29 mm ; 0.83x1.14 in)</li>
2056
	 * <li>G12 (14x21 mm ; 0.55x0.83 in)</li>
2057
	 * <li><b>ISO Press</b></li>
2058
	 * <li>RA0 (860x1220 mm ; 33.86x48.03 in)</li>
2059
	 * <li>RA1 (610x860 mm ; 24.02x33.86 in)</li>
2060
	 * <li>RA2 (430x610 mm ; 16.93x24.02 in)</li>
2061
	 * <li>RA3 (305x430 mm ; 12.01x16.93 in)</li>
2062
	 * <li>RA4 (215x305 mm ; 8.46x12.01 in)</li>
2063
	 * <li>SRA0 (900x1280 mm ; 35.43x50.39 in)</li>
2064
	 * <li>SRA1 (640x900 mm ; 25.20x35.43 in)</li>
2065
	 * <li>SRA2 (450x640 mm ; 17.72x25.20 in)</li>
2066
	 * <li>SRA3 (320x450 mm ; 12.60x17.72 in)</li>
2067
	 * <li>SRA4 (225x320 mm ; 8.86x12.60 in)</li>
2068
	 * <li><b>German DIN 476</b></li>
2069
	 * <li>4A0 (1682x2378 mm ; 66.22x93.62 in)</li>
2070
	 * <li>2A0 (1189x1682 mm ; 46.81x66.22 in)</li>
2071
	 * <li><b>Variations on the ISO Standard</b></li>
2072
	 * <li>A2_EXTRA (445x619 mm ; 17.52x24.37 in)</li>
2073
	 * <li>A3+ (329x483 mm ; 12.95x19.02 in)</li>
2074
	 * <li>A3_EXTRA (322x445 mm ; 12.68x17.52 in)</li>
2075
	 * <li>A3_SUPER (305x508 mm ; 12.01x20.00 in)</li>
2076
	 * <li>SUPER_A3 (305x487 mm ; 12.01x19.17 in)</li>
2077
	 * <li>A4_EXTRA (235x322 mm ; 9.25x12.68 in)</li>
2078
	 * <li>A4_SUPER (229x322 mm ; 9.02x12.68 in)</li>
2079
	 * <li>SUPER_A4 (227x356 mm ; 8.94x14.02 in)</li>
2080
	 * <li>A4_LONG (210x348 mm ; 8.27x13.70 in)</li>
2081
	 * <li>F4 (210x330 mm ; 8.27x12.99 in)</li>
2082
	 * <li>SO_B5_EXTRA (202x276 mm ; 7.95x10.87 in)</li>
2083
	 * <li>A5_EXTRA (173x235 mm ; 6.81x9.25 in)</li>
2084
	 * <li><b>ANSI Series</b></li>
2085
	 * <li>ANSI_E (864x1118 mm ; 34.00x44.00 in)</li>
2086
	 * <li>ANSI_D (559x864 mm ; 22.00x34.00 in)</li>
2087
	 * <li>ANSI_C (432x559 mm ; 17.00x22.00 in)</li>
2088
	 * <li>ANSI_B (279x432 mm ; 11.00x17.00 in)</li>
2089
	 * <li>ANSI_A (216x279 mm ; 8.50x11.00 in)</li>
2090
	 * <li><b>Traditional 'Loose' North American Paper Sizes</b></li>
2091
	 * <li>LEDGER, USLEDGER (432x279 mm ; 17.00x11.00 in)</li>
2092
	 * <li>TABLOID, USTABLOID, BIBLE, ORGANIZERK (279x432 mm ; 11.00x17.00 in)</li>
2093
	 * <li>LETTER, USLETTER, ORGANIZERM (216x279 mm ; 8.50x11.00 in)</li>
2094
	 * <li>LEGAL, USLEGAL (216x356 mm ; 8.50x14.00 in)</li>
2095
	 * <li>GLETTER, GOVERNMENTLETTER (203x267 mm ; 8.00x10.50 in)</li>
2096
	 * <li>JLEGAL, JUNIORLEGAL (203x127 mm ; 8.00x5.00 in)</li>
2097
	 * <li><b>Other North American Paper Sizes</b></li>
2098
	 * <li>QUADDEMY (889x1143 mm ; 35.00x45.00 in)</li>
2099
	 * <li>SUPER_B (330x483 mm ; 13.00x19.00 in)</li>
2100
	 * <li>QUARTO (229x279 mm ; 9.00x11.00 in)</li>
2101
	 * <li>FOLIO, GOVERNMENTLEGAL (216x330 mm ; 8.50x13.00 in)</li>
2102
	 * <li>EXECUTIVE, MONARCH (184x267 mm ; 7.25x10.50 in)</li>
2103
	 * <li>MEMO, STATEMENT, ORGANIZERL (140x216 mm ; 5.50x8.50 in)</li>
2104
	 * <li>FOOLSCAP (210x330 mm ; 8.27x13.00 in)</li>
2105
	 * <li>COMPACT (108x171 mm ; 4.25x6.75 in)</li>
2106
	 * <li>ORGANIZERJ (70x127 mm ; 2.75x5.00 in)</li>
2107
	 * <li><b>Canadian standard CAN 2-9.60M</b></li>
2108
	 * <li>P1 (560x860 mm ; 22.05x33.86 in)</li>
2109
	 * <li>P2 (430x560 mm ; 16.93x22.05 in)</li>
2110
	 * <li>P3 (280x430 mm ; 11.02x16.93 in)</li>
2111
	 * <li>P4 (215x280 mm ; 8.46x11.02 in)</li>
2112
	 * <li>P5 (140x215 mm ; 5.51x8.46 in)</li>
2113
	 * <li>P6 (107x140 mm ; 4.21x5.51 in)</li>
2114
	 * <li><b>North American Architectural Sizes</b></li>
2115
	 * <li>ARCH_E (914x1219 mm ; 36.00x48.00 in)</li>
2116
	 * <li>ARCH_E1 (762x1067 mm ; 30.00x42.00 in)</li>
2117
	 * <li>ARCH_D (610x914 mm ; 24.00x36.00 in)</li>
2118
	 * <li>ARCH_C, BROADSHEET (457x610 mm ; 18.00x24.00 in)</li>
2119
	 * <li>ARCH_B (305x457 mm ; 12.00x18.00 in)</li>
2120
	 * <li>ARCH_A (229x305 mm ; 9.00x12.00 in)</li>
2121
	 * <li><b>Announcement Envelopes</b></li>
2122
	 * <li>ANNENV_A2 (111x146 mm ; 4.37x5.75 in)</li>
2123
	 * <li>ANNENV_A6 (121x165 mm ; 4.75x6.50 in)</li>
2124
	 * <li>ANNENV_A7 (133x184 mm ; 5.25x7.25 in)</li>
2125
	 * <li>ANNENV_A8 (140x206 mm ; 5.50x8.12 in)</li>
2126
	 * <li>ANNENV_A10 (159x244 mm ; 6.25x9.62 in)</li>
2127
	 * <li>ANNENV_SLIM (98x225 mm ; 3.87x8.87 in)</li>
2128
	 * <li><b>Commercial Envelopes</b></li>
2129
	 * <li>COMMENV_N6_1/4 (89x152 mm ; 3.50x6.00 in)</li>
2130
	 * <li>COMMENV_N6_3/4 (92x165 mm ; 3.62x6.50 in)</li>
2131
	 * <li>COMMENV_N8 (98x191 mm ; 3.87x7.50 in)</li>
2132
	 * <li>COMMENV_N9 (98x225 mm ; 3.87x8.87 in)</li>
2133
	 * <li>COMMENV_N10 (105x241 mm ; 4.12x9.50 in)</li>
2134
	 * <li>COMMENV_N11 (114x263 mm ; 4.50x10.37 in)</li>
2135
	 * <li>COMMENV_N12 (121x279 mm ; 4.75x11.00 in)</li>
2136
	 * <li>COMMENV_N14 (127x292 mm ; 5.00x11.50 in)</li>
2137
	 * <li><b>Catalogue Envelopes</b></li>
2138
	 * <li>CATENV_N1 (152x229 mm ; 6.00x9.00 in)</li>
2139
	 * <li>CATENV_N1_3/4 (165x241 mm ; 6.50x9.50 in)</li>
2140
	 * <li>CATENV_N2 (165x254 mm ; 6.50x10.00 in)</li>
2141
	 * <li>CATENV_N3 (178x254 mm ; 7.00x10.00 in)</li>
2142
	 * <li>CATENV_N6 (191x267 mm ; 7.50x10.50 in)</li>
2143
	 * <li>CATENV_N7 (203x279 mm ; 8.00x11.00 in)</li>
2144
	 * <li>CATENV_N8 (210x286 mm ; 8.25x11.25 in)</li>
2145
	 * <li>CATENV_N9_1/2 (216x267 mm ; 8.50x10.50 in)</li>
2146
	 * <li>CATENV_N9_3/4 (222x286 mm ; 8.75x11.25 in)</li>
2147
	 * <li>CATENV_N10_1/2 (229x305 mm ; 9.00x12.00 in)</li>
2148
	 * <li>CATENV_N12_1/2 (241x318 mm ; 9.50x12.50 in)</li>
2149
	 * <li>CATENV_N13_1/2 (254x330 mm ; 10.00x13.00 in)</li>
2150
	 * <li>CATENV_N14_1/4 (286x311 mm ; 11.25x12.25 in)</li>
2151
	 * <li>CATENV_N14_1/2 (292x368 mm ; 11.50x14.50 in)</li>
2152
	 * <li><b>Japanese (JIS P 0138-61) Standard B-Series</b></li>
2153
	 * <li>JIS_B0 (1030x1456 mm ; 40.55x57.32 in)</li>
2154
	 * <li>JIS_B1 (728x1030 mm ; 28.66x40.55 in)</li>
2155
	 * <li>JIS_B2 (515x728 mm ; 20.28x28.66 in)</li>
2156
	 * <li>JIS_B3 (364x515 mm ; 14.33x20.28 in)</li>
2157
	 * <li>JIS_B4 (257x364 mm ; 10.12x14.33 in)</li>
2158
	 * <li>JIS_B5 (182x257 mm ; 7.17x10.12 in)</li>
2159
	 * <li>JIS_B6 (128x182 mm ; 5.04x7.17 in)</li>
2160
	 * <li>JIS_B7 (91x128 mm ; 3.58x5.04 in)</li>
2161
	 * <li>JIS_B8 (64x91 mm ; 2.52x3.58 in)</li>
2162
	 * <li>JIS_B9 (45x64 mm ; 1.77x2.52 in)</li>
2163
	 * <li>JIS_B10 (32x45 mm ; 1.26x1.77 in)</li>
2164
	 * <li>JIS_B11 (22x32 mm ; 0.87x1.26 in)</li>
2165
	 * <li>JIS_B12 (16x22 mm ; 0.63x0.87 in)</li>
2166
	 * <li><b>PA Series</b></li>
2167
	 * <li>PA0 (840x1120 mm ; 33.07x44.09 in)</li>
2168
	 * <li>PA1 (560x840 mm ; 22.05x33.07 in)</li>
2169
	 * <li>PA2 (420x560 mm ; 16.54x22.05 in)</li>
2170
	 * <li>PA3 (280x420 mm ; 11.02x16.54 in)</li>
2171
	 * <li>PA4 (210x280 mm ; 8.27x11.02 in)</li>
2172
	 * <li>PA5 (140x210 mm ; 5.51x8.27 in)</li>
2173
	 * <li>PA6 (105x140 mm ; 4.13x5.51 in)</li>
2174
	 * <li>PA7 (70x105 mm ; 2.76x4.13 in)</li>
2175
	 * <li>PA8 (52x70 mm ; 2.05x2.76 in)</li>
2176
	 * <li>PA9 (35x52 mm ; 1.38x2.05 in)</li>
2177
	 * <li>PA10 (26x35 mm ; 1.02x1.38 in)</li>
2178
	 * <li><b>Standard Photographic Print Sizes</b></li>
2179
	 * <li>PASSPORT_PHOTO (35x45 mm ; 1.38x1.77 in)</li>
2180
	 * <li>E (82x120 mm ; 3.25x4.72 in)</li>
2181
	 * <li>3R, L (89x127 mm ; 3.50x5.00 in)</li>
2182
	 * <li>4R, KG (102x152 mm ; 4.02x5.98 in)</li>
2183
	 * <li>4D (120x152 mm ; 4.72x5.98 in)</li>
2184
	 * <li>5R, 2L (127x178 mm ; 5.00x7.01 in)</li>
2185
	 * <li>6R, 8P (152x203 mm ; 5.98x7.99 in)</li>
2186
	 * <li>8R, 6P (203x254 mm ; 7.99x10.00 in)</li>
2187
	 * <li>S8R, 6PW (203x305 mm ; 7.99x12.01 in)</li>
2188
	 * <li>10R, 4P (254x305 mm ; 10.00x12.01 in)</li>
2189
	 * <li>S10R, 4PW (254x381 mm ; 10.00x15.00 in)</li>
2190
	 * <li>11R (279x356 mm ; 10.98x14.02 in)</li>
2191
	 * <li>S11R (279x432 mm ; 10.98x17.01 in)</li>
2192
	 * <li>12R (305x381 mm ; 12.01x15.00 in)</li>
2193
	 * <li>S12R (305x456 mm ; 12.01x17.95 in)</li>
2194
	 * <li><b>Common Newspaper Sizes</b></li>
2195
	 * <li>NEWSPAPER_BROADSHEET (750x600 mm ; 29.53x23.62 in)</li>
2196
	 * <li>NEWSPAPER_BERLINER (470x315 mm ; 18.50x12.40 in)</li>
2197
	 * <li>NEWSPAPER_COMPACT, NEWSPAPER_TABLOID (430x280 mm ; 16.93x11.02 in)</li>
2198
	 * <li><b>Business Cards</b></li>
2199
	 * <li>CREDIT_CARD, BUSINESS_CARD, BUSINESS_CARD_ISO7810 (54x86 mm ; 2.13x3.37 in)</li>
2200
	 * <li>BUSINESS_CARD_ISO216 (52x74 mm ; 2.05x2.91 in)</li>
2201
	 * <li>BUSINESS_CARD_IT, BUSINESS_CARD_UK, BUSINESS_CARD_FR, BUSINESS_CARD_DE, BUSINESS_CARD_ES (55x85 mm ; 2.17x3.35 in)</li>
2202
	 * <li>BUSINESS_CARD_US, BUSINESS_CARD_CA (51x89 mm ; 2.01x3.50 in)</li>
2203
	 * <li>BUSINESS_CARD_JP (55x91 mm ; 2.17x3.58 in)</li>
2204
	 * <li>BUSINESS_CARD_HK (54x90 mm ; 2.13x3.54 in)</li>
2205
	 * <li>BUSINESS_CARD_AU, BUSINESS_CARD_DK, BUSINESS_CARD_SE (55x90 mm ; 2.17x3.54 in)</li>
2206
	 * <li>BUSINESS_CARD_RU, BUSINESS_CARD_CZ, BUSINESS_CARD_FI, BUSINESS_CARD_HU, BUSINESS_CARD_IL (50x90 mm ; 1.97x3.54 in)</li>
2207
	 * <li><b>Billboards</b></li>
2208
	 * <li>4SHEET (1016x1524 mm ; 40.00x60.00 in)</li>
2209
	 * <li>6SHEET (1200x1800 mm ; 47.24x70.87 in)</li>
2210
	 * <li>12SHEET (3048x1524 mm ; 120.00x60.00 in)</li>
2211
	 * <li>16SHEET (2032x3048 mm ; 80.00x120.00 in)</li>
2212
	 * <li>32SHEET (4064x3048 mm ; 160.00x120.00 in)</li>
2213
	 * <li>48SHEET (6096x3048 mm ; 240.00x120.00 in)</li>
2214
	 * <li>64SHEET (8128x3048 mm ; 320.00x120.00 in)</li>
2215
	 * <li>96SHEET (12192x3048 mm ; 480.00x120.00 in)</li>
2216
	 * <li><b>Old Imperial English (some are still used in USA)</b></li>
2217
	 * <li>EN_EMPEROR (1219x1829 mm ; 48.00x72.00 in)</li>
2218
	 * <li>EN_ANTIQUARIAN (787x1346 mm ; 31.00x53.00 in)</li>
2219
	 * <li>EN_GRAND_EAGLE (730x1067 mm ; 28.75x42.00 in)</li>
2220
	 * <li>EN_DOUBLE_ELEPHANT (679x1016 mm ; 26.75x40.00 in)</li>
2221
	 * <li>EN_ATLAS (660x864 mm ; 26.00x34.00 in)</li>
2222
	 * <li>EN_COLOMBIER (597x876 mm ; 23.50x34.50 in)</li>
2223
	 * <li>EN_ELEPHANT (584x711 mm ; 23.00x28.00 in)</li>
2224
	 * <li>EN_DOUBLE_DEMY (572x902 mm ; 22.50x35.50 in)</li>
2225
	 * <li>EN_IMPERIAL (559x762 mm ; 22.00x30.00 in)</li>
2226
	 * <li>EN_PRINCESS (546x711 mm ; 21.50x28.00 in)</li>
2227
	 * <li>EN_CARTRIDGE (533x660 mm ; 21.00x26.00 in)</li>
2228
	 * <li>EN_DOUBLE_LARGE_POST (533x838 mm ; 21.00x33.00 in)</li>
2229
	 * <li>EN_ROYAL (508x635 mm ; 20.00x25.00 in)</li>
2230
	 * <li>EN_SHEET, EN_HALF_POST (495x597 mm ; 19.50x23.50 in)</li>
2231
	 * <li>EN_SUPER_ROYAL (483x686 mm ; 19.00x27.00 in)</li>
2232
	 * <li>EN_DOUBLE_POST (483x775 mm ; 19.00x30.50 in)</li>
2233
	 * <li>EN_MEDIUM (445x584 mm ; 17.50x23.00 in)</li>
2234
	 * <li>EN_DEMY (445x572 mm ; 17.50x22.50 in)</li>
2235
	 * <li>EN_LARGE_POST (419x533 mm ; 16.50x21.00 in)</li>
2236
	 * <li>EN_COPY_DRAUGHT (406x508 mm ; 16.00x20.00 in)</li>
2237
	 * <li>EN_POST (394x489 mm ; 15.50x19.25 in)</li>
2238
	 * <li>EN_CROWN (381x508 mm ; 15.00x20.00 in)</li>
2239
	 * <li>EN_PINCHED_POST (375x470 mm ; 14.75x18.50 in)</li>
2240
	 * <li>EN_BRIEF (343x406 mm ; 13.50x16.00 in)</li>
2241
	 * <li>EN_FOOLSCAP (343x432 mm ; 13.50x17.00 in)</li>
2242
	 * <li>EN_SMALL_FOOLSCAP (337x419 mm ; 13.25x16.50 in)</li>
2243
	 * <li>EN_POTT (318x381 mm ; 12.50x15.00 in)</li>
2244
	 * <li><b>Old Imperial Belgian</b></li>
2245
	 * <li>BE_GRAND_AIGLE (700x1040 mm ; 27.56x40.94 in)</li>
2246
	 * <li>BE_COLOMBIER (620x850 mm ; 24.41x33.46 in)</li>
2247
	 * <li>BE_DOUBLE_CARRE (620x920 mm ; 24.41x36.22 in)</li>
2248
	 * <li>BE_ELEPHANT (616x770 mm ; 24.25x30.31 in)</li>
2249
	 * <li>BE_PETIT_AIGLE (600x840 mm ; 23.62x33.07 in)</li>
2250
	 * <li>BE_GRAND_JESUS (550x730 mm ; 21.65x28.74 in)</li>
2251
	 * <li>BE_JESUS (540x730 mm ; 21.26x28.74 in)</li>
2252
	 * <li>BE_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2253
	 * <li>BE_GRAND_MEDIAN (460x605 mm ; 18.11x23.82 in)</li>
2254
	 * <li>BE_DOUBLE_POSTE (435x565 mm ; 17.13x22.24 in)</li>
2255
	 * <li>BE_COQUILLE (430x560 mm ; 16.93x22.05 in)</li>
2256
	 * <li>BE_PETIT_MEDIAN (415x530 mm ; 16.34x20.87 in)</li>
2257
	 * <li>BE_RUCHE (360x460 mm ; 14.17x18.11 in)</li>
2258
	 * <li>BE_PROPATRIA (345x430 mm ; 13.58x16.93 in)</li>
2259
	 * <li>BE_LYS (317x397 mm ; 12.48x15.63 in)</li>
2260
	 * <li>BE_POT (307x384 mm ; 12.09x15.12 in)</li>
2261
	 * <li>BE_ROSETTE (270x347 mm ; 10.63x13.66 in)</li>
2262
	 * <li><b>Old Imperial French</b></li>
2263
	 * <li>FR_UNIVERS (1000x1300 mm ; 39.37x51.18 in)</li>
2264
	 * <li>FR_DOUBLE_COLOMBIER (900x1260 mm ; 35.43x49.61 in)</li>
2265
	 * <li>FR_GRANDE_MONDE (900x1260 mm ; 35.43x49.61 in)</li>
2266
	 * <li>FR_DOUBLE_SOLEIL (800x1200 mm ; 31.50x47.24 in)</li>
2267
	 * <li>FR_DOUBLE_JESUS (760x1120 mm ; 29.92x44.09 in)</li>
2268
	 * <li>FR_GRAND_AIGLE (750x1060 mm ; 29.53x41.73 in)</li>
2269
	 * <li>FR_PETIT_AIGLE (700x940 mm ; 27.56x37.01 in)</li>
2270
	 * <li>FR_DOUBLE_RAISIN (650x1000 mm ; 25.59x39.37 in)</li>
2271
	 * <li>FR_JOURNAL (650x940 mm ; 25.59x37.01 in)</li>
2272
	 * <li>FR_COLOMBIER_AFFICHE (630x900 mm ; 24.80x35.43 in)</li>
2273
	 * <li>FR_DOUBLE_CAVALIER (620x920 mm ; 24.41x36.22 in)</li>
2274
	 * <li>FR_CLOCHE (600x800 mm ; 23.62x31.50 in)</li>
2275
	 * <li>FR_SOLEIL (600x800 mm ; 23.62x31.50 in)</li>
2276
	 * <li>FR_DOUBLE_CARRE (560x900 mm ; 22.05x35.43 in)</li>
2277
	 * <li>FR_DOUBLE_COQUILLE (560x880 mm ; 22.05x34.65 in)</li>
2278
	 * <li>FR_JESUS (560x760 mm ; 22.05x29.92 in)</li>
2279
	 * <li>FR_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2280
	 * <li>FR_CAVALIER (460x620 mm ; 18.11x24.41 in)</li>
2281
	 * <li>FR_DOUBLE_COURONNE (460x720 mm ; 18.11x28.35 in)</li>
2282
	 * <li>FR_CARRE (450x560 mm ; 17.72x22.05 in)</li>
2283
	 * <li>FR_COQUILLE (440x560 mm ; 17.32x22.05 in)</li>
2284
	 * <li>FR_DOUBLE_TELLIERE (440x680 mm ; 17.32x26.77 in)</li>
2285
	 * <li>FR_DOUBLE_CLOCHE (400x600 mm ; 15.75x23.62 in)</li>
2286
	 * <li>FR_DOUBLE_POT (400x620 mm ; 15.75x24.41 in)</li>
2287
	 * <li>FR_ECU (400x520 mm ; 15.75x20.47 in)</li>
2288
	 * <li>FR_COURONNE (360x460 mm ; 14.17x18.11 in)</li>
2289
	 * <li>FR_TELLIERE (340x440 mm ; 13.39x17.32 in)</li>
2290
	 * <li>FR_POT (310x400 mm ; 12.20x15.75 in)</li>
2291
	 * </ul>
2292
	 * @return array containing page width and height in points
2293
	 * @access public
2294
	 * @since 5.0.010 (2010-05-17)
2295
	 */
2296
	public function getPageSizeFromFormat($format) {
2297
		// Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
2298
		switch (strtoupper($format)) {
2299
			// ISO 216 A Series + 2 SIS 014711 extensions
2300
			case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
2301
			case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
2302
			case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
2303
			case 'A3' : {$pf = array(  841.890, 1190.551); break;}
2304
			case 'A4' : {$pf = array(  595.276,  841.890); break;}
2305
			case 'A5' : {$pf = array(  419.528,  595.276); break;}
2306
			case 'A6' : {$pf = array(  297.638,  419.528); break;}
2307
			case 'A7' : {$pf = array(  209.764,  297.638); break;}
2308
			case 'A8' : {$pf = array(  147.402,  209.764); break;}
2309
			case 'A9' : {$pf = array(  104.882,  147.402); break;}
2310
			case 'A10': {$pf = array(   73.701,  104.882); break;}
2311
			case 'A11': {$pf = array(   51.024,   73.701); break;}
2312
			case 'A12': {$pf = array(   36.850,   51.024); break;}
2313
			// ISO 216 B Series + 2 SIS 014711 extensions
2314
			case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
2315
			case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
2316
			case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
2317
			case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
2318
			case 'B4' : {$pf = array(  708.661, 1000.630); break;}
2319
			case 'B5' : {$pf = array(  498.898,  708.661); break;}
2320
			case 'B6' : {$pf = array(  354.331,  498.898); break;}
2321
			case 'B7' : {$pf = array(  249.449,  354.331); break;}
2322
			case 'B8' : {$pf = array(  175.748,  249.449); break;}
2323
			case 'B9' : {$pf = array(  124.724,  175.748); break;}
2324
			case 'B10': {$pf = array(   87.874,  124.724); break;}
2325
			case 'B11': {$pf = array(   62.362,   87.874); break;}
2326
			case 'B12': {$pf = array(   42.520,   62.362); break;}
2327
			// ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
2328
			case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
2329
			case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
2330
			case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
2331
			case 'C3' : {$pf = array(  918.425, 1298.268); break;}
2332
			case 'C4' : {$pf = array(  649.134,  918.425); break;}
2333
			case 'C5' : {$pf = array(  459.213,  649.134); break;}
2334
			case 'C6' : {$pf = array(  323.150,  459.213); break;}
2335
			case 'C7' : {$pf = array(  229.606,  323.150); break;}
2336
			case 'C8' : {$pf = array(  161.575,  229.606); break;}
2337
			case 'C9' : {$pf = array(  113.386,  161.575); break;}
2338
			case 'C10': {$pf = array(   79.370,  113.386); break;}
2339
			case 'C11': {$pf = array(   56.693,   79.370); break;}
2340
			case 'C12': {$pf = array(   39.685,   56.693); break;}
2341
			case 'C76': {$pf = array(  229.606,  459.213); break;}
2342
			case 'DL' : {$pf = array(  311.811,  623.622); break;}
2343
			// SIS 014711 E Series
2344
			case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
2345
			case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
2346
			case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
2347
			case 'E3' : {$pf = array(  878.740, 1247.244); break;}
2348
			case 'E4' : {$pf = array(  623.622,  878.740); break;}
2349
			case 'E5' : {$pf = array(  439.370,  623.622); break;}
2350
			case 'E6' : {$pf = array(  311.811,  439.370); break;}
2351
			case 'E7' : {$pf = array(  221.102,  311.811); break;}
2352
			case 'E8' : {$pf = array(  155.906,  221.102); break;}
2353
			case 'E9' : {$pf = array(  110.551,  155.906); break;}
2354
			case 'E10': {$pf = array(   76.535,  110.551); break;}
2355
			case 'E11': {$pf = array(   53.858,   76.535); break;}
2356
			case 'E12': {$pf = array(   36.850,   53.858); break;}
2357
			// SIS 014711 G Series
2358
			case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
2359
			case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
2360
			case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
2361
			case 'G3' : {$pf = array(  958.110, 1357.795); break;}
2362
			case 'G4' : {$pf = array(  677.480,  958.110); break;}
2363
			case 'G5' : {$pf = array(  479.055,  677.480); break;}
2364
			case 'G6' : {$pf = array(  337.323,  479.055); break;}
2365
			case 'G7' : {$pf = array(  238.110,  337.323); break;}
2366
			case 'G8' : {$pf = array(  167.244,  238.110); break;}
2367
			case 'G9' : {$pf = array(  119.055,  167.244); break;}
2368
			case 'G10': {$pf = array(   82.205,  119.055); break;}
2369
			case 'G11': {$pf = array(   59.528,   82.205); break;}
2370
			case 'G12': {$pf = array(   39.685,   59.528); break;}
2371
			// ISO Press
2372
			case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
2373
			case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
2374
			case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
2375
			case 'RA3': {$pf = array(  864.567, 1218.898); break;}
2376
			case 'RA4': {$pf = array(  609.449,  864.567); break;}
2377
			case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
2378
			case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
2379
			case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
2380
			case 'SRA3': {$pf = array(  907.087, 1275.591); break;}
2381
			case 'SRA4': {$pf = array(  637.795,  907.087); break;}
2382
			// German  DIN 476
2383
			case '4A0': {$pf = array( 4767.874, 6740.787); break;}
2384
			case '2A0': {$pf = array( 3370.394, 4767.874); break;}
2385
			// Variations on the ISO Standard
2386
			case 'A2_EXTRA'   : {$pf = array( 1261.417, 1754.646); break;}
2387
			case 'A3+'        : {$pf = array(  932.598, 1369.134); break;}
2388
			case 'A3_EXTRA'   : {$pf = array(  912.756, 1261.417); break;}
2389
			case 'A3_SUPER'   : {$pf = array(  864.567, 1440.000); break;}
2390
			case 'SUPER_A3'   : {$pf = array(  864.567, 1380.472); break;}
2391
			case 'A4_EXTRA'   : {$pf = array(  666.142,  912.756); break;}
2392
			case 'A4_SUPER'   : {$pf = array(  649.134,  912.756); break;}
2393
			case 'SUPER_A4'   : {$pf = array(  643.465, 1009.134); break;}
2394
			case 'A4_LONG'    : {$pf = array(  595.276,  986.457); break;}
2395
			case 'F4'         : {$pf = array(  595.276,  935.433); break;}
2396
			case 'SO_B5_EXTRA': {$pf = array(  572.598,  782.362); break;}
2397
			case 'A5_EXTRA'   : {$pf = array(  490.394,  666.142); break;}
2398
			// ANSI Series
2399
			case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
2400
			case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
2401
			case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
2402
			case 'ANSI_B': {$pf = array(  792.000, 1224.000); break;}
2403
			case 'ANSI_A': {$pf = array(  612.000,  792.000); break;}
2404
			// Traditional 'Loose' North American Paper Sizes
2405
			case 'USLEDGER':
2406
			case 'LEDGER' : {$pf = array( 1224.000,  792.000); break;}
2407
			case 'ORGANIZERK':
2408
			case 'BIBLE':
2409
			case 'USTABLOID':
2410
			case 'TABLOID': {$pf = array(  792.000, 1224.000); break;}
2411
			case 'ORGANIZERM':
2412
			case 'USLETTER':
2413
			case 'LETTER' : {$pf = array(  612.000,  792.000); break;}
2414
			case 'USLEGAL':
2415
			case 'LEGAL'  : {$pf = array(  612.000, 1008.000); break;}
2416
			case 'GOVERNMENTLETTER':
2417
			case 'GLETTER': {$pf = array(  576.000,  756.000); break;}
2418
			case 'JUNIORLEGAL':
2419
			case 'JLEGAL' : {$pf = array(  576.000,  360.000); break;}
2420
			// Other North American Paper Sizes
2421
			case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
2422
			case 'SUPER_B': {$pf = array(  936.000, 1368.000); break;}
2423
			case 'QUARTO': {$pf = array(  648.000,  792.000); break;}
2424
			case 'GOVERNMENTLEGAL':
2425
			case 'FOLIO': {$pf = array(  612.000,  936.000); break;}
2426
			case 'MONARCH':
2427
			case 'EXECUTIVE': {$pf = array(  522.000,  756.000); break;}
2428
			case 'ORGANIZERL':
2429
			case 'STATEMENT':
2430
			case 'MEMO': {$pf = array(  396.000,  612.000); break;}
2431
			case 'FOOLSCAP': {$pf = array(  595.440,  936.000); break;}
2432
			case 'COMPACT': {$pf = array(  306.000,  486.000); break;}
2433
			case 'ORGANIZERJ': {$pf = array(  198.000,  360.000); break;}
2434
			// Canadian standard CAN 2-9.60M
2435
			case 'P1': {$pf = array( 1587.402, 2437.795); break;}
2436
			case 'P2': {$pf = array( 1218.898, 1587.402); break;}
2437
			case 'P3': {$pf = array(  793.701, 1218.898); break;}
2438
			case 'P4': {$pf = array(  609.449,  793.701); break;}
2439
			case 'P5': {$pf = array(  396.850,  609.449); break;}
2440
			case 'P6': {$pf = array(  303.307,  396.850); break;}
2441
			// North American Architectural Sizes
2442
			case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
2443
			case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
2444
			case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
2445
			case 'BROADSHEET':
2446
			case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
2447
			case 'ARCH_B' : {$pf = array(  864.000, 1296.000); break;}
2448
			case 'ARCH_A' : {$pf = array(  648.000,  864.000); break;}
2449
			// --- North American Envelope Sizes ---
2450
			//   - Announcement Envelopes
2451
			case 'ANNENV_A2'  : {$pf = array(  314.640,  414.000); break;}
2452
			case 'ANNENV_A6'  : {$pf = array(  342.000,  468.000); break;}
2453
			case 'ANNENV_A7'  : {$pf = array(  378.000,  522.000); break;}
2454
			case 'ANNENV_A8'  : {$pf = array(  396.000,  584.640); break;}
2455
			case 'ANNENV_A10' : {$pf = array(  450.000,  692.640); break;}
2456
			case 'ANNENV_SLIM': {$pf = array(  278.640,  638.640); break;}
2457
			//   - Commercial Envelopes
2458
			case 'COMMENV_N6_1/4': {$pf = array(  252.000,  432.000); break;}
2459
			case 'COMMENV_N6_3/4': {$pf = array(  260.640,  468.000); break;}
2460
			case 'COMMENV_N8'    : {$pf = array(  278.640,  540.000); break;}
2461
			case 'COMMENV_N9'    : {$pf = array(  278.640,  638.640); break;}
2462
			case 'COMMENV_N10'   : {$pf = array(  296.640,  684.000); break;}
2463
			case 'COMMENV_N11'   : {$pf = array(  324.000,  746.640); break;}
2464
			case 'COMMENV_N12'   : {$pf = array(  342.000,  792.000); break;}
2465
			case 'COMMENV_N14'   : {$pf = array(  360.000,  828.000); break;}
2466
			//   - Catalogue Envelopes
2467
			case 'CATENV_N1'     : {$pf = array(  432.000,  648.000); break;}
2468
			case 'CATENV_N1_3/4' : {$pf = array(  468.000,  684.000); break;}
2469
			case 'CATENV_N2'     : {$pf = array(  468.000,  720.000); break;}
2470
			case 'CATENV_N3'     : {$pf = array(  504.000,  720.000); break;}
2471
			case 'CATENV_N6'     : {$pf = array(  540.000,  756.000); break;}
2472
			case 'CATENV_N7'     : {$pf = array(  576.000,  792.000); break;}
2473
			case 'CATENV_N8'     : {$pf = array(  594.000,  810.000); break;}
2474
			case 'CATENV_N9_1/2' : {$pf = array(  612.000,  756.000); break;}
2475
			case 'CATENV_N9_3/4' : {$pf = array(  630.000,  810.000); break;}
2476
			case 'CATENV_N10_1/2': {$pf = array(  648.000,  864.000); break;}
2477
			case 'CATENV_N12_1/2': {$pf = array(  684.000,  900.000); break;}
2478
			case 'CATENV_N13_1/2': {$pf = array(  720.000,  936.000); break;}
2479
			case 'CATENV_N14_1/4': {$pf = array(  810.000,  882.000); break;}
2480
			case 'CATENV_N14_1/2': {$pf = array(  828.000, 1044.000); break;}
2481
			// Japanese (JIS P 0138-61) Standard B-Series
2482
			case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
2483
			case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
2484
			case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
2485
			case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
2486
			case 'JIS_B4' : {$pf = array(  728.504, 1031.811); break;}
2487
			case 'JIS_B5' : {$pf = array(  515.906,  728.504); break;}
2488
			case 'JIS_B6' : {$pf = array(  362.835,  515.906); break;}
2489
			case 'JIS_B7' : {$pf = array(  257.953,  362.835); break;}
2490
			case 'JIS_B8' : {$pf = array(  181.417,  257.953); break;}
2491
			case 'JIS_B9' : {$pf = array(  127.559,  181.417); break;}
2492
			case 'JIS_B10': {$pf = array(   90.709,  127.559); break;}
2493
			case 'JIS_B11': {$pf = array(   62.362,   90.709); break;}
2494
			case 'JIS_B12': {$pf = array(   45.354,   62.362); break;}
2495
			// PA Series
2496
			case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
2497
			case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
2498
			case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
2499
			case 'PA3' : {$pf = array(  793.701, 1190.551); break;}
2500
			case 'PA4' : {$pf = array(  595.276,  793.701); break;}
2501
			case 'PA5' : {$pf = array(  396.850,  595.276); break;}
2502
			case 'PA6' : {$pf = array(  297.638,  396.850); break;}
2503
			case 'PA7' : {$pf = array(  198.425,  297.638); break;}
2504
			case 'PA8' : {$pf = array(  147.402,  198.425); break;}
2505
			case 'PA9' : {$pf = array(   99.213,  147.402); break;}
2506
			case 'PA10': {$pf = array(   73.701,   99.213); break;}
2507
			// Standard Photographic Print Sizes
2508
			case 'PASSPORT_PHOTO': {$pf = array(   99.213,  127.559); break;}
2509
			case 'E'   : {$pf = array(  233.858,  340.157); break;}
2510
			case 'L':
2511
			case '3R'  : {$pf = array(  252.283,  360.000); break;}
2512
			case 'KG':
2513
			case '4R'  : {$pf = array(  289.134,  430.866); break;}
2514
			case '4D'  : {$pf = array(  340.157,  430.866); break;}
2515
			case '2L':
2516
			case '5R'  : {$pf = array(  360.000,  504.567); break;}
2517
			case '8P':
2518
			case '6R'  : {$pf = array(  430.866,  575.433); break;}
2519
			case '6P':
2520
			case '8R'  : {$pf = array(  575.433,  720.000); break;}
2521
			case '6PW':
2522
			case 'S8R' : {$pf = array(  575.433,  864.567); break;}
2523
			case '4P':
2524
			case '10R' : {$pf = array(  720.000,  864.567); break;}
2525
			case '4PW':
2526
			case 'S10R': {$pf = array(  720.000, 1080.000); break;}
2527
			case '11R' : {$pf = array(  790.866, 1009.134); break;}
2528
			case 'S11R': {$pf = array(  790.866, 1224.567); break;}
2529
			case '12R' : {$pf = array(  864.567, 1080.000); break;}
2530
			case 'S12R': {$pf = array(  864.567, 1292.598); break;}
2531
			// Common Newspaper Sizes
2532
			case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
2533
			case 'NEWSPAPER_BERLINER'  : {$pf = array( 1332.283,  892.913); break;}
2534
			case 'NEWSPAPER_TABLOID':
2535
			case 'NEWSPAPER_COMPACT'   : {$pf = array( 1218.898,  793.701); break;}
2536
			// Business Cards
2537
			case 'CREDIT_CARD':
2538
			case 'BUSINESS_CARD':
2539
			case 'BUSINESS_CARD_ISO7810': {$pf = array(  153.014,  242.646); break;}
2540
			case 'BUSINESS_CARD_ISO216' : {$pf = array(  147.402,  209.764); break;}
2541
			case 'BUSINESS_CARD_IT':
2542
			case 'BUSINESS_CARD_UK':
2543
			case 'BUSINESS_CARD_FR':
2544
			case 'BUSINESS_CARD_DE':
2545
			case 'BUSINESS_CARD_ES'     : {$pf = array(  155.906,  240.945); break;}
2546
			case 'BUSINESS_CARD_CA':
2547
			case 'BUSINESS_CARD_US'     : {$pf = array(  144.567,  252.283); break;}
2548
			case 'BUSINESS_CARD_JP'     : {$pf = array(  155.906,  257.953); break;}
2549
			case 'BUSINESS_CARD_HK'     : {$pf = array(  153.071,  255.118); break;}
2550
			case 'BUSINESS_CARD_AU':
2551
			case 'BUSINESS_CARD_DK':
2552
			case 'BUSINESS_CARD_SE'     : {$pf = array(  155.906,  255.118); break;}
2553
			case 'BUSINESS_CARD_RU':
2554
			case 'BUSINESS_CARD_CZ':
2555
			case 'BUSINESS_CARD_FI':
2556
			case 'BUSINESS_CARD_HU':
2557
			case 'BUSINESS_CARD_IL'     : {$pf = array(  141.732,  255.118); break;}
2558
			// Billboards
2559
			case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
2560
			case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
2561
			case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
2562
			case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
2563
			case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
2564
			case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
2565
			case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
2566
			case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
2567
			// Old European Sizes
2568
			//   - Old Imperial English Sizes
2569
			case 'EN_EMPEROR'          : {$pf = array( 3456.000, 5184.000); break;}
2570
			case 'EN_ANTIQUARIAN'      : {$pf = array( 2232.000, 3816.000); break;}
2571
			case 'EN_GRAND_EAGLE'      : {$pf = array( 2070.000, 3024.000); break;}
2572
			case 'EN_DOUBLE_ELEPHANT'  : {$pf = array( 1926.000, 2880.000); break;}
2573
			case 'EN_ATLAS'            : {$pf = array( 1872.000, 2448.000); break;}
2574
			case 'EN_COLOMBIER'        : {$pf = array( 1692.000, 2484.000); break;}
2575
			case 'EN_ELEPHANT'         : {$pf = array( 1656.000, 2016.000); break;}
2576
			case 'EN_DOUBLE_DEMY'      : {$pf = array( 1620.000, 2556.000); break;}
2577
			case 'EN_IMPERIAL'         : {$pf = array( 1584.000, 2160.000); break;}
2578
			case 'EN_PRINCESS'         : {$pf = array( 1548.000, 2016.000); break;}
2579
			case 'EN_CARTRIDGE'        : {$pf = array( 1512.000, 1872.000); break;}
2580
			case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
2581
			case 'EN_ROYAL'            : {$pf = array( 1440.000, 1800.000); break;}
2582
			case 'EN_SHEET':
2583
			case 'EN_HALF_POST'        : {$pf = array( 1404.000, 1692.000); break;}
2584
			case 'EN_SUPER_ROYAL'      : {$pf = array( 1368.000, 1944.000); break;}
2585
			case 'EN_DOUBLE_POST'      : {$pf = array( 1368.000, 2196.000); break;}
2586
			case 'EN_MEDIUM'           : {$pf = array( 1260.000, 1656.000); break;}
2587
			case 'EN_DEMY'             : {$pf = array( 1260.000, 1620.000); break;}
2588
			case 'EN_LARGE_POST'       : {$pf = array( 1188.000, 1512.000); break;}
2589
			case 'EN_COPY_DRAUGHT'     : {$pf = array( 1152.000, 1440.000); break;}
2590
			case 'EN_POST'             : {$pf = array( 1116.000, 1386.000); break;}
2591
			case 'EN_CROWN'            : {$pf = array( 1080.000, 1440.000); break;}
2592
			case 'EN_PINCHED_POST'     : {$pf = array( 1062.000, 1332.000); break;}
2593
			case 'EN_BRIEF'            : {$pf = array(  972.000, 1152.000); break;}
2594
			case 'EN_FOOLSCAP'         : {$pf = array(  972.000, 1224.000); break;}
2595
			case 'EN_SMALL_FOOLSCAP'   : {$pf = array(  954.000, 1188.000); break;}
2596
			case 'EN_POTT'             : {$pf = array(  900.000, 1080.000); break;}
2597
			//   - Old Imperial Belgian Sizes
2598
			case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
2599
			case 'BE_COLOMBIER'   : {$pf = array( 1757.480, 2409.449); break;}
2600
			case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
2601
			case 'BE_ELEPHANT'    : {$pf = array( 1746.142, 2182.677); break;}
2602
			case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
2603
			case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
2604
			case 'BE_JESUS'       : {$pf = array( 1530.709, 2069.291); break;}
2605
			case 'BE_RAISIN'      : {$pf = array( 1417.323, 1842.520); break;}
2606
			case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
2607
			case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
2608
			case 'BE_COQUILLE'    : {$pf = array( 1218.898, 1587.402); break;}
2609
			case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
2610
			case 'BE_RUCHE'       : {$pf = array( 1020.472, 1303.937); break;}
2611
			case 'BE_PROPATRIA'   : {$pf = array(  977.953, 1218.898); break;}
2612
			case 'BE_LYS'         : {$pf = array(  898.583, 1125.354); break;}
2613
			case 'BE_POT'         : {$pf = array(  870.236, 1088.504); break;}
2614
			case 'BE_ROSETTE'     : {$pf = array(  765.354,  983.622); break;}
2615
			//   - Old Imperial French Sizes
2616
			case 'FR_UNIVERS'          : {$pf = array( 2834.646, 3685.039); break;}
2617
			case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
2618
			case 'FR_GRANDE_MONDE'     : {$pf = array( 2551.181, 3571.654); break;}
2619
			case 'FR_DOUBLE_SOLEIL'    : {$pf = array( 2267.717, 3401.575); break;}
2620
			case 'FR_DOUBLE_JESUS'     : {$pf = array( 2154.331, 3174.803); break;}
2621
			case 'FR_GRAND_AIGLE'      : {$pf = array( 2125.984, 3004.724); break;}
2622
			case 'FR_PETIT_AIGLE'      : {$pf = array( 1984.252, 2664.567); break;}
2623
			case 'FR_DOUBLE_RAISIN'    : {$pf = array( 1842.520, 2834.646); break;}
2624
			case 'FR_JOURNAL'          : {$pf = array( 1842.520, 2664.567); break;}
2625
			case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
2626
			case 'FR_DOUBLE_CAVALIER'  : {$pf = array( 1757.480, 2607.874); break;}
2627
			case 'FR_CLOCHE'           : {$pf = array( 1700.787, 2267.717); break;}
2628
			case 'FR_SOLEIL'           : {$pf = array( 1700.787, 2267.717); break;}
2629
			case 'FR_DOUBLE_CARRE'     : {$pf = array( 1587.402, 2551.181); break;}
2630
			case 'FR_DOUBLE_COQUILLE'  : {$pf = array( 1587.402, 2494.488); break;}
2631
			case 'FR_JESUS'            : {$pf = array( 1587.402, 2154.331); break;}
2632
			case 'FR_RAISIN'           : {$pf = array( 1417.323, 1842.520); break;}
2633
			case 'FR_CAVALIER'         : {$pf = array( 1303.937, 1757.480); break;}
2634
			case 'FR_DOUBLE_COURONNE'  : {$pf = array( 1303.937, 2040.945); break;}
2635
			case 'FR_CARRE'            : {$pf = array( 1275.591, 1587.402); break;}
2636
			case 'FR_COQUILLE'         : {$pf = array( 1247.244, 1587.402); break;}
2637
			case 'FR_DOUBLE_TELLIERE'  : {$pf = array( 1247.244, 1927.559); break;}
2638
			case 'FR_DOUBLE_CLOCHE'    : {$pf = array( 1133.858, 1700.787); break;}
2639
			case 'FR_DOUBLE_POT'       : {$pf = array( 1133.858, 1757.480); break;}
2640
			case 'FR_ECU'              : {$pf = array( 1133.858, 1474.016); break;}
2641
			case 'FR_COURONNE'         : {$pf = array( 1020.472, 1303.937); break;}
2642
			case 'FR_TELLIERE'         : {$pf = array(  963.780, 1247.244); break;}
2643
			case 'FR_POT'              : {$pf = array(  878.740, 1133.858); break;}
2644
			// DEFAULT ISO A4
2645
			default: {$pf = array(  595.276,  841.890); break;}
2646
		}
2647
		return $pf;
2648
	}
2649
 
2650
	/**
2651
	 * Change the format of the current page
2652
	 * @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 numners (width, height) or an array containing the following measures and options:<ul>
2653
	 * <li>['format'] = page format name (one of the above);</li>
2654
	 * <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>
2655
	 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2656
	 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2657
	 * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
2658
	 * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
2659
	 * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
2660
	 * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
2661
	 * <li>['CropBox'] : the visible region of default user space:</li>
2662
	 * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
2663
	 * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
2664
	 * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
2665
	 * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
2666
	 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2667
	 * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
2668
	 * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
2669
	 * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
2670
	 * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
2671
	 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2672
	 * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
2673
	 * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
2674
	 * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
2675
	 * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
2676
	 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2677
	 * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
2678
	 * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
2679
	 * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
2680
	 * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
2681
	 * <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>
2682
	 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2683
	 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2684
	 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2685
	 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2686
	 * <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>
2687
	 * <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>
2688
	 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2689
	 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2690
	 * <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>
2691
	 * <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>
2692
	 * <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>
2693
	 * <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>
2694
	 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2695
	 * </ul>
2696
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2697
	 * <li>P or Portrait (default)</li>
2698
	 * <li>L or Landscape</li>
2699
	 * <li>'' (empty string) for automatic orientation</li>
2700
	 * </ul>
2701
	 * @access protected
2702
	 * @since 3.0.015 (2008-06-06)
2703
	 * @see getPageSizeFromFormat()
2704
	 */
2705
	protected function setPageFormat($format, $orientation='P') {
2706
		if (!empty($format) AND isset($this->pagedim[$this->page])) {
2707
			// remove inherited values
2708
			unset($this->pagedim[$this->page]);
2709
		}
2710
		if (is_string($format)) {
2711
			// get page measures from format name
2712
			$pf = $this->getPageSizeFromFormat($format);
2713
			$this->fwPt = $pf[0];
2714
			$this->fhPt = $pf[1];
2715
		} else {
2716
			// the boundaries of the physical medium on which the page shall be displayed or printed
2717
			if (isset($format['MediaBox'])) {
2718
				$this->setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false);
2719
				$this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2720
				$this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2721
			} else {
2722
				if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2723
					$pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2724
				} else {
2725
					if (!isset($format['format'])) {
2726
						// default value
2727
						$format['format'] = 'A4';
2728
					}
2729
					$pf = $this->getPageSizeFromFormat($format['format']);
2730
				}
2731
				$this->fwPt = $pf[0];
2732
				$this->fhPt = $pf[1];
2733
				$this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2734
			}
2735
			// the visible region of default user space
2736
			if (isset($format['CropBox'])) {
2737
				$this->setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false);
2738
			}
2739
			// the region to which the contents of the page shall be clipped when output in a production environment
2740
			if (isset($format['BleedBox'])) {
2741
				$this->setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false);
2742
			}
2743
			// the intended dimensions of the finished page after trimming
2744
			if (isset($format['TrimBox'])) {
2745
				$this->setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false);
2746
			}
2747
			// the page's meaningful content (including potential white space)
2748
			if (isset($format['ArtBox'])) {
2749
				$this->setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false);
2750
			}
2751
			// specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2752
			if (isset($format['BoxColorInfo'])) {
2753
				$this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2754
			}
2755
			if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2756
				// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2757
				$this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2758
			}
2759
			if (isset($format['PZ'])) {
2760
				// The page's preferred zoom (magnification) factor
2761
				$this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2762
			}
2763
			if (isset($format['trans'])) {
2764
				// The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2765
				if (isset($format['trans']['Dur'])) {
2766
					// The page's display duration
2767
					$this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2768
				}
2769
				$stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2770
				if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2771
					// The transition style that shall be used when moving to this page from another during a presentation
2772
					$this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2773
					$valid_effect = array('Split', 'Blinds');
2774
					$valid_vals = array('H', 'V');
2775
					if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2776
						$this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2777
					}
2778
					$valid_effect = array('Split', 'Box', 'Fly');
2779
					$valid_vals = array('I', 'O');
2780
					if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2781
						$this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2782
					}
2783
					$valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2784
					if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2785
						if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2786
							OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2787
							OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2788
							$this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2789
						}
2790
					}
2791
					if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2792
						$this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2793
					}
2794
					if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2795
						$this->pagedim[$this->page]['trans']['B'] = 'true';
2796
					}
2797
				} else {
2798
					$this->pagedim[$this->page]['trans']['S'] = 'R';
2799
				}
2800
				if (isset($format['trans']['D'])) {
2801
					// The duration of the transition effect, in seconds
2802
					$this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2803
				} else {
2804
					$this->pagedim[$this->page]['trans']['D'] = 1;
2805
				}
2806
			}
2807
		}
2808
		$this->setPageOrientation($orientation);
2809
	}
2810
 
2811
	/**
2812
	 * Set page boundaries.
2813
	 * @param int $page page number
2814
	 * @param string $type valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
2815
	 * @param float $llx lower-left x coordinate in user units
2816
	 * @param float $lly lower-left y coordinate in user units
2817
	 * @param float $urx upper-right x coordinate in user units
2818
	 * @param float $ury upper-right y coordinate in user units
2819
	 * @param boolean $points if true uses user units as unit of measure, otherwise uses PDF points
2820
	 * @access public
2821
	 * @since 5.0.010 (2010-05-17)
2822
	 */
2823
	public function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false) {
2824
		if (!isset($this->pagedim[$page])) {
2825
			// initialize array
2826
			$this->pagedim[$page] = array();
2827
		}
2828
		$pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2829
		if (!in_array($type, $pageboxes)) {
2830
			return;
2831
		}
2832
		if ($points) {
2833
			$k = 1;
2834
		} else {
2835
			$k = $this->k;
2836
		}
2837
		$this->pagedim[$page][$type]['llx'] = ($llx * $k);
2838
		$this->pagedim[$page][$type]['lly'] = ($lly * $k);
2839
		$this->pagedim[$page][$type]['urx'] = ($urx * $k);
2840
		$this->pagedim[$page][$type]['ury'] = ($ury * $k);
2841
	}
2842
 
2843
	/**
2844
	 * Swap X and Y coordinates of page boxes (change page boxes orientation).
2845
	 * @param int $page page number
2846
	 * @access protected
2847
	 * @since 5.0.010 (2010-05-17)
2848
	 */
2849
	protected function swapPageBoxCoordinates($page) {
2850
		$pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2851
		foreach ($pageboxes as $type) {
2852
			// swap X and Y coordinates
2853
			if (isset($this->pagedim[$page][$type])) {
2854
				$tmp = $this->pagedim[$page][$type]['llx'];
2855
				$this->pagedim[$page][$type]['llx'] = $this->pagedim[$page][$type]['lly'];
2856
				$this->pagedim[$page][$type]['lly'] = $tmp;
2857
				$tmp = $this->pagedim[$page][$type]['urx'];
2858
				$this->pagedim[$page][$type]['urx'] = $this->pagedim[$page][$type]['ury'];
2859
				$this->pagedim[$page][$type]['ury'] = $tmp;
2860
			}
2861
		}
2862
	}
2863
 
2864
	/**
2865
	 * Set page orientation.
2866
	 * @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>
2867
	 * @param boolean $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2868
	 * @param float $bottommargin bottom margin of the page.
2869
	 * @access public
2870
	 * @since 3.0.015 (2008-06-06)
2871
	 */
2872
	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2873
		if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2874
			// the boundaries of the physical medium on which the page shall be displayed or printed
2875
			$this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2876
		}
2877
		if (!isset($this->pagedim[$this->page]['CropBox'])) {
2878
			// the visible region of default user space
2879
			$this->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);
2880
		}
2881
		if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2882
			// the region to which the contents of the page shall be clipped when output in a production environment
2883
			$this->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);
2884
		}
2885
		if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2886
			// the intended dimensions of the finished page after trimming
2887
			$this->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);
2888
		}
2889
		if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2890
			// the page's meaningful content (including potential white space)
2891
			$this->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);
2892
		}
2893
		if (!isset($this->pagedim[$this->page]['Rotate'])) {
2894
			// The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2895
			$this->pagedim[$this->page]['Rotate'] = 0;
2896
		}
2897
		if (!isset($this->pagedim[$this->page]['PZ'])) {
2898
			// The page's preferred zoom (magnification) factor
2899
			$this->pagedim[$this->page]['PZ'] = 1;
2900
		}
2901
		if ($this->fwPt > $this->fhPt) {
2902
			// landscape
2903
			$default_orientation = 'L';
2904
		} else {
2905
			// portrait
2906
			$default_orientation = 'P';
2907
		}
2908
		$valid_orientations = array('P', 'L');
2909
		if (empty($orientation)) {
2910
			$orientation = $default_orientation;
2911
		} else {
2912
			$orientation = strtoupper($orientation{0});
2913
		}
2914
		if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2915
			$this->CurOrientation = $orientation;
2916
			$this->wPt = $this->fhPt;
2917
			$this->hPt = $this->fwPt;
2918
		} else {
2919
			$this->CurOrientation = $default_orientation;
2920
			$this->wPt = $this->fwPt;
2921
			$this->hPt = $this->fhPt;
2922
		}
2923
		if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2924
			// swap X and Y coordinates (change page orientation)
2925
			$this->swapPageBoxCoordinates($this->page);
2926
		}
2927
		$this->w = $this->wPt / $this->k;
2928
		$this->h = $this->hPt / $this->k;
2929
		if ($this->empty_string($autopagebreak)) {
2930
			if (isset($this->AutoPageBreak)) {
2931
				$autopagebreak = $this->AutoPageBreak;
2932
			} else {
2933
				$autopagebreak = true;
2934
			}
2935
		}
2936
		if ($this->empty_string($bottommargin)) {
2937
			if (isset($this->bMargin)) {
2938
				$bottommargin = $this->bMargin;
2939
			} else {
2940
				// default value = 2 cm
2941
				$bottommargin = 2 * 28.35 / $this->k;
2942
			}
2943
		}
2944
		$this->SetAutoPageBreak($autopagebreak, $bottommargin);
2945
		// store page dimensions
2946
		$this->pagedim[$this->page]['w'] = $this->wPt;
2947
		$this->pagedim[$this->page]['h'] = $this->hPt;
2948
		$this->pagedim[$this->page]['wk'] = $this->w;
2949
		$this->pagedim[$this->page]['hk'] = $this->h;
2950
		$this->pagedim[$this->page]['tm'] = $this->tMargin;
2951
		$this->pagedim[$this->page]['bm'] = $bottommargin;
2952
		$this->pagedim[$this->page]['lm'] = $this->lMargin;
2953
		$this->pagedim[$this->page]['rm'] = $this->rMargin;
2954
		$this->pagedim[$this->page]['pb'] = $autopagebreak;
2955
		$this->pagedim[$this->page]['or'] = $this->CurOrientation;
2956
		$this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2957
		$this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2958
	}
2959
 
2960
	/**
2961
	 * Set regular expression to detect withespaces or word separators.
2962
	 * The pattern delimiter must be the forward-slash character '/'.
2963
	 * Some example patterns are:
2964
	 * <pre>
2965
	 * Non-Unicode or missing PCRE unicode support: '/[^\S\xa0]/'
2966
	 * Unicode and PCRE unicode support: '/[^\S\P{Z}\xa0]/u'
2967
	 * Unicode and PCRE unicode support in Chinese mode: '/[^\S\P{Z}\P{Lo}\xa0]/u'
2968
	 * if PCRE unicode support is turned ON (\P is the negate class of \p):
2969
	 * \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
2970
	 * \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
2971
	 * \p{Lo} is needed for Chinese characters because are packed next to each other without spaces in between.
2972
	 * </pre>
2973
	 * @param string $re regular expression (leave empty for default).
2974
	 * @access public
2975
	 * @since 4.6.016 (2009-06-15)
2976
	 */
2977
	public function setSpacesRE($re='/[^\S\xa0]/') {
2978
		$this->re_spaces = $re;
2979
		$re_parts = explode('/', $re);
2980
		// get pattern parts
2981
		$this->re_space = array();
2982
		if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2983
			$this->re_space['p'] = $re_parts[1];
2984
		} else {
2985
			$this->re_space['p'] = '[\s]';
2986
		}
2987
		// set pattern modifiers
2988
		if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2989
			$this->re_space['m'] = $re_parts[2];
2990
		} else {
2991
			$this->re_space['m'] = '';
2992
		}
2993
	}
2994
 
2995
	/**
2996
	 * Enable or disable Right-To-Left language mode
2997
	 * @param Boolean $enable if true enable Right-To-Left language mode.
2998
	 * @param Boolean $resetx if true reset the X position on direction change.
2999
	 * @access public
3000
	 * @since 2.0.000 (2008-01-03)
3001
	 */
3002
	public function setRTL($enable, $resetx=true) {
3003
		$enable = $enable ? true : false;
3004
		$resetx = ($resetx AND ($enable != $this->rtl));
3005
		$this->rtl = $enable;
3006
		$this->tmprtl = false;
3007
		if ($resetx) {
3008
			$this->Ln(0);
3009
		}
3010
	}
3011
 
3012
	/**
3013
	 * Return the RTL status
3014
	 * @return boolean
3015
	 * @access public
3016
	 * @since 4.0.012 (2008-07-24)
3017
	 */
3018
	public function getRTL() {
3019
		return $this->rtl;
3020
	}
3021
 
3022
	/**
3023
	 * Force temporary RTL language direction
3024
	 * @param mixed $mode can be false, 'L' for LTR or 'R' for RTL
3025
	 * @access public
3026
	 * @since 2.1.000 (2008-01-09)
3027
	 */
3028
	public function setTempRTL($mode) {
3029
		$newmode = false;
3030
		switch (strtoupper($mode)) {
3031
			case 'LTR':
3032
			case 'L': {
3033
				if ($this->rtl) {
3034
					$newmode = 'L';
3035
				}
3036
				break;
3037
			}
3038
			case 'RTL':
3039
			case 'R': {
3040
				if (!$this->rtl) {
3041
					$newmode = 'R';
3042
				}
3043
				break;
3044
			}
3045
			case false:
3046
			default: {
3047
				$newmode = false;
3048
				break;
3049
			}
3050
		}
3051
		$this->tmprtl = $newmode;
3052
	}
3053
 
3054
	/**
3055
	 * Return the current temporary RTL status
3056
	 * @return boolean
3057
	 * @access public
3058
	 * @since 4.8.014 (2009-11-04)
3059
	 */
3060
	public function isRTLTextDir() {
3061
		return ($this->rtl OR ($this->tmprtl == 'R'));
3062
	}
3063
 
3064
	/**
3065
	 * Set the last cell height.
3066
	 * @param float $h cell height.
3067
	 * @author Nicola Asuni
3068
	 * @access public
3069
	 * @since 1.53.0.TC034
3070
	 */
3071
	public function setLastH($h) {
3072
		$this->lasth = $h;
3073
	}
3074
 
3075
	/**
3076
	 * Reset the last cell height.
3077
	 * @access public
3078
	 * @since 5.9.000 (2010-10-03)
3079
	 */
3080
	public function resetLastH() {
3081
		$this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
3082
	}
3083
 
3084
	/**
3085
	 * Get the last cell height.
3086
	 * @return last cell height
3087
	 * @access public
3088
	 * @since 4.0.017 (2008-08-05)
3089
	 */
3090
	public function getLastH() {
3091
		return $this->lasth;
3092
	}
3093
 
3094
	/**
3095
	 * Set the adjusting factor to convert pixels to user units.
3096
	 * @param float $scale adjusting factor to convert pixels to user units.
3097
	 * @author Nicola Asuni
3098
	 * @access public
3099
	 * @since 1.5.2
3100
	 */
3101
	public function setImageScale($scale) {
3102
		$this->imgscale = $scale;
3103
	}
3104
 
3105
	/**
3106
	 * Returns the adjusting factor to convert pixels to user units.
3107
	 * @return float adjusting factor to convert pixels to user units.
3108
	 * @author Nicola Asuni
3109
	 * @access public
3110
	 * @since 1.5.2
3111
	 */
3112
	public function getImageScale() {
3113
		return $this->imgscale;
3114
	}
3115
 
3116
	/**
3117
	 * Returns an array of page dimensions:
3118
	 * <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>
3119
	 * @param int $pagenum page number (empty = current page)
3120
	 * @return array of page dimensions.
3121
	 * @author Nicola Asuni
3122
	 * @access public
3123
	 * @since 4.5.027 (2009-03-16)
3124
	 */
3125
	public function getPageDimensions($pagenum='') {
3126
		if (empty($pagenum)) {
3127
			$pagenum = $this->page;
3128
		}
3129
		return $this->pagedim[$pagenum];
3130
	}
3131
 
3132
	/**
3133
	 * Returns the page width in units.
3134
	 * @param int $pagenum page number (empty = current page)
3135
	 * @return int page width.
3136
	 * @author Nicola Asuni
3137
	 * @access public
3138
	 * @since 1.5.2
3139
	 * @see getPageDimensions()
3140
	 */
3141
	public function getPageWidth($pagenum='') {
3142
		if (empty($pagenum)) {
3143
			return $this->w;
3144
		}
3145
		return $this->pagedim[$pagenum]['w'];
3146
	}
3147
 
3148
	/**
3149
	 * Returns the page height in units.
3150
	 * @param int $pagenum page number (empty = current page)
3151
	 * @return int page height.
3152
	 * @author Nicola Asuni
3153
	 * @access public
3154
	 * @since 1.5.2
3155
	 * @see getPageDimensions()
3156
	 */
3157
	public function getPageHeight($pagenum='') {
3158
		if (empty($pagenum)) {
3159
			return $this->h;
3160
		}
3161
		return $this->pagedim[$pagenum]['h'];
3162
	}
3163
 
3164
	/**
3165
	 * Returns the page break margin.
3166
	 * @param int $pagenum page number (empty = current page)
3167
	 * @return int page break margin.
3168
	 * @author Nicola Asuni
3169
	 * @access public
3170
	 * @since 1.5.2
3171
	 * @see getPageDimensions()
3172
	 */
3173
	public function getBreakMargin($pagenum='') {
3174
		if (empty($pagenum)) {
3175
			return $this->bMargin;
3176
		}
3177
		return $this->pagedim[$pagenum]['bm'];
3178
	}
3179
 
3180
	/**
3181
	 * Returns the scale factor (number of points in user unit).
3182
	 * @return int scale factor.
3183
	 * @author Nicola Asuni
3184
	 * @access public
3185
	 * @since 1.5.2
3186
	 */
3187
	public function getScaleFactor() {
3188
		return $this->k;
3189
	}
3190
 
3191
	/**
3192
	 * Defines the left, top and right margins.
3193
	 * @param float $left Left margin.
3194
	 * @param float $top Top margin.
3195
	 * @param float $right Right margin. Default value is the left one.
3196
	 * @param boolean $keepmargins if true overwrites the default page margins
3197
	 * @access public
3198
	 * @since 1.0
3199
	 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
3200
	 */
3201
	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
3202
		//Set left, top and right margins
3203
		$this->lMargin = $left;
3204
		$this->tMargin = $top;
3205
		if ($right == -1) {
3206
			$right = $left;
3207
		}
3208
		$this->rMargin = $right;
3209
		if ($keepmargins) {
3210
			// overwrite original values
3211
			$this->original_lMargin = $this->lMargin;
3212
			$this->original_rMargin = $this->rMargin;
3213
		}
3214
	}
3215
 
3216
	/**
3217
	 * 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.
3218
	 * @param float $margin The margin.
3219
	 * @access public
3220
	 * @since 1.4
3221
	 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3222
	 */
3223
	public function SetLeftMargin($margin) {
3224
		//Set left margin
3225
		$this->lMargin = $margin;
3226
		if (($this->page > 0) AND ($this->x < $margin)) {
3227
			$this->x = $margin;
3228
		}
3229
	}
3230
 
3231
	/**
3232
	 * Defines the top margin. The method can be called before creating the first page.
3233
	 * @param float $margin The margin.
3234
	 * @access public
3235
	 * @since 1.5
3236
	 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3237
	 */
3238
	public function SetTopMargin($margin) {
3239
		//Set top margin
3240
		$this->tMargin = $margin;
3241
		if (($this->page > 0) AND ($this->y < $margin)) {
3242
			$this->y = $margin;
3243
		}
3244
	}
3245
 
3246
	/**
3247
	 * Defines the right margin. The method can be called before creating the first page.
3248
	 * @param float $margin The margin.
3249
	 * @access public
3250
	 * @since 1.5
3251
	 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
3252
	 */
3253
	public function SetRightMargin($margin) {
3254
		$this->rMargin = $margin;
3255
		if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
3256
			$this->x = $this->w - $margin;
3257
		}
3258
	}
3259
 
3260
	/**
3261
	 * Set the same internal Cell padding for top, right, bottom, left-
3262
	 * @param float $pad internal padding.
3263
	 * @access public
3264
	 * @since 2.1.000 (2008-01-09)
3265
	 * @see getCellPaddings(), setCellPaddings()
3266
	 */
3267
	public function SetCellPadding($pad) {
3268
		if ($pad >= 0) {
3269
			$this->cell_padding['L'] = $pad;
3270
			$this->cell_padding['T'] = $pad;
3271
			$this->cell_padding['R'] = $pad;
3272
			$this->cell_padding['B'] = $pad;
3273
		}
3274
	}
3275
 
3276
	/**
3277
	 * Set the internal Cell paddings.
3278
	 * @param float $left left padding
3279
	 * @param float $top top padding
3280
	 * @param float $right right padding
3281
	 * @param float $bottom bottom padding
3282
	 * @access public
3283
	 * @since 5.9.000 (2010-10-03)
3284
	 * @see getCellPaddings(), SetCellPadding()
3285
	 */
3286
	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
3287
		if (($left !== '') AND ($left >= 0)) {
3288
			$this->cell_padding['L'] = $left;
3289
		}
3290
		if (($top !== '') AND ($top >= 0)) {
3291
			$this->cell_padding['T'] = $top;
3292
		}
3293
		if (($right !== '') AND ($right >= 0)) {
3294
			$this->cell_padding['R'] = $right;
3295
		}
3296
		if (($bottom !== '') AND ($bottom >= 0)) {
3297
			$this->cell_padding['B'] = $bottom;
3298
		}
3299
	}
3300
 
3301
	/**
3302
	 * Get the internal Cell padding array.
3303
	 * @return array of padding values
3304
	 * @access public
3305
	 * @since 5.9.000 (2010-10-03)
3306
	 * @see setCellPaddings(), SetCellPadding()
3307
	 */
3308
	public function getCellPaddings() {
3309
		return $this->cell_padding;
3310
	}
3311
 
3312
	/**
3313
	 * Set the internal Cell margins.
3314
	 * @param float $left left margin
3315
	 * @param float $top top margin
3316
	 * @param float $right right margin
3317
	 * @param float $bottom bottom margin
3318
	 * @access public
3319
	 * @since 5.9.000 (2010-10-03)
3320
	 * @see getCellMargins()
3321
	 */
3322
	public function setCellMargins($left='', $top='', $right='', $bottom='') {
3323
		if (($left !== '') AND ($left >= 0)) {
3324
			$this->cell_margin['L'] = $left;
3325
		}
3326
		if (($top !== '') AND ($top >= 0)) {
3327
			$this->cell_margin['T'] = $top;
3328
		}
3329
		if (($right !== '') AND ($right >= 0)) {
3330
			$this->cell_margin['R'] = $right;
3331
		}
3332
		if (($bottom !== '') AND ($bottom >= 0)) {
3333
			$this->cell_margin['B'] = $bottom;
3334
		}
3335
	}
3336
 
3337
	/**
3338
	 * Get the internal Cell margin array.
3339
	 * @return array of margin values
3340
	 * @access public
3341
	 * @since 5.9.000 (2010-10-03)
3342
	 * @see setCellMargins()
3343
	 */
3344
	public function getCellMargins() {
3345
		return $this->cell_margin;
3346
	}
3347
 
3348
	/**
3349
	 * Adjust the internal Cell padding array to take account of the line width.
3350
	 * @param mixed $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)))
3351
	 * @return array of adjustments
3352
	 * @access public
3353
	 * @since 5.9.000 (2010-10-03)
3354
	 */
3355
	protected function adjustCellPadding($brd=0) {
3356
		if (empty($brd)) {
3357
			return;
3358
		}
3359
		if (is_string($brd)) {
3360
			// convert string to array
3361
			$slen = strlen($brd);
3362
			$newbrd = array();
3363
			for ($i = 0; $i < $slen; ++$i) {
3364
				$newbrd[$brd{$i}] = true;
3365
			}
3366
			$brd = $newbrd;
3367
		} elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
3368
			$brd = array('LRTB' => true);
3369
		}
3370
		if (!is_array($brd)) {
3371
			return;
3372
		}
3373
		// store current cell padding
3374
		$cp = $this->cell_padding;
3375
		// select border mode
3376
		if (isset($brd['mode'])) {
3377
			$mode = $brd['mode'];
3378
			unset($brd['mode']);
3379
		} else {
3380
			$mode = 'normal';
3381
		}
3382
		// process borders
3383
		foreach ($brd as $border => $style) {
3384
			$line_width = $this->LineWidth;
3385
			if (is_array($style) AND isset($style['width'])) {
3386
				// get border width
3387
				$line_width = $style['width'];
3388
			}
3389
			$adj = 0; // line width inside the cell
3390
			switch ($mode) {
3391
				case 'ext': {
3392
					$adj = 0;
3393
					break;
3394
				}
3395
				case 'int': {
3396
					$adj = $line_width;
3397
					break;
3398
				}
3399
				case 'normal':
3400
				default: {
3401
					$adj = ($line_width / 2);
3402
					break;
3403
				}
3404
			}
3405
			// correct internal cell padding if required to avoid overlap between text and lines
3406
			if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
3407
				$this->cell_padding['T'] = $adj;
3408
			}
3409
			if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
3410
				$this->cell_padding['R'] = $adj;
3411
			}
3412
			if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
3413
				$this->cell_padding['B'] = $adj;
3414
			}
3415
			if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
3416
				$this->cell_padding['L'] = $adj;
3417
			}
3418
		}
3419
		return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
3420
	}
3421
 
3422
	/**
3423
	 * 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.
3424
	 * @param boolean $auto Boolean indicating if mode should be on or off.
3425
	 * @param float $margin Distance from the bottom of the page.
3426
	 * @access public
3427
	 * @since 1.0
3428
	 * @see Cell(), MultiCell(), AcceptPageBreak()
3429
	 */
3430
	public function SetAutoPageBreak($auto, $margin=0) {
3431
		$this->AutoPageBreak = $auto;
3432
		$this->bMargin = $margin;
3433
		$this->PageBreakTrigger = $this->h - $margin;
3434
	}
3435
 
3436
	/**
3437
	 * Defines the way the document is to be displayed by the viewer.
3438
	 * @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>
3439
	 * @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>
3440
	 * @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>
3441
	 * @access public
3442
	 * @since 1.2
3443
	 */
3444
	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
3445
		if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
3446
			$this->ZoomMode = $zoom;
3447
		} else {
3448
			$this->Error('Incorrect zoom display mode: '.$zoom);
3449
		}
3450
		switch ($layout) {
3451
			case 'default':
3452
			case 'single':
3453
			case 'SinglePage': {
3454
				$this->LayoutMode = 'SinglePage';
3455
				break;
3456
			}
3457
			case 'continuous':
3458
			case 'OneColumn': {
3459
				$this->LayoutMode = 'OneColumn';
3460
				break;
3461
			}
3462
			case 'two':
3463
			case 'TwoColumnLeft': {
3464
				$this->LayoutMode = 'TwoColumnLeft';
3465
				break;
3466
			}
3467
			case 'TwoColumnRight': {
3468
				$this->LayoutMode = 'TwoColumnRight';
3469
				break;
3470
			}
3471
			case 'TwoPageLeft': {
3472
				$this->LayoutMode = 'TwoPageLeft';
3473
				break;
3474
			}
3475
			case 'TwoPageRight': {
3476
				$this->LayoutMode = 'TwoPageRight';
3477
				break;
3478
			}
3479
			default: {
3480
				$this->LayoutMode = 'SinglePage';
3481
			}
3482
		}
3483
		switch ($mode) {
3484
			case 'UseNone': {
3485
				$this->PageMode = 'UseNone';
3486
				break;
3487
			}
3488
			case 'UseOutlines': {
3489
				$this->PageMode = 'UseOutlines';
3490
				break;
3491
			}
3492
			case 'UseThumbs': {
3493
				$this->PageMode = 'UseThumbs';
3494
				break;
3495
			}
3496
			case 'FullScreen': {
3497
				$this->PageMode = 'FullScreen';
3498
				break;
3499
			}
3500
			case 'UseOC': {
3501
				$this->PageMode = 'UseOC';
3502
				break;
3503
			}
3504
			case '': {
3505
				$this->PageMode = 'UseAttachments';
3506
				break;
3507
			}
3508
			default: {
3509
				$this->PageMode = 'UseNone';
3510
			}
3511
		}
3512
	}
3513
 
3514
	/**
3515
	 * 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.
3516
	 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
3517
	 * @param boolean $compress Boolean indicating if compression must be enabled.
3518
	 * @access public
3519
	 * @since 1.4
3520
	 */
3521
	public function SetCompression($compress) {
3522
		if (function_exists('gzcompress')) {
3523
			$this->compress = $compress ? true : false;
3524
		} else {
3525
			$this->compress = false;
3526
		}
3527
	}
3528
 
3529
	/**
3530
	 * Turn on/off Unicode mode for document information dictionary (meta tags).
3531
	 * This has effect only when unicode mode is set to false.
3532
	 * @param boolean $uni if true set the meta information in Unicode
3533
	 * @access public
3534
	 * @since 5.9.027 (2010-12-01)
3535
	 */
3536
	public function SetDocInfoUnicode($unicode=true) {
3537
		$this->docinfounicode = $unicode ? true : false;
3538
	}
3539
 
3540
	/**
3541
	 * Defines the title of the document.
3542
	 * @param string $title The title.
3543
	 * @access public
3544
	 * @since 1.2
3545
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
3546
	 */
3547
	public function SetTitle($title) {
3548
		$this->title = $title;
3549
	}
3550
 
3551
	/**
3552
	 * Defines the subject of the document.
3553
	 * @param string $subject The subject.
3554
	 * @access public
3555
	 * @since 1.2
3556
	 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
3557
	 */
3558
	public function SetSubject($subject) {
3559
		$this->subject = $subject;
3560
	}
3561
 
3562
	/**
3563
	 * Defines the author of the document.
3564
	 * @param string $author The name of the author.
3565
	 * @access public
3566
	 * @since 1.2
3567
	 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
3568
	 */
3569
	public function SetAuthor($author) {
3570
		$this->author = $author;
3571
	}
3572
 
3573
	/**
3574
	 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
3575
	 * @param string $keywords The list of keywords.
3576
	 * @access public
3577
	 * @since 1.2
3578
	 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
3579
	 */
3580
	public function SetKeywords($keywords) {
3581
		$this->keywords = $keywords;
3582
	}
3583
 
3584
	/**
3585
	 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
3586
	 * @param string $creator The name of the creator.
3587
	 * @access public
3588
	 * @since 1.2
3589
	 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
3590
	 */
3591
	public function SetCreator($creator) {
3592
		$this->creator = $creator;
3593
	}
3594
 
3595
	/**
3596
	 * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
3597
	 * 2004-06-11 :: Nicola Asuni : changed bold tag with strong
3598
	 * @param string $msg The error message
3599
	 * @access public
3600
	 * @since 1.0
3601
	 */
3602
	public function Error($msg) {
3603
		// unset all class variables
3604
		$this->_destroy(true);
3605
		// exit program and print error
3606
		die('<strong>TCPDF ERROR: </strong>'.$msg);
3607
	}
3608
 
3609
	/**
3610
	 * This method begins the generation of the PDF document.
3611
	 * It is not necessary to call it explicitly because AddPage() does it automatically.
3612
	 * Note: no page is created by this method
3613
	 * @access public
3614
	 * @since 1.0
3615
	 * @see AddPage(), Close()
3616
	 */
3617
	public function Open() {
3618
		$this->state = 1;
3619
	}
3620
 
3621
	/**
3622
	 * Terminates the PDF document.
3623
	 * It is not necessary to call this method explicitly because Output() does it automatically.
3624
	 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3625
	 * @access public
3626
	 * @since 1.0
3627
	 * @see Open(), Output()
3628
	 */
3629
	public function Close() {
3630
		if ($this->state == 3) {
3631
			return;
3632
		}
3633
		if ($this->page == 0) {
3634
			$this->AddPage();
3635
		}
3636
		// save current graphic settings
3637
		$gvars = $this->getGraphicVars();
3638
		$this->setEqualColumns();
3639
		$this->lastpage(true);
3640
		$this->SetAutoPageBreak(false);
3641
		$this->x = 0;
3642
		$this->y = $this->h - (1 / $this->k);
3643
		$this->lMargin = 0;
3644
		$this->_out('q');
3645
		$this->SetFont('helvetica', '', 1);
3646
		$this->setTextRenderingMode(0, false, false);
3647
		$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";
3648
		$lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3649
		$this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3650
		$this->_out('Q');
3651
		// restore graphic settings
3652
		$this->setGraphicVars($gvars);
3653
		// close page
3654
		$this->endPage();
3655
		// close document
3656
		$this->_enddoc();
3657
		// unset all class variables (except critical ones)
3658
		$this->_destroy(false);
3659
	}
3660
 
3661
	/**
3662
	 * Move pointer at the specified document page and update page dimensions.
3663
	 * @param int $pnum page number (1 ... numpages)
3664
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3665
	 * @access public
3666
	 * @since 2.1.000 (2008-01-07)
3667
	 * @see getPage(), lastpage(), getNumPages()
3668
	 */
3669
	public function setPage($pnum, $resetmargins=false) {
3670
		if (($pnum == $this->page) AND ($this->state == 2)) {
3671
			return;
3672
		}
3673
		if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3674
			$this->state = 2;
3675
			// save current graphic settings
3676
			//$gvars = $this->getGraphicVars();
3677
			$oldpage = $this->page;
3678
			$this->page = $pnum;
3679
			$this->wPt = $this->pagedim[$this->page]['w'];
3680
			$this->hPt = $this->pagedim[$this->page]['h'];
3681
			$this->w = $this->pagedim[$this->page]['wk'];
3682
			$this->h = $this->pagedim[$this->page]['hk'];
3683
			$this->tMargin = $this->pagedim[$this->page]['tm'];
3684
			$this->bMargin = $this->pagedim[$this->page]['bm'];
3685
			$this->original_lMargin = $this->pagedim[$this->page]['olm'];
3686
			$this->original_rMargin = $this->pagedim[$this->page]['orm'];
3687
			$this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3688
			$this->CurOrientation = $this->pagedim[$this->page]['or'];
3689
			$this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3690
			// restore graphic settings
3691
			//$this->setGraphicVars($gvars);
3692
			if ($resetmargins) {
3693
				$this->lMargin = $this->pagedim[$this->page]['olm'];
3694
				$this->rMargin = $this->pagedim[$this->page]['orm'];
3695
				$this->SetY($this->tMargin);
3696
			} else {
3697
				// account for booklet mode
3698
				if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3699
					$deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3700
					$this->lMargin += $deltam;
3701
					$this->rMargin -= $deltam;
3702
				}
3703
			}
3704
		} else {
3705
			$this->Error('Wrong page number on setPage() function: '.$pnum);
3706
		}
3707
	}
3708
 
3709
	/**
3710
	 * Reset pointer to the last document page.
3711
	 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3712
	 * @access public
3713
	 * @since 2.0.000 (2008-01-04)
3714
	 * @see setPage(), getPage(), getNumPages()
3715
	 */
3716
	public function lastPage($resetmargins=false) {
3717
		$this->setPage($this->getNumPages(), $resetmargins);
3718
	}
3719
 
3720
	/**
3721
	 * Get current document page number.
3722
	 * @return int page number
3723
	 * @access public
3724
	 * @since 2.1.000 (2008-01-07)
3725
	 * @see setPage(), lastpage(), getNumPages()
3726
	 */
3727
	public function getPage() {
3728
		return $this->page;
3729
	}
3730
 
3731
	/**
3732
	 * Get the total number of insered pages.
3733
	 * @return int number of pages
3734
	 * @access public
3735
	 * @since 2.1.000 (2008-01-07)
3736
	 * @see setPage(), getPage(), lastpage()
3737
	 */
3738
	public function getNumPages() {
3739
		return $this->numpages;
3740
	}
3741
 
3742
	/**
3743
	 * Adds a new TOC (Table Of Content) page to the document.
3744
	 * @param string $orientation page orientation.
3745
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3746
	 * @access public
3747
	 * @since 5.0.001 (2010-05-06)
3748
	 * @see AddPage(), startPage(), endPage(), endTOCPage()
3749
	 */
3750
	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3751
		$this->AddPage($orientation, $format, $keepmargins, true);
3752
	}
3753
 
3754
	/**
3755
	 * Terminate the current TOC (Table Of Content) page
3756
	 * @access public
3757
	 * @since 5.0.001 (2010-05-06)
3758
	 * @see AddPage(), startPage(), endPage(), addTOCPage()
3759
	 */
3760
	public function endTOCPage() {
3761
		$this->endPage(true);
3762
	}
3763
 
3764
	/**
3765
	 * 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).
3766
	 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3767
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3768
	 * @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().
3769
	 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3770
	 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3771
	 * @access public
3772
	 * @since 1.0
3773
	 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3774
	 */
3775
	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3776
		if ($this->inxobj) {
3777
			// we are inside an XObject template
3778
			return;
3779
		}
3780
		if (!isset($this->original_lMargin) OR $keepmargins) {
3781
			$this->original_lMargin = $this->lMargin;
3782
		}
3783
		if (!isset($this->original_rMargin) OR $keepmargins) {
3784
			$this->original_rMargin = $this->rMargin;
3785
		}
3786
		// terminate previous page
3787
		$this->endPage();
3788
		// start new page
3789
		$this->startPage($orientation, $format, $tocpage);
3790
	}
3791
 
3792
	/**
3793
	 * Terminate the current page
3794
	 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3795
	 * @access public
3796
	 * @since 4.2.010 (2008-11-14)
3797
	 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3798
	 */
3799
	public function endPage($tocpage=false) {
3800
		// check if page is already closed
3801
		if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3802
			return;
3803
		}
3804
		$this->InFooter = true;
3805
		// print page footer
3806
		$this->setFooter();
3807
		// close page
3808
		$this->_endpage();
3809
		// mark page as closed
3810
		$this->pageopen[$this->page] = false;
3811
		$this->InFooter = false;
3812
		if ($tocpage) {
3813
			$this->tocpage = false;
3814
		}
3815
	}
3816
 
3817
	/**
3818
	 * Starts a new page to the document. The page must be closed using the endPage() function.
3819
	 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3820
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3821
	 * @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().
3822
	 * @access public
3823
	 * @since 4.2.010 (2008-11-14)
3824
	 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3825
	 */
3826
	public function startPage($orientation='', $format='', $tocpage=false) {
3827
		if ($tocpage) {
3828
			$this->tocpage = true;
3829
		}
3830
		if ($this->numpages > $this->page) {
3831
			// this page has been already added
3832
			$this->setPage($this->page + 1);
3833
			$this->SetY($this->tMargin);
3834
			return;
3835
		}
3836
		// start a new page
3837
		if ($this->state == 0) {
3838
			$this->Open();
3839
		}
3840
		++$this->numpages;
3841
		$this->swapMargins($this->booklet);
3842
		// save current graphic settings
3843
		$gvars = $this->getGraphicVars();
3844
		// start new page
3845
		$this->_beginpage($orientation, $format);
3846
		// mark page as open
3847
		$this->pageopen[$this->page] = true;
3848
		// restore graphic settings
3849
		$this->setGraphicVars($gvars);
3850
		// mark this point
3851
		$this->setPageMark();
3852
		// print page header
3853
		$this->setHeader();
3854
		// restore graphic settings
3855
		$this->setGraphicVars($gvars);
3856
		// mark this point
3857
		$this->setPageMark();
3858
		// print table header (if any)
3859
		$this->setTableHeader();
3860
		// set mark for empty page check
3861
		$this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3862
	}
3863
 
3864
	/**
3865
	 * Set start-writing mark on current page stream used to put borders and fills.
3866
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3867
	 * This function must be called after calling Image() function for a background image.
3868
	 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3869
	 * @access public
3870
	 * @since 4.0.016 (2008-07-30)
3871
	 */
3872
	public function setPageMark() {
3873
		$this->intmrk[$this->page] = $this->pagelen[$this->page];
3874
		$this->bordermrk[$this->page] = $this->intmrk[$this->page];
3875
		$this->setContentMark();
3876
	}
3877
 
3878
	/**
3879
	 * Set start-writing mark on selected page.
3880
	 * Borders and fills are always created after content and inserted on the position marked by this method.
3881
	 * @param int $page page number (default is the current page)
3882
	 * @access protected
3883
	 * @since 4.6.021 (2009-07-20)
3884
	 */
3885
	protected function setContentMark($page=0) {
3886
		if ($page <= 0) {
3887
			$page = $this->page;
3888
		}
3889
		if (isset($this->footerlen[$page])) {
3890
			$this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3891
		} else {
3892
			$this->cntmrk[$page] = $this->pagelen[$page];
3893
		}
3894
	}
3895
 
3896
	/**
3897
	 * Set header data.
3898
	 * @param string $ln header image logo
3899
	 * @param string $lw header image logo width in mm
3900
	 * @param string $ht string to print as title on document header
3901
	 * @param string $hs string to print on document header
3902
	 * @access public
3903
	 */
3904
	public function setHeaderData($ln='', $lw=0, $ht='', $hs='') {
3905
		$this->header_logo = $ln;
3906
		$this->header_logo_width = $lw;
3907
		$this->header_title = $ht;
3908
		$this->header_string = $hs;
3909
	}
3910
 
3911
	/**
3912
	 * Returns header data:
3913
	 * <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>
3914
	 * @return array()
3915
	 * @access public
3916
	 * @since 4.0.012 (2008-07-24)
3917
	 */
3918
	public function getHeaderData() {
3919
		$ret = array();
3920
		$ret['logo'] = $this->header_logo;
3921
		$ret['logo_width'] = $this->header_logo_width;
3922
		$ret['title'] = $this->header_title;
3923
		$ret['string'] = $this->header_string;
3924
		return $ret;
3925
	}
3926
 
3927
	/**
3928
	 * Set header margin.
3929
	 * (minimum distance between header and top page margin)
3930
	 * @param int $hm distance in user units
3931
	 * @access public
3932
	 */
3933
	public function setHeaderMargin($hm=10) {
3934
		$this->header_margin = $hm;
3935
	}
3936
 
3937
	/**
3938
	 * Returns header margin in user units.
3939
	 * @return float
3940
	 * @since 4.0.012 (2008-07-24)
3941
	 * @access public
3942
	 */
3943
	public function getHeaderMargin() {
3944
		return $this->header_margin;
3945
	}
3946
 
3947
	/**
3948
	 * Set footer margin.
3949
	 * (minimum distance between footer and bottom page margin)
3950
	 * @param int $fm distance in user units
3951
	 * @access public
3952
	 */
3953
	public function setFooterMargin($fm=10) {
3954
		$this->footer_margin = $fm;
3955
	}
3956
 
3957
	/**
3958
	 * Returns footer margin in user units.
3959
	 * @return float
3960
	 * @since 4.0.012 (2008-07-24)
3961
	 * @access public
3962
	 */
3963
	public function getFooterMargin() {
3964
		return $this->footer_margin;
3965
	}
3966
	/**
3967
	 * Set a flag to print page header.
3968
	 * @param boolean $val set to true to print the page header (default), false otherwise.
3969
	 * @access public
3970
	 */
3971
	public function setPrintHeader($val=true) {
3972
		$this->print_header = $val;
3973
	}
3974
 
3975
	/**
3976
	 * Set a flag to print page footer.
3977
	 * @param boolean $value set to true to print the page footer (default), false otherwise.
3978
	 * @access public
3979
	 */
3980
	public function setPrintFooter($val=true) {
3981
		$this->print_footer = $val;
3982
	}
3983
 
3984
	/**
3985
	 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3986
	 * @return float
3987
	 * @access public
3988
	 */
3989
	public function getImageRBX() {
3990
		return $this->img_rb_x;
3991
	}
3992
 
3993
	/**
3994
	 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3995
	 * @return float
3996
	 * @access public
3997
	 */
3998
	public function getImageRBY() {
3999
		return $this->img_rb_y;
4000
	}
4001
 
4002
	/**
4003
	 * This method is used to render the page header.
4004
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4005
	 * @access public
4006
	 */
4007
	public function Header() {
4008
		$ormargins = $this->getOriginalMargins();
4009
		$headerfont = $this->getHeaderFont();
4010
		$headerdata = $this->getHeaderData();
4011
		if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
4012
			$imgtype = $this->getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
4013
			if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
4014
				$this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4015
			} elseif ($imgtype == 'svg') {
4016
				$this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4017
			} else {
4018
				$this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4019
			}
4020
			$imgy = $this->getImageRBY();
4021
		} else {
4022
			$imgy = $this->GetY();
4023
		}
4024
		$cell_height = round(($this->getCellHeightRatio() * $headerfont[2]) / $this->getScaleFactor(), 2);
4025
		// set starting margin for text data cell
4026
		if ($this->getRTL()) {
4027
			$header_x = $ormargins['right'] + ($headerdata['logo_width'] * 1.1);
4028
		} else {
4029
			$header_x = $ormargins['left'] + ($headerdata['logo_width'] * 1.1);
4030
		}
4031
		$this->SetTextColor(0, 0, 0);
4032
		// header title
4033
		$this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
4034
		$this->SetX($header_x);
4035
		$this->Cell(0, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
4036
		// header string
4037
		$this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
4038
		$this->SetX($header_x);
4039
		$this->MultiCell(0, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false);
4040
		// print an ending header line
4041
		$this->SetLineStyle(array('width' => 0.85 / $this->getScaleFactor(), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
4042
		$this->SetY((2.835 / $this->getScaleFactor()) + max($imgy, $this->GetY()));
4043
		if ($this->getRTL()) {
4044
			$this->SetX($ormargins['right']);
4045
		} else {
4046
			$this->SetX($ormargins['left']);
4047
		}
4048
		$this->Cell(0, 0, '', 'T', 0, 'C');
4049
	}
4050
 
4051
	/**
4052
	 * This method is used to render the page footer.
4053
	 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4054
	 * @access public
4055
	 */
4056
	public function Footer() {
4057
		$cur_y = $this->GetY();
4058
		$ormargins = $this->getOriginalMargins();
4059
		$this->SetTextColor(0, 0, 0);
4060
		//set style for cell border
4061
		$line_width = 0.85 / $this->getScaleFactor();
4062
		$this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
4063
		//print document barcode
4064
		$barcode = $this->getBarcode();
4065
		if (!empty($barcode)) {
4066
			$this->Ln($line_width);
4067
			$barcode_width = round(($this->getPageWidth() - $ormargins['left'] - $ormargins['right']) / 3);
4068
			$style = array(
4069
				'position' => $this->rtl?'R':'L',
4070
				'align' => $this->rtl?'R':'L',
4071
				'stretch' => false,
4072
				'fitwidth' => true,
4073
				'cellfitalign' => '',
4074
				'border' => false,
4075
				'padding' => 0,
4076
				'fgcolor' => array(0,0,0),
4077
				'bgcolor' => false,
4078
				'text' => false
4079
			);
4080
			$this->write1DBarcode($barcode, 'C128B', '', $cur_y + $line_width, '', (($this->getFooterMargin() / 3) - $line_width), 0.3, $style, '');
4081
		}
4082
		if (empty($this->pagegroups)) {
4083
			$pagenumtxt = $this->l['w_page'].' '.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
4084
		} else {
4085
			$pagenumtxt = $this->l['w_page'].' '.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
4086
		}
4087
		$this->SetY($cur_y);
4088
		//Print page number
4089
		if ($this->getRTL()) {
4090
			$this->SetX($ormargins['right']);
4091
			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
4092
		} else {
4093
			$this->SetX($ormargins['left']);
4094
			$this->Cell(0, 0, $pagenumtxt, 'T', 0, 'R');
4095
		}
4096
	}
4097
 
4098
	/**
4099
	 * This method is used to render the page header.
4100
	 * @access protected
4101
	 * @since 4.0.012 (2008-07-24)
4102
	 */
4103
	protected function setHeader() {
4104
		if ($this->print_header) {
4105
			$this->setGraphicVars($this->default_graphic_vars);
4106
			$temp_thead = $this->thead;
4107
			$temp_theadMargins = $this->theadMargins;
4108
			$lasth = $this->lasth;
4109
			$this->_out('q');
4110
			$this->rMargin = $this->original_rMargin;
4111
			$this->lMargin = $this->original_lMargin;
4112
			$this->SetCellPadding(0);
4113
			//set current position
4114
			if ($this->rtl) {
4115
				$this->SetXY($this->original_rMargin, $this->header_margin);
4116
			} else {
4117
				$this->SetXY($this->original_lMargin, $this->header_margin);
4118
			}
4119
			$this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
4120
			$this->Header();
4121
			//restore position
4122
			if ($this->rtl) {
4123
				$this->SetXY($this->original_rMargin, $this->tMargin);
4124
			} else {
4125
				$this->SetXY($this->original_lMargin, $this->tMargin);
4126
			}
4127
			$this->_out('Q');
4128
			$this->lasth = $lasth;
4129
			$this->thead = $temp_thead;
4130
			$this->theadMargins = $temp_theadMargins;
4131
			$this->newline = false;
4132
		}
4133
	}
4134
 
4135
	/**
4136
	 * This method is used to render the page footer.
4137
	 * @access protected
4138
	 * @since 4.0.012 (2008-07-24)
4139
	 */
4140
	protected function setFooter() {
4141
		//Page footer
4142
		// save current graphic settings
4143
		$gvars = $this->getGraphicVars();
4144
		// mark this point
4145
		$this->footerpos[$this->page] = $this->pagelen[$this->page];
4146
		$this->_out("\n");
4147
		if ($this->print_footer) {
4148
			$this->setGraphicVars($this->default_graphic_vars);
4149
			$this->current_column = 0;
4150
			$this->num_columns = 1;
4151
			$temp_thead = $this->thead;
4152
			$temp_theadMargins = $this->theadMargins;
4153
			$lasth = $this->lasth;
4154
			$this->_out('q');
4155
			$this->rMargin = $this->original_rMargin;
4156
			$this->lMargin = $this->original_lMargin;
4157
			$this->SetCellPadding(0);
4158
			//set current position
4159
			$footer_y = $this->h - $this->footer_margin;
4160
			if ($this->rtl) {
4161
				$this->SetXY($this->original_rMargin, $footer_y);
4162
			} else {
4163
				$this->SetXY($this->original_lMargin, $footer_y);
4164
			}
4165
			$this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
4166
			$this->Footer();
4167
			//restore position
4168
			if ($this->rtl) {
4169
				$this->SetXY($this->original_rMargin, $this->tMargin);
4170
			} else {
4171
				$this->SetXY($this->original_lMargin, $this->tMargin);
4172
			}
4173
			$this->_out('Q');
4174
			$this->lasth = $lasth;
4175
			$this->thead = $temp_thead;
4176
			$this->theadMargins = $temp_theadMargins;
4177
		}
4178
		// restore graphic settings
4179
		$this->setGraphicVars($gvars);
4180
		$this->current_column = $gvars['current_column'];
4181
		$this->num_columns = $gvars['num_columns'];
4182
		// calculate footer length
4183
		$this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
4184
	}
4185
 
4186
	/**
4187
	 * This method is used to render the table header on new page (if any).
4188
	 * @access protected
4189
	 * @since 4.5.030 (2009-03-25)
4190
	 */
4191
	protected function setTableHeader() {
4192
		if ($this->num_columns > 1) {
4193
			// multi column mode
4194
			return;
4195
		}
4196
		if (isset($this->theadMargins['top'])) {
4197
			// restore the original top-margin
4198
			$this->tMargin = $this->theadMargins['top'];
4199
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
4200
			$this->y = $this->tMargin;
4201
		}
4202
		if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
4203
			// set margins
4204
			$prev_lMargin = $this->lMargin;
4205
			$prev_rMargin = $this->rMargin;
4206
			$prev_cell_padding = $this->cell_padding;
4207
			$this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
4208
			$this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
4209
			$this->cell_padding = $this->theadMargins['cell_padding'];
4210
			if ($this->rtl) {
4211
				$this->x = $this->w - $this->rMargin;
4212
			} else {
4213
				$this->x = $this->lMargin;
4214
			}
4215
			// print table header
4216
			$this->writeHTML($this->thead, false, false, false, false, '');
4217
			// set new top margin to skip the table headers
4218
			if (!isset($this->theadMargins['top'])) {
4219
				$this->theadMargins['top'] = $this->tMargin;
4220
			}
4221
			$this->tMargin = $this->y;
4222
			$this->pagedim[$this->page]['tm'] = $this->tMargin;
4223
			$this->lasth = 0;
4224
			$this->lMargin = $prev_lMargin;
4225
			$this->rMargin = $prev_rMargin;
4226
			$this->cell_padding = $prev_cell_padding;
4227
		}
4228
	}
4229
 
4230
	/**
4231
	 * Returns the current page number.
4232
	 * @return int page number
4233
	 * @access public
4234
	 * @since 1.0
4235
	 * @see AliasNbPages(), getAliasNbPages()
4236
	 */
4237
	public function PageNo() {
4238
		return $this->page;
4239
	}
4240
 
4241
	/**
4242
	 * Defines a new spot color.
4243
	 * It can be expressed in RGB components or gray scale.
4244
	 * The method can be called before the first page is created and the value is retained from page to page.
4245
	 * @param int $c Cyan color for CMYK. Value between 0 and 100
4246
	 * @param int $m Magenta color for CMYK. Value between 0 and 100
4247
	 * @param int $y Yellow color for CMYK. Value between 0 and 100
4248
	 * @param int $k Key (Black) color for CMYK. Value between 0 and 100
4249
	 * @access public
4250
	 * @since 4.0.024 (2008-09-12)
4251
	 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4252
	 */
4253
	public function AddSpotColor($name, $c, $m, $y, $k) {
4254
		if (!isset($this->spot_colors[$name])) {
4255
			$i = 1 + count($this->spot_colors);
4256
			$this->spot_colors[$name] = array('i' => $i, 'c' => $c, 'm' => $m, 'y' => $y, 'k' => $k);
4257
		}
4258
		$color = preg_replace('/[\s]*/', '', $name); // remove extra spaces
4259
		$color = strtolower($color);
4260
		if (!isset($this->spotcolor[$color])) {
4261
			$this->spotcolor[$color] = array($c, $m, $y, $k, $name);
4262
		}
4263
	}
4264
 
4265
	/**
4266
	 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
4267
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4268
	 * The method can be called before the first page is created and the value is retained from page to page.
4269
	 * @param array $color array of colors
4270
	 * @param boolean $ret if true do not send the PDF command.
4271
	 * @return string the PDF command
4272
	 * @access public
4273
	 * @since 3.1.000 (2008-06-11)
4274
	 * @see SetDrawColor()
4275
	 */
4276
	public function SetDrawColorArray($color, $ret=false) {
4277
		if (is_array($color)) {
4278
			$color = array_values($color);
4279
			$r = isset($color[0]) ? $color[0] : -1;
4280
			$g = isset($color[1]) ? $color[1] : -1;
4281
			$b = isset($color[2]) ? $color[2] : -1;
4282
			$k = isset($color[3]) ? $color[3] : -1;
4283
			$name = isset($color[4]) ? $color[4] : ''; // spot color name
4284
			if ($r >= 0) {
4285
				return $this->SetDrawColor($r, $g, $b, $k, $ret, $name);
4286
			}
4287
		}
4288
		return '';
4289
	}
4290
 
4291
	/**
4292
	 * 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.
4293
	 * @param int $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4294
	 * @param int $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4295
	 * @param int $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4296
	 * @param int $col4 KEY (BLACK) color for CMYK (0-100).
4297
	 * @param boolean $ret if true do not send the command.
4298
	 * @param string $name spot color name (if any)
4299
	 * @return string the PDF command
4300
	 * @access public
4301
	 * @since 1.3
4302
	 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4303
	 */
4304
	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4305
		// set default values
4306
		if (!is_numeric($col1)) {
4307
			$col1 = 0;
4308
		}
4309
		if (!is_numeric($col2)) {
4310
			$col2 = -1;
4311
		}
4312
		if (!is_numeric($col3)) {
4313
			$col3 = -1;
4314
		}
4315
		if (!is_numeric($col4)) {
4316
			$col4 = -1;
4317
		}
4318
		//Set color for all stroking operations
4319
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4320
			// Grey scale
4321
			$this->DrawColor = sprintf('%.3F G', ($col1 / 255));
4322
			$this->strokecolor = array('G' => $col1);
4323
		} elseif ($col4 == -1) {
4324
			// RGB
4325
			$this->DrawColor = sprintf('%.3F %.3F %.3F RG', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4326
			$this->strokecolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4327
		} elseif (empty($name)) {
4328
			// CMYK
4329
			$this->DrawColor = sprintf('%.3F %.3F %.3F %.3F K', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4330
			$this->strokecolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4331
		} else {
4332
			// SPOT COLOR
4333
			$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4334
			$this->DrawColor = sprintf('/CS%d CS %.3F SCN', $this->spot_colors[$name]['i'], 1);
4335
			$this->strokecolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4336
		}
4337
		if ($this->page > 0) {
4338
			if (!$ret) {
4339
				$this->_out($this->DrawColor);
4340
			}
4341
			return $this->DrawColor;
4342
		}
4343
		return '';
4344
	}
4345
 
4346
	/**
4347
	 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
4348
	 * @param string $name name of the spot color
4349
	 * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4350
	 * @access public
4351
	 * @since 4.0.024 (2008-09-12)
4352
	 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4353
	 */
4354
	public function SetDrawSpotColor($name, $tint=100) {
4355
		if (!isset($this->spot_colors[$name])) {
4356
			$this->Error('Undefined spot color: '.$name);
4357
		}
4358
		$this->DrawColor = sprintf('/CS%d CS %.3F SCN', $this->spot_colors[$name]['i'], ($tint / 100));
4359
		$this->strokecolor = array('C' => $this->spot_colors[$name]['c'], 'M' => $this->spot_colors[$name]['m'], 'Y' => $this->spot_colors[$name]['y'], 'K' => $this->spot_colors[$name]['k'], 'name' => $name);
4360
		if ($this->page > 0) {
4361
			$this->_out($this->DrawColor);
4362
		}
4363
	}
4364
 
4365
	/**
4366
	 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
4367
	 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4368
	 * The method can be called before the first page is created and the value is retained from page to page.
4369
	 * @param array $color array of colors
4370
	 * @param boolean $ret if true do not send the PDF command.
4371
	 * @access public
4372
	 * @since 3.1.000 (2008-6-11)
4373
	 * @see SetFillColor()
4374
	 */
4375
	public function SetFillColorArray($color, $ret=false) {
4376
		if (is_array($color)) {
4377
			$color = array_values($color);
4378
			$r = isset($color[0]) ? $color[0] : -1;
4379
			$g = isset($color[1]) ? $color[1] : -1;
4380
			$b = isset($color[2]) ? $color[2] : -1;
4381
			$k = isset($color[3]) ? $color[3] : -1;
4382
			$name = isset($color[4]) ? $color[4] : ''; // spot color name
4383
			if ($r >= 0) {
4384
				$this->SetFillColor($r, $g, $b, $k, $ret, $name);
4385
			}
4386
		}
4387
	}
4388
 
4389
	/**
4390
	 * 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.
4391
	 * @param int $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4392
	 * @param int $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4393
	 * @param int $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4394
	 * @param int $col4 KEY (BLACK) color for CMYK (0-100).
4395
	 * @param boolean $ret if true do not send the command.
4396
	 * @param string $name spot color name (if any)
4397
	 * @return string the PDF command
4398
	 * @access public
4399
	 * @since 1.3
4400
	 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4401
	 */
4402
	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4403
		// set default values
4404
		if (!is_numeric($col1)) {
4405
			$col1 = 0;
4406
		}
4407
		if (!is_numeric($col2)) {
4408
			$col2 = -1;
4409
		}
4410
		if (!is_numeric($col3)) {
4411
			$col3 = -1;
4412
		}
4413
		if (!is_numeric($col4)) {
4414
			$col4 = -1;
4415
		}
4416
		//Set color for all filling operations
4417
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4418
			// Grey scale
4419
			$this->FillColor = sprintf('%.3F g', ($col1 / 255));
4420
			$this->bgcolor = array('G' => $col1);
4421
		} elseif ($col4 == -1) {
4422
			// RGB
4423
			$this->FillColor = sprintf('%.3F %.3F %.3F rg', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4424
			$this->bgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4425
		} elseif (empty($name)) {
4426
			// CMYK
4427
			$this->FillColor = sprintf('%.3F %.3F %.3F %.3F k', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4428
			$this->bgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4429
		} else {
4430
			// SPOT COLOR
4431
			$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4432
			$this->FillColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], 1);
4433
			$this->bgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4434
		}
4435
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4436
		if ($this->page > 0) {
4437
			if (!$ret) {
4438
				$this->_out($this->FillColor);
4439
			}
4440
			return $this->FillColor;
4441
		}
4442
		return '';
4443
	}
4444
 
4445
	/**
4446
	 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
4447
	 * @param string $name name of the spot color
4448
	 * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4449
	 * @access public
4450
	 * @since 4.0.024 (2008-09-12)
4451
	 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
4452
	 */
4453
	public function SetFillSpotColor($name, $tint=100) {
4454
		if (!isset($this->spot_colors[$name])) {
4455
			$this->Error('Undefined spot color: '.$name);
4456
		}
4457
		$this->FillColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], ($tint / 100));
4458
		$this->bgcolor = array('C' => $this->spot_colors[$name]['c'], 'M' => $this->spot_colors[$name]['m'], 'Y' => $this->spot_colors[$name]['y'], 'K' => $this->spot_colors[$name]['k'], 'name' => $name);
4459
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4460
		if ($this->page > 0) {
4461
			$this->_out($this->FillColor);
4462
		}
4463
	}
4464
 
4465
	/**
4466
	 * Defines the color used for text. It can be expressed in RGB components or gray scale.
4467
	 * The method can be called before the first page is created and the value is retained from page to page.
4468
	 * @param array $color array of colors
4469
	 * @param boolean $ret if true do not send the PDF command.
4470
	 * @access public
4471
	 * @since 3.1.000 (2008-6-11)
4472
	 * @see SetFillColor()
4473
	 */
4474
	public function SetTextColorArray($color, $ret=false) {
4475
		if (is_array($color)) {
4476
			$color = array_values($color);
4477
			$r = isset($color[0]) ? $color[0] : -1;
4478
			$g = isset($color[1]) ? $color[1] : -1;
4479
			$b = isset($color[2]) ? $color[2] : -1;
4480
			$k = isset($color[3]) ? $color[3] : -1;
4481
			$name = isset($color[4]) ? $color[4] : ''; // spot color name
4482
			if ($r >= 0) {
4483
				$this->SetTextColor($r, $g, $b, $k, $ret, $name);
4484
			}
4485
		}
4486
	}
4487
 
4488
	/**
4489
	 * 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.
4490
	 * @param int $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4491
	 * @param int $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4492
	 * @param int $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4493
	 * @param int $col4 KEY (BLACK) color for CMYK (0-100).
4494
	 * @param boolean $ret if true do not send the command.
4495
	 * @param string $name spot color name (if any)
4496
	 * @access public
4497
	 * @since 1.3
4498
	 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4499
	 */
4500
	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4501
		// set default values
4502
		if (!is_numeric($col1)) {
4503
			$col1 = 0;
4504
		}
4505
		if (!is_numeric($col2)) {
4506
			$col2 = -1;
4507
		}
4508
		if (!is_numeric($col3)) {
4509
			$col3 = -1;
4510
		}
4511
		if (!is_numeric($col4)) {
4512
			$col4 = -1;
4513
		}
4514
		//Set color for text
4515
		if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4516
			// Grey scale
4517
			$this->TextColor = sprintf('%.3F g', ($col1 / 255));
4518
			$this->fgcolor = array('G' => $col1);
4519
		} elseif ($col4 == -1) {
4520
			// RGB
4521
			$this->TextColor = sprintf('%.3F %.3F %.3F rg', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4522
			$this->fgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4523
		} elseif (empty($name)) {
4524
			// CMYK
4525
			$this->TextColor = sprintf('%.3F %.3F %.3F %.3F k', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4526
			$this->fgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4527
		} else {
4528
			// SPOT COLOR
4529
			$this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4530
			$this->TextColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], 1);
4531
			$this->fgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4532
		}
4533
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4534
	}
4535
 
4536
	/**
4537
	 * Defines the spot color used for text.
4538
	 * @param string $name name of the spot color
4539
	 * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4540
	 * @access public
4541
	 * @since 4.0.024 (2008-09-12)
4542
	 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
4543
	 */
4544
	public function SetTextSpotColor($name, $tint=100) {
4545
		if (!isset($this->spot_colors[$name])) {
4546
			$this->Error('Undefined spot color: '.$name);
4547
		}
4548
		$this->TextColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], ($tint / 100));
4549
		$this->fgcolor = array('C' => $this->spot_colors[$name]['c'], 'M' => $this->spot_colors[$name]['m'], 'Y' => $this->spot_colors[$name]['y'], 'K' => $this->spot_colors[$name]['k'], 'name' => $name);
4550
		$this->ColorFlag = ($this->FillColor != $this->TextColor);
4551
		if ($this->page > 0) {
4552
			$this->_out($this->TextColor);
4553
		}
4554
	}
4555
 
4556
	/**
4557
	 * Returns the length of a string in user unit. A font must be selected.<br>
4558
	 * @param string $s The string whose length is to be computed
4559
	 * @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.
4560
	 * @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-trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4561
	 * @param float $fontsize Font size in points. The default value is the current size.
4562
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4563
	 * @return mixed int total string length or array of characted widths
4564
	 * @author Nicola Asuni
4565
	 * @access public
4566
	 * @since 1.2
4567
	 */
4568
	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4569
		return $this->GetArrStringWidth($this->utf8Bidi($this->UTF8StringToArray($s), $s, $this->tmprtl), $fontname, $fontstyle, $fontsize, $getarray);
4570
	}
4571
 
4572
	/**
4573
	 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4574
	 * @param string $sa The array of chars whose total length is to be computed
4575
	 * @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.
4576
	 * @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 trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4577
	 * @param float $fontsize Font size in points. The default value is the current size.
4578
	 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4579
	 * @return mixed int total string length or array of characted widths
4580
	 * @author Nicola Asuni
4581
	 * @access public
4582
	 * @since 2.4.000 (2008-03-06)
4583
	 */
4584
	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4585
		// store current values
4586
		if (!$this->empty_string($fontname)) {
4587
			$prev_FontFamily = $this->FontFamily;
4588
			$prev_FontStyle = $this->FontStyle;
4589
			$prev_FontSizePt = $this->FontSizePt;
4590
			$this->SetFont($fontname, $fontstyle, $fontsize);
4591
		}
4592
		// convert UTF-8 array to Latin1 if required
4593
		$sa = $this->UTF8ArrToLatin1($sa);
4594
		$w = 0; // total width
4595
		$wa = array(); // array of characters widths
4596
		foreach ($sa as $ck => $char) {
4597
			// character width
4598
			$cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4599
			$wa[] = $cw;
4600
			$w += $cw;
4601
		}
4602
		// restore previous values
4603
		if (!$this->empty_string($fontname)) {
4604
			$this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt);
4605
		}
4606
		if ($getarray) {
4607
			return $wa;
4608
		}
4609
		return $w;
4610
	}
4611
 
4612
	/**
4613
	 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking/kerning).
4614
	 * @param int $char The char code whose length is to be returned
4615
	 * @param boolean $notlast set to false for the latest character on string, true otherwise (default)
4616
	 * @return float char width
4617
	 * @author Nicola Asuni
4618
	 * @access public
4619
	 * @since 2.4.000 (2008-03-06)
4620
	 */
4621
	public function GetCharWidth($char, $notlast=true) {
4622
		// get raw width
4623
		$chw = $this->getRawCharWidth($char);
4624
		if (($this->font_spacing != 0) AND $notlast) {
4625
			// increase/decrease font spacing
4626
			$chw += $this->font_spacing;
4627
		}
4628
		if ($this->font_stretching != 100) {
4629
			// fixed stretching mode
4630
			$chw *= ($this->font_stretching / 100);
4631
		}
4632
		return $chw;
4633
	}
4634
 
4635
	/**
4636
	 * Returns the length of the char in user unit for the current font.
4637
	 * @param int $char The char code whose length is to be returned
4638
	 * @return float char width
4639
	 * @author Nicola Asuni
4640
	 * @access public
4641
	 * @since 5.9.000 (2010-09-28)
4642
	 */
4643
	public function getRawCharWidth($char) {
4644
		if ($char == 173) {
4645
			// SHY character will not be printed
4646
			return (0);
4647
		}
4648
		$cw = &$this->CurrentFont['cw'];
4649
		if (isset($cw[$char])) {
4650
			$w = $cw[$char];
4651
		} elseif (isset($this->CurrentFont['dw'])) {
4652
			// default width
4653
			$w = $this->CurrentFont['dw'];
4654
		} elseif (isset($cw[32])) {
4655
			// default width
4656
			$w = $cw[32];
4657
		} else {
4658
			$w = 600;
4659
		}
4660
		return ($w * $this->FontSize / 1000);
4661
	}
4662
 
4663
	/**
4664
	 * Returns the numbero of characters in a string.
4665
	 * @param string $s The input string.
4666
	 * @return int number of characters
4667
	 * @access public
4668
	 * @since 2.0.0001 (2008-01-07)
4669
	 */
4670
	public function GetNumChars($s) {
4671
		if ($this->isUnicodeFont()) {
4672
			return count($this->UTF8StringToArray($s));
4673
		}
4674
		return strlen($s);
4675
	}
4676
 
4677
	/**
4678
	 * Fill the list of available fonts ($this->fontlist).
4679
	 * @access protected
4680
	 * @since 4.0.013 (2008-07-28)
4681
	 */
4682
	protected function getFontsList() {
4683
		$fontsdir = opendir($this->_getfontpath());
4684
		while (($file = readdir($fontsdir)) !== false) {
4685
			if (substr($file, -4) == '.php') {
4686
				array_push($this->fontlist, strtolower(basename($file, '.php')));
4687
			}
4688
		}
4689
		closedir($fontsdir);
4690
	}
4691
 
4692
	/**
4693
	 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4694
	 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4695
	 * 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.
4696
	 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4697
	 * @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>
4698
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4699
	 * @return array containing the font data, or false in case of error.
4700
	 * @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.
4701
	 * @access public
4702
	 * @since 1.5
4703
	 * @see SetFont(), setFontSubsetting()
4704
	 */
4705
	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4706
		if ($subset === 'default') {
4707
			$subset = $this->font_subsetting;
4708
		}
4709
		if ($this->empty_string($family)) {
4710
			if (!$this->empty_string($this->FontFamily)) {
4711
				$family = $this->FontFamily;
4712
			} else {
4713
				$this->Error('Empty font family');
4714
			}
4715
		}
4716
		// move embedded styles on $style
4717
		if (substr($family, -1) == 'I') {
4718
			$style .= 'I';
4719
			$family = substr($family, 0, -1);
4720
		}
4721
		if (substr($family, -1) == 'B') {
4722
			$style .= 'B';
4723
			$family = substr($family, 0, -1);
4724
		}
4725
		// normalize family name
4726
		$family = strtolower($family);
4727
		if ((!$this->isunicode) AND ($family == 'arial')) {
4728
			$family = 'helvetica';
4729
		}
4730
		if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4731
			$style = '';
4732
		}
4733
		$tempstyle = strtoupper($style);
4734
		$style = '';
4735
		// underline
4736
		if (strpos($tempstyle, 'U') !== false) {
4737
			$this->underline = true;
4738
		} else {
4739
			$this->underline = false;
4740
		}
4741
		// line-through (deleted)
4742
		if (strpos($tempstyle, 'D') !== false) {
4743
			$this->linethrough = true;
4744
		} else {
4745
			$this->linethrough = false;
4746
		}
4747
		// overline
4748
		if (strpos($tempstyle, 'O') !== false) {
4749
			$this->overline = true;
4750
		} else {
4751
			$this->overline = false;
4752
		}
4753
		// bold
4754
		if (strpos($tempstyle, 'B') !== false) {
4755
			$style .= 'B';
4756
		}
4757
		// oblique
4758
		if (strpos($tempstyle, 'I') !== false) {
4759
			$style .= 'I';
4760
		}
4761
		$bistyle = $style;
4762
		$fontkey = $family.$style;
4763
		$font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4764
		$fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4765
		// check if the font has been already added
4766
		$fb = $this->getFontBuffer($fontkey);
4767
		if ($fb !== false) {
4768
			if ($this->inxobj) {
4769
				// we are inside an XObject template
4770
				$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4771
			}
4772
			return $fontdata;
4773
		}
4774
		if (isset($type)) {
4775
			unset($type);
4776
		}
4777
		if (isset($cw)) {
4778
			unset($cw);
4779
		}
4780
		// get specified font directory (if any)
4781
		$fontdir = false;
4782
		if (!$this->empty_string($fontfile)) {
4783
			$fontdir = dirname($fontfile);
4784
			if ($this->empty_string($fontdir) OR ($fontdir == '.')) {
4785
				$fontdir = '';
4786
			} else {
4787
				$fontdir .= '/';
4788
			}
4789
		}
4790
		// search and include font file
4791
		if ($this->empty_string($fontfile) OR (!file_exists($fontfile))) {
4792
			// build a standard filenames for specified font
4793
			$fontfile1 = str_replace(' ', '', $family).strtolower($style).'.php';
4794
			$fontfile2 = str_replace(' ', '', $family).'.php';
4795
			// search files on various directories
4796
			if (($fontdir !== false) AND file_exists($fontdir.$fontfile1)) {
4797
				$fontfile = $fontdir.$fontfile1;
4798
			} elseif (file_exists($this->_getfontpath().$fontfile1)) {
4799
				$fontfile = $this->_getfontpath().$fontfile1;
4800
			} elseif (file_exists($fontfile1)) {
4801
				$fontfile = $fontfile1;
4802
			} elseif (($fontdir !== false) AND file_exists($fontdir.$fontfile2)) {
4803
				$fontfile = $fontdir.$fontfile2;
4804
			} elseif (file_exists($this->_getfontpath().$fontfile2)) {
4805
				$fontfile = $this->_getfontpath().$fontfile2;
4806
			} else {
4807
				$fontfile = $fontfile2;
4808
			}
4809
		}
4810
		// include font file
4811
		if (file_exists($fontfile)) {
4812
			include($fontfile);
4813
		} else {
4814
			$this->Error('Could not include font definition file: '.$family.'');
4815
		}
4816
		// check font parameters
4817
		if ((!isset($type)) OR (!isset($cw))) {
4818
			$this->Error('The font definition file has a bad format: '.$fontfile.'');
4819
		}
4820
		// SET default parameters
4821
		if (!isset($file) OR $this->empty_string($file)) {
4822
			$file = '';
4823
		}
4824
		if (!isset($enc) OR $this->empty_string($enc)) {
4825
			$enc = '';
4826
		}
4827
		if (!isset($cidinfo) OR $this->empty_string($cidinfo)) {
4828
			$cidinfo = array('Registry'=>'Adobe','Ordering'=>'Identity','Supplement'=>0);
4829
			$cidinfo['uni2cid'] = array();
4830
		}
4831
		if (!isset($ctg) OR $this->empty_string($ctg)) {
4832
			$ctg = '';
4833
		}
4834
		if (!isset($desc) OR $this->empty_string($desc)) {
4835
			$desc = array();
4836
		}
4837
		if (!isset($up) OR $this->empty_string($up)) {
4838
			$up = -100;
4839
		}
4840
		if (!isset($ut) OR $this->empty_string($ut)) {
4841
			$ut = 50;
4842
		}
4843
		if (!isset($cw) OR $this->empty_string($cw)) {
4844
			$cw = array();
4845
		}
4846
		if (!isset($dw) OR $this->empty_string($dw)) {
4847
			// set default width
4848
			if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4849
				$dw = $desc['MissingWidth'];
4850
			} elseif (isset($cw[32])) {
4851
				$dw = $cw[32];
4852
			} else {
4853
				$dw = 600;
4854
			}
4855
		}
4856
		++$this->numfonts;
4857
		if ($type == 'cidfont0') {
4858
			// register CID font (all styles at once)
4859
			$styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4860
			$sname = $name.$styles[$bistyle];
4861
			// artificial bold
4862
			if (strpos($bistyle, 'B') !== false) {
4863
				if (isset($desc['StemV'])) {
4864
					$desc['StemV'] *= 2;
4865
				} else {
4866
					$desc['StemV'] = 120;
4867
				}
4868
			}
4869
			// artificial italic
4870
			if (strpos($bistyle, 'I') !== false) {
4871
				if (isset($desc['ItalicAngle'])) {
4872
					$desc['ItalicAngle'] -= 11;
4873
				} else {
4874
					$desc['ItalicAngle'] = -11;
4875
				}
4876
			}
4877
		} elseif ($type == 'core') {
4878
			$name = $this->CoreFonts[$fontkey];
4879
			$subset = false;
4880
		} elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4881
			$subset = false;
4882
		} elseif ($type == 'TrueTypeUnicode') {
4883
			$enc = 'Identity-H';
4884
		} else {
4885
			$this->Error('Unknow font type: '.$type.'');
4886
		}
4887
		// initialize subsetchars to contain default ASCII values (0-255)
4888
		$subsetchars = array_fill(0, 256, true);
4889
		$this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4890
		if ($this->inxobj) {
4891
			// we are inside an XObject template
4892
			$this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4893
		}
4894
		if (isset($diff) AND (!empty($diff))) {
4895
			//Search existing encodings
4896
			$d = 0;
4897
			$nb = count($this->diffs);
4898
			for ($i=1; $i <= $nb; ++$i) {
4899
				if ($this->diffs[$i] == $diff) {
4900
					$d = $i;
4901
					break;
4902
				}
4903
			}
4904
			if ($d == 0) {
4905
				$d = $nb + 1;
4906
				$this->diffs[$d] = $diff;
4907
			}
4908
			$this->setFontSubBuffer($fontkey, 'diff', $d);
4909
		}
4910
		if (!$this->empty_string($file)) {
4911
			if (!isset($this->FontFiles[$file])) {
4912
				if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4913
					$this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4914
				} elseif ($type != 'core') {
4915
					$this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4916
				}
4917
			} else {
4918
				// update fontkeys that are sharing this font file
4919
				$this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4920
				if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4921
					$this->FontFiles[$file]['fontkeys'][] = $fontkey;
4922
				}
4923
			}
4924
		}
4925
		return $fontdata;
4926
	}
4927
 
4928
	/**
4929
	 * Sets the font used to print character strings.
4930
	 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4931
	 * The method can be called before the first page is created and the font is retained from page to page.
4932
	 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4933
	 * 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 />
4934
	 * @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.
4935
	 * @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 trough</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.
4936
	 * @param float $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
4937
	 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4938
	 * @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.
4939
	 * @author Nicola Asuni
4940
	 * @access public
4941
	 * @since 1.0
4942
	 * @see AddFont(), SetFontSize()
4943
	 */
4944
	public function SetFont($family, $style='', $size=0, $fontfile='', $subset='default') {
4945
		//Select a font; size given in points
4946
		if ($size == 0) {
4947
			$size = $this->FontSizePt;
4948
		}
4949
		// try to add font (if not already added)
4950
		$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4951
		$this->FontFamily = $fontdata['family'];
4952
		$this->FontStyle = $fontdata['style'];
4953
		$this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4954
		$this->SetFontSize($size);
4955
	}
4956
 
4957
	/**
4958
	 * Defines the size of the current font.
4959
	 * @param float $size The size (in points)
4960
	 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4961
	 * @access public
4962
	 * @since 1.0
4963
	 * @see SetFont()
4964
	 */
4965
	public function SetFontSize($size, $out=true) {
4966
		// font size in points
4967
		$this->FontSizePt = $size;
4968
		// font size in user units
4969
		$this->FontSize = $size / $this->k;
4970
		// calculate some font metrics
4971
		if (isset($this->CurrentFont['desc']['FontBBox'])) {
4972
			$bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4973
			$font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4974
		} else {
4975
			$font_height = $size * 1.219;
4976
		}
4977
		if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4978
			$font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4979
		}
4980
		if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4981
			$font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4982
		}
4983
		if (!isset($font_ascent) AND !isset($font_descent)) {
4984
			// core font
4985
			$font_ascent = 0.76 * $font_height;
4986
			$font_descent = $font_height - $font_ascent;
4987
		} elseif (!isset($font_descent)) {
4988
			$font_descent = $font_height - $font_ascent;
4989
		} elseif (!isset($font_ascent)) {
4990
			$font_ascent = $font_height - $font_descent;
4991
		}
4992
		$this->FontAscent = $font_ascent / $this->k;
4993
		$this->FontDescent = $font_descent / $this->k;
4994
		if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i']))) {
4995
			$this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4996
		}
4997
	}
4998
 
4999
	/**
5000
	 * Return the font descent value
5001
	 * @param string $font font name
5002
	 * @param string $style font style
5003
	 * @param float $size The size (in points)
5004
	 * @return int font descent
5005
	 * @access public
5006
	 * @author Nicola Asuni
5007
	 * @since 4.9.003 (2010-03-30)
5008
	 */
5009
	public function getFontDescent($font, $style='', $size=0) {
5010
		$fontdata = $this->AddFont($font, $style);
5011
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5012
		if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
5013
			$descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
5014
		} else {
5015
			$descent = 1.219 * 0.24 * $size;
5016
		}
5017
		return ($descent / $this->k);
5018
	}
5019
 
5020
	/**
5021
	 * Return the font ascent value
5022
	 * @param string $font font name
5023
	 * @param string $style font style
5024
	 * @param float $size The size (in points)
5025
	 * @return int font ascent
5026
	 * @access public
5027
	 * @author Nicola Asuni
5028
	 * @since 4.9.003 (2010-03-30)
5029
	 */
5030
	public function getFontAscent($font, $style='', $size=0) {
5031
		$fontdata = $this->AddFont($font, $style);
5032
		$fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5033
		if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
5034
			$ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
5035
		} else {
5036
			$ascent = 1.219 * 0.76 * $size;
5037
		}
5038
		return ($ascent / $this->k);
5039
	}
5040
 
5041
	/**
5042
	 * Defines the default monospaced font.
5043
	 * @param string $font Font name.
5044
	 * @access public
5045
	 * @since 4.5.025
5046
	 */
5047
	public function SetDefaultMonospacedFont($font) {
5048
		$this->default_monospaced_font = $font;
5049
	}
5050
 
5051
	/**
5052
	 * 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 />
5053
	 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
5054
	 * @access public
5055
	 * @since 1.5
5056
	 * @see Cell(), Write(), Image(), Link(), SetLink()
5057
	 */
5058
	public function AddLink() {
5059
		//Create a new internal link
5060
		$n = count($this->links) + 1;
5061
		$this->links[$n] = array(0, 0);
5062
		return $n;
5063
	}
5064
 
5065
	/**
5066
	 * Defines the page and position a link points to.
5067
	 * @param int $link The link identifier returned by AddLink()
5068
	 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
5069
	 * @param int $page Number of target page; -1 indicates the current page. This is the default value
5070
	 * @access public
5071
	 * @since 1.5
5072
	 * @see AddLink()
5073
	 */
5074
	public function SetLink($link, $y=0, $page=-1) {
5075
		if ($y == -1) {
5076
			$y = $this->y;
5077
		}
5078
		if ($page == -1) {
5079
			$page = $this->page;
5080
		}
5081
		$this->links[$link] = array($page, $y);
5082
	}
5083
 
5084
	/**
5085
	 * Puts a link on a rectangular area of the page.
5086
	 * 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.
5087
	 * @param float $x Abscissa of the upper-left corner of the rectangle
5088
	 * @param float $y Ordinate of the upper-left corner of the rectangle
5089
	 * @param float $w Width of the rectangle
5090
	 * @param float $h Height of the rectangle
5091
	 * @param mixed $link URL or identifier returned by AddLink()
5092
	 * @param int $spaces number of spaces on the text to link
5093
	 * @access public
5094
	 * @since 1.5
5095
	 * @see AddLink(), Annotation(), Cell(), Write(), Image()
5096
	 */
5097
	public function Link($x, $y, $w, $h, $link, $spaces=0) {
5098
		$this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
5099
	}
5100
 
5101
	/**
5102
	 * Puts a markup annotation on a rectangular area of the page.
5103
	 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
5104
	 * @param float $x Abscissa of the upper-left corner of the rectangle
5105
	 * @param float $y Ordinate of the upper-left corner of the rectangle
5106
	 * @param float $w Width of the rectangle
5107
	 * @param float $h Height of the rectangle
5108
	 * @param string $text annotation text or alternate content
5109
	 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
5110
	 * @param int $spaces number of spaces on the text to link
5111
	 * @access public
5112
	 * @since 4.0.018 (2008-08-06)
5113
	 */
5114
	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
5115
		if ($this->inxobj) {
5116
			// store parameters for later use on template
5117
			$this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
5118
			return;
5119
		}
5120
		if ($x === '') {
5121
			$x = $this->x;
5122
		}
5123
		if ($y === '') {
5124
			$y = $this->y;
5125
		}
5126
		// check page for no-write regions and adapt page margins if necessary
5127
		$this->checkPageRegions($h, $x, $y);
5128
		// recalculate coordinates to account for graphic transformations
5129
		if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
5130
			for ($i=$this->transfmatrix_key; $i > 0; --$i) {
5131
				$maxid = count($this->transfmatrix[$i]) - 1;
5132
				for ($j=$maxid; $j >= 0; --$j) {
5133
					$ctm = $this->transfmatrix[$i][$j];
5134
					if (isset($ctm['a'])) {
5135
						$x = $x * $this->k;
5136
						$y = ($this->h - $y) * $this->k;
5137
						$w = $w * $this->k;
5138
						$h = $h * $this->k;
5139
						// top left
5140
						$xt = $x;
5141
						$yt = $y;
5142
						$x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5143
						$y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5144
						// top right
5145
						$xt = $x + $w;
5146
						$yt = $y;
5147
						$x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5148
						$y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5149
						// bottom left
5150
						$xt = $x;
5151
						$yt = $y - $h;
5152
						$x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5153
						$y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5154
						// bottom right
5155
						$xt = $x + $w;
5156
						$yt = $y - $h;
5157
						$x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5158
						$y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5159
						// new coordinates (rectangle area)
5160
						$x = min($x1, $x2, $x3, $x4);
5161
						$y = max($y1, $y2, $y3, $y4);
5162
						$w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
5163
						$h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
5164
						$x = $x / $this->k;
5165
						$y = $this->h - ($y / $this->k);
5166
					}
5167
				}
5168
			}
5169
		}
5170
		if ($this->page <= 0) {
5171
			$page = 1;
5172
		} else {
5173
			$page = $this->page;
5174
		}
5175
		if (!isset($this->PageAnnots[$page])) {
5176
			$this->PageAnnots[$page] = array();
5177
		}
5178
		++$this->n;
5179
		$this->PageAnnots[$page][] = array('n' => $this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
5180
		if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!$this->empty_string($opt['FS'])) AND file_exists($opt['FS']) AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
5181
			++$this->n;
5182
			$this->embeddedfiles[basename($opt['FS'])] = array('n' => $this->n, 'file' => $opt['FS']);
5183
		}
5184
		// Add widgets annotation's icons
5185
		if (isset($opt['mk']['i']) AND file_exists($opt['mk']['i'])) {
5186
			$this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
5187
		}
5188
		if (isset($opt['mk']['ri']) AND file_exists($opt['mk']['ri'])) {
5189
			$this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5190
		}
5191
		if (isset($opt['mk']['ix']) AND file_exists($opt['mk']['ix'])) {
5192
			$this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5193
		}
5194
	}
5195
 
5196
	/**
5197
	 * Embedd the attached files.
5198
	 * @since 4.4.000 (2008-12-07)
5199
	 * @access protected
5200
	 * @see Annotation()
5201
	 */
5202
	protected function _putEmbeddedFiles() {
5203
		reset($this->embeddedfiles);
5204
		foreach ($this->embeddedfiles as $filename => $filedata) {
5205
			$data = file_get_contents($filedata['file']);
5206
			$filter = '';
5207
			if ($this->compress) {
5208
				$data = gzcompress($data);
5209
				$filter = ' /Filter /FlateDecode';
5210
			}
5211
			$stream = $this->_getrawstream($data, $filedata['n']);
5212
			$out = $this->_getobj($filedata['n'])."\n";
5213
			$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' >>';
5214
			$out .= ' stream'."\n".$stream."\n".'endstream';
5215
			$out .= "\n".'endobj';
5216
			$this->_out($out);
5217
		}
5218
	}
5219
 
5220
	/**
5221
	 * Prints a text cell at the specified position.
5222
	 * This method allows to place a string precisely on the page.
5223
	 * @param float $x Abscissa of the cell origin
5224
	 * @param float $y Ordinate of the cell origin
5225
	 * @param string $txt String to print
5226
	 * @param int $fstroke outline size in user units (false = disable)
5227
	 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
5228
	 * @param boolean $ffill if true fills the text
5229
	 * @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)))
5230
	 * @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.
5231
	 * @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>
5232
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5233
	 * @param mixed $link URL or identifier returned by AddLink().
5234
	 * @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.
5235
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5236
	 * @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>
5237
	 * @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>
5238
	 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
5239
	 * @access public
5240
	 * @since 1.0
5241
	 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5242
	 */
5243
	public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5244
		$textrendermode = $this->textrendermode;
5245
		$textstrokewidth = $this->textstrokewidth;
5246
		$this->setTextRenderingMode($fstroke, $ffill, $fclip);
5247
		$this->SetXY($x, $y, $rtloff);
5248
		$this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5249
		// restore previous rendering mode
5250
		$this->textrendermode = $textrendermode;
5251
		$this->textstrokewidth = $textstrokewidth;
5252
	}
5253
 
5254
	/**
5255
	 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5256
	 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5257
	 * This method is called automatically and should not be called directly by the application.
5258
	 * @return boolean
5259
	 * @access public
5260
	 * @since 1.4
5261
	 * @see SetAutoPageBreak()
5262
	 */
5263
	public function AcceptPageBreak() {
5264
		if ($this->num_columns > 1) {
5265
			// multi column mode
5266
			if($this->current_column < ($this->num_columns - 1)) {
5267
				// go to next column
5268
				$this->selectColumn($this->current_column + 1);
5269
			} else {
5270
				// add a new page
5271
				$this->AddPage();
5272
				// set first column
5273
				$this->selectColumn(0);
5274
			}
5275
			// avoid page breaking from checkPageBreak()
5276
			return false;
5277
		}
5278
		return $this->AutoPageBreak;
5279
	}
5280
 
5281
	/**
5282
	 * Add page if needed.
5283
	 * @param float $h Cell height. Default value: 0.
5284
	 * @param mixed $y starting y position, leave empty for current position.
5285
	 * @param boolean $addpage if true add a page, otherwise only return the true/false state
5286
	 * @return boolean true in case of page break, false otherwise.
5287
	 * @since 3.2.000 (2008-07-01)
5288
	 * @access protected
5289
	 */
5290
	protected function checkPageBreak($h=0, $y='', $addpage=true) {
5291
		if ($this->empty_string($y)) {
5292
			$y = $this->y;
5293
		}
5294
		$current_page = $this->page;
5295
		if ((($y + $h) > $this->PageBreakTrigger) AND (!$this->InFooter) AND ($this->AcceptPageBreak())) {
5296
			if ($addpage) {
5297
				//Automatic page break
5298
				$x = $this->x;
5299
				$this->AddPage($this->CurOrientation);
5300
				$this->y = $this->tMargin;
5301
				$oldpage = $this->page - 1;
5302
				if ($this->rtl) {
5303
					if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5304
						$this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5305
					} else {
5306
						$this->x = $x;
5307
					}
5308
				} else {
5309
					if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5310
						$this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5311
					} else {
5312
						$this->x = $x;
5313
					}
5314
				}
5315
			}
5316
			return true;
5317
		}
5318
		if ($current_page != $this->page) {
5319
			// account for columns mode
5320
			return true;
5321
		}
5322
		return false;
5323
	}
5324
 
5325
	/**
5326
	 * Removes SHY characters from text.
5327
	 * Unicode Data:<ul>
5328
	 * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
5329
	 * <li>HTML Entity (decimal): &amp;#173;</li>
5330
	 * <li>HTML Entity (hex): &amp;#xad;</li>
5331
	 * <li>HTML Entity (named): &amp;shy;</li>
5332
	 * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
5333
	 * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
5334
	 * <li>UTF-8 character: chr(194).chr(173)</li>
5335
	 * </ul>
5336
	 * @param string $txt input string
5337
	 * @return string without SHY characters.
5338
	 * @access public
5339
	 * @since (4.5.019) 2009-02-28
5340
	 */
5341
	public function removeSHY($txt='') {
5342
		$txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
5343
		if (!$this->isunicode) {
5344
			$txt = preg_replace('/([\\xad]{1})/', '', $txt);
5345
		}
5346
		return $txt;
5347
	}
5348
 
5349
	/**
5350
	 * 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 />
5351
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5352
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5353
	 * @param float $h Cell height. Default value: 0.
5354
	 * @param string $txt String to print. Default value: empty string.
5355
	 * @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)))
5356
	 * @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.
5357
	 * @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>
5358
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5359
	 * @param mixed $link URL or identifier returned by AddLink().
5360
	 * @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.
5361
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5362
	 * @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>
5363
	 * @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>
5364
	 * @access public
5365
	 * @since 1.0
5366
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5367
	 */
5368
	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') {
5369
		$prev_cell_margin = $this->cell_margin;
5370
		$prev_cell_padding = $this->cell_padding;
5371
		$this->adjustCellPadding($border);
5372
		if (!$ignore_min_height) {
5373
			$min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5374
			if ($h < $min_cell_height) {
5375
				$h = $min_cell_height;
5376
			}
5377
		}
5378
		$this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5379
		$this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5380
		$this->cell_padding = $prev_cell_padding;
5381
		$this->cell_margin = $prev_cell_margin;
5382
	}
5383
 
5384
	/**
5385
	 * 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 />
5386
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5387
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5388
	 * @param float $h Cell height. Default value: 0.
5389
	 * @param string $txt String to print. Default value: empty string.
5390
	 * @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)))
5391
	 * @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.
5392
	 * @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>
5393
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5394
	 * @param mixed $link URL or identifier returned by AddLink().
5395
	 * @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.
5396
	 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5397
	 * @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>
5398
	 * @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>
5399
	 * @return string containing cell code
5400
	 * @access protected
5401
	 * @since 1.0
5402
	 * @see Cell()
5403
	 */
5404
	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') {
5405
		$prev_cell_margin = $this->cell_margin;
5406
		$prev_cell_padding = $this->cell_padding;
5407
		$txt = $this->removeSHY($txt);
5408
		$rs = ''; //string to be returned
5409
		$this->adjustCellPadding($border);
5410
		if (!$ignore_min_height) {
5411
			$min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5412
			if ($h < $min_cell_height) {
5413
				$h = $min_cell_height;
5414
			}
5415
		}
5416
		$k = $this->k;
5417
		// check page for no-write regions and adapt page margins if necessary
5418
		$this->checkPageRegions($h, $this->x, $this->y);
5419
		if ($this->rtl) {
5420
			$x = $this->x - $this->cell_margin['R'];
5421
		} else {
5422
			$x = $this->x + $this->cell_margin['L'];
5423
		}
5424
		$y = $this->y + $this->cell_margin['T'];
5425
		$prev_font_stretching = $this->font_stretching;
5426
		$prev_font_spacing = $this->font_spacing;
5427
		// cell vertical alignment
5428
		switch ($calign) {
5429
			case 'A': {
5430
				// font top
5431
				switch ($valign) {
5432
					case 'T': {
5433
						// top
5434
						$y -= $this->cell_padding['T'];
5435
						break;
5436
					}
5437
					case 'B': {
5438
						// bottom
5439
						$y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5440
						break;
5441
					}
5442
					default:
5443
					case 'C':
5444
					case 'M': {
5445
						// center
5446
						$y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5447
						break;
5448
					}
5449
				}
5450
				break;
5451
			}
5452
			case 'L': {
5453
				// font baseline
5454
				switch ($valign) {
5455
					case 'T': {
5456
						// top
5457
						$y -= ($this->cell_padding['T'] + $this->FontAscent);
5458
						break;
5459
					}
5460
					case 'B': {
5461
						// bottom
5462
						$y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5463
						break;
5464
					}
5465
					default:
5466
					case 'C':
5467
					case 'M': {
5468
						// center
5469
						$y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5470
						break;
5471
					}
5472
				}
5473
				break;
5474
			}
5475
			case 'D': {
5476
				// font bottom
5477
				switch ($valign) {
5478
					case 'T': {
5479
						// top
5480
						$y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5481
						break;
5482
					}
5483
					case 'B': {
5484
						// bottom
5485
						$y -= ($h - $this->cell_padding['B']);
5486
						break;
5487
					}
5488
					default:
5489
					case 'C':
5490
					case 'M': {
5491
						// center
5492
						$y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5493
						break;
5494
					}
5495
				}
5496
				break;
5497
			}
5498
			case 'B': {
5499
				// cell bottom
5500
				$y -= $h;
5501
				break;
5502
			}
5503
			case 'C':
5504
			case 'M': {
5505
				// cell center
5506
				$y -= ($h / 2);
5507
				break;
5508
			}
5509
			default:
5510
			case 'T': {
5511
				// cell top
5512
				break;
5513
			}
5514
		}
5515
		// text vertical alignment
5516
		switch ($valign) {
5517
			case 'T': {
5518
				// top
5519
				$yt = $y + $this->cell_padding['T'];
5520
				break;
5521
			}
5522
			case 'B': {
5523
				// bottom
5524
				$yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5525
				break;
5526
			}
5527
			default:
5528
			case 'C':
5529
			case 'M': {
5530
				// center
5531
				$yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5532
				break;
5533
			}
5534
		}
5535
		$basefonty = $yt + $this->FontAscent;
5536
		if ($this->empty_string($w) OR ($w <= 0)) {
5537
			if ($this->rtl) {
5538
				$w = $x - $this->lMargin;
5539
			} else {
5540
				$w = $this->w - $this->rMargin - $x;
5541
			}
5542
		}
5543
		$s = '';
5544
		// fill and borders
5545
		if (is_string($border) AND (strlen($border) == 4)) {
5546
			// full border
5547
			$border = 1;
5548
		}
5549
		if ($fill OR ($border == 1)) {
5550
			if ($fill) {
5551
				$op = ($border == 1) ? 'B' : 'f';
5552
			} else {
5553
				$op = 'S';
5554
			}
5555
			if ($this->rtl) {
5556
				$xk = (($x - $w) * $k);
5557
			} else {
5558
				$xk = ($x * $k);
5559
			}
5560
			$s .= sprintf('%.2F %.2F %.2F %.2F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5561
		}
5562
		// draw borders
5563
		$s .= $this->getCellBorder($x, $y, $w, $h, $border);
5564
		if ($txt != '') {
5565
			$txt2 = $txt;
5566
			if ($this->isunicode) {
5567
				if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5568
					$txt2 = $this->UTF8ToLatin1($txt2);
5569
				} else {
5570
					$unicode = $this->UTF8StringToArray($txt); // array of UTF-8 unicode values
5571
					$unicode = $this->utf8Bidi($unicode, '', $this->tmprtl);
5572
					if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5573
						// ---- Fix for bug #2977340 "Incorrect Thai characters position arrangement" ----
5574
						// NOTE: this doesn't work with HTML justification
5575
						// Symbols that could overlap on the font top (only works in LTR)
5576
						$topchar = array(3611, 3613, 3615, 3650, 3651, 3652); // chars that extends on top
5577
						$topsym = array(3633, 3636, 3637, 3638, 3639, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662); // symbols with top position
5578
						$numchars = count($unicode); // number of chars
5579
						$unik = 0;
5580
						$uniblock = array();
5581
						$uniblock[$unik] = array();
5582
						$uniblock[$unik][] = $unicode[0];
5583
						// resolve overlapping conflicts by splitting the string in several parts
5584
						for ($i = 1; $i < $numchars; ++$i) {
5585
							// check if symbols overlaps at top
5586
							if (in_array($unicode[$i], $topsym) AND (in_array($unicode[($i - 1)], $topsym) OR in_array($unicode[($i - 1)], $topchar))) {
5587
								// move symbols to another array
5588
								++$unik;
5589
								$uniblock[$unik] = array();
5590
								$uniblock[$unik][] = $unicode[$i];
5591
								++$unik;
5592
								$uniblock[$unik] = array();
5593
								$unicode[$i] = 0x200b; // Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
5594
							} else {
5595
								$uniblock[$unik][] = $unicode[$i];
5596
							}
5597
						}
5598
						// ---- END OF Fix for bug #2977340
5599
					}
5600
					$txt2 = $this->arrUTF8ToUTF16BE($unicode, false);
5601
				}
5602
			}
5603
			$txt2 = $this->_escape($txt2);
5604
			// get current text width (considering general font stretching and spacing)
5605
			$txwidth = $this->GetStringWidth($txt);
5606
			$width = $txwidth;
5607
			// check for stretch mode
5608
			if ($stretch > 0) {
5609
				// calculate ratio between cell width and text width
5610
				if ($width <= 0) {
5611
					$ratio = 1;
5612
				} else {
5613
					$ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5614
				}
5615
				// check if stretching is required
5616
				if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5617
					// the text will be stretched to fit cell width
5618
					if ($stretch > 2) {
5619
						// set new character spacing
5620
						$this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5621
					} else {
5622
						// set new horizontal stretching
5623
						$this->font_stretching *= $ratio;
5624
					}
5625
					// recalculate text width (the text fills the entire cell)
5626
					$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5627
					// reset alignment
5628
					$align = '';
5629
				}
5630
			}
5631
			if ($this->font_stretching != 100) {
5632
				// apply font stretching
5633
				$rs .= sprintf('BT %.2F Tz ET ', $this->font_stretching);
5634
			}
5635
			if ($this->font_spacing != 0) {
5636
				// increase/decrease font spacing
5637
				$rs .= sprintf('BT %.2F Tc ET ', ($this->font_spacing * $this->k));
5638
			}
5639
			if ($this->ColorFlag) {
5640
				$s .= 'q '.$this->TextColor.' ';
5641
			}
5642
			// rendering mode
5643
			$s .= sprintf('BT %d Tr %.2F w ET ', $this->textrendermode, $this->textstrokewidth);
5644
			// count number of spaces
5645
			$ns = substr_count($txt, chr(32));
5646
			// Justification
5647
			$spacewidth = 0;
5648
			if (($align == 'J') AND ($ns > 0)) {
5649
				if ($this->isUnicodeFont()) {
5650
					// get string width without spaces
5651
					$width = $this->GetStringWidth(str_replace(' ', '', $txt));
5652
					// calculate average space width
5653
					$spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / $this->FontSize;
5654
					if ($this->font_stretching != 100) {
5655
						// word spacing is affected by stretching
5656
						$spacewidth /= ($this->font_stretching / 100);
5657
					}
5658
					// set word position to be used with TJ operator
5659
					$txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%.3F', $spacewidth).' (', $txt2);
5660
					$unicode_justification = true;
5661
				} else {
5662
					// get string width
5663
					$width = $txwidth;
5664
					// new space width
5665
					$spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5666
					if ($this->font_stretching != 100) {
5667
						// word spacing (Tw) is affected by stretching
5668
						$spacewidth /= ($this->font_stretching / 100);
5669
					}
5670
					// set word spacing
5671
					$rs .= sprintf('BT %.3F Tw ET ', $spacewidth);
5672
				}
5673
				$width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5674
			}
5675
			// replace carriage return characters
5676
			$txt2 = str_replace("\r", ' ', $txt2);
5677
			switch ($align) {
5678
				case 'C': {
5679
					$dx = ($w - $width) / 2;
5680
					break;
5681
				}
5682
				case 'R': {
5683
					if ($this->rtl) {
5684
						$dx = $this->cell_padding['R'];
5685
					} else {
5686
						$dx = $w - $width - $this->cell_padding['R'];
5687
					}
5688
					break;
5689
				}
5690
				case 'L': {
5691
					if ($this->rtl) {
5692
						$dx = $w - $width - $this->cell_padding['L'];
5693
					} else {
5694
						$dx = $this->cell_padding['L'];
5695
					}
5696
					break;
5697
				}
5698
				case 'J':
5699
				default: {
5700
					if ($this->rtl) {
5701
						$dx = $this->cell_padding['R'];
5702
					} else {
5703
						$dx = $this->cell_padding['L'];
5704
					}
5705
					break;
5706
				}
5707
			}
5708
			if ($this->rtl) {
5709
				$xdx = $x - $dx - $width;
5710
			} else {
5711
				$xdx = $x + $dx;
5712
			}
5713
			$xdk = $xdx * $k;
5714
			// print text
5715
			$s .= sprintf('BT %.2F %.2F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5716
			if (isset($uniblock)) {
5717
				// print overlapping characters as separate string
5718
				$xshift = 0; // horizontal shift
5719
				$ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5720
				$spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5721
				foreach ($uniblock as $uk => $uniarr) {
5722
					if (($uk % 2) == 0) {
5723
						// x space to skip
5724
						if ($spacewidth != 0) {
5725
							// justification shift
5726
							$xshift += (count(array_keys($uniarr, 32)) * $spw);
5727
						}
5728
						$xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5729
					} else {
5730
						// character to print
5731
						$topchr = $this->arrUTF8ToUTF16BE($uniarr, false);
5732
						$topchr = $this->_escape($topchr);
5733
						$s .= sprintf(' BT %.2F %.2F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5734
					}
5735
				}
5736
			}
5737
			if ($this->underline) {
5738
				$s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5739
			}
5740
			if ($this->linethrough) {
5741
				$s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5742
			}
5743
			if ($this->overline) {
5744
				$s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5745
			}
5746
			if ($this->ColorFlag) {
5747
				$s .= ' Q';
5748
			}
5749
			if ($link) {
5750
				$this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5751
			}
5752
		}
5753
		// output cell
5754
		if ($s) {
5755
			// output cell
5756
			$rs .= $s;
5757
			if ($this->font_spacing != 0) {
5758
				// reset font spacing mode
5759
				$rs .= ' BT 0 Tc ET';
5760
			}
5761
			if ($this->font_stretching != 100) {
5762
				// reset font stretching mode
5763
				$rs .= ' BT 100 Tz ET';
5764
			}
5765
		}
5766
		// reset word spacing
5767
		if (!$this->isUnicodeFont() AND ($align == 'J')) {
5768
			$rs .= ' BT 0 Tw ET';
5769
		}
5770
		// reset stretching and spacing
5771
		$this->font_stretching = $prev_font_stretching;
5772
		$this->font_spacing = $prev_font_spacing;
5773
		$this->lasth = $h;
5774
		if ($ln > 0) {
5775
			//Go to the beginning of the next line
5776
			$this->y = $y + $h + $this->cell_margin['B'];
5777
			if ($ln == 1) {
5778
				if ($this->rtl) {
5779
					$this->x = $this->w - $this->rMargin;
5780
				} else {
5781
					$this->x = $this->lMargin;
5782
				}
5783
			}
5784
		} else {
5785
			// go left or right by case
5786
			if ($this->rtl) {
5787
				$this->x = $x - $w - $this->cell_margin['L'];
5788
			} else {
5789
				$this->x = $x + $w + $this->cell_margin['R'];
5790
			}
5791
		}
5792
		$gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5793
		$rs = $gstyles.$rs;
5794
		$this->cell_padding = $prev_cell_padding;
5795
		$this->cell_margin = $prev_cell_margin;
5796
		return $rs;
5797
	}
5798
 
5799
	/**
5800
	 * Returns the code to draw the cell border
5801
	 * @param float $x X coordinate.
5802
	 * @param float $y Y coordinate.
5803
	 * @param float $w Cell width.
5804
	 * @param float $h Cell height.
5805
	 * @param mixed $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)))
5806
	 * @param string $mode border position respect the square edge: normal: centered; ext: external; int: internal;
5807
	 * @return string containing cell border code
5808
	 * @access protected
5809
	 * @see SetLineStyle()
5810
	 * @since 5.7.000 (2010-08-02)
5811
	 */
5812
	protected function getCellBorder($x, $y, $w, $h, $brd) {
5813
		$s = ''; // string to be returned
5814
		if (empty($brd)) {
5815
			return $s;
5816
		}
5817
		if ($brd == 1) {
5818
			$brd = array('LRTB' => true);
5819
		}
5820
		// calculate coordinates for border
5821
		$k = $this->k;
5822
		if ($this->rtl) {
5823
			$xeL = ($x - $w) * $k;
5824
			$xeR = $x * $k;
5825
		} else {
5826
			$xeL = $x * $k;
5827
			$xeR = ($x + $w) * $k;
5828
		}
5829
		$yeL = (($this->h - ($y + $h)) * $k);
5830
		$yeT = (($this->h - $y) * $k);
5831
		$xeT = $xeL;
5832
		$xeB = $xeR;
5833
		$yeR = $yeT;
5834
		$yeB = $yeL;
5835
		if (is_string($brd)) {
5836
			// convert string to array
5837
			$slen = strlen($brd);
5838
			$newbrd = array();
5839
			for ($i = 0; $i < $slen; ++$i) {
5840
				$newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
5841
			}
5842
			$brd = $newbrd;
5843
		}
5844
		if (isset($brd['mode'])) {
5845
			$mode = $brd['mode'];
5846
			unset($brd['mode']);
5847
		} else {
5848
			$mode = 'normal';
5849
		}
5850
		foreach ($brd as $border => $style) {
5851
			if (is_array($style) AND !empty($style)) {
5852
				// apply border style
5853
				$prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5854
				$s .= $this->SetLineStyle($style, true)."\n";
5855
			}
5856
			switch ($mode) {
5857
				case 'ext': {
5858
					$off = (($this->LineWidth / 2) * $k);
5859
					$xL = $xeL - $off;
5860
					$xR = $xeR + $off;
5861
					$yT = $yeT + $off;
5862
					$yL = $yeL - $off;
5863
					$xT = $xL;
5864
					$xB = $xR;
5865
					$yR = $yT;
5866
					$yB = $yL;
5867
					$w += $this->LineWidth;
5868
					$h += $this->LineWidth;
5869
					break;
5870
				}
5871
				case 'int': {
5872
					$off = ($this->LineWidth / 2) * $k;
5873
					$xL = $xeL + $off;
5874
					$xR = $xeR - $off;
5875
					$yT = $yeT - $off;
5876
					$yL = $yeL + $off;
5877
					$xT = $xL;
5878
					$xB = $xR;
5879
					$yR = $yT;
5880
					$yB = $yL;
5881
					$w -= $this->LineWidth;
5882
					$h -= $this->LineWidth;
5883
					break;
5884
				}
5885
				case 'normal':
5886
				default: {
5887
					$xL = $xeL;
5888
					$xT = $xeT;
5889
					$xB = $xeB;
5890
					$xR = $xeR;
5891
					$yL = $yeL;
5892
					$yT = $yeT;
5893
					$yB = $yeB;
5894
					$yR = $yeR;
5895
					break;
5896
				}
5897
			}
5898
			// draw borders by case
5899
			if (strlen($border) == 4) {
5900
				$s .= sprintf('%.2F %.2F %.2F %.2F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5901
			} elseif (strlen($border) == 3) {
5902
				if (strpos($border,'B') === false) { // LTR
5903
					$s .= sprintf('%.2F %.2F m ', $xL, $yL);
5904
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5905
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5906
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5907
					$s .= 'S ';
5908
				} elseif (strpos($border,'L') === false) { // TRB
5909
					$s .= sprintf('%.2F %.2F m ', $xT, $yT);
5910
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5911
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5912
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5913
					$s .= 'S ';
5914
				} elseif (strpos($border,'T') === false) { // RBL
5915
					$s .= sprintf('%.2F %.2F m ', $xR, $yR);
5916
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5917
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5918
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5919
					$s .= 'S ';
5920
				} elseif (strpos($border,'R') === false) { // BLT
5921
					$s .= sprintf('%.2F %.2F m ', $xB, $yB);
5922
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5923
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5924
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5925
					$s .= 'S ';
5926
				}
5927
			} elseif (strlen($border) == 2) {
5928
				if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5929
					$s .= sprintf('%.2F %.2F m ', $xL, $yL);
5930
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5931
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5932
					$s .= 'S ';
5933
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5934
					$s .= sprintf('%.2F %.2F m ', $xT, $yT);
5935
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5936
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5937
					$s .= 'S ';
5938
				} elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5939
					$s .= sprintf('%.2F %.2F m ', $xR, $yR);
5940
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5941
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5942
					$s .= 'S ';
5943
				} elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5944
					$s .= sprintf('%.2F %.2F m ', $xB, $yB);
5945
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5946
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5947
					$s .= 'S ';
5948
				} elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5949
					$s .= sprintf('%.2F %.2F m ', $xL, $yL);
5950
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5951
					$s .= 'S ';
5952
					$s .= sprintf('%.2F %.2F m ', $xR, $yR);
5953
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5954
					$s .= 'S ';
5955
				} elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5956
					$s .= sprintf('%.2F %.2F m ', $xT, $yT);
5957
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5958
					$s .= 'S ';
5959
					$s .= sprintf('%.2F %.2F m ', $xB, $yB);
5960
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5961
					$s .= 'S ';
5962
				}
5963
			} else { // strlen($border) == 1
5964
				if (strpos($border,'L') !== false) { // L
5965
					$s .= sprintf('%.2F %.2F m ', $xL, $yL);
5966
					$s .= sprintf('%.2F %.2F l ', $xT, $yT);
5967
					$s .= 'S ';
5968
				} elseif (strpos($border,'T') !== false) { // T
5969
					$s .= sprintf('%.2F %.2F m ', $xT, $yT);
5970
					$s .= sprintf('%.2F %.2F l ', $xR, $yR);
5971
					$s .= 'S ';
5972
				} elseif (strpos($border,'R') !== false) { // R
5973
					$s .= sprintf('%.2F %.2F m ', $xR, $yR);
5974
					$s .= sprintf('%.2F %.2F l ', $xB, $yB);
5975
					$s .= 'S ';
5976
				} elseif (strpos($border,'B') !== false) { // B
5977
					$s .= sprintf('%.2F %.2F m ', $xB, $yB);
5978
					$s .= sprintf('%.2F %.2F l ', $xL, $yL);
5979
					$s .= 'S ';
5980
				}
5981
			}
5982
			if (is_array($style) AND !empty($style)) {
5983
				// reset border style to previous value
5984
				$s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5985
			}
5986
		}
5987
		return $s;
5988
	}
5989
 
5990
	/**
5991
	 * This method allows printing text with line breaks.
5992
	 * 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 />
5993
	 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5994
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5995
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
5996
	 * @param string $txt String to print
5997
	 * @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)))
5998
	 * @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>
5999
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6000
	 * @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>
6001
	 * @param float $x x position in user units
6002
	 * @param float $y y position in user units
6003
	 * @param boolean $reseth if true reset the last cell height (default true).
6004
	 * @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.
6005
	 * @param boolean $ishtml set to true if $txt is HTML content (default = false).
6006
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
6007
	 * @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.
6008
	 * @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.
6009
	 * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size.
6010
	 * @return int Return the number of cells or 1 for html mode.
6011
	 * @access public
6012
	 * @since 1.3
6013
	 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
6014
	 */
6015
	public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
6016
		$prev_cell_margin = $this->cell_margin;
6017
		$prev_cell_padding = $this->cell_padding;
6018
		// adjust internal padding
6019
		$this->adjustCellPadding($border);
6020
		$mc_padding = $this->cell_padding;
6021
		$mc_margin = $this->cell_margin;
6022
		$this->cell_padding['T'] = 0;
6023
		$this->cell_padding['B'] = 0;
6024
		$this->setCellMargins(0, 0, 0, 0);
6025
		if ($this->empty_string($this->lasth) OR $reseth) {
6026
			// reset row height
6027
			$this->resetLastH();
6028
		}
6029
		if (!$this->empty_string($y)) {
6030
			$this->SetY($y);
6031
		} else {
6032
			$y = $this->GetY();
6033
		}
6034
		$resth = 0;
6035
		if ((!$this->InFooter) AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
6036
			// spit cell in more pages/columns
6037
			$newh = $this->PageBreakTrigger - $y;
6038
			$resth = $h - $newh; // cell to be printed on the next page/column
6039
			$h = $newh;
6040
		}
6041
		// get current page number
6042
		$startpage = $this->page;
6043
		// get current column
6044
		$startcolumn = $this->current_column;
6045
		if (!$this->empty_string($x)) {
6046
			$this->SetX($x);
6047
		} else {
6048
			$x = $this->GetX();
6049
		}
6050
		// check page for no-write regions and adapt page margins if necessary
6051
		$this->checkPageRegions(0, $x, $y);
6052
		// apply margins
6053
		$oy = $y + $mc_margin['T'];
6054
		if ($this->rtl) {
6055
			$ox = $this->w - $x - $mc_margin['R'];
6056
		} else {
6057
			$ox = $x + $mc_margin['L'];
6058
		}
6059
		$this->x = $ox;
6060
		$this->y = $oy;
6061
		// set width
6062
		if ($this->empty_string($w) OR ($w <= 0)) {
6063
			if ($this->rtl) {
6064
				$w = $this->x - $this->lMargin - $mc_margin['L'];
6065
			} else {
6066
				$w = $this->w - $this->x - $this->rMargin - $mc_margin['R'];
6067
			}
6068
		}
6069
		// store original margin values
6070
		$lMargin = $this->lMargin;
6071
		$rMargin = $this->rMargin;
6072
		if ($this->rtl) {
6073
			$this->rMargin = $this->w - $this->x;
6074
			$this->lMargin = $this->x - $w;
6075
		} else {
6076
			$this->lMargin = $this->x;
6077
			$this->rMargin = $this->w - $this->x - $w;
6078
		}
6079
		if ($autopadding) {
6080
			// add top padding
6081
			$this->y += $mc_padding['T'];
6082
		}
6083
		if ($ishtml) { // ******* Write HTML text
6084
			$this->writeHTML($txt, true, 0, $reseth, true, $align);
6085
			$nl = 1;
6086
		} else { // ******* Write simple text
6087
			// vertical alignment
6088
			if ($maxh > 0) {
6089
				// get text height
6090
				$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6091
				if ($fitcell) {
6092
					$prev_FontSizePt = $this->FontSizePt;
6093
					// try to reduce font size to fit text on cell (use a quick search algorithm)
6094
					$fmin = 1;
6095
					$fmax = $this->FontSizePt;
6096
					$prev_text_height = $text_height;
6097
					$maxit = 100; // max number of iterations
6098
					while ($maxit > 0) {
6099
						$fmid = (($fmax + $fmin) / 2);
6100
						$this->SetFontSize($fmid, false);
6101
						$this->resetLastH();
6102
						$text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6103
						if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
6104
							break;
6105
						} elseif ($text_height < $maxh) {
6106
							$fmin = $fmid;
6107
						} else {
6108
							$fmax = $fmid;
6109
						}
6110
						--$maxit;
6111
					}
6112
					$this->SetFontSize($this->FontSizePt);
6113
				}
6114
				if ($text_height < $maxh) {
6115
					if ($valign == 'M') {
6116
						// text vertically centered
6117
						$this->y += (($maxh - $text_height) / 2);
6118
					} elseif ($valign == 'B') {
6119
						// text vertically aligned on bottom
6120
						$this->y += ($maxh - $text_height);
6121
					}
6122
				}
6123
			}
6124
			$nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6125
			if ($fitcell) {
6126
				// restore font size
6127
				$this->SetFontSize($prev_FontSizePt);
6128
			}
6129
		}
6130
		if ($autopadding) {
6131
			// add bottom padding
6132
			$this->y += $mc_padding['B'];
6133
		}
6134
		// Get end-of-text Y position
6135
		$currentY = $this->y;
6136
		// get latest page number
6137
		$endpage = $this->page;
6138
		if ($resth > 0) {
6139
			$skip = ($endpage - $startpage);
6140
			$tmpresth = $resth;
6141
			while ($tmpresth > 0) {
6142
				if ($skip <= 0) {
6143
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
6144
					$this->checkPageBreak($this->PageBreakTrigger + 1);
6145
				}
6146
				if ($this->num_columns > 1) {
6147
					$tmpresth -= ($this->h - $this->y - $this->bMargin);
6148
				} else {
6149
					$tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6150
				}
6151
				--$skip;
6152
			}
6153
			$currentY = $this->y;
6154
			$endpage = $this->page;
6155
		}
6156
		// get latest column
6157
		$endcolumn = $this->current_column;
6158
		if ($this->num_columns == 0) {
6159
			$this->num_columns = 1;
6160
		}
6161
		// get border modes
6162
		$border_start = $this->getBorderMode($border, $position='start');
6163
		$border_end = $this->getBorderMode($border, $position='end');
6164
		$border_middle = $this->getBorderMode($border, $position='middle');
6165
		// design borders around HTML cells.
6166
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6167
			$ccode = '';
6168
			$this->setPage($page);
6169
			if ($this->num_columns < 2) {
6170
				// single-column mode
6171
				$this->SetX($x);
6172
				$this->y = $this->tMargin;
6173
			}
6174
			// account for margin changes
6175
			if ($page > $startpage) {
6176
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6177
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6178
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6179
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6180
				}
6181
			}
6182
			if ($startpage == $endpage) {
6183
				// single page
6184
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6185
					$this->selectColumn($column);
6186
					if ($this->rtl) {
6187
						$this->x -= $mc_margin['R'];
6188
					} else {
6189
						$this->x += $mc_margin['L'];
6190
					}
6191
					if ($startcolumn == $endcolumn) { // single column
6192
						$cborder = $border;
6193
						$h = max($h, ($currentY - $oy));
6194
						$this->y = $oy;
6195
					} elseif ($column == $startcolumn) { // first column
6196
						$cborder = $border_start;
6197
						$this->y = $oy;
6198
						$h = $this->h - $this->y - $this->bMargin;
6199
					} elseif ($column == $endcolumn) { // end column
6200
						$cborder = $border_end;
6201
						$h = $currentY - $this->y;
6202
						if ($resth > $h) {
6203
							$h = $resth;
6204
						}
6205
					} else { // middle column
6206
						$cborder = $border_middle;
6207
						$h = $this->h - $this->y - $this->bMargin;
6208
						$resth -= $h;
6209
					}
6210
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6211
				} // end for each column
6212
			} elseif ($page == $startpage) { // first page
6213
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6214
					$this->selectColumn($column);
6215
					if ($this->rtl) {
6216
						$this->x -= $mc_margin['R'];
6217
					} else {
6218
						$this->x += $mc_margin['L'];
6219
					}
6220
					if ($column == $startcolumn) { // first column
6221
						$cborder = $border_start;
6222
						$this->y = $oy;
6223
						$h = $this->h - $this->y - $this->bMargin;
6224
					} else { // middle column
6225
						$cborder = $border_middle;
6226
						$h = $this->h - $this->y - $this->bMargin;
6227
						$resth -= $h;
6228
					}
6229
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6230
				} // end for each column
6231
			} elseif ($page == $endpage) { // last page
6232
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6233
					$this->selectColumn($column);
6234
					if ($this->rtl) {
6235
						$this->x -= $mc_margin['R'];
6236
					} else {
6237
						$this->x += $mc_margin['L'];
6238
					}
6239
					if ($column == $endcolumn) {
6240
						// end column
6241
						$cborder = $border_end;
6242
						$h = $currentY - $this->y;
6243
						if ($resth > $h) {
6244
							$h = $resth;
6245
						}
6246
					} else {
6247
						// middle column
6248
						$cborder = $border_middle;
6249
						$h = $this->h - $this->y - $this->bMargin;
6250
						$resth -= $h;
6251
					}
6252
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6253
				} // end for each column
6254
			} else { // middle page
6255
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6256
					$this->selectColumn($column);
6257
					if ($this->rtl) {
6258
						$this->x -= $mc_margin['R'];
6259
					} else {
6260
						$this->x += $mc_margin['L'];
6261
					}
6262
					$cborder = $border_middle;
6263
					$h = $this->h - $this->y - $this->bMargin;
6264
					$resth -= $h;
6265
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6266
				} // end for each column
6267
			}
6268
			if ($cborder OR $fill) {
6269
				// draw border and fill
6270
				if ($this->inxobj) {
6271
					// we are inside an XObject template
6272
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6273
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6274
						$pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6275
					} else {
6276
						$pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
6277
					}
6278
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6279
					$pstart = substr($pagebuff, 0, $pagemark);
6280
					$pend = substr($pagebuff, $pagemark);
6281
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6282
					$pagemark += strlen($ccode);
6283
				} else {
6284
					if (end($this->transfmrk[$this->page]) !== false) {
6285
						$pagemarkkey = key($this->transfmrk[$this->page]);
6286
						$pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
6287
					} elseif ($this->InFooter) {
6288
						$pagemark = &$this->footerpos[$this->page];
6289
					} else {
6290
						$pagemark = &$this->intmrk[$this->page];
6291
					}
6292
					$pagebuff = $this->getPageBuffer($this->page);
6293
					$pstart = substr($pagebuff, 0, $pagemark);
6294
					$pend = substr($pagebuff, $pagemark);
6295
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6296
					$pagemark += strlen($ccode);
6297
				}
6298
			}
6299
		} // end for each page
6300
		// Get end-of-cell Y position
6301
		$currentY = $this->GetY();
6302
		// restore original margin values
6303
		$this->SetLeftMargin($lMargin);
6304
		$this->SetRightMargin($rMargin);
6305
		if ($ln > 0) {
6306
			//Go to the beginning of the next line
6307
			$this->SetY($currentY + $mc_margin['B']);
6308
			if ($ln == 2) {
6309
				$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6310
			}
6311
		} else {
6312
			// go left or right by case
6313
			$this->setPage($startpage);
6314
			$this->y = $y;
6315
			$this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6316
		}
6317
		$this->setContentMark();
6318
		$this->cell_padding = $prev_cell_padding;
6319
		$this->cell_margin = $prev_cell_margin;
6320
		return $nl;
6321
	}
6322
 
6323
	/**
6324
	 * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
6325
	 * @param mixed $brd Indicates if borders must be drawn around the cell block. 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: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6326
	 * @param string multicell position: 'start', 'middle', 'end'
6327
	 * @return border mode array
6328
	 * @access protected
6329
	 * @since 4.4.002 (2008-12-09)
6330
	 */
6331
	protected function getBorderMode($brd, $position='start') {
6332
		if ((!$this->opencell) OR empty($brd)) {
6333
			return $brd;
6334
		}
6335
		if ($brd == 1) {
6336
			$brd = 'LTRB';
6337
		}
6338
		if (is_string($brd)) {
6339
			// convert string to array
6340
			$slen = strlen($brd);
6341
			$newbrd = array();
6342
			for ($i = 0; $i < $slen; ++$i) {
6343
				$newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
6344
			}
6345
			$brd = $newbrd;
6346
		}
6347
		foreach ($brd as $border => $style) {
6348
			switch ($position) {
6349
				case 'start': {
6350
					if (strpos($border, 'B') !== false) {
6351
						// remove bottom line
6352
						$newkey = str_replace('B', '', $border);
6353
						if (strlen($newkey) > 0) {
6354
							$brd[$newkey] = $style;
6355
						}
6356
						unset($brd[$border]);
6357
					}
6358
					break;
6359
				}
6360
				case 'middle': {
6361
					if (strpos($border, 'B') !== false) {
6362
						// remove bottom line
6363
						$newkey = str_replace('B', '', $border);
6364
						if (strlen($newkey) > 0) {
6365
							$brd[$newkey] = $style;
6366
						}
6367
						unset($brd[$border]);
6368
						$border = $newkey;
6369
					}
6370
					if (strpos($border, 'T') !== false) {
6371
						// remove bottom line
6372
						$newkey = str_replace('T', '', $border);
6373
						if (strlen($newkey) > 0) {
6374
							$brd[$newkey] = $style;
6375
						}
6376
						unset($brd[$border]);
6377
					}
6378
					break;
6379
				}
6380
				case 'end': {
6381
					if (strpos($border, 'T') !== false) {
6382
						// remove bottom line
6383
						$newkey = str_replace('T', '', $border);
6384
						if (strlen($newkey) > 0) {
6385
							$brd[$newkey] = $style;
6386
						}
6387
						unset($brd[$border]);
6388
					}
6389
					break;
6390
				}
6391
			}
6392
		}
6393
		return $brd;
6394
	}
6395
 
6396
	/**
6397
	 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6398
	 * @param string $txt String for calculating his height
6399
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6400
	 * @param boolean $reseth if true reset the last cell height (default false).
6401
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6402
	 * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
6403
	 * @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)))
6404
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6405
	 * @author Alexander Escalona Fern�ndez, Nicola Asuni
6406
	 * @access public
6407
	 * @since 4.5.011
6408
	 */
6409
	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6410
		if ($txt === '') {
6411
			// empty string
6412
			return 1;
6413
		}
6414
		// adjust internal padding
6415
		$prev_cell_padding = $this->cell_padding;
6416
		$prev_lasth = $this->lasth;
6417
		if (is_array($cellpadding)) {
6418
			$this->cell_padding = $cellpadding;
6419
		}
6420
		$this->adjustCellPadding($border);
6421
		if ($this->empty_string($w) OR ($w <= 0)) {
6422
			if ($this->rtl) {
6423
				$w = $this->x - $this->lMargin;
6424
			} else {
6425
				$w = $this->w - $this->rMargin - $this->x;
6426
			}
6427
		}
6428
		$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6429
		if ($reseth) {
6430
			// reset row height
6431
			$this->resetLastH();
6432
		}
6433
		$lines = 1;
6434
		$sum = 0;
6435
		$chars = $this->utf8Bidi($this->UTF8StringToArray($txt), $txt, $this->tmprtl);
6436
		$charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6437
		$length = count($chars);
6438
		$lastSeparator = -1;
6439
		for ($i = 0; $i < $length; ++$i) {
6440
			$charWidth = $charsWidth[$i];
6441
			if (preg_match($this->re_spaces, $this->unichr($chars[$i]))) {
6442
				$lastSeparator = $i;
6443
			}
6444
			if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
6445
				++$lines;
6446
				if ($lastSeparator != -1) {
6447
					$i = $lastSeparator;
6448
					$lastSeparator = -1;
6449
					$sum = 0;
6450
				} else {
6451
					$sum = $charWidth;
6452
				}
6453
			} else {
6454
				$sum += $charWidth;
6455
			}
6456
		}
6457
		if ($chars[($length - 1)] == 10) {
6458
			--$lines;
6459
		}
6460
		$this->cell_padding = $prev_cell_padding;
6461
		$this->lasth = $prev_lasth;
6462
		return $lines;
6463
	}
6464
 
6465
	/**
6466
	 * This method return the estimated needed height for print a simple text string in Multicell() method.
6467
	 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6468
	 * <pre>
6469
	 *  // store current object
6470
	 *  $pdf->startTransaction();
6471
	 *  // store starting values
6472
	 *  $start_y = $pdf->GetY();
6473
	 *  $start_page = $pdf->getPage();
6474
	 *  // call your printing functions with your parameters
6475
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6476
	 *  $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6477
	 *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6478
	 *  // get the new Y
6479
	 *  $end_y = $pdf->GetY();
6480
	 *  $end_page = $pdf->getPage();
6481
	 *  // calculate height
6482
	 *  $height = 0;
6483
	 *  if ($end_page == $start_page) {
6484
	 *  	$height = $end_y - $start_y;
6485
	 *  } else {
6486
	 *  	for ($page=$start_page; $page <= $end_page; ++$page) {
6487
	 *  		$this->setPage($page);
6488
	 *  		if ($page == $start_page) {
6489
	 *  			// first page
6490
	 *  			$height = $this->h - $start_y - $this->bMargin;
6491
	 *  		} elseif ($page == $end_page) {
6492
	 *  			// last page
6493
	 *  			$height = $end_y - $this->tMargin;
6494
	 *  		} else {
6495
	 *  			$height = $this->h - $this->tMargin - $this->bMargin;
6496
	 *  		}
6497
	 *  	}
6498
	 *  }
6499
	 *  // restore previous object
6500
	 *  $pdf = $pdf->rollbackTransaction();
6501
	 * </pre>
6502
	 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6503
	 * @param string $txt String for calculating his height
6504
	 * @param boolean $reseth if true reset the last cell height (default false).
6505
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6506
	 * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
6507
	 * @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)))
6508
	 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6509
	 * @author Nicola Asuni, Alexander Escalona Fern�ndez
6510
	 * @access public
6511
	 */
6512
	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6513
		// adjust internal padding
6514
		$prev_cell_padding = $this->cell_padding;
6515
		$prev_lasth = $this->lasth;
6516
		if (is_array($cellpadding)) {
6517
			$this->cell_padding = $cellpadding;
6518
		}
6519
		$this->adjustCellPadding($border);
6520
		$lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6521
		$height = $lines * ($this->FontSize * $this->cell_height_ratio);
6522
		if ($autopadding) {
6523
			// add top and bottom padding
6524
			$height += ($this->cell_padding['T'] + $this->cell_padding['B']);
6525
		}
6526
		$this->cell_padding = $prev_cell_padding;
6527
		$this->lasth = $prev_lasth;
6528
		return $height;
6529
	}
6530
 
6531
	/**
6532
	 * This method prints text from the current position.<br />
6533
	 * @param float $h Line height
6534
	 * @param string $txt String to print
6535
	 * @param mixed $link URL or identifier returned by AddLink()
6536
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6537
	 * @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>
6538
	 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6539
	 * @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.
6540
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
6541
	 * @param boolean $firstblock if true the string is the starting of a line.
6542
	 * @param float $maxh maximum height. The remaining unprinted text will be returned. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6543
	 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6544
	 * @param array $margin margin array of the parent container
6545
	 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6546
	 * @access public
6547
	 * @since 1.5
6548
	 */
6549
	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6550
		// check page for no-write regions and adapt page margins if necessary
6551
		$this->checkPageRegions($h, $this->x, $this->y);
6552
		if (strlen($txt) == 0) {
6553
			// fix empty text
6554
			$txt = ' ';
6555
		}
6556
		if ($margin === '') {
6557
			// set default margins
6558
			$margin = $this->cell_margin;
6559
		}
6560
		// remove carriage returns
6561
		$s = str_replace("\r", '', $txt);
6562
		// check if string contains arabic text
6563
		if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $s)) {
6564
			$arabic = true;
6565
		} else {
6566
			$arabic = false;
6567
		}
6568
		// check if string contains RTL text
6569
		if ($arabic OR ($this->tmprtl == 'R') OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $s)) {
6570
			$rtlmode = true;
6571
		} else {
6572
			$rtlmode = false;
6573
		}
6574
		// get a char width
6575
		$chrwidth = $this->GetCharWidth('.');
6576
		// get array of unicode values
6577
		$chars = $this->UTF8StringToArray($s);
6578
		// get array of chars
6579
		$uchars = $this->UTF8ArrayToUniArray($chars);
6580
		// get the number of characters
6581
		$nb = count($chars);
6582
		// replacement for SHY character (minus symbol)
6583
		$shy_replacement = 45;
6584
		$shy_replacement_char = $this->unichr($shy_replacement);
6585
		// widht for SHY replacement
6586
		$shy_replacement_width = $this->GetCharWidth($shy_replacement);
6587
		// max Y
6588
		$maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
6589
		// calculate remaining line width ($w)
6590
		if ($this->rtl) {
6591
			$w = $this->x - $this->lMargin;
6592
		} else {
6593
			$w = $this->w - $this->rMargin - $this->x;
6594
		}
6595
		// max column width
6596
		$wmax = $w - $wadj;
6597
		if (!$firstline) {
6598
			$wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6599
		}
6600
		if ((!$firstline) AND (($chrwidth > $wmax) OR ($this->GetCharWidth($chars[0]) > $wmax))) {
6601
			// a single character do not fit on column
6602
			return '';
6603
		}
6604
		// minimum row height
6605
		$row_height = max($h, $this->FontSize * $this->cell_height_ratio);
6606
		$start_page = $this->page;
6607
		$i = 0; // character position
6608
		$j = 0; // current starting position
6609
		$sep = -1; // position of the last blank space
6610
		$shy = false; // true if the last blank is a soft hypen (SHY)
6611
		$l = 0; // current string length
6612
		$nl = 0; //number of lines
6613
		$linebreak = false;
6614
		$pc = 0; // previous character
6615
		// for each character
6616
		while ($i < $nb) {
6617
			if (($maxh > 0) AND ($this->y >= $maxy) ) {
6618
				break;
6619
			}
6620
			//Get the current character
6621
			$c = $chars[$i];
6622
			if ($c == 10) { // 10 = "\n" = new line
6623
				//Explicit line break
6624
				if ($align == 'J') {
6625
					if ($this->rtl) {
6626
						$talign = 'R';
6627
					} else {
6628
						$talign = 'L';
6629
					}
6630
				} else {
6631
					$talign = $align;
6632
				}
6633
				$tmpstr = $this->UniArrSubString($uchars, $j, $i);
6634
				if ($firstline) {
6635
					$startx = $this->x;
6636
					$tmparr = array_slice($chars, $j, ($i - $j));
6637
					if ($rtlmode) {
6638
						$tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6639
					}
6640
					$linew = $this->GetArrStringWidth($tmparr);
6641
					unset($tmparr);
6642
					if ($this->rtl) {
6643
						$this->endlinex = $startx - $linew;
6644
					} else {
6645
						$this->endlinex = $startx + $linew;
6646
					}
6647
					$w = $linew;
6648
					$tmpcellpadding = $this->cell_padding;
6649
					if ($maxh == 0) {
6650
						$this->SetCellPadding(0);
6651
					}
6652
				}
6653
				if ($firstblock AND $this->isRTLTextDir()) {
6654
					$tmpstr = $this->stringRightTrim($tmpstr);
6655
				}
6656
				// Skip newlines at the begining of a page or column
6657
				if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6658
					$this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6659
				}
6660
				unset($tmpstr);
6661
				if ($firstline) {
6662
					$this->cell_padding = $tmpcellpadding;
6663
					return ($this->UniArrSubString($uchars, $i));
6664
				}
6665
				++$nl;
6666
				$j = $i + 1;
6667
				$l = 0;
6668
				$sep = -1;
6669
				$shy = false;
6670
				// account for margin changes
6671
				if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
6672
					$this->AcceptPageBreak();
6673
					if ($this->rtl) {
6674
						$this->x -= $margin['R'];
6675
					} else {
6676
						$this->x += $margin['L'];
6677
					}
6678
					$this->lMargin += $margin['L'];
6679
					$this->rMargin += $margin['R'];
6680
				}
6681
				$w = $this->getRemainingWidth();
6682
				$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6683
			} else {
6684
				// 160 is the non-breaking space.
6685
				// 173 is SHY (Soft Hypen).
6686
				// \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6687
				// \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6688
				// \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6689
				if (($c != 160) AND (($c == 173) OR preg_match($this->re_spaces, $this->unichr($c)))) {
6690
					// update last blank space position
6691
					$sep = $i;
6692
					// check if is a SHY
6693
					if ($c == 173) {
6694
						$shy = true;
6695
						if ($pc == 45) {
6696
							$tmp_shy_replacement_width = 0;
6697
							$tmp_shy_replacement_char = '';
6698
						} else {
6699
							$tmp_shy_replacement_width = $shy_replacement_width;
6700
							$tmp_shy_replacement_char = $shy_replacement_char;
6701
						}
6702
					} else {
6703
						$shy = false;
6704
					}
6705
				}
6706
				// update string length
6707
				if ($this->isUnicodeFont() AND ($arabic)) {
6708
					// with bidirectional algorithm some chars may be changed affecting the line length
6709
					// *** very slow ***
6710
					$l = $this->GetArrStringWidth($this->utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl));
6711
				} else {
6712
					$l += $this->GetCharWidth($c);
6713
				}
6714
				if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
6715
					// we have reached the end of column
6716
					if ($sep == -1) {
6717
						// check if the line was already started
6718
						if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $chrwidth)))
6719
							OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $chrwidth)))) {
6720
							// print a void cell and go to next line
6721
							$this->Cell($w, $h, '', 0, 1);
6722
							$linebreak = true;
6723
							if ($firstline) {
6724
								return ($this->UniArrSubString($uchars, $j));
6725
							}
6726
						} else {
6727
							// truncate the word because do not fit on column
6728
							$tmpstr = $this->UniArrSubString($uchars, $j, $i);
6729
							if ($firstline) {
6730
								$startx = $this->x;
6731
								$tmparr = array_slice($chars, $j, ($i - $j));
6732
								if ($rtlmode) {
6733
									$tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6734
								}
6735
								$linew = $this->GetArrStringWidth($tmparr);
6736
								unset($tmparr);
6737
								if ($this->rtl) {
6738
									$this->endlinex = $startx - $linew;
6739
								} else {
6740
									$this->endlinex = $startx + $linew;
6741
								}
6742
								$w = $linew;
6743
								$tmpcellpadding = $this->cell_padding;
6744
								if ($maxh == 0) {
6745
									$this->SetCellPadding(0);
6746
								}
6747
							}
6748
							if ($firstblock AND $this->isRTLTextDir()) {
6749
								$tmpstr = $this->stringRightTrim($tmpstr);
6750
							}
6751
							$this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6752
							unset($tmpstr);
6753
							if ($firstline) {
6754
								$this->cell_padding = $tmpcellpadding;
6755
								return ($this->UniArrSubString($uchars, $i));
6756
							}
6757
							$j = $i;
6758
							--$i;
6759
						}
6760
					} else {
6761
						// word wrapping
6762
						if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6763
							$endspace = 1;
6764
						} else {
6765
							$endspace = 0;
6766
						}
6767
						if ($shy) {
6768
							// add hypen (minus symbol) at the end of the line
6769
							$shy_width = $tmp_shy_replacement_width;
6770
							if ($this->rtl) {
6771
								$shy_char_left = $tmp_shy_replacement_char;
6772
								$shy_char_right = '';
6773
							} else {
6774
								$shy_char_left = '';
6775
								$shy_char_right = $tmp_shy_replacement_char;
6776
							}
6777
						} else {
6778
							$shy_width = 0;
6779
							$shy_char_left = '';
6780
							$shy_char_right = '';
6781
						}
6782
						$tmpstr = $this->UniArrSubString($uchars, $j, ($sep + $endspace));
6783
						if ($firstline) {
6784
							$startx = $this->x;
6785
							$tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6786
							if ($rtlmode) {
6787
								$tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6788
							}
6789
							$linew = $this->GetArrStringWidth($tmparr);
6790
							unset($tmparr);
6791
							if ($this->rtl) {
6792
								$this->endlinex = $startx - $linew - $shy_width;
6793
							} else {
6794
								$this->endlinex = $startx + $linew + $shy_width;
6795
							}
6796
							$w = $linew;
6797
							$tmpcellpadding = $this->cell_padding;
6798
							if ($maxh == 0) {
6799
								$this->SetCellPadding(0);
6800
							}
6801
						}
6802
						// print the line
6803
						if ($firstblock AND $this->isRTLTextDir()) {
6804
							$tmpstr = $this->stringRightTrim($tmpstr);
6805
						}
6806
						$this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6807
						unset($tmpstr);
6808
						if ($firstline) {
6809
							// return the remaining text
6810
							$this->cell_padding = $tmpcellpadding;
6811
							return ($this->UniArrSubString($uchars, ($sep + $endspace)));
6812
						}
6813
						$i = $sep;
6814
						$sep = -1;
6815
						$shy = false;
6816
						$j = ($i+1);
6817
					}
6818
					// account for margin changes
6819
					if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
6820
						$this->AcceptPageBreak();
6821
						if ($this->rtl) {
6822
							$this->x -= $margin['R'];
6823
						} else {
6824
							$this->x += $margin['L'];
6825
						}
6826
						$this->lMargin += $margin['L'];
6827
						$this->rMargin += $margin['R'];
6828
					}
6829
					$w = $this->getRemainingWidth();
6830
					$wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6831
					if ($linebreak) {
6832
						$linebreak = false;
6833
					} else {
6834
						++$nl;
6835
						$l = 0;
6836
					}
6837
				}
6838
			}
6839
			// save last character
6840
			$pc = $c;
6841
			++$i;
6842
		} // end while i < nb
6843
		// print last substring (if any)
6844
		if ($l > 0) {
6845
			switch ($align) {
6846
				case 'J':
6847
				case 'C': {
6848
					$w = $w;
6849
					break;
6850
				}
6851
				case 'L': {
6852
					if ($this->rtl) {
6853
						$w = $w;
6854
					} else {
6855
						$w = $l;
6856
					}
6857
					break;
6858
				}
6859
				case 'R': {
6860
					if ($this->rtl) {
6861
						$w = $l;
6862
					} else {
6863
						$w = $w;
6864
					}
6865
					break;
6866
				}
6867
				default: {
6868
					$w = $l;
6869
					break;
6870
				}
6871
			}
6872
			$tmpstr = $this->UniArrSubString($uchars, $j, $nb);
6873
			if ($firstline) {
6874
				$startx = $this->x;
6875
				$tmparr = array_slice($chars, $j, ($nb - $j));
6876
				if ($rtlmode) {
6877
					$tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6878
				}
6879
				$linew = $this->GetArrStringWidth($tmparr);
6880
				unset($tmparr);
6881
				if ($this->rtl) {
6882
					$this->endlinex = $startx - $linew;
6883
				} else {
6884
					$this->endlinex = $startx + $linew;
6885
				}
6886
				$w = $linew;
6887
				$tmpcellpadding = $this->cell_padding;
6888
				if ($maxh == 0) {
6889
					$this->SetCellPadding(0);
6890
				}
6891
			}
6892
			if ($firstblock AND $this->isRTLTextDir()) {
6893
				$tmpstr = $this->stringRightTrim($tmpstr);
6894
			}
6895
			$this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6896
			unset($tmpstr);
6897
			if ($firstline) {
6898
				$this->cell_padding = $tmpcellpadding;
6899
				return ($this->UniArrSubString($uchars, $nb));
6900
			}
6901
			++$nl;
6902
		}
6903
		if ($firstline) {
6904
			return '';
6905
		}
6906
		return $nl;
6907
	}
6908
 
6909
	/**
6910
	 * Returns the remaining width between the current position and margins.
6911
	 * @return int Return the remaining width
6912
	 * @access protected
6913
	 */
6914
	protected function getRemainingWidth() {
6915
		$this->checkPageRegions(0, $this->x, $this->y);
6916
		if ($this->rtl) {
6917
			return ($this->x - $this->lMargin);
6918
		} else {
6919
			return ($this->w - $this->rMargin - $this->x);
6920
		}
6921
	}
6922
 
6923
	/**
6924
	 * Extract a slice of the $strarr array and return it as string.
6925
	 * @param string $strarr The input array of characters.
6926
	 * @param int $start the starting element of $strarr.
6927
	 * @param int $end first element that will not be returned.
6928
	 * @return Return part of a string
6929
	 * @access public
6930
	 */
6931
	public function UTF8ArrSubString($strarr, $start='', $end='') {
6932
		if (strlen($start) == 0) {
6933
			$start = 0;
6934
		}
6935
		if (strlen($end) == 0) {
6936
			$end = count($strarr);
6937
		}
6938
		$string = '';
6939
		for ($i=$start; $i < $end; ++$i) {
6940
			$string .= $this->unichr($strarr[$i]);
6941
		}
6942
		return $string;
6943
	}
6944
 
6945
	/**
6946
	 * Extract a slice of the $uniarr array and return it as string.
6947
	 * @param string $uniarr The input array of characters.
6948
	 * @param int $start the starting element of $strarr.
6949
	 * @param int $end first element that will not be returned.
6950
	 * @return Return part of a string
6951
	 * @access public
6952
	 * @since 4.5.037 (2009-04-07)
6953
	 */
6954
	public function UniArrSubString($uniarr, $start='', $end='') {
6955
		if (strlen($start) == 0) {
6956
			$start = 0;
6957
		}
6958
		if (strlen($end) == 0) {
6959
			$end = count($uniarr);
6960
		}
6961
		$string = '';
6962
		for ($i=$start; $i < $end; ++$i) {
6963
			$string .= $uniarr[$i];
6964
		}
6965
		return $string;
6966
	}
6967
 
6968
	/**
6969
	 * Convert an array of UTF8 values to array of unicode characters
6970
	 * @param string $ta The input array of UTF8 values.
6971
	 * @return Return array of unicode characters
6972
	 * @access public
6973
	 * @since 4.5.037 (2009-04-07)
6974
	 */
6975
	public function UTF8ArrayToUniArray($ta) {
6976
		return array_map(array($this, 'unichr'), $ta);
6977
	}
6978
 
6979
	/**
6980
	 * Returns the unicode caracter specified by UTF-8 value
6981
	 * @param int $c UTF-8 value
6982
	 * @return Returns the specified character.
6983
	 * @author Miguel Perez, Nicola Asuni
6984
	 * @access public
6985
	 * @since 2.3.000 (2008-03-05)
6986
	 */
6987
	public function unichr($c) {
6988
		if (!$this->isunicode) {
6989
			return chr($c);
6990
		} elseif ($c <= 0x7F) {
6991
			// one byte
6992
			return chr($c);
6993
		} elseif ($c <= 0x7FF) {
6994
			// two bytes
6995
			return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
6996
		} elseif ($c <= 0xFFFF) {
6997
			// three bytes
6998
			return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
6999
		} elseif ($c <= 0x10FFFF) {
7000
			// four bytes
7001
			return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
7002
		} else {
7003
			return '';
7004
		}
7005
	}
7006
 
7007
	/**
7008
	 * Return the image type given the file name or array returned by getimagesize() function.
7009
	 * @param string $imgfile image file name
7010
	 * @param array $iminfo array of image information returned by getimagesize() function.
7011
	 * @return string image type
7012
	 * @since 4.8.017 (2009-11-27)
7013
	 */
7014
	public function getImageFileType($imgfile, $iminfo=array()) {
7015
		$type = '';
7016
		if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
7017
			$mime = explode('/', $iminfo['mime']);
7018
			if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
7019
				$type = strtolower(trim($mime[1]));
7020
			}
7021
		}
7022
		if (empty($type)) {
7023
			$fileinfo = pathinfo($imgfile);
7024
			if (isset($fileinfo['extension']) AND (!$this->empty_string($fileinfo['extension']))) {
7025
				$type = strtolower(trim($fileinfo['extension']));
7026
			}
7027
		}
7028
		if ($type == 'jpg') {
7029
			$type = 'jpeg';
7030
		}
7031
		return $type;
7032
	}
7033
 
7034
	/**
7035
	 * Set the block dimensions accounting for page breaks and page/column fitting
7036
	 * @param float $w width
7037
	 * @param float $h height
7038
	 * @param float $x X coordinate
7039
	 * @param float $y Y coodiante
7040
	 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
7041
	 * @access protected
7042
	 * @since 5.5.009 (2010-07-05)
7043
	 */
7044
	protected function fitBlock(&$w, &$h, &$x, &$y, $fitonpage=false) {
7045
		if ($w <= 0) {
7046
			// set maximum width
7047
			$w = ($this->w - $this->lMargin - $this->rMargin);
7048
		}
7049
		if ($h <= 0) {
7050
			// set maximum height
7051
			$h = ($this->PageBreakTrigger - $this->tMargin);
7052
		}
7053
		// resize the block to be vertically contained on a single page or single column
7054
		if ($fitonpage OR $this->AutoPageBreak) {
7055
			$ratio_wh = ($w / $h);
7056
			if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
7057
				$h = $this->PageBreakTrigger - $this->tMargin;
7058
				$w = ($h * $ratio_wh);
7059
			}
7060
			// resize the block to be horizontally contained on a single page or single column
7061
			if ($fitonpage) {
7062
				$maxw = ($this->w - $this->lMargin - $this->rMargin);
7063
				if ($w > $maxw) {
7064
					$w = $maxw;
7065
					$h = ($w / $ratio_wh);
7066
				}
7067
			}
7068
		}
7069
		// Check whether we need a new page or new column first as this does not fit
7070
		$prev_x = $this->x;
7071
		$prev_y = $this->y;
7072
		if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
7073
			$y = $this->y;
7074
			if ($this->rtl) {
7075
				$x += ($prev_x - $this->x);
7076
			} else {
7077
				$x += ($this->x - $prev_x);
7078
			}
7079
		}
7080
		// resize the block to be contained on the remaining available page or column space
7081
		if ($fitonpage) {
7082
			$ratio_wh = ($w / $h);
7083
			if (($y + $h) > $this->PageBreakTrigger) {
7084
				$h = $this->PageBreakTrigger - $y;
7085
				$w = ($h * $ratio_wh);
7086
			}
7087
			if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
7088
				$w = $this->w - $this->rMargin - $x;
7089
				$h = ($w / $ratio_wh);
7090
			} elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
7091
				$w = $x - $this->lMargin;
7092
				$h = ($w / $ratio_wh);
7093
			}
7094
		}
7095
	}
7096
 
7097
	/**
7098
	 * Puts an image in the page.
7099
	 * The upper-left corner must be given.
7100
	 * The dimensions can be specified in different ways:<ul>
7101
	 * <li>explicit width and height (expressed in user unit)</li>
7102
	 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
7103
	 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
7104
	 * 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;
7105
	 * The format can be specified explicitly or inferred from the file extension.<br />
7106
	 * It is possible to put a link on the image.<br />
7107
	 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
7108
	 * @param string $file Name of the file containing the image or a '@' character followed by the image data string.
7109
	 * @param float $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
7110
	 * @param float $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
7111
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7112
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7113
	 * @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.
7114
	 * @param mixed $link URL or identifier returned by AddLink().
7115
	 * @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>
7116
	 * @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).
7117
	 * @param int $dpi dot-per-inch resolution used on resize
7118
	 * @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>
7119
	 * @param boolean $ismask true if this image is a mask, false otherwise
7120
	 * @param mixed $imgmask image object returned by this function or false
7121
	 * @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)))
7122
	 * @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).
7123
	 * @param boolean $hidden if true do not display the image.
7124
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
7125
	 * @return image information
7126
	 * @access public
7127
	 * @since 1.1
7128
	 */
7129
	public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false) {
7130
		if ($x === '') {
7131
			$x = $this->x;
7132
		}
7133
		if ($y === '') {
7134
			$y = $this->y;
7135
		}
7136
		// check page for no-write regions and adapt page margins if necessary
7137
		$this->checkPageRegions($h, $x, $y);
7138
		$cached_file = false; // true when the file is cached
7139
		// check if we are passing an image as file or string
7140
		if ($file{0} === '@') { // image from string
7141
			$imgdata = substr($file, 1);
7142
			$file = tempnam(K_PATH_CACHE, 'img_');
7143
			$fp = fopen($file, 'w');
7144
			fwrite($fp, $imgdata);
7145
			fclose($fp);
7146
			unset($imgdata);
7147
			$cached_file = true;
7148
			$imsize = @getimagesize($file);
7149
			if ($imsize === FALSE) {
7150
				unlink($file);
7151
				$cached_file = false;
7152
			}
7153
		} else { // image file
7154
			// check if is local file
7155
			if (!@file_exists($file)) {
7156
				// encode spaces on filename (file is probably an URL)
7157
				$file = str_replace(' ', '%20', $file);
7158
			}
7159
			// get image dimensions
7160
			$imsize = @getimagesize($file);
7161
			if ($imsize === FALSE) {
7162
				if (function_exists('curl_init')) {
7163
					// try to get remote file data using cURL
7164
					$cs = curl_init(); // curl session
7165
					curl_setopt($cs, CURLOPT_URL, $file);
7166
					curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
7167
					curl_setopt($cs, CURLOPT_FAILONERROR, true);
7168
					curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
7169
					curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
7170
					curl_setopt($cs, CURLOPT_TIMEOUT, 30);
7171
					$imgdata = curl_exec($cs);
7172
					curl_close($cs);
7173
					if($imgdata !== FALSE) {
7174
						// copy image to cache
7175
						$file = tempnam(K_PATH_CACHE, 'img_');
7176
						$fp = fopen($file, 'w');
7177
						fwrite($fp, $imgdata);
7178
						fclose($fp);
7179
						unset($imgdata);
7180
						$cached_file = true;
7181
						$imsize = @getimagesize($file);
7182
						if ($imsize === FALSE) {
7183
							unlink($file);
7184
							$cached_file = false;
7185
						}
7186
					}
7187
				} elseif (($w > 0) AND ($h > 0)) {
7188
					// get measures from specified data
7189
					$pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7190
					$ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7191
					$imsize = array($pw, $ph);
7192
				}
7193
			}
7194
		}
7195
		if ($imsize === FALSE) {
7196
			$this->Error('[Image] Unable to get image: '.$file);
7197
		}
7198
		// get original image width and height in pixels
7199
		list($pixw, $pixh) = $imsize;
7200
		// calculate image width and height on document
7201
		if (($w <= 0) AND ($h <= 0)) {
7202
			// convert image size to document unit
7203
			$w = $this->pixelsToUnits($pixw);
7204
			$h = $this->pixelsToUnits($pixh);
7205
		} elseif ($w <= 0) {
7206
			$w = $h * $pixw / $pixh;
7207
		} elseif ($h <= 0) {
7208
			$h = $w * $pixh / $pixw;
7209
		} elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7210
			if (strlen($fitbox) !== 2) {
7211
				// set default alignment
7212
				$fitbox = '--';
7213
			}
7214
			// scale image dimensions proportionally to fit within the ($w, $h) box
7215
			if ((($w * $pixh) / ($h * $pixw)) < 1) {
7216
				// store current height
7217
				$oldh = $h;
7218
				// calculate new height
7219
				$h = $w * $pixh / $pixw;
7220
				// height difference
7221
				$hdiff = ($oldh - $h);
7222
				// vertical alignment
7223
				switch (strtoupper($fitbox{1})) {
7224
					case 'T': {
7225
						break;
7226
					}
7227
					case 'M': {
7228
						$y += ($hdiff / 2);
7229
						break;
7230
					}
7231
					case 'B': {
7232
						$y += $hdiff;
7233
						break;
7234
					}
7235
				}
7236
			} else {
7237
				// store current width
7238
				$oldw = $w;
7239
				// calculate new width
7240
				$w = $h * $pixw / $pixh;
7241
				// width difference
7242
				$wdiff = ($oldw - $w);
7243
				// horizontal alignment
7244
				switch (strtoupper($fitbox{0})) {
7245
					case 'L': {
7246
						if ($this->rtl) {
7247
							$x -= $wdiff;
7248
						}
7249
						break;
7250
					}
7251
					case 'C': {
7252
						if ($this->rtl) {
7253
							$x -= ($wdiff / 2);
7254
						} else {
7255
							$x += ($wdiff / 2);
7256
						}
7257
						break;
7258
					}
7259
					case 'R': {
7260
						if (!$this->rtl) {
7261
							$x += $wdiff;
7262
						}
7263
						break;
7264
					}
7265
				}
7266
			}
7267
		}
7268
		// fit the image on available space
7269
		$this->fitBlock($w, $h, $x, $y, $fitonpage);
7270
		// calculate new minimum dimensions in pixels
7271
		$neww = round($w * $this->k * $dpi / $this->dpi);
7272
		$newh = round($h * $this->k * $dpi / $this->dpi);
7273
		// check if resize is necessary (resize is used only to reduce the image)
7274
		$newsize = ($neww * $newh);
7275
		$pixsize = ($pixw * $pixh);
7276
		if (intval($resize) == 2) {
7277
			$resize = true;
7278
		} elseif ($newsize >= $pixsize) {
7279
			$resize = false;
7280
		}
7281
		// check if image has been already added on document
7282
		$newimage = true;
7283
		if (in_array($file, $this->imagekeys)) {
7284
			$newimage = false;
7285
			// get existing image data
7286
			$info = $this->getImageBuffer($file);
7287
			// check if the newer image is larger
7288
			$oldsize = ($info['w'] * $info['h']);
7289
			if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7290
				$newimage = true;
7291
			}
7292
		}
7293
		if ($newimage) {
7294
			//First use of image, get info
7295
			$type = strtolower($type);
7296
			if ($type == '') {
7297
				$type = $this->getImageFileType($file, $imsize);
7298
			} elseif ($type == 'jpg') {
7299
				$type = 'jpeg';
7300
			}
7301
			$mqr = $this->get_mqr();
7302
			$this->set_mqr(false);
7303
			// Specific image handlers
7304
			$mtd = '_parse'.$type;
7305
			// GD image handler function
7306
			$gdfunction = 'imagecreatefrom'.$type;
7307
			$info = false;
7308
			if ((method_exists($this, $mtd)) AND (!($resize AND function_exists($gdfunction)))) {
7309
				// TCPDF image functions
7310
				$info = $this->$mtd($file);
7311
				if ($info == 'pngalpha') {
7312
					return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign);
7313
				}
7314
			}
7315
			if (!$info) {
7316
				if (function_exists($gdfunction)) {
7317
					// GD library
7318
					$img = $gdfunction($file);
7319
					if ($resize) {
7320
						$imgr = imagecreatetruecolor($neww, $newh);
7321
						if (($type == 'gif') OR ($type == 'png')) {
7322
							$imgr = $this->_setGDImageTransparency($imgr, $img);
7323
						}
7324
						imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7325
						if (($type == 'gif') OR ($type == 'png')) {
7326
							$info = $this->_toPNG($imgr);
7327
						} else {
7328
							$info = $this->_toJPEG($imgr);
7329
						}
7330
					} else {
7331
						if (($type == 'gif') OR ($type == 'png')) {
7332
							$info = $this->_toPNG($img);
7333
						} else {
7334
							$info = $this->_toJPEG($img);
7335
						}
7336
					}
7337
				} elseif (extension_loaded('imagick')) {
7338
					// ImageMagick library
7339
					$img = new Imagick();
7340
					if ($type == 'SVG') {
7341
						// get SVG file content
7342
						$svgimg = file_get_contents($file);
7343
						// get width and height
7344
						$regs = array();
7345
						if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7346
							$svgtag = $regs[1];
7347
							$tmp = array();
7348
							if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7349
								$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7350
								$owu = sprintf('%.3F', ($ow * $dpi / 72)).$this->pdfunit;
7351
								$svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7352
							} else {
7353
								$ow = $w;
7354
							}
7355
							$tmp = array();
7356
							if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7357
								$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7358
								$ohu = sprintf('%.3F', ($oh * $dpi / 72)).$this->pdfunit;
7359
								$svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7360
							} else {
7361
								$oh = $h;
7362
							}
7363
							$tmp = array();
7364
							if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7365
								$vbw = ($ow * $this->imgscale * $this->k);
7366
								$vbh = ($oh * $this->imgscale * $this->k);
7367
								$vbox = sprintf(' viewBox="0 0 %.3F %.3F" ', $vbw, $vbh);
7368
								$svgtag = $vbox.$svgtag;
7369
							}
7370
							$svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7371
						}
7372
						$img->readImageBlob($svgimg);
7373
					} else {
7374
						$img->readImage($file);
7375
					}
7376
					if ($resize) {
7377
						$img->resizeImage($neww, $newh, 10, 1, false);
7378
					}
7379
					$img->setCompressionQuality($this->jpeg_quality);
7380
					$img->setImageFormat('jpeg');
7381
					$tempname = tempnam(K_PATH_CACHE, 'jpg_');
7382
					$img->writeImage($tempname);
7383
					$info = $this->_parsejpeg($tempname);
7384
					unlink($tempname);
7385
					$img->destroy();
7386
				} else {
7387
					return;
7388
				}
7389
			}
7390
			if ($info === false) {
7391
				//If false, we cannot process image
7392
				return;
7393
			}
7394
			$this->set_mqr($mqr);
7395
			if ($ismask) {
7396
				// force grayscale
7397
				$info['cs'] = 'DeviceGray';
7398
			}
7399
			$info['i'] = $this->numimages;
7400
			if (!in_array($file, $this->imagekeys)) {
7401
				++$info['i'];
7402
			}
7403
			if ($imgmask !== false) {
7404
				$info['masked'] = $imgmask;
7405
			}
7406
			// add image to document
7407
			$this->setImageBuffer($file, $info);
7408
		}
7409
		if ($cached_file) {
7410
			// remove cached file
7411
			unlink($file);
7412
		}
7413
		// set alignment
7414
		$this->img_rb_y = $y + $h;
7415
		// set alignment
7416
		if ($this->rtl) {
7417
			if ($palign == 'L') {
7418
				$ximg = $this->lMargin;
7419
			} elseif ($palign == 'C') {
7420
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7421
			} elseif ($palign == 'R') {
7422
				$ximg = $this->w - $this->rMargin - $w;
7423
			} else {
7424
				$ximg = $x - $w;
7425
			}
7426
			$this->img_rb_x = $ximg;
7427
		} else {
7428
			if ($palign == 'L') {
7429
				$ximg = $this->lMargin;
7430
			} elseif ($palign == 'C') {
7431
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7432
			} elseif ($palign == 'R') {
7433
				$ximg = $this->w - $this->rMargin - $w;
7434
			} else {
7435
				$ximg = $x;
7436
			}
7437
			$this->img_rb_x = $ximg + $w;
7438
		}
7439
		if ($ismask OR $hidden) {
7440
			// image is not displayed
7441
			return $info['i'];
7442
		}
7443
		$xkimg = $ximg * $this->k;
7444
		$this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
7445
		if (!empty($border)) {
7446
			$bx = $this->x;
7447
			$by = $this->y;
7448
			$this->x = $ximg;
7449
			if ($this->rtl) {
7450
				$this->x += $w;
7451
			}
7452
			$this->y = $y;
7453
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7454
			$this->x = $bx;
7455
			$this->y = $by;
7456
		}
7457
		if ($link) {
7458
			$this->Link($ximg, $y, $w, $h, $link, 0);
7459
		}
7460
		// set pointer to align the next text/objects
7461
		switch($align) {
7462
			case 'T': {
7463
				$this->y = $y;
7464
				$this->x = $this->img_rb_x;
7465
				break;
7466
			}
7467
			case 'M': {
7468
				$this->y = $y + round($h/2);
7469
				$this->x = $this->img_rb_x;
7470
				break;
7471
			}
7472
			case 'B': {
7473
				$this->y = $this->img_rb_y;
7474
				$this->x = $this->img_rb_x;
7475
				break;
7476
			}
7477
			case 'N': {
7478
				$this->SetY($this->img_rb_y);
7479
				break;
7480
			}
7481
			default:{
7482
				break;
7483
			}
7484
		}
7485
		$this->endlinex = $this->img_rb_x;
7486
		if ($this->inxobj) {
7487
			// we are inside an XObject template
7488
			$this->xobjects[$this->xobjid]['images'][] = $info['i'];
7489
		}
7490
		return $info['i'];
7491
	}
7492
 
7493
	/**
7494
	 * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist)
7495
	 * @param boolean $mqr FALSE for off, TRUE for on.
7496
	 * @since 4.6.025 (2009-08-17)
7497
	 */
7498
	public function set_mqr($mqr) {
7499
		if(!defined('PHP_VERSION_ID')) {
7500
			$version = PHP_VERSION;
7501
			define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
7502
		}
7503
		if (PHP_VERSION_ID < 50300) {
7504
			@set_magic_quotes_runtime($mqr);
7505
		}
7506
	}
7507
 
7508
	/**
7509
	 * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist)
7510
	 * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise.
7511
	 * @since 4.6.025 (2009-08-17)
7512
	 */
7513
	public function get_mqr() {
7514
		if(!defined('PHP_VERSION_ID')) {
7515
			$version = PHP_VERSION;
7516
			define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
7517
		}
7518
		if (PHP_VERSION_ID < 50300) {
7519
			return @get_magic_quotes_runtime();
7520
		}
7521
		return 0;
7522
	}
7523
 
7524
	/**
7525
	 * Convert the loaded image to a JPEG and then return a structure for the PDF creator.
7526
	 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
7527
	 * @param string $file Image file name.
7528
	 * @param image $image Image object.
7529
	 * return image JPEG image object.
7530
	 * @access protected
7531
	 */
7532
	protected function _toJPEG($image) {
7533
		$tempname = tempnam(K_PATH_CACHE, 'jpg_');
7534
		imagejpeg($image, $tempname, $this->jpeg_quality);
7535
		imagedestroy($image);
7536
		$retvars = $this->_parsejpeg($tempname);
7537
		// tidy up by removing temporary image
7538
		unlink($tempname);
7539
		return $retvars;
7540
	}
7541
 
7542
	/**
7543
	 * Convert the loaded image to a PNG and then return a structure for the PDF creator.
7544
	 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
7545
	 * @param string $file Image file name.
7546
	 * @param image $image Image object.
7547
	 * return image PNG image object.
7548
	 * @access protected
7549
	 * @since 4.9.016 (2010-04-20)
7550
	 */
7551
	protected function _toPNG($image) {
7552
		// set temporary image file name
7553
		$tempname = tempnam(K_PATH_CACHE, 'jpg_');
7554
		// turn off interlaced mode
7555
		imageinterlace($image, 0);
7556
		// create temporary PNG image
7557
		imagepng($image, $tempname);
7558
		// remove image from memory
7559
		imagedestroy($image);
7560
		// get PNG image data
7561
		$retvars = $this->_parsepng($tempname);
7562
		// tidy up by removing temporary image
7563
		unlink($tempname);
7564
		return $retvars;
7565
	}
7566
 
7567
	/**
7568
	 * Set the transparency for the given GD image.
7569
	 * @param image $new_image GD image object
7570
	 * @param image $image GD image object.
7571
	 * return GD image object.
7572
	 * @access protected
7573
	 * @since 4.9.016 (2010-04-20)
7574
	 */
7575
	protected function _setGDImageTransparency($new_image, $image) {
7576
		// transparency index
7577
		$tid = imagecolortransparent($image);
7578
		// default transparency color
7579
		$tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
7580
		if ($tid >= 0) {
7581
			// get the colors for the transparency index
7582
			$tcol = imagecolorsforindex($image, $tid);
7583
		}
7584
		$tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
7585
		imagefill($new_image, 0, 0, $tid);
7586
		imagecolortransparent($new_image, $tid);
7587
		return $new_image;
7588
	}
7589
 
7590
	/**
7591
	 * Extract info from a JPEG file without using the GD library.
7592
	 * @param string $file image file to parse
7593
	 * @return array structure containing the image data
7594
	 * @access protected
7595
	 */
7596
	protected function _parsejpeg($file) {
7597
		$a = getimagesize($file);
7598
		if (empty($a)) {
7599
			$this->Error('Missing or incorrect image file: '.$file);
7600
		}
7601
		if ($a[2] != 2) {
7602
			$this->Error('Not a JPEG file: '.$file);
7603
		}
7604
		if ((!isset($a['channels'])) OR ($a['channels'] == 3)) {
7605
			$colspace = 'DeviceRGB';
7606
		} elseif ($a['channels'] == 4) {
7607
			$colspace = 'DeviceCMYK';
7608
		} else {
7609
			$colspace = 'DeviceGray';
7610
		}
7611
		$bpc = isset($a['bits']) ? $a['bits'] : 8;
7612
		$data = file_get_contents($file);
7613
		return array('w' => $a[0], 'h' => $a[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
7614
	}
7615
 
7616
	/**
7617
	 * Extract info from a PNG file without using the GD library.
7618
	 * @param string $file image file to parse
7619
	 * @return array structure containing the image data
7620
	 * @access protected
7621
	 */
7622
	protected function _parsepng($file) {
7623
		$f = fopen($file, 'rb');
7624
		if ($f === false) {
7625
			$this->Error('Can\'t open image file: '.$file);
7626
		}
7627
		//Check signature
7628
		if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
7629
			$this->Error('Not a PNG file: '.$file);
7630
		}
7631
		//Read header chunk
7632
		fread($f, 4);
7633
		if (fread($f, 4) != 'IHDR') {
7634
			$this->Error('Incorrect PNG file: '.$file);
7635
		}
7636
		$w = $this->_freadint($f);
7637
		$h = $this->_freadint($f);
7638
		$bpc = ord(fread($f, 1));
7639
		if ($bpc > 8) {
7640
			//$this->Error('16-bit depth not supported: '.$file);
7641
			fclose($f);
7642
			return false;
7643
		}
7644
		$ct = ord(fread($f, 1));
7645
		if ($ct == 0) {
7646
			$colspace = 'DeviceGray';
7647
		} elseif ($ct == 2) {
7648
			$colspace = 'DeviceRGB';
7649
		} elseif ($ct == 3) {
7650
			$colspace = 'Indexed';
7651
		} else {
7652
			// alpha channel
7653
			fclose($f);
7654
			return 'pngalpha';
7655
		}
7656
		if (ord(fread($f, 1)) != 0) {
7657
			//$this->Error('Unknown compression method: '.$file);
7658
			fclose($f);
7659
			return false;
7660
		}
7661
		if (ord(fread($f, 1)) != 0) {
7662
			//$this->Error('Unknown filter method: '.$file);
7663
			fclose($f);
7664
			return false;
7665
		}
7666
		if (ord(fread($f, 1)) != 0) {
7667
			//$this->Error('Interlacing not supported: '.$file);
7668
			fclose($f);
7669
			return false;
7670
		}
7671
		fread($f, 4);
7672
		$parms = '/DecodeParms << /Predictor 15 /Colors '.($ct == 2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
7673
		//Scan chunks looking for palette, transparency and image data
7674
		$pal = '';
7675
		$trns = '';
7676
		$data = '';
7677
		do {
7678
			$n = $this->_freadint($f);
7679
			$type = fread($f, 4);
7680
			if ($type == 'PLTE') {
7681
				//Read palette
7682
				$pal = $this->rfread($f, $n);
7683
				fread($f, 4);
7684
			} elseif ($type == 'tRNS') {
7685
				//Read transparency info
7686
				$t = $this->rfread($f, $n);
7687
				if ($ct == 0) {
7688
					$trns = array(ord(substr($t, 1, 1)));
7689
				} elseif ($ct == 2) {
7690
					$trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1)));
7691
				} else {
7692
					$pos = strpos($t, chr(0));
7693
					if ($pos !== false) {
7694
						$trns = array($pos);
7695
					}
7696
				}
7697
				fread($f, 4);
7698
			} elseif ($type == 'IDAT') {
7699
				//Read image data block
7700
				$data .= $this->rfread($f, $n);
7701
				fread($f, 4);
7702
			} elseif ($type == 'IEND') {
7703
				break;
7704
			} else {
7705
				$this->rfread($f, $n + 4);
7706
			}
7707
		} while ($n);
7708
		if (($colspace == 'Indexed') AND (empty($pal))) {
7709
			//$this->Error('Missing palette in '.$file);
7710
			fclose($f);
7711
			return false;
7712
		}
7713
		fclose($f);
7714
		return array('w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
7715
	}
7716
 
7717
	/**
7718
	 * Binary-safe and URL-safe file read.
7719
	 * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
7720
	 * @param resource $handle
7721
	 * @param int $length
7722
	 * @return Returns the read string or FALSE in case of error.
7723
	 * @author Nicola Asuni
7724
	 * @access protected
7725
	 * @since 4.5.027 (2009-03-16)
7726
	 */
7727
	protected function rfread($handle, $length) {
7728
		$data = fread($handle, $length);
7729
		if ($data === false) {
7730
			return false;
7731
		}
7732
		$rest = $length - strlen($data);
7733
		if ($rest > 0) {
7734
			$data .= $this->rfread($handle, $rest);
7735
		}
7736
		return $data;
7737
	}
7738
 
7739
	/**
7740
	 * Extract info from a PNG image with alpha channel using the GD library.
7741
	 * @param string $file Name of the file containing the image.
7742
	 * @param float $x Abscissa of the upper-left corner.
7743
	 * @param float $y Ordinate of the upper-left corner.
7744
	 * @param float $wpx Original width of the image in pixels.
7745
	 * @param float $hpx original height of the image in pixels.
7746
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7747
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7748
	 * @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.
7749
	 * @param mixed $link URL or identifier returned by AddLink().
7750
	 * @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>
7751
	 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7752
	 * @param int $dpi dot-per-inch resolution used on resize
7753
	 * @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>
7754
	 * @author Nicola Asuni
7755
	 * @access protected
7756
	 * @since 4.3.007 (2008-12-04)
7757
	 * @see Image()
7758
	 */
7759
	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign) {
7760
		// create temp image file (without alpha channel)
7761
		$tempfile_plain = tempnam(K_PATH_CACHE, 'mskp_');
7762
		// create temp alpha file
7763
		$tempfile_alpha = tempnam(K_PATH_CACHE, 'mska_');
7764
		if (extension_loaded('imagick')) { // ImageMagick
7765
			// ImageMagick library
7766
			$img = new Imagick();
7767
			$img->readImage($file);
7768
			// clone image object
7769
			$imga = $img->clone();
7770
			// extract alpha channel
7771
			$img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7772
			$img->negateImage(true);
7773
			$img->setImageFormat('png');
7774
			$img->writeImage($tempfile_alpha);
7775
			// remove alpha channel
7776
			$imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7777
			$imga->setImageFormat('png');
7778
			$imga->writeImage($tempfile_plain);
7779
		} else { // GD library
7780
			// generate images
7781
			$img = imagecreatefrompng($file);
7782
			$imgalpha = imagecreate($wpx, $hpx);
7783
			// generate gray scale palette (0 -> 255)
7784
			for ($c = 0; $c < 256; ++$c) {
7785
				ImageColorAllocate($imgalpha, $c, $c, $c);
7786
			}
7787
			// extract alpha channel
7788
			for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7789
				for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7790
					$color = imagecolorat($img, $xpx, $ypx);
7791
					$alpha = ($color >> 24); // shifts off the first 24 bits (where 8x3 are used for each color), and returns the remaining 7 allocated bits (commonly used for alpha)
7792
					$alpha = (((127 - $alpha) / 127) * 255); // GD alpha is only 7 bit (0 -> 127)
7793
					$alpha = $this->getGDgamma($alpha); // correct gamma
7794
					imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7795
				}
7796
			}
7797
			imagepng($imgalpha, $tempfile_alpha);
7798
			imagedestroy($imgalpha);
7799
			// extract image without alpha channel
7800
			$imgplain = imagecreatetruecolor($wpx, $hpx);
7801
			imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7802
			imagepng($imgplain, $tempfile_plain);
7803
			imagedestroy($imgplain);
7804
		}
7805
		// embed mask image
7806
		$imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7807
		// embed image, masked with previously embedded mask
7808
		$this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7809
		// remove temp files
7810
		unlink($tempfile_alpha);
7811
		unlink($tempfile_plain);
7812
	}
7813
 
7814
	/**
7815
	 * Correct the gamma value to be used with GD library
7816
	 * @param float $v the gamma value to be corrected
7817
	 * @access protected
7818
	 * @since 4.3.007 (2008-12-04)
7819
	 */
7820
	protected function getGDgamma($v) {
7821
		return (pow(($v / 255), 2.2) * 255);
7822
	}
7823
 
7824
	/**
7825
	 * Performs a line break.
7826
	 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7827
	 * @param float $h The height of the break. By default, the value equals the height of the last printed cell.
7828
	 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7829
	 * @access public
7830
	 * @since 1.0
7831
	 * @see Cell()
7832
	 */
7833
	public function Ln($h='', $cell=false) {
7834
		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'])) {
7835
			// revove vertical space from the top of the column
7836
			return;
7837
		}
7838
		if ($cell) {
7839
			if ($this->rtl) {
7840
				$cellpadding = $this->cell_padding['R'];
7841
			} else {
7842
				$cellpadding = $this->cell_padding['L'];
7843
			}
7844
		} else {
7845
			$cellpadding = 0;
7846
		}
7847
		if ($this->rtl) {
7848
			$this->x = $this->w - $this->rMargin - $cellpadding;
7849
		} else {
7850
			$this->x = $this->lMargin + $cellpadding;
7851
		}
7852
		if (is_string($h)) {
7853
			$this->y += $this->lasth;
7854
		} else {
7855
			$this->y += $h;
7856
		}
7857
		$this->newline = true;
7858
	}
7859
 
7860
	/**
7861
	 * Returns the relative X value of current position.
7862
	 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7863
	 * @return float
7864
	 * @access public
7865
	 * @since 1.2
7866
	 * @see SetX(), GetY(), SetY()
7867
	 */
7868
	public function GetX() {
7869
		//Get x position
7870
		if ($this->rtl) {
7871
			return ($this->w - $this->x);
7872
		} else {
7873
			return $this->x;
7874
		}
7875
	}
7876
 
7877
	/**
7878
	 * Returns the absolute X value of current position.
7879
	 * @return float
7880
	 * @access public
7881
	 * @since 1.2
7882
	 * @see SetX(), GetY(), SetY()
7883
	 */
7884
	public function GetAbsX() {
7885
		return $this->x;
7886
	}
7887
 
7888
	/**
7889
	 * Returns the ordinate of the current position.
7890
	 * @return float
7891
	 * @access public
7892
	 * @since 1.0
7893
	 * @see SetY(), GetX(), SetX()
7894
	 */
7895
	public function GetY() {
7896
		return $this->y;
7897
	}
7898
 
7899
	/**
7900
	 * Defines the abscissa of the current position.
7901
	 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7902
	 * @param float $x The value of the abscissa.
7903
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7904
	 * @access public
7905
	 * @since 1.2
7906
	 * @see GetX(), GetY(), SetY(), SetXY()
7907
	 */
7908
	public function SetX($x, $rtloff=false) {
7909
		if (!$rtloff AND $this->rtl) {
7910
			if ($x >= 0) {
7911
				$this->x = $this->w - $x;
7912
			} else {
7913
				$this->x = abs($x);
7914
			}
7915
		} else {
7916
			if ($x >= 0) {
7917
				$this->x = $x;
7918
			} else {
7919
				$this->x = $this->w + $x;
7920
			}
7921
		}
7922
		if ($this->x < 0) {
7923
			$this->x = 0;
7924
		}
7925
		if ($this->x > $this->w) {
7926
			$this->x = $this->w;
7927
		}
7928
	}
7929
 
7930
	/**
7931
	 * Moves the current abscissa back to the left margin and sets the ordinate.
7932
	 * If the passed value is negative, it is relative to the bottom of the page.
7933
	 * @param float $y The value of the ordinate.
7934
	 * @param bool $resetx if true (default) reset the X position.
7935
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7936
	 * @access public
7937
	 * @since 1.0
7938
	 * @see GetX(), GetY(), SetY(), SetXY()
7939
	 */
7940
	public function SetY($y, $resetx=true, $rtloff=false) {
7941
		if ($resetx) {
7942
			//reset x
7943
			if (!$rtloff AND $this->rtl) {
7944
				$this->x = $this->w - $this->rMargin;
7945
			} else {
7946
				$this->x = $this->lMargin;
7947
			}
7948
		}
7949
		if ($y >= 0) {
7950
			$this->y = $y;
7951
		} else {
7952
			$this->y = $this->h + $y;
7953
		}
7954
		if ($this->y < 0) {
7955
			$this->y = 0;
7956
		}
7957
		if ($this->y > $this->h) {
7958
			$this->y = $this->h;
7959
		}
7960
	}
7961
 
7962
	/**
7963
	 * Defines the abscissa and ordinate of the current position.
7964
	 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7965
	 * @param float $x The value of the abscissa.
7966
	 * @param float $y The value of the ordinate.
7967
	 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7968
	 * @access public
7969
	 * @since 1.2
7970
	 * @see SetX(), SetY()
7971
	 */
7972
	public function SetXY($x, $y, $rtloff=false) {
7973
		$this->SetY($y, false, $rtloff);
7974
		$this->SetX($x, $rtloff);
7975
	}
7976
 
7977
	/**
7978
	 * Send the document to a given destination: string, local file or browser.
7979
	 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7980
	 * The method first calls Close() if necessary to terminate the document.
7981
	 * @param string $name The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
7982
	 * @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>
7983
	 * @access public
7984
	 * @since 1.0
7985
	 * @see Close()
7986
	 */
7987
	public function Output($name='doc.pdf', $dest='I') {
7988
		//Output PDF to some destination
7989
		//Finish document if necessary
7990
		if ($this->state < 3) {
7991
			$this->Close();
7992
		}
7993
		//Normalize parameters
7994
		if (is_bool($dest)) {
7995
			$dest = $dest ? 'D' : 'F';
7996
		}
7997
		$dest = strtoupper($dest);
7998
		if ($dest{0} != 'F') {
7999
			$name = preg_replace('/[\s]+/', '_', $name);
8000
			$name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
8001
		}
8002
		if ($this->sign) {
8003
			// *** apply digital signature to the document ***
8004
			// get the document content
8005
			$pdfdoc = $this->getBuffer();
8006
			// remove last newline
8007
			$pdfdoc = substr($pdfdoc, 0, -1);
8008
			// Remove the original buffer
8009
			if (isset($this->diskcache) AND $this->diskcache) {
8010
				// remove buffer file from cache
8011
				unlink($this->buffer);
8012
			}
8013
			unset($this->buffer);
8014
			// remove filler space
8015
			$byterange_string_len = strlen($this->byterange_string);
8016
			// define the ByteRange
8017
			$byte_range = array();
8018
			$byte_range[0] = 0;
8019
			$byte_range[1] = strpos($pdfdoc, $this->byterange_string) + $byterange_string_len + 10;
8020
			$byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
8021
			$byte_range[3] = strlen($pdfdoc) - $byte_range[2];
8022
			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
8023
			// replace the ByteRange
8024
			$byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
8025
			$byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
8026
			$pdfdoc = str_replace($this->byterange_string, $byterange, $pdfdoc);
8027
			// write the document to a temporary folder
8028
			$tempdoc = tempnam(K_PATH_CACHE, 'tmppdf_');
8029
			$f = fopen($tempdoc, 'wb');
8030
			if (!$f) {
8031
				$this->Error('Unable to create temporary file: '.$tempdoc);
8032
			}
8033
			$pdfdoc_length = strlen($pdfdoc);
8034
			fwrite($f, $pdfdoc, $pdfdoc_length);
8035
			fclose($f);
8036
			// get digital signature via openssl library
8037
			$tempsign = tempnam(K_PATH_CACHE, 'tmpsig_');
8038
			if (empty($this->signature_data['extracerts'])) {
8039
				openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
8040
			} else {
8041
				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']);
8042
			}
8043
			unlink($tempdoc);
8044
			// read signature
8045
			$signature = file_get_contents($tempsign);
8046
			unlink($tempsign);
8047
			// extract signature
8048
			$signature = substr($signature, $pdfdoc_length);
8049
			$signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
8050
			$tmparr = explode("\n\n", $signature);
8051
			$signature = $tmparr[1];
8052
			unset($tmparr);
8053
			// decode signature
8054
			$signature = base64_decode(trim($signature));
8055
			// convert signature to hex
8056
			$signature = current(unpack('H*', $signature));
8057
			$signature = str_pad($signature, $this->signature_max_length, '0');
8058
			// Add signature to the document
8059
			$pdfdoc = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
8060
			$this->diskcache = false;
8061
			$this->buffer = &$pdfdoc;
8062
			$this->bufferlen = strlen($pdfdoc);
8063
		}
8064
		switch($dest) {
8065
			case 'I': {
8066
				// Send PDF to the standard output
8067
				if (ob_get_contents()) {
8068
					$this->Error('Some data has already been output, can\'t send PDF file');
8069
				}
8070
				if (php_sapi_name() != 'cli') {
8071
					//We send to a browser
8072
					header('Content-Type: application/pdf');
8073
					if (headers_sent()) {
8074
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
8075
					}
8076
					header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8077
					header('Pragma: public');
8078
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8079
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8080
					header('Content-Length: '.$this->bufferlen);
8081
					header('Content-Disposition: inline; filename="'.basename($name).'";');
8082
				}
8083
				echo $this->getBuffer();
8084
				break;
8085
			}
8086
			case 'D': {
8087
				// Download PDF as file
8088
				if (ob_get_contents()) {
8089
					$this->Error('Some data has already been output, can\'t send PDF file');
8090
				}
8091
				header('Content-Description: File Transfer');
8092
				if (headers_sent()) {
8093
					$this->Error('Some data has already been output to browser, can\'t send PDF file');
8094
				}
8095
				header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8096
				header('Pragma: public');
8097
				header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8098
				header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8099
				// force download dialog
8100
				header('Content-Type: application/force-download');
8101
				header('Content-Type: application/octet-stream', false);
8102
				header('Content-Type: application/download', false);
8103
				header('Content-Type: application/pdf', false);
8104
				// use the Content-Disposition header to supply a recommended filename
8105
				header('Content-Disposition: attachment; filename="'.basename($name).'";');
8106
				header('Content-Transfer-Encoding: binary');
8107
				header('Content-Length: '.$this->bufferlen);
8108
				echo $this->getBuffer();
8109
				break;
8110
			}
8111
			case 'F':
8112
			case 'FI':
8113
			case 'FD': {
8114
				// Save PDF to a local file
8115
				if ($this->diskcache) {
8116
					copy($this->buffer, $name);
8117
				} else {
8118
					$f = fopen($name, 'wb');
8119
					if (!$f) {
8120
						$this->Error('Unable to create output file: '.$name);
8121
					}
8122
					fwrite($f, $this->getBuffer(), $this->bufferlen);
8123
					fclose($f);
8124
				}
8125
				if ($dest == 'FI') {
8126
					// send headers to browser
8127
					header('Content-Type: application/pdf');
8128
					header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8129
					header('Pragma: public');
8130
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8131
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8132
					header('Content-Length: '.filesize($name));
8133
					header('Content-Disposition: inline; filename="'.basename($name).'";');
8134
					// send document to the browser
8135
					echo file_get_contents($name);
8136
				} elseif ($dest == 'FD') {
8137
					// send headers to browser
8138
					if (ob_get_contents()) {
8139
						$this->Error('Some data has already been output, can\'t send PDF file');
8140
					}
8141
					header('Content-Description: File Transfer');
8142
					if (headers_sent()) {
8143
						$this->Error('Some data has already been output to browser, can\'t send PDF file');
8144
					}
8145
					header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8146
					header('Pragma: public');
8147
					header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8148
					header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8149
					// force download dialog
8150
					header('Content-Type: application/force-download');
8151
					header('Content-Type: application/octet-stream', false);
8152
					header('Content-Type: application/download', false);
8153
					header('Content-Type: application/pdf', false);
8154
					// use the Content-Disposition header to supply a recommended filename
8155
					header('Content-Disposition: attachment; filename="'.basename($name).'";');
8156
					header('Content-Transfer-Encoding: binary');
8157
					header('Content-Length: '.filesize($name));
8158
					// send document to the browser
8159
					echo file_get_contents($name);
8160
				}
8161
				break;
8162
			}
8163
			case 'E': {
8164
				// Return PDF as base64 mime multi-part email attachment (RFC 2045)
8165
				$retval = 'Content-Type: application/pdf;'."\r\n";
8166
				$retval .= ' name="'.$name.'"'."\r\n";
8167
				$retval .= 'Content-Transfer-Encoding: base64'."\r\n";
8168
				$retval .= 'Content-Disposition: attachment;'."\r\n";
8169
				$retval .= ' filename="'.$name.'"'."\r\n\r\n";
8170
				$retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
8171
				return $retval;
8172
			}
8173
			case 'S': {
8174
				// Returns PDF as a string
8175
				return $this->getBuffer();
8176
			}
8177
			default: {
8178
				$this->Error('Incorrect output destination: '.$dest);
8179
			}
8180
		}
8181
		return '';
8182
	}
8183
 
8184
	/**
8185
	 * Unset all class variables except the following critical variables: internal_encoding, state, bufferlen, buffer and diskcache.
8186
	 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
8187
	 * @param boolean $preserve_objcopy if true preserves the objcopy variable
8188
	 * @access public
8189
	 * @since 4.5.016 (2009-02-24)
8190
	 */
8191
	public function _destroy($destroyall=false, $preserve_objcopy=false) {
8192
		if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!$this->empty_string($this->buffer))) {
8193
			// remove buffer file from cache
8194
			unlink($this->buffer);
8195
		}
8196
		foreach (array_keys(get_object_vars($this)) as $val) {
8197
			if ($destroyall OR (
8198
				($val != 'internal_encoding')
8199
				AND ($val != 'state')
8200
				AND ($val != 'bufferlen')
8201
				AND ($val != 'buffer')
8202
				AND ($val != 'diskcache')
8203
				AND ($val != 'sign')
8204
				AND ($val != 'signature_data')
8205
				AND ($val != 'signature_max_length')
8206
				AND ($val != 'byterange_string')
8207
				)) {
8208
				if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
8209
					unset($this->$val);
8210
				}
8211
			}
8212
		}
8213
	}
8214
 
8215
	/**
8216
	 * Check for locale-related bug
8217
	 * @access protected
8218
	 */
8219
	protected function _dochecks() {
8220
		//Check for locale-related bug
8221
		if (1.1 == 1) {
8222
			$this->Error('Don\'t alter the locale before including class file');
8223
		}
8224
		//Check for decimal separator
8225
		if (sprintf('%.1F', 1.0) != '1.0') {
8226
			setlocale(LC_NUMERIC, 'C');
8227
		}
8228
	}
8229
 
8230
	/**
8231
	 * Return fonts path
8232
	 * @return string
8233
	 * @access protected
8234
	 */
8235
	protected function _getfontpath() {
8236
		if (!defined('K_PATH_FONTS') AND is_dir(dirname(__FILE__).'/fonts')) {
8237
			define('K_PATH_FONTS', dirname(__FILE__).'/fonts/');
8238
		}
8239
		return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
8240
	}
8241
 
8242
	/**
8243
	 * Output pages.
8244
	 * @access protected
8245
	 */
8246
	protected function _putpages() {
8247
		$nb = $this->numpages;
8248
		if (!empty($this->AliasNbPages)) {
8249
			$nbs = $this->formatPageNumber($nb);
8250
			$nbu = $this->UTF8ToUTF16BE($nbs, false); // replacement for unicode font
8251
			$alias_a = $this->_escape($this->AliasNbPages);
8252
			$alias_au = $this->_escape('{'.$this->AliasNbPages.'}');
8253
			if ($this->isunicode) {
8254
				$alias_b = $this->_escape($this->UTF8ToLatin1($this->AliasNbPages));
8255
				$alias_bu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNbPages.'}'));
8256
				$alias_c = $this->_escape($this->utf8StrRev($this->AliasNbPages, false, $this->tmprtl));
8257
				$alias_cu = $this->_escape($this->utf8StrRev('{'.$this->AliasNbPages.'}', false, $this->tmprtl));
8258
			}
8259
		}
8260
		if (!empty($this->AliasNumPage)) {
8261
			$alias_pa = $this->_escape($this->AliasNumPage);
8262
			$alias_pau = $this->_escape('{'.$this->AliasNumPage.'}');
8263
			if ($this->isunicode) {
8264
				$alias_pb = $this->_escape($this->UTF8ToLatin1($this->AliasNumPage));
8265
				$alias_pbu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNumPage.'}'));
8266
				$alias_pc = $this->_escape($this->utf8StrRev($this->AliasNumPage, false, $this->tmprtl));
8267
				$alias_pcu = $this->_escape($this->utf8StrRev('{'.$this->AliasNumPage.'}', false, $this->tmprtl));
8268
			}
8269
		}
8270
		$pagegroupnum = 0;
8271
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8272
		for ($n=1; $n <= $nb; ++$n) {
8273
			$temppage = $this->getPageBuffer($n);
8274
			if (!empty($this->pagegroups)) {
8275
				if(isset($this->newpagegroup[$n])) {
8276
					$pagegroupnum = 0;
8277
				}
8278
				++$pagegroupnum;
8279
				foreach ($this->pagegroups as $k => $v) {
8280
					// replace total pages group numbers
8281
					$vs = $this->formatPageNumber($v);
8282
					$vu = $this->UTF8ToUTF16BE($vs, false);
8283
					$alias_ga = $this->_escape($k);
8284
					$alias_gau = $this->_escape('{'.$k.'}');
8285
					if ($this->isunicode) {
8286
						$alias_gb = $this->_escape($this->UTF8ToLatin1($k));
8287
						$alias_gbu = $this->_escape($this->UTF8ToLatin1('{'.$k.'}'));
8288
						$alias_gc = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
8289
						$alias_gcu = $this->_escape($this->utf8StrRev('{'.$k.'}', false, $this->tmprtl));
8290
					}
8291
					$temppage = str_replace($alias_gau, $vu, $temppage);
8292
					if ($this->isunicode) {
8293
						$temppage = str_replace($alias_gbu, $vu, $temppage);
8294
						$temppage = str_replace($alias_gcu, $vu, $temppage);
8295
						$temppage = str_replace($alias_gb, $vs, $temppage);
8296
						$temppage = str_replace($alias_gc, $vs, $temppage);
8297
					}
8298
					$temppage = str_replace($alias_ga, $vs, $temppage);
8299
					// replace page group numbers
8300
					$pvs = $this->formatPageNumber($pagegroupnum);
8301
					$pvu = $this->UTF8ToUTF16BE($pvs, false);
8302
					$pk = str_replace('{nb', '{pnb', $k);
8303
					$alias_pga = $this->_escape($pk);
8304
					$alias_pgau = $this->_escape('{'.$pk.'}');
8305
					if ($this->isunicode) {
8306
						$alias_pgb = $this->_escape($this->UTF8ToLatin1($pk));
8307
						$alias_pgbu = $this->_escape($this->UTF8ToLatin1('{'.$pk.'}'));
8308
						$alias_pgc = $this->_escape($this->utf8StrRev($pk, false, $this->tmprtl));
8309
						$alias_pgcu = $this->_escape($this->utf8StrRev('{'.$pk.'}', false, $this->tmprtl));
8310
					}
8311
					$temppage = str_replace($alias_pgau, $pvu, $temppage);
8312
					if ($this->isunicode) {
8313
						$temppage = str_replace($alias_pgbu, $pvu, $temppage);
8314
						$temppage = str_replace($alias_pgcu, $pvu, $temppage);
8315
						$temppage = str_replace($alias_pgb, $pvs, $temppage);
8316
						$temppage = str_replace($alias_pgc, $pvs, $temppage);
8317
					}
8318
					$temppage = str_replace($alias_pga, $pvs, $temppage);
8319
				}
8320
			}
8321
			if (!empty($this->AliasNbPages)) {
8322
				// replace total pages number
8323
				$temppage = str_replace($alias_au, $nbu, $temppage);
8324
				if ($this->isunicode) {
8325
					$temppage = str_replace($alias_bu, $nbu, $temppage);
8326
					$temppage = str_replace($alias_cu, $nbu, $temppage);
8327
					$temppage = str_replace($alias_b, $nbs, $temppage);
8328
					$temppage = str_replace($alias_c, $nbs, $temppage);
8329
				}
8330
				$temppage = str_replace($alias_a, $nbs, $temppage);
8331
			}
8332
			if (!empty($this->AliasNumPage)) {
8333
				// replace page number
8334
				$pnbs = $this->formatPageNumber($n);
8335
				$pnbu = $this->UTF8ToUTF16BE($pnbs, false); // replacement for unicode font
8336
				$temppage = str_replace($alias_pau, $pnbu, $temppage);
8337
				if ($this->isunicode) {
8338
					$temppage = str_replace($alias_pbu, $pnbu, $temppage);
8339
					$temppage = str_replace($alias_pcu, $pnbu, $temppage);
8340
					$temppage = str_replace($alias_pb, $pnbs, $temppage);
8341
					$temppage = str_replace($alias_pc, $pnbs, $temppage);
8342
				}
8343
				$temppage = str_replace($alias_pa, $pnbs, $temppage);
8344
			}
8345
			$temppage = str_replace($this->epsmarker, '', $temppage);
8346
			//Page
8347
			$this->page_obj_id[$n] = $this->_newobj();
8348
			$out = '<<';
8349
			$out .= ' /Type /Page';
8350
			$out .= ' /Parent 1 0 R';
8351
			$out .= ' /LastModified '.$this->_datestring();
8352
			$out .= ' /Resources 2 0 R';
8353
			$boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
8354
			foreach ($boxes as $box) {
8355
				$out .= ' /'.$box;
8356
				$out .= sprintf(' [%.2F %.2F %.2F %.2F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
8357
			}
8358
			if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8359
				$out .= ' /BoxColorInfo <<';
8360
				foreach ($boxes as $box) {
8361
					if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8362
						$out .= ' /'.$box.' <<';
8363
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8364
							$color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8365
							$out .= ' /C [';
8366
							$out .= sprintf(' %.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
8367
							$out .= ' ]';
8368
						}
8369
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8370
							$out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8371
						}
8372
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8373
							$out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8374
						}
8375
						if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8376
							$dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8377
							$out .= ' /D [';
8378
							foreach ($dashes as $dash) {
8379
								$out .= sprintf(' %.3F', ($dash * $this->k));
8380
							}
8381
							$out .= ' ]';
8382
						}
8383
						$out .= ' >>';
8384
					}
8385
				}
8386
				$out .= ' >>';
8387
			}
8388
			$out .= ' /Contents '.($this->n + 1).' 0 R';
8389
			$out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8390
			$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8391
			if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8392
				// page transitions
8393
				if (isset($this->pagedim[$n]['trans']['Dur'])) {
8394
					$out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8395
				}
8396
				$out .= ' /Trans <<';
8397
				$out .= ' /Type /Trans';
8398
				if (isset($this->pagedim[$n]['trans']['S'])) {
8399
					$out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8400
				}
8401
				if (isset($this->pagedim[$n]['trans']['D'])) {
8402
					$out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8403
				}
8404
				if (isset($this->pagedim[$n]['trans']['Dm'])) {
8405
					$out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8406
				}
8407
				if (isset($this->pagedim[$n]['trans']['M'])) {
8408
					$out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8409
				}
8410
				if (isset($this->pagedim[$n]['trans']['Di'])) {
8411
					$out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8412
				}
8413
				if (isset($this->pagedim[$n]['trans']['SS'])) {
8414
					$out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8415
				}
8416
				if (isset($this->pagedim[$n]['trans']['B'])) {
8417
					$out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8418
				}
8419
				$out .= ' >>';
8420
			}
8421
			$out .= $this->_getannotsrefs($n);
8422
			$out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8423
			$out .= ' >>';
8424
			$out .= "\n".'endobj';
8425
			$this->_out($out);
8426
			//Page content
8427
			$p = ($this->compress) ? gzcompress($temppage) : $temppage;
8428
			$this->_newobj();
8429
			$p = $this->_getrawstream($p);
8430
			$this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8431
			if ($this->diskcache) {
8432
				// remove temporary files
8433
				unlink($this->pages[$n]);
8434
			}
8435
		}
8436
		//Pages root
8437
		$out = $this->_getobj(1)."\n";
8438
		$out .= '<< /Type /Pages /Kids [';
8439
		foreach($this->page_obj_id as $page_obj) {
8440
			$out .= ' '.$page_obj.' 0 R';
8441
		}
8442
		$out .= ' ] /Count '.$nb.' >>';
8443
		$out .= "\n".'endobj';
8444
		$this->_out($out);
8445
	}
8446
 
8447
	/**
8448
	 * Output references to page annotations
8449
	 * @param int $n page number
8450
	 * @access protected
8451
	 * @author Nicola Asuni
8452
	 * @since 4.7.000 (2008-08-29)
8453
	 * @deprecated
8454
	 */
8455
	protected function _putannotsrefs($n) {
8456
		$this->_out($this->_getannotsrefs($n));
8457
	}
8458
 
8459
	/**
8460
	 * Get references to page annotations.
8461
	 * @param int $n page number
8462
	 * @return string
8463
	 * @access protected
8464
	 * @author Nicola Asuni
8465
	 * @since 5.0.010 (2010-05-17)
8466
	 */
8467
	protected function _getannotsrefs($n) {
8468
		if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8469
			return '';
8470
		}
8471
		$out = ' /Annots [';
8472
		if (isset($this->PageAnnots[$n])) {
8473
			foreach ($this->PageAnnots[$n] as $key => $val) {
8474
				if (!in_array($val['n'], $this->radio_groups)) {
8475
					$out .= ' '.$val['n'].' 0 R';
8476
				}
8477
			}
8478
			// add radiobutton groups
8479
			if (isset($this->radiobutton_groups[$n])) {
8480
				foreach ($this->radiobutton_groups[$n] as $key => $data) {
8481
					if (isset($data['n'])) {
8482
						$out .= ' '.$data['n'].' 0 R';
8483
					}
8484
				}
8485
			}
8486
		}
8487
		if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8488
			// set reference for signature object
8489
			$out .= ' '.$this->sig_obj_id.' 0 R';
8490
		}
8491
		$out .= ' ]';
8492
		return $out;
8493
	}
8494
 
8495
	/**
8496
	 * Output annotations objects for all pages.
8497
	 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8498
	 * See section 12.5 of PDF 32000_2008 reference.
8499
	 * @access protected
8500
	 * @author Nicola Asuni
8501
	 * @since 4.0.018 (2008-08-06)
8502
	 */
8503
	protected function _putannotsobjs() {
8504
		// reset object counter
8505
		for ($n=1; $n <= $this->numpages; ++$n) {
8506
			if (isset($this->PageAnnots[$n])) {
8507
				// set page annotations
8508
				foreach ($this->PageAnnots[$n] as $key => $pl) {
8509
					$annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8510
					// create annotation object for grouping radiobuttons
8511
					if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8512
						$radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8513
						$annots = '<<';
8514
						$annots .= ' /Type /Annot';
8515
						$annots .= ' /Subtype /Widget';
8516
						$annots .= ' /Rect [0 0 0 0]';
8517
						$annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8518
						$annots .= ' /FT /Btn';
8519
						$annots .= ' /Ff 49152';
8520
						$annots .= ' /Kids [';
8521
						foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8522
							if ($key !== 'n') {
8523
								$annots .= ' '.$data['kid'].' 0 R';
8524
								if ($data['def'] !== 'Off') {
8525
									$defval = $data['def'];
8526
								}
8527
							}
8528
						}
8529
						$annots .= ' ]';
8530
						if (isset($defval)) {
8531
							$annots .= ' /V /'.$defval;
8532
						}
8533
						$annots .= ' >>';
8534
						$this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8535
						$this->form_obj_id[] = $radio_button_obj_id;
8536
						// store object id to be used on Parent entry of Kids
8537
						$this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8538
					}
8539
					$formfield = false;
8540
					$pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8541
					$a = $pl['x'] * $this->k;
8542
					$b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8543
					$c = $pl['w'] * $this->k;
8544
					$d = $pl['h'] * $this->k;
8545
					$rect = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
8546
					// create new annotation object
8547
					$annots = '<</Type /Annot';
8548
					$annots .= ' /Subtype /'.$pl['opt']['subtype'];
8549
					$annots .= ' /Rect ['.$rect.']';
8550
					$ft = array('Btn', 'Tx', 'Ch', 'Sig');
8551
					if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8552
						$annots .= ' /FT /'.$pl['opt']['ft'];
8553
						$formfield = true;
8554
					}
8555
					$annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8556
					$annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8557
					$annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8558
					$annots .= ' /M '.$this->_datestring($annot_obj_id);
8559
					if (isset($pl['opt']['f'])) {
8560
						$val = 0;
8561
						if (is_array($pl['opt']['f'])) {
8562
							foreach ($pl['opt']['f'] as $f) {
8563
								switch (strtolower($f)) {
8564
									case 'invisible': {
8565
										$val += 1 << 0;
8566
										break;
8567
									}
8568
									case 'hidden': {
8569
										$val += 1 << 1;
8570
										break;
8571
									}
8572
									case 'print': {
8573
										$val += 1 << 2;
8574
										break;
8575
									}
8576
									case 'nozoom': {
8577
										$val += 1 << 3;
8578
										break;
8579
									}
8580
									case 'norotate': {
8581
										$val += 1 << 4;
8582
										break;
8583
									}
8584
									case 'noview': {
8585
										$val += 1 << 5;
8586
										break;
8587
									}
8588
									case 'readonly': {
8589
										$val += 1 << 6;
8590
										break;
8591
									}
8592
									case 'locked': {
8593
										$val += 1 << 8;
8594
										break;
8595
									}
8596
									case 'togglenoview': {
8597
										$val += 1 << 9;
8598
										break;
8599
									}
8600
									case 'lockedcontents': {
8601
										$val += 1 << 10;
8602
										break;
8603
									}
8604
									default: {
8605
										break;
8606
									}
8607
								}
8608
							}
8609
						} else {
8610
							$val = intval($pl['opt']['f']);
8611
						}
8612
						$annots .= ' /F '.intval($val);
8613
					}
8614
					if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8615
						$annots .= ' /AS /'.$pl['opt']['as'];
8616
					}
8617
					if (isset($pl['opt']['ap'])) {
8618
						// appearance stream
8619
						$annots .= ' /AP <<';
8620
						if (is_array($pl['opt']['ap'])) {
8621
							foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8622
								// $apmode can be: n = normal; r = rollover; d = down;
8623
								$annots .= ' /'.strtoupper($apmode);
8624
								if (is_array($apdef)) {
8625
									$annots .= ' <<';
8626
									foreach ($apdef as $apstate => $stream) {
8627
										// reference to XObject that define the appearance for this mode-state
8628
										$apsobjid = $this->_putAPXObject($c, $d, $stream);
8629
										$annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8630
									}
8631
									$annots .= ' >>';
8632
								} else {
8633
									// reference to XObject that define the appearance for this mode
8634
									$apsobjid = $this->_putAPXObject($c, $d, $apdef);
8635
									$annots .= ' '.$apsobjid.' 0 R';
8636
								}
8637
							}
8638
						} else {
8639
							$annots .= $pl['opt']['ap'];
8640
						}
8641
						$annots .= ' >>';
8642
					}
8643
					if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8644
						$annots .= ' /BS <<';
8645
						$annots .= ' /Type /Border';
8646
						if (isset($pl['opt']['bs']['w'])) {
8647
							$annots .= ' /W '.intval($pl['opt']['bs']['w']);
8648
						}
8649
						$bstyles = array('S', 'D', 'B', 'I', 'U');
8650
						if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8651
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8652
						}
8653
						if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8654
							$annots .= ' /D [';
8655
							foreach ($pl['opt']['bs']['d'] as $cord) {
8656
								$annots .= ' '.intval($cord);
8657
							}
8658
							$annots .= ']';
8659
						}
8660
						$annots .= ' >>';
8661
					} else {
8662
						$annots .= ' /Border [';
8663
						if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8664
							$annots .= intval($pl['opt']['border'][0]).' ';
8665
							$annots .= intval($pl['opt']['border'][1]).' ';
8666
							$annots .= intval($pl['opt']['border'][2]);
8667
							if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8668
								$annots .= ' [';
8669
								foreach ($pl['opt']['border'][3] as $dash) {
8670
									$annots .= intval($dash).' ';
8671
								}
8672
								$annots .= ']';
8673
							}
8674
						} else {
8675
							$annots .= '0 0 0';
8676
						}
8677
						$annots .= ']';
8678
					}
8679
					if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8680
						$annots .= ' /BE <<';
8681
						$bstyles = array('S', 'C');
8682
						if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $markups)) {
8683
							$annots .= ' /S /'.$pl['opt']['bs']['s'];
8684
						} else {
8685
							$annots .= ' /S /S';
8686
						}
8687
						if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8688
							$annots .= ' /I '.sprintf(' %.4F', $pl['opt']['be']['i']);
8689
						}
8690
						$annots .= '>>';
8691
					}
8692
					if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8693
						$annots .= ' /C [';
8694
						foreach ($pl['opt']['c'] as $col) {
8695
							$col = intval($col);
8696
							$color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8697
							$annots .= sprintf(' %.4F', $color);
8698
						}
8699
						$annots .= ']';
8700
					}
8701
					//$annots .= ' /StructParent ';
8702
					//$annots .= ' /OC ';
8703
					$markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8704
					if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8705
						// this is a markup type
8706
						if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8707
							$annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8708
						}
8709
						//$annots .= ' /Popup ';
8710
						if (isset($pl['opt']['ca'])) {
8711
							$annots .= ' /CA '.sprintf('%.4F', floatval($pl['opt']['ca']));
8712
						}
8713
						if (isset($pl['opt']['rc'])) {
8714
							$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8715
						}
8716
						$annots .= ' /CreationDate '.$this->_datestring($annot_obj_id);
8717
						//$annots .= ' /IRT ';
8718
						if (isset($pl['opt']['subj'])) {
8719
							$annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8720
						}
8721
						//$annots .= ' /RT ';
8722
						//$annots .= ' /IT ';
8723
						//$annots .= ' /ExData ';
8724
					}
8725
					$lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8726
					// Annotation types
8727
					switch (strtolower($pl['opt']['subtype'])) {
8728
						case 'text': {
8729
							if (isset($pl['opt']['open'])) {
8730
								$annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8731
							}
8732
							$iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8733
							if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8734
								$annots .= ' /Name /'.$pl['opt']['name'];
8735
							} else {
8736
								$annots .= ' /Name /Note';
8737
							}
8738
							$statemodels = array('Marked', 'Review');
8739
							if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8740
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8741
							} else {
8742
								$pl['opt']['statemodel'] = 'Marked';
8743
								$annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8744
							}
8745
							if ($pl['opt']['statemodel'] == 'Marked') {
8746
								$states = array('Accepted', 'Unmarked');
8747
							} else {
8748
								$states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8749
							}
8750
							if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8751
								$annots .= ' /State /'.$pl['opt']['state'];
8752
							} else {
8753
								if ($pl['opt']['statemodel'] == 'Marked') {
8754
									$annots .= ' /State /Unmarked';
8755
								} else {
8756
									$annots .= ' /State /None';
8757
								}
8758
							}
8759
							break;
8760
						}
8761
						case 'link': {
8762
							if(is_string($pl['txt'])) {
8763
								// external URI link
8764
								$annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8765
							} else {
8766
								// internal link
8767
								$l = $this->links[$pl['txt']];
8768
								$annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %.2F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
8769
							}
8770
							$hmodes = array('N', 'I', 'O', 'P');
8771
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8772
								$annots .= ' /H /'.$pl['opt']['h'];
8773
							} else {
8774
								$annots .= ' /H /I';
8775
							}
8776
							//$annots .= ' /PA ';
8777
							//$annots .= ' /Quadpoints ';
8778
							break;
8779
						}
8780
						case 'freetext': {
8781
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8782
								$annots .= ' /DA ('.$pl['opt']['da'].')';
8783
							}
8784
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8785
								$annots .= ' /Q '.intval($pl['opt']['q']);
8786
							}
8787
							if (isset($pl['opt']['rc'])) {
8788
								$annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8789
							}
8790
							if (isset($pl['opt']['ds'])) {
8791
								$annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8792
							}
8793
							if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8794
								$annots .= ' /CL [';
8795
								foreach ($pl['opt']['cl'] as $cl) {
8796
									$annots .= sprintf('%.4F ', $cl * $this->k);
8797
								}
8798
								$annots .= ']';
8799
							}
8800
							$tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8801
							if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8802
								$annots .= ' /IT /'.$pl['opt']['it'];
8803
							}
8804
							if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8805
								$l = $pl['opt']['rd'][0] * $this->k;
8806
								$r = $pl['opt']['rd'][1] * $this->k;
8807
								$t = $pl['opt']['rd'][2] * $this->k;
8808
								$b = $pl['opt']['rd'][3] * $this->k;
8809
								$annots .= ' /RD ['.sprintf('%.2F %.2F %.2F %.2F', $l, $r, $t, $b).']';
8810
							}
8811
							if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8812
								$annots .= ' /LE /'.$pl['opt']['le'];
8813
							}
8814
							break;
8815
						}
8816
						case 'line': {
8817
							break;
8818
						}
8819
						case 'square': {
8820
							break;
8821
						}
8822
						case 'circle': {
8823
							break;
8824
						}
8825
						case 'polygon': {
8826
							break;
8827
						}
8828
						case 'polyline': {
8829
							break;
8830
						}
8831
						case 'highlight': {
8832
							break;
8833
						}
8834
						case 'underline': {
8835
							break;
8836
						}
8837
						case 'squiggly': {
8838
							break;
8839
						}
8840
						case 'strikeout': {
8841
							break;
8842
						}
8843
						case 'stamp': {
8844
							break;
8845
						}
8846
						case 'caret': {
8847
							break;
8848
						}
8849
						case 'ink': {
8850
							break;
8851
						}
8852
						case 'popup': {
8853
							break;
8854
						}
8855
						case 'fileattachment': {
8856
							if (!isset($pl['opt']['fs'])) {
8857
								break;
8858
							}
8859
							$filename = basename($pl['opt']['fs']);
8860
							if (isset($this->embeddedfiles[$filename]['n'])) {
8861
								$annots .= ' /FS <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
8862
								$iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8863
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8864
									$annots .= ' /Name /'.$pl['opt']['name'];
8865
								} else {
8866
									$annots .= ' /Name /PushPin';
8867
								}
8868
							}
8869
							break;
8870
						}
8871
						case 'sound': {
8872
							if (!isset($pl['opt']['fs'])) {
8873
								break;
8874
							}
8875
							$filename = basename($pl['opt']['fs']);
8876
							if (isset($this->embeddedfiles[$filename]['n'])) {
8877
								// ... TO BE COMPLETED ...
8878
								// /R /C /B /E /CO /CP
8879
								$annots .= ' /Sound <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
8880
								$iconsapp = array('Speaker', 'Mic');
8881
								if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8882
									$annots .= ' /Name /'.$pl['opt']['name'];
8883
								} else {
8884
									$annots .= ' /Name /Speaker';
8885
								}
8886
							}
8887
							break;
8888
						}
8889
						case 'movie': {
8890
							break;
8891
						}
8892
						case 'widget': {
8893
							$hmode = array('N', 'I', 'O', 'P', 'T');
8894
							if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8895
								$annots .= ' /H /'.$pl['opt']['h'];
8896
							}
8897
							if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8898
								$annots .= ' /MK <<';
8899
								if (isset($pl['opt']['mk']['r'])) {
8900
									$annots .= ' /R '.$pl['opt']['mk']['r'];
8901
								}
8902
								if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8903
									$annots .= ' /BC [';
8904
									foreach($pl['opt']['mk']['bc'] AS $col) {
8905
										$col = intval($col);
8906
										$color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8907
										$annots .= sprintf(' %.2F', $color);
8908
									}
8909
									$annots .= ']';
8910
								}
8911
								if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8912
									$annots .= ' /BG [';
8913
									foreach($pl['opt']['mk']['bg'] AS $col) {
8914
										$col = intval($col);
8915
										$color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8916
										$annots .= sprintf(' %.2F', $color);
8917
									}
8918
									$annots .= ']';
8919
								}
8920
								if (isset($pl['opt']['mk']['ca'])) {
8921
									$annots .= ' /CA '.$pl['opt']['mk']['ca'];
8922
								}
8923
								if (isset($pl['opt']['mk']['rc'])) {
8924
									$annots .= ' /RC '.$pl['opt']['mk']['rc'];
8925
								}
8926
								if (isset($pl['opt']['mk']['ac'])) {
8927
									$annots .= ' /AC '.$pl['opt']['mk']['ac'];
8928
								}
8929
								if (isset($pl['opt']['mk']['i'])) {
8930
									$info = $this->getImageBuffer($pl['opt']['mk']['i']);
8931
									if ($info !== false) {
8932
										$annots .= ' /I '.$info['n'].' 0 R';
8933
									}
8934
								}
8935
								if (isset($pl['opt']['mk']['ri'])) {
8936
									$info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8937
									if ($info !== false) {
8938
										$annots .= ' /RI '.$info['n'].' 0 R';
8939
									}
8940
								}
8941
								if (isset($pl['opt']['mk']['ix'])) {
8942
									$info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8943
									if ($info !== false) {
8944
										$annots .= ' /IX '.$info['n'].' 0 R';
8945
									}
8946
								}
8947
								if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8948
									$annots .= ' /IF <<';
8949
									$if_sw = array('A', 'B', 'S', 'N');
8950
									if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8951
										$annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8952
									}
8953
									$if_s = array('A', 'P');
8954
									if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8955
										$annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8956
									}
8957
									if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8958
										$annots .= sprintf(' /A [%.2F %.2F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8959
									}
8960
									if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8961
										$annots .= ' /FB true';
8962
									}
8963
									$annots .= '>>';
8964
								}
8965
								if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8966
									$annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8967
								} else {
8968
									$annots .= ' /TP 0';
8969
								}
8970
								$annots .= '>>';
8971
							} // end MK
8972
							// --- Entries for field dictionaries ---
8973
							if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8974
								// set parent
8975
								$annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8976
							}
8977
							if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8978
								$annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8979
							}
8980
							if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8981
								$annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8982
							}
8983
							if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8984
								$annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8985
							}
8986
							if (isset($pl['opt']['ff'])) {
8987
								if (is_array($pl['opt']['ff'])) {
8988
									// array of bit settings
8989
									$flag = 0;
8990
									foreach($pl['opt']['ff'] as $val) {
8991
										$flag += 1 << ($val - 1);
8992
									}
8993
								} else {
8994
									$flag = intval($pl['opt']['ff']);
8995
								}
8996
								$annots .= ' /Ff '.$flag;
8997
							}
8998
							if (isset($pl['opt']['maxlen'])) {
8999
								$annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
9000
							}
9001
							if (isset($pl['opt']['v'])) {
9002
								$annots .= ' /V';
9003
								if (is_array($pl['opt']['v'])) {
9004
									foreach ($pl['opt']['v'] AS $optval) {
9005
										if (is_float($optval)) {
9006
											$optval = sprintf('%.2F', $optval);
9007
										}
9008
										$annots .= ' '.$optval;
9009
									}
9010
								} else {
9011
									$annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
9012
								}
9013
							}
9014
							if (isset($pl['opt']['dv'])) {
9015
								$annots .= ' /DV';
9016
								if (is_array($pl['opt']['dv'])) {
9017
									foreach ($pl['opt']['dv'] AS $optval) {
9018
										if (is_float($optval)) {
9019
											$optval = sprintf('%.2F', $optval);
9020
										}
9021
										$annots .= ' '.$optval;
9022
									}
9023
								} else {
9024
									$annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
9025
								}
9026
							}
9027
							if (isset($pl['opt']['rv'])) {
9028
								$annots .= ' /RV';
9029
								if (is_array($pl['opt']['rv'])) {
9030
									foreach ($pl['opt']['rv'] AS $optval) {
9031
										if (is_float($optval)) {
9032
											$optval = sprintf('%.2F', $optval);
9033
										}
9034
										$annots .= ' '.$optval;
9035
									}
9036
								} else {
9037
									$annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
9038
								}
9039
							}
9040
							if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
9041
								$annots .= ' /A << '.$pl['opt']['a'].' >>';
9042
							}
9043
							if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
9044
								$annots .= ' /AA << '.$pl['opt']['aa'].' >>';
9045
							}
9046
							if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
9047
								$annots .= ' /DA ('.$pl['opt']['da'].')';
9048
							}
9049
							if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
9050
								$annots .= ' /Q '.intval($pl['opt']['q']);
9051
							}
9052
							if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
9053
								$annots .= ' /Opt [';
9054
								foreach($pl['opt']['opt'] AS $copt) {
9055
									if (is_array($copt)) {
9056
										$annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
9057
									} else {
9058
										$annots .= ' '.$this->_textstring($copt, $annot_obj_id);
9059
									}
9060
								}
9061
								$annots .= ']';
9062
							}
9063
							if (isset($pl['opt']['ti'])) {
9064
								$annots .= ' /TI '.intval($pl['opt']['ti']);
9065
							}
9066
							if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
9067
								$annots .= ' /I [';
9068
								foreach($pl['opt']['i'] AS $copt) {
9069
									$annots .= intval($copt).' ';
9070
								}
9071
								$annots .= ']';
9072
							}
9073
							break;
9074
						}
9075
						case 'screen': {
9076
							break;
9077
						}
9078
						case 'printermark': {
9079
							break;
9080
						}
9081
						case 'trapnet': {
9082
							break;
9083
						}
9084
						case 'watermark': {
9085
							break;
9086
						}
9087
						case '3d': {
9088
							break;
9089
						}
9090
						default: {
9091
							break;
9092
						}
9093
					}
9094
					$annots .= '>>';
9095
					// create new annotation object
9096
					$this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
9097
					if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
9098
						// store reference of form object
9099
						$this->form_obj_id[] = $annot_obj_id;
9100
					}
9101
				}
9102
			}
9103
		} // end for each page
9104
	}
9105
 
9106
	/**
9107
	 * Put appearance streams XObject used to define annotation's appearance states
9108
	 * @param int $w annotation width
9109
	 * @param int $h annotation height
9110
	 * @param string $stream appearance stream
9111
	 * @return int object ID
9112
	 * @access protected
9113
	 * @since 4.8.001 (2009-09-09)
9114
	 */
9115
	protected function _putAPXObject($w=0, $h=0, $stream='') {
9116
		$stream = trim($stream);
9117
		$out = $this->_getobj()."\n";
9118
		$this->xobjects['AX'.$this->n] = array('n' => $this->n);
9119
		$out .= '<<';
9120
		$out .= ' /Type /XObject';
9121
		$out .= ' /Subtype /Form';
9122
		$out .= ' /FormType 1';
9123
		if ($this->compress) {
9124
			$stream = gzcompress($stream);
9125
			$out .= ' /Filter /FlateDecode';
9126
		}
9127
		$rect = sprintf('%.2F %.2F', $w, $h);
9128
		$out .= ' /BBox [0 0 '.$rect.']';
9129
		$out .= ' /Matrix [1 0 0 1 0 0]';
9130
		$out .= ' /Resources <<';
9131
		$out .= ' /ProcSet [/PDF /Text]';
9132
		$out .= ' /Font <<';
9133
		foreach ($this->annotation_fonts as $fontkey => $fontid) {
9134
			$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9135
		}
9136
		$out .= ' >>';
9137
		$out .= ' >>';
9138
		$stream = $this->_getrawstream($stream);
9139
		$out .= ' /Length '.strlen($stream);
9140
		$out .= ' >>';
9141
		$out .= ' stream'."\n".$stream."\n".'endstream';
9142
		$out .= "\n".'endobj';
9143
		$this->_out($out);
9144
		return $this->n;
9145
	}
9146
 
9147
	/**
9148
	 * Get ULONG from string (Big Endian 32-bit unsigned integer).
9149
	 * @param string $str string from where to extract value
9150
	 * @param int $offset point from where to read the data
9151
	 * @return int 32 bit value
9152
	 * @author Nicola Asuni
9153
	 * @access protected
9154
	 * @since 5.2.000 (2010-06-02)
9155
	 */
9156
	protected function _getULONG(&$str, &$offset) {
9157
		$v = unpack('Ni', substr($str, $offset, 4));
9158
		$offset += 4;
9159
		return $v['i'];
9160
	}
9161
 
9162
	/**
9163
	 * Get USHORT from string (Big Endian 16-bit unsigned integer).
9164
	 * @param string $str string from where to extract value
9165
	 * @param int $offset point from where to read the data
9166
	 * @return int 16 bit value
9167
	 * @author Nicola Asuni
9168
	 * @access protected
9169
	 * @since 5.2.000 (2010-06-02)
9170
	 */
9171
	protected function _getUSHORT(&$str, &$offset) {
9172
		$v = unpack('ni', substr($str, $offset, 2));
9173
		$offset += 2;
9174
		return $v['i'];
9175
	}
9176
 
9177
	/**
9178
	 * Get SHORT from string (Big Endian 16-bit signed integer).
9179
	 * @param string $str string from where to extract value
9180
	 * @param int $offset point from where to read the data
9181
	 * @return int 16 bit value
9182
	 * @author Nicola Asuni
9183
	 * @access protected
9184
	 * @since 5.2.000 (2010-06-02)
9185
	 */
9186
	protected function _getSHORT(&$str, &$offset) {
9187
		$v = unpack('si', substr($str, $offset, 2));
9188
		$offset += 2;
9189
		return $v['i'];
9190
	}
9191
 
9192
	/**
9193
	 * Get BYTE from string (8-bit unsigned integer).
9194
	 * @param string $str string from where to extract value
9195
	 * @param int $offset point from where to read the data
9196
	 * @return int 8 bit value
9197
	 * @author Nicola Asuni
9198
	 * @access protected
9199
	 * @since 5.2.000 (2010-06-02)
9200
	 */
9201
	protected function _getBYTE(&$str, &$offset) {
9202
		$v = unpack('Ci', substr($str, $offset, 1));
9203
		++$offset;
9204
		return $v['i'];
9205
	}
9206
 
9207
	/**
9208
	 * Returns a subset of the TrueType font data without the unused glyphs.
9209
	 * @param string $font TrueType font data
9210
	 * @param array $subsetchars array of used characters (the glyphs to keep)
9211
	 * @return string a subset of TrueType font data without the unused glyphs
9212
	 * @author Nicola Asuni
9213
	 * @access protected
9214
	 * @since 5.2.000 (2010-06-02)
9215
	 */
9216
	protected function _getTrueTypeFontSubset($font, $subsetchars) {
9217
		ksort($subsetchars);
9218
		$offset = 0; // offset position of the font data
9219
		if ($this->_getULONG($font, $offset) != 0x10000) {
9220
			// sfnt version must be 0x00010000 for TrueType version 1.0.
9221
			return $font;
9222
		}
9223
		// get number of tables
9224
		$numTables = $this->_getUSHORT($font, $offset);
9225
		// skip searchRange, entrySelector and rangeShift
9226
		$offset += 6;
9227
		// tables array
9228
		$table = array();
9229
		// for each table
9230
		for ($i = 0; $i < $numTables; ++$i) {
9231
			// get table info
9232
			$tag = substr($font, $offset, 4);
9233
			$offset += 4;
9234
			$table[$tag] = array();
9235
			$table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
9236
			$table[$tag]['offset'] = $this->_getULONG($font, $offset);
9237
			$table[$tag]['length'] = $this->_getULONG($font, $offset);
9238
		}
9239
		// check magicNumber
9240
		$offset = $table['head']['offset'] + 12;
9241
		if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
9242
			// magicNumber must be 0x5F0F3CF5
9243
			return $font;
9244
		}
9245
		// get offset mode (indexToLocFormat : 0 = short, 1 = long)
9246
		$offset = $table['head']['offset'] + 50;
9247
		$short_offset = ($this->_getSHORT($font, $offset) == 0);
9248
		// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
9249
		$indexToLoc = array();
9250
		$offset = $table['loca']['offset'];
9251
		if ($short_offset) {
9252
			// short version
9253
			$n = $table['loca']['length'] / 2; // numGlyphs + 1
9254
			for ($i = 0; $i < $n; ++$i) {
9255
				$indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
9256
			}
9257
		} else {
9258
			// long version
9259
			$n = $table['loca']['length'] / 4; // numGlyphs + 1
9260
			for ($i = 0; $i < $n; ++$i) {
9261
				$indexToLoc[$i] = $this->_getULONG($font, $offset);
9262
			}
9263
		}
9264
		// get glyphs indexes of chars from cmap table
9265
		$subsetglyphs = array(); // glyph IDs on key
9266
		$subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
9267
		$offset = $table['cmap']['offset'] + 2;
9268
		$numEncodingTables = $this->_getUSHORT($font, $offset);
9269
		$encodingTables = array();
9270
		for ($i = 0; $i < $numEncodingTables; ++$i) {
9271
			$encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
9272
			$encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
9273
			$encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
9274
		}
9275
		foreach ($encodingTables as $enctable) {
9276
			if (($enctable['platformID'] == 3) AND ($enctable['encodingID'] == 0)) {
9277
				$modesymbol = true;
9278
			} else {
9279
				$modesymbol = false;
9280
			}
9281
			$offset = $table['cmap']['offset'] + $enctable['offset'];
9282
			$format = $this->_getUSHORT($font, $offset);
9283
			switch ($format) {
9284
				case 0: { // Format 0: Byte encoding table
9285
					$offset += 4; // skip length and version/language
9286
					for ($k = 0; $k < 256; ++$k) {
9287
						if (isset($subsetchars[$k])) {
9288
							$g = $this->_getBYTE($font, $offset);
9289
							$subsetglyphs[$g] = $k;
9290
						} else {
9291
							++$offset;
9292
						}
9293
					}
9294
					break;
9295
				}
9296
				case 2: { // Format 2: High-byte mapping through table
9297
					$offset += 4; // skip length and version
9298
					// to be implemented ...
9299
					break;
9300
				}
9301
				case 4: { // Format 4: Segment mapping to delta values
9302
					$length = $this->_getUSHORT($font, $offset);
9303
					$offset += 2; // skip version/language
9304
					$segCount = ($this->_getUSHORT($font, $offset) / 2);
9305
					$offset += 6; // skip searchRange, entrySelector, rangeShift
9306
					$endCount = array(); // array of end character codes for each segment
9307
					for ($k = 0; $k < $segCount; ++$k) {
9308
						$endCount[$k] = $this->_getUSHORT($font, $offset);
9309
					}
9310
					$offset += 2; // skip reservedPad
9311
					$startCount = array(); // array of start character codes for each segment
9312
					for ($k = 0; $k < $segCount; ++$k) {
9313
						$startCount[$k] = $this->_getUSHORT($font, $offset);
9314
					}
9315
					$idDelta = array(); // delta for all character codes in segment
9316
					for ($k = 0; $k < $segCount; ++$k) {
9317
						$idDelta[$k] = $this->_getUSHORT($font, $offset);
9318
					}
9319
					$idRangeOffset = array(); // Offsets into glyphIdArray or 0
9320
					for ($k = 0; $k < $segCount; ++$k) {
9321
						$idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
9322
					}
9323
					$gidlen = ($length / 2) - 8 - (4 * $segCount);
9324
					$glyphIdArray = array(); // glyph index array
9325
					for ($k = 0; $k < $gidlen; ++$k) {
9326
						$glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
9327
					}
9328
					for ($k = 0; $k < $segCount; ++$k) {
9329
						for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
9330
							if (isset($subsetchars[$c])) {
9331
								if ($idRangeOffset[$k] == 0) {
9332
									$g = $c;
9333
								} else {
9334
									$gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
9335
									$g = $glyphIdArray[$gid];
9336
								}
9337
								$g += ($idDelta[$k] - 65536);
9338
								if ($g < 0) {
9339
									$g = 0;
9340
								}
9341
								$subsetglyphs[$g] = $c;
9342
							}
9343
						}
9344
					}
9345
					break;
9346
				}
9347
				case 6: { // Format 6: Trimmed table mapping
9348
					$offset += 4; // skip length and version/language
9349
					$firstCode = $this->_getUSHORT($font, $offset);
9350
					$entryCount = $this->_getUSHORT($font, $offset);
9351
					for ($k = 0; $k < $entryCount; ++$k) {
9352
						$c = ($k + $firstCode);
9353
						if (isset($subsetchars[$c])) {
9354
							$g = $this->_getUSHORT($font, $offset);
9355
							$subsetglyphs[$g] = $c;
9356
						} else {
9357
							$offset += 2;
9358
						}
9359
					}
9360
					break;
9361
				}
9362
				case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
9363
					$offset += 10; // skip length and version
9364
					// to be implemented ...
9365
					break;
9366
				}
9367
				case 10: { // Format 10: Trimmed array
9368
					$offset += 10; // skip length and version/language
9369
					$startCharCode = $this->_getULONG($font, $offset);
9370
					$numChars = $this->_getULONG($font, $offset);
9371
					for ($k = 0; $k < $numChars; ++$k) {
9372
						$c = ($k + $startCharCode);
9373
						if (isset($subsetchars[$c])) {
9374
							$g = $this->_getUSHORT($font, $offset);
9375
							$subsetglyphs[$g] = $c;
9376
						} else {
9377
							$offset += 2;
9378
						}
9379
					}
9380
					break;
9381
				}
9382
				case 12: { // Format 12: Segmented coverage
9383
					$offset += 10; // skip length and version/language
9384
					$nGroups = $this->_getULONG($font, $offset);
9385
					for ($k = 0; $k < $nGroups; ++$k) {
9386
						$startCharCode = $this->_getULONG($font, $offset);
9387
						$endCharCode = $this->_getULONG($font, $offset);
9388
						$startGlyphCode = $this->_getULONG($font, $offset);
9389
						for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
9390
							if (isset($subsetchars[$c])) {
9391
								$subsetglyphs[$startGlyphCode] = $c;
9392
							}
9393
							++$startGlyphCode;
9394
						}
9395
					}
9396
					break;
9397
				}
9398
			}
9399
		}
9400
		// sort glyphs by key
9401
		ksort($subsetglyphs);
9402
		// add composite glyps to $subsetglyphs and remove missing glyphs
9403
		foreach ($subsetglyphs as $key => $val) {
9404
			if (isset($indexToLoc[$key])) {
9405
				$offset = $table['glyf']['offset'] + $indexToLoc[$key];
9406
				$numberOfContours = $this->_getSHORT($font, $offset);
9407
				if ($numberOfContours < 0) { // composite glyph
9408
					$offset += 8; // skip xMin, yMin, xMax, yMax
9409
					do {
9410
						$flags = $this->_getUSHORT($font, $offset);
9411
						$glyphIndex = $this->_getUSHORT($font, $offset);
9412
						if (!isset($subsetglyphs[$glyphIndex]) AND isset($indexToLoc[$glyphIndex])) {
9413
							// add missing glyphs
9414
							$subsetglyphs[$glyphIndex] = true;
9415
						}
9416
						// skip some bytes by case
9417
						if ($flags & 1) {
9418
							$offset += 4;
9419
						} else {
9420
							$offset += 2;
9421
						}
9422
						if ($flags & 8) {
9423
							$offset += 2;
9424
						} elseif ($flags & 64) {
9425
							$offset += 4;
9426
						} elseif ($flags & 128) {
9427
							$offset += 8;
9428
						}
9429
					} while ($flags & 32);
9430
				}
9431
			} else {
9432
				unset($subsetglyphs[$key]);
9433
			}
9434
		}
9435
		// build new glyf table with only used glyphs
9436
		$glyf = '';
9437
		$glyfSize = 0;
9438
		// create new empty indexToLoc table
9439
		$newIndexToLoc = array_fill(0, count($indexToLoc), 0);
9440
		$goffset = 0;
9441
		foreach ($subsetglyphs as $glyphID => $char) {
9442
			if (isset($indexToLoc[$glyphID]) AND isset($indexToLoc[($glyphID + 1)])) {
9443
				$start = $indexToLoc[$glyphID];
9444
				$length = ($indexToLoc[($glyphID + 1)] - $start);
9445
				$glyf .= substr($font, ($table['glyf']['offset'] + $start), $length);
9446
				$newIndexToLoc[$glyphID] = $goffset;
9447
				$goffset += $length;
9448
			}
9449
		}
9450
		// build new loca table
9451
		$loca = '';
9452
		if ($short_offset) {
9453
			foreach ($newIndexToLoc as $glyphID => $offset) {
9454
				$loca .= pack('n', ($offset / 2));
9455
			}
9456
		} else {
9457
			foreach ($newIndexToLoc as $glyphID => $offset) {
9458
				$loca .= pack('N', $offset);
9459
			}
9460
		}
9461
		// array of table names to preserve (loca and glyf tables will be added later)
9462
		//$table_names = array ('cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt ', 'fpgm', 'prep');
9463
		// the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
9464
		$table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
9465
		// get the tables to preserve
9466
		$offset = 12;
9467
		foreach ($table as $tag => $val) {
9468
			if (in_array($tag, $table_names)) {
9469
				$table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
9470
				if ($tag == 'head') {
9471
					// set the checkSumAdjustment to 0
9472
					$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
9473
				}
9474
				$pad = 4 - ($table[$tag]['length'] % 4);
9475
				if ($pad != 4) {
9476
					// the length of a table must be a multiple of four bytes
9477
					$table[$tag]['length'] += $pad;
9478
					$table[$tag]['data'] .= str_repeat("\x0", $pad);
9479
				}
9480
				$table[$tag]['offset'] = $offset;
9481
				$offset += $table[$tag]['length'];
9482
				// check sum is not changed (so keep the following line commented)
9483
				//$table[$tag]['checkSum'] = $this->_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
9484
			} else {
9485
				unset($table[$tag]);
9486
			}
9487
		}
9488
		// add loca
9489
		$table['loca']['data'] = $loca;
9490
		$table['loca']['length'] = strlen($loca);
9491
		$pad = 4 - ($table['loca']['length'] % 4);
9492
		if ($pad != 4) {
9493
			// the length of a table must be a multiple of four bytes
9494
			$table['loca']['length'] += $pad;
9495
			$table['loca']['data'] .= str_repeat("\x0", $pad);
9496
		}
9497
		$table['loca']['offset'] = $offset;
9498
		$table['loca']['checkSum'] = $this->_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
9499
		$offset += $table['loca']['length'];
9500
		// add glyf
9501
		$table['glyf']['data'] = $glyf;
9502
		$table['glyf']['length'] = strlen($glyf);
9503
		$pad = 4 - ($table['glyf']['length'] % 4);
9504
		if ($pad != 4) {
9505
			// the length of a table must be a multiple of four bytes
9506
			$table['glyf']['length'] += $pad;
9507
			$table['glyf']['data'] .= str_repeat("\x0", $pad);
9508
		}
9509
		$table['glyf']['offset'] = $offset;
9510
		$table['glyf']['checkSum'] = $this->_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
9511
		// rebuild font
9512
		$font = '';
9513
		$font .= pack('N', 0x10000); // sfnt version
9514
		$numTables = count($table);
9515
		$font .= pack('n', $numTables); // numTables
9516
		$entrySelector = floor(log($numTables, 2));
9517
		$searchRange = pow(2, $entrySelector) * 16;
9518
		$rangeShift = ($numTables * 16) - $searchRange;
9519
		$font .= pack('n', $searchRange); // searchRange
9520
		$font .= pack('n', $entrySelector); // entrySelector
9521
		$font .= pack('n', $rangeShift); // rangeShift
9522
		$offset = ($numTables * 16);
9523
		foreach ($table as $tag => $data) {
9524
			$font .= $tag; // tag
9525
			$font .= pack('N', $data['checkSum']); // checkSum
9526
			$font .= pack('N', ($data['offset'] + $offset)); // offset
9527
			$font .= pack('N', $data['length']); // length
9528
		}
9529
		foreach ($table as $data) {
9530
			$font .= $data['data'];
9531
		}
9532
		// set checkSumAdjustment on head table
9533
		$checkSumAdjustment = 0xB1B0AFBA - $this->_getTTFtableChecksum($font, strlen($font));
9534
		$font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
9535
		return $font;
9536
	}
9537
 
9538
	/**
9539
	 * Returs the checksum of a TTF table.
9540
	 * @param string $table table to check
9541
	 * @param int $length lenght of table in bytes
9542
	 * @return int checksum
9543
	 * @author Nicola Asuni
9544
	 * @access protected
9545
	 * @since 5.2.000 (2010-06-02)
9546
	 */
9547
	protected function _getTTFtableChecksum($table, $length) {
9548
		$sum = 0;
9549
		$tlen = ($length / 4);
9550
		$offset = 0;
9551
		for ($i = 0; $i < $tlen; ++$i) {
9552
			$v = unpack('Ni', substr($table, $offset, 4));
9553
			$sum += $v['i'];
9554
			$offset += 4;
9555
		}
9556
		$sum = unpack('Ni', pack('N', $sum));
9557
		return $sum['i'];
9558
	}
9559
 
9560
	/**
9561
	 * Outputs font widths
9562
	 * @param array $font font data
9563
	 * @param int $cidoffset offset for CID values
9564
	 * @return PDF command string for font widths
9565
	 * @author Nicola Asuni
9566
	 * @access protected
9567
	 * @since 4.4.000 (2008-12-07)
9568
	 */
9569
	protected function _putfontwidths($font, $cidoffset=0) {
9570
		ksort($font['cw']);
9571
		$rangeid = 0;
9572
		$range = array();
9573
		$prevcid = -2;
9574
		$prevwidth = -1;
9575
		$interval = false;
9576
		// for each character
9577
		foreach ($font['cw'] as $cid => $width) {
9578
			$cid -= $cidoffset;
9579
			if ($font['subset'] AND ($cid > 255) AND (!isset($font['subsetchars'][$cid]))) {
9580
				// ignore the unused characters (font subsetting)
9581
				continue;
9582
			}
9583
			if ($width != $font['dw']) {
9584
				if ($cid == ($prevcid + 1)) {
9585
					// consecutive CID
9586
					if ($width == $prevwidth) {
9587
						if ($width == $range[$rangeid][0]) {
9588
							$range[$rangeid][] = $width;
9589
						} else {
9590
							array_pop($range[$rangeid]);
9591
							// new range
9592
							$rangeid = $prevcid;
9593
							$range[$rangeid] = array();
9594
							$range[$rangeid][] = $prevwidth;
9595
							$range[$rangeid][] = $width;
9596
						}
9597
						$interval = true;
9598
						$range[$rangeid]['interval'] = true;
9599
					} else {
9600
						if ($interval) {
9601
							// new range
9602
							$rangeid = $cid;
9603
							$range[$rangeid] = array();
9604
							$range[$rangeid][] = $width;
9605
						} else {
9606
							$range[$rangeid][] = $width;
9607
						}
9608
						$interval = false;
9609
					}
9610
				} else {
9611
					// new range
9612
					$rangeid = $cid;
9613
					$range[$rangeid] = array();
9614
					$range[$rangeid][] = $width;
9615
					$interval = false;
9616
				}
9617
				$prevcid = $cid;
9618
				$prevwidth = $width;
9619
			}
9620
		}
9621
		// optimize ranges
9622
		$prevk = -1;
9623
		$nextk = -1;
9624
		$prevint = false;
9625
		foreach ($range as $k => $ws) {
9626
			$cws = count($ws);
9627
			if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
9628
				if (isset($range[$k]['interval'])) {
9629
					unset($range[$k]['interval']);
9630
				}
9631
				$range[$prevk] = array_merge($range[$prevk], $range[$k]);
9632
				unset($range[$k]);
9633
			} else {
9634
				$prevk = $k;
9635
			}
9636
			$nextk = $k + $cws;
9637
			if (isset($ws['interval'])) {
9638
				if ($cws > 3) {
9639
					$prevint = true;
9640
				} else {
9641
					$prevint = false;
9642
				}
9643
				unset($range[$k]['interval']);
9644
				--$nextk;
9645
			} else {
9646
				$prevint = false;
9647
			}
9648
		}
9649
		// output data
9650
		$w = '';
9651
		foreach ($range as $k => $ws) {
9652
			if (count(array_count_values($ws)) == 1) {
9653
				// interval mode is more compact
9654
				$w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
9655
			} else {
9656
				// range mode
9657
				$w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
9658
			}
9659
		}
9660
		return '/W ['.$w.' ]';
9661
	}
9662
 
9663
	/**
9664
	 * Output fonts.
9665
	 * @author Nicola Asuni
9666
	 * @access protected
9667
	 */
9668
	protected function _putfonts() {
9669
		$nf = $this->n;
9670
		foreach ($this->diffs as $diff) {
9671
			//Encodings
9672
			$this->_newobj();
9673
			$this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
9674
		}
9675
		$mqr = $this->get_mqr();
9676
		$this->set_mqr(false);
9677
		foreach ($this->FontFiles as $file => $info) {
9678
			// search and get font file to embedd
9679
			$fontdir = $info['fontdir'];
9680
			$file = strtolower($file);
9681
			$fontfile = '';
9682
			// search files on various directories
9683
			if (($fontdir !== false) AND file_exists($fontdir.$file)) {
9684
				$fontfile = $fontdir.$file;
9685
			} elseif (file_exists($this->_getfontpath().$file)) {
9686
				$fontfile = $this->_getfontpath().$file;
9687
			} elseif (file_exists($file)) {
9688
				$fontfile = $file;
9689
			}
9690
			if (!$this->empty_string($fontfile)) {
9691
				$font = file_get_contents($fontfile);
9692
				$compressed = (substr($file, -2) == '.z');
9693
				if ((!$compressed) AND (isset($info['length2']))) {
9694
					$header = (ord($font{0}) == 128);
9695
					if ($header) {
9696
						//Strip first binary header
9697
						$font = substr($font, 6);
9698
					}
9699
					if ($header AND (ord($font{$info['length1']}) == 128)) {
9700
						//Strip second binary header
9701
						$font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
9702
					}
9703
				} elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
9704
					if ($compressed) {
9705
						// uncompress font
9706
						$font = gzuncompress($font);
9707
					}
9708
					// merge subset characters
9709
					$subsetchars = array(); // used chars
9710
					foreach ($info['fontkeys'] as $fontkey) {
9711
						$fontinfo = $this->getFontBuffer($fontkey);
9712
						$subsetchars += $fontinfo['subsetchars'];
9713
					}
9714
					$font = $this->_getTrueTypeFontSubset($font, $subsetchars);
9715
					if ($compressed) {
9716
						// recompress font
9717
						$font = gzcompress($font);
9718
					}
9719
				}
9720
				$this->_newobj();
9721
				$this->FontFiles[$file]['n'] = $this->n;
9722
				$stream = $this->_getrawstream($font);
9723
				$out = '<< /Length '.strlen($stream);
9724
				if ($compressed) {
9725
					$out .= ' /Filter /FlateDecode';
9726
				}
9727
				$out .= ' /Length1 '.$info['length1'];
9728
				if (isset($info['length2'])) {
9729
					$out .= ' /Length2 '.$info['length2'].' /Length3 0';
9730
				}
9731
				$out .= ' >>';
9732
				$out .= ' stream'."\n".$stream."\n".'endstream';
9733
				$out .= "\n".'endobj';
9734
				$this->_out($out);
9735
			}
9736
		}
9737
		$this->set_mqr($mqr);
9738
		foreach ($this->fontkeys as $k) {
9739
			//Font objects
9740
			$font = $this->getFontBuffer($k);
9741
			$type = $font['type'];
9742
			$name = $font['name'];
9743
			if ($type == 'core') {
9744
				// standard core font
9745
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
9746
				$out .= '<</Type /Font';
9747
				$out .= ' /Subtype /Type1';
9748
				$out .= ' /BaseFont /'.$name;
9749
				$out .= ' /Name /F'.$font['i'];
9750
				if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
9751
					$out .= ' /Encoding /WinAnsiEncoding';
9752
				}
9753
				if ($k == 'helvetica') {
9754
					// add default font for annotations
9755
					$this->annotation_fonts[$k] = $font['i'];
9756
				}
9757
				$out .= ' >>';
9758
				$out .= "\n".'endobj';
9759
				$this->_out($out);
9760
			} elseif (($type == 'Type1') OR ($type == 'TrueType')) {
9761
				// additional Type1 or TrueType font
9762
				$out = $this->_getobj($this->font_obj_ids[$k])."\n";
9763
				$out .= '<</Type /Font';
9764
				$out .= ' /Subtype /'.$type;
9765
				$out .= ' /BaseFont /'.$name;
9766
				$out .= ' /Name /F'.$font['i'];
9767
				$out .= ' /FirstChar 32 /LastChar 255';
9768
				$out .= ' /Widths '.($this->n + 1).' 0 R';
9769
				$out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
9770
				if ($font['enc']) {
9771
					if (isset($font['diff'])) {
9772
						$out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
9773
					} else {
9774
						$out .= ' /Encoding /WinAnsiEncoding';
9775
					}
9776
				}
9777
				$out .= ' >>';
9778
				$out .= "\n".'endobj';
9779
				$this->_out($out);
9780
				// Widths
9781
				$this->_newobj();
9782
				$cw = &$font['cw'];
9783
				$s = '[';
9784
				for ($i = 32; $i < 256; ++$i) {
9785
					$s .= $cw[$i].' ';
9786
				}
9787
				$s .= ']';
9788
				$s .= "\n".'endobj';
9789
				$this->_out($s);
9790
				//Descriptor
9791
				$this->_newobj();
9792
				$s = '<</Type /FontDescriptor /FontName /'.$name;
9793
				foreach ($font['desc'] as $fdk => $fdv) {
9794
					if(is_float($fdv)) {
9795
						$fdv = sprintf('%.3F', $fdv);
9796
					}
9797
					$s .= ' /'.$fdk.' '.$fdv.'';
9798
				}
9799
				if (!$this->empty_string($font['file'])) {
9800
					$s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9801
				}
9802
				$s .= '>>';
9803
				$s .= "\n".'endobj';
9804
				$this->_out($s);
9805
			} else {
9806
				// additional types
9807
				$mtd = '_put'.strtolower($type);
9808
				if (!method_exists($this, $mtd)) {
9809
					$this->Error('Unsupported font type: '.$type);
9810
				}
9811
				$this->$mtd($font);
9812
			}
9813
		}
9814
	}
9815
 
9816
	/**
9817
	 * Adds unicode fonts.<br>
9818
	 * Based on PDF Reference 1.3 (section 5)
9819
	 * @param array $font font data
9820
	 * @access protected
9821
	 * @author Nicola Asuni
9822
	 * @since 1.52.0.TC005 (2005-01-05)
9823
	 */
9824
	protected function _puttruetypeunicode($font) {
9825
		$fontname = '';
9826
		if ($font['subset']) {
9827
			// change name for font subsetting
9828
			$subtag = sprintf('%06u', $font['i']);
9829
			$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9830
			$fontname .= $subtag.'+';
9831
		}
9832
		$fontname .= $font['name'];
9833
		// Type0 Font
9834
		// A composite font composed of other fonts, organized hierarchically
9835
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9836
		$out .= '<< /Type /Font';
9837
		$out .= ' /Subtype /Type0';
9838
		$out .= ' /BaseFont /'.$fontname;
9839
		$out .= ' /Name /F'.$font['i'];
9840
		$out .= ' /Encoding /'.$font['enc'];
9841
		$out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9842
		$out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9843
		$out .= ' >>';
9844
		$out .= "\n".'endobj';
9845
		$this->_out($out);
9846
		// ToUnicode map for Identity-H
9847
		$stream = "/CIDInit /ProcSet findresource begin\n";
9848
		$stream .= "12 dict begin\n";
9849
		$stream .= "begincmap\n";
9850
		$stream .= "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
9851
		$stream .= "/CMapName /Adobe-Identity-UCS def\n";
9852
		$stream .= "/CMapType 2 def\n";
9853
		$stream .= "/WMode 0 def\n";
9854
		$stream .= "1 begincodespacerange\n";
9855
		$stream .= "<0000> <FFFF>\n";
9856
		$stream .= "endcodespacerange\n";
9857
		$stream .= "100 beginbfrange\n";
9858
		$stream .= "<0000> <00ff> <0000>\n";
9859
		$stream .= "<0100> <01ff> <0100>\n";
9860
		$stream .= "<0200> <02ff> <0200>\n";
9861
		$stream .= "<0300> <03ff> <0300>\n";
9862
		$stream .= "<0400> <04ff> <0400>\n";
9863
		$stream .= "<0500> <05ff> <0500>\n";
9864
		$stream .= "<0600> <06ff> <0600>\n";
9865
		$stream .= "<0700> <07ff> <0700>\n";
9866
		$stream .= "<0800> <08ff> <0800>\n";
9867
		$stream .= "<0900> <09ff> <0900>\n";
9868
		$stream .= "<0a00> <0aff> <0a00>\n";
9869
		$stream .= "<0b00> <0bff> <0b00>\n";
9870
		$stream .= "<0c00> <0cff> <0c00>\n";
9871
		$stream .= "<0d00> <0dff> <0d00>\n";
9872
		$stream .= "<0e00> <0eff> <0e00>\n";
9873
		$stream .= "<0f00> <0fff> <0f00>\n";
9874
		$stream .= "<1000> <10ff> <1000>\n";
9875
		$stream .= "<1100> <11ff> <1100>\n";
9876
		$stream .= "<1200> <12ff> <1200>\n";
9877
		$stream .= "<1300> <13ff> <1300>\n";
9878
		$stream .= "<1400> <14ff> <1400>\n";
9879
		$stream .= "<1500> <15ff> <1500>\n";
9880
		$stream .= "<1600> <16ff> <1600>\n";
9881
		$stream .= "<1700> <17ff> <1700>\n";
9882
		$stream .= "<1800> <18ff> <1800>\n";
9883
		$stream .= "<1900> <19ff> <1900>\n";
9884
		$stream .= "<1a00> <1aff> <1a00>\n";
9885
		$stream .= "<1b00> <1bff> <1b00>\n";
9886
		$stream .= "<1c00> <1cff> <1c00>\n";
9887
		$stream .= "<1d00> <1dff> <1d00>\n";
9888
		$stream .= "<1e00> <1eff> <1e00>\n";
9889
		$stream .= "<1f00> <1fff> <1f00>\n";
9890
		$stream .= "<2000> <20ff> <2000>\n";
9891
		$stream .= "<2100> <21ff> <2100>\n";
9892
		$stream .= "<2200> <22ff> <2200>\n";
9893
		$stream .= "<2300> <23ff> <2300>\n";
9894
		$stream .= "<2400> <24ff> <2400>\n";
9895
		$stream .= "<2500> <25ff> <2500>\n";
9896
		$stream .= "<2600> <26ff> <2600>\n";
9897
		$stream .= "<2700> <27ff> <2700>\n";
9898
		$stream .= "<2800> <28ff> <2800>\n";
9899
		$stream .= "<2900> <29ff> <2900>\n";
9900
		$stream .= "<2a00> <2aff> <2a00>\n";
9901
		$stream .= "<2b00> <2bff> <2b00>\n";
9902
		$stream .= "<2c00> <2cff> <2c00>\n";
9903
		$stream .= "<2d00> <2dff> <2d00>\n";
9904
		$stream .= "<2e00> <2eff> <2e00>\n";
9905
		$stream .= "<2f00> <2fff> <2f00>\n";
9906
		$stream .= "<3000> <30ff> <3000>\n";
9907
		$stream .= "<3100> <31ff> <3100>\n";
9908
		$stream .= "<3200> <32ff> <3200>\n";
9909
		$stream .= "<3300> <33ff> <3300>\n";
9910
		$stream .= "<3400> <34ff> <3400>\n";
9911
		$stream .= "<3500> <35ff> <3500>\n";
9912
		$stream .= "<3600> <36ff> <3600>\n";
9913
		$stream .= "<3700> <37ff> <3700>\n";
9914
		$stream .= "<3800> <38ff> <3800>\n";
9915
		$stream .= "<3900> <39ff> <3900>\n";
9916
		$stream .= "<3a00> <3aff> <3a00>\n";
9917
		$stream .= "<3b00> <3bff> <3b00>\n";
9918
		$stream .= "<3c00> <3cff> <3c00>\n";
9919
		$stream .= "<3d00> <3dff> <3d00>\n";
9920
		$stream .= "<3e00> <3eff> <3e00>\n";
9921
		$stream .= "<3f00> <3fff> <3f00>\n";
9922
		$stream .= "<4000> <40ff> <4000>\n";
9923
		$stream .= "<4100> <41ff> <4100>\n";
9924
		$stream .= "<4200> <42ff> <4200>\n";
9925
		$stream .= "<4300> <43ff> <4300>\n";
9926
		$stream .= "<4400> <44ff> <4400>\n";
9927
		$stream .= "<4500> <45ff> <4500>\n";
9928
		$stream .= "<4600> <46ff> <4600>\n";
9929
		$stream .= "<4700> <47ff> <4700>\n";
9930
		$stream .= "<4800> <48ff> <4800>\n";
9931
		$stream .= "<4900> <49ff> <4900>\n";
9932
		$stream .= "<4a00> <4aff> <4a00>\n";
9933
		$stream .= "<4b00> <4bff> <4b00>\n";
9934
		$stream .= "<4c00> <4cff> <4c00>\n";
9935
		$stream .= "<4d00> <4dff> <4d00>\n";
9936
		$stream .= "<4e00> <4eff> <4e00>\n";
9937
		$stream .= "<4f00> <4fff> <4f00>\n";
9938
		$stream .= "<5000> <50ff> <5000>\n";
9939
		$stream .= "<5100> <51ff> <5100>\n";
9940
		$stream .= "<5200> <52ff> <5200>\n";
9941
		$stream .= "<5300> <53ff> <5300>\n";
9942
		$stream .= "<5400> <54ff> <5400>\n";
9943
		$stream .= "<5500> <55ff> <5500>\n";
9944
		$stream .= "<5600> <56ff> <5600>\n";
9945
		$stream .= "<5700> <57ff> <5700>\n";
9946
		$stream .= "<5800> <58ff> <5800>\n";
9947
		$stream .= "<5900> <59ff> <5900>\n";
9948
		$stream .= "<5a00> <5aff> <5a00>\n";
9949
		$stream .= "<5b00> <5bff> <5b00>\n";
9950
		$stream .= "<5c00> <5cff> <5c00>\n";
9951
		$stream .= "<5d00> <5dff> <5d00>\n";
9952
		$stream .= "<5e00> <5eff> <5e00>\n";
9953
		$stream .= "<5f00> <5fff> <5f00>\n";
9954
		$stream .= "<6000> <60ff> <6000>\n";
9955
		$stream .= "<6100> <61ff> <6100>\n";
9956
		$stream .= "<6200> <62ff> <6200>\n";
9957
		$stream .= "<6300> <63ff> <6300>\n";
9958
		$stream .= "endbfrange\n";
9959
		$stream .= "100 beginbfrange\n";
9960
		$stream .= "<6400> <64ff> <6400>\n";
9961
		$stream .= "<6500> <65ff> <6500>\n";
9962
		$stream .= "<6600> <66ff> <6600>\n";
9963
		$stream .= "<6700> <67ff> <6700>\n";
9964
		$stream .= "<6800> <68ff> <6800>\n";
9965
		$stream .= "<6900> <69ff> <6900>\n";
9966
		$stream .= "<6a00> <6aff> <6a00>\n";
9967
		$stream .= "<6b00> <6bff> <6b00>\n";
9968
		$stream .= "<6c00> <6cff> <6c00>\n";
9969
		$stream .= "<6d00> <6dff> <6d00>\n";
9970
		$stream .= "<6e00> <6eff> <6e00>\n";
9971
		$stream .= "<6f00> <6fff> <6f00>\n";
9972
		$stream .= "<7000> <70ff> <7000>\n";
9973
		$stream .= "<7100> <71ff> <7100>\n";
9974
		$stream .= "<7200> <72ff> <7200>\n";
9975
		$stream .= "<7300> <73ff> <7300>\n";
9976
		$stream .= "<7400> <74ff> <7400>\n";
9977
		$stream .= "<7500> <75ff> <7500>\n";
9978
		$stream .= "<7600> <76ff> <7600>\n";
9979
		$stream .= "<7700> <77ff> <7700>\n";
9980
		$stream .= "<7800> <78ff> <7800>\n";
9981
		$stream .= "<7900> <79ff> <7900>\n";
9982
		$stream .= "<7a00> <7aff> <7a00>\n";
9983
		$stream .= "<7b00> <7bff> <7b00>\n";
9984
		$stream .= "<7c00> <7cff> <7c00>\n";
9985
		$stream .= "<7d00> <7dff> <7d00>\n";
9986
		$stream .= "<7e00> <7eff> <7e00>\n";
9987
		$stream .= "<7f00> <7fff> <7f00>\n";
9988
		$stream .= "<8000> <80ff> <8000>\n";
9989
		$stream .= "<8100> <81ff> <8100>\n";
9990
		$stream .= "<8200> <82ff> <8200>\n";
9991
		$stream .= "<8300> <83ff> <8300>\n";
9992
		$stream .= "<8400> <84ff> <8400>\n";
9993
		$stream .= "<8500> <85ff> <8500>\n";
9994
		$stream .= "<8600> <86ff> <8600>\n";
9995
		$stream .= "<8700> <87ff> <8700>\n";
9996
		$stream .= "<8800> <88ff> <8800>\n";
9997
		$stream .= "<8900> <89ff> <8900>\n";
9998
		$stream .= "<8a00> <8aff> <8a00>\n";
9999
		$stream .= "<8b00> <8bff> <8b00>\n";
10000
		$stream .= "<8c00> <8cff> <8c00>\n";
10001
		$stream .= "<8d00> <8dff> <8d00>\n";
10002
		$stream .= "<8e00> <8eff> <8e00>\n";
10003
		$stream .= "<8f00> <8fff> <8f00>\n";
10004
		$stream .= "<9000> <90ff> <9000>\n";
10005
		$stream .= "<9100> <91ff> <9100>\n";
10006
		$stream .= "<9200> <92ff> <9200>\n";
10007
		$stream .= "<9300> <93ff> <9300>\n";
10008
		$stream .= "<9400> <94ff> <9400>\n";
10009
		$stream .= "<9500> <95ff> <9500>\n";
10010
		$stream .= "<9600> <96ff> <9600>\n";
10011
		$stream .= "<9700> <97ff> <9700>\n";
10012
		$stream .= "<9800> <98ff> <9800>\n";
10013
		$stream .= "<9900> <99ff> <9900>\n";
10014
		$stream .= "<9a00> <9aff> <9a00>\n";
10015
		$stream .= "<9b00> <9bff> <9b00>\n";
10016
		$stream .= "<9c00> <9cff> <9c00>\n";
10017
		$stream .= "<9d00> <9dff> <9d00>\n";
10018
		$stream .= "<9e00> <9eff> <9e00>\n";
10019
		$stream .= "<9f00> <9fff> <9f00>\n";
10020
		$stream .= "<a000> <a0ff> <a000>\n";
10021
		$stream .= "<a100> <a1ff> <a100>\n";
10022
		$stream .= "<a200> <a2ff> <a200>\n";
10023
		$stream .= "<a300> <a3ff> <a300>\n";
10024
		$stream .= "<a400> <a4ff> <a400>\n";
10025
		$stream .= "<a500> <a5ff> <a500>\n";
10026
		$stream .= "<a600> <a6ff> <a600>\n";
10027
		$stream .= "<a700> <a7ff> <a700>\n";
10028
		$stream .= "<a800> <a8ff> <a800>\n";
10029
		$stream .= "<a900> <a9ff> <a900>\n";
10030
		$stream .= "<aa00> <aaff> <aa00>\n";
10031
		$stream .= "<ab00> <abff> <ab00>\n";
10032
		$stream .= "<ac00> <acff> <ac00>\n";
10033
		$stream .= "<ad00> <adff> <ad00>\n";
10034
		$stream .= "<ae00> <aeff> <ae00>\n";
10035
		$stream .= "<af00> <afff> <af00>\n";
10036
		$stream .= "<b000> <b0ff> <b000>\n";
10037
		$stream .= "<b100> <b1ff> <b100>\n";
10038
		$stream .= "<b200> <b2ff> <b200>\n";
10039
		$stream .= "<b300> <b3ff> <b300>\n";
10040
		$stream .= "<b400> <b4ff> <b400>\n";
10041
		$stream .= "<b500> <b5ff> <b500>\n";
10042
		$stream .= "<b600> <b6ff> <b600>\n";
10043
		$stream .= "<b700> <b7ff> <b700>\n";
10044
		$stream .= "<b800> <b8ff> <b800>\n";
10045
		$stream .= "<b900> <b9ff> <b900>\n";
10046
		$stream .= "<ba00> <baff> <ba00>\n";
10047
		$stream .= "<bb00> <bbff> <bb00>\n";
10048
		$stream .= "<bc00> <bcff> <bc00>\n";
10049
		$stream .= "<bd00> <bdff> <bd00>\n";
10050
		$stream .= "<be00> <beff> <be00>\n";
10051
		$stream .= "<bf00> <bfff> <bf00>\n";
10052
		$stream .= "<c000> <c0ff> <c000>\n";
10053
		$stream .= "<c100> <c1ff> <c100>\n";
10054
		$stream .= "<c200> <c2ff> <c200>\n";
10055
		$stream .= "<c300> <c3ff> <c300>\n";
10056
		$stream .= "<c400> <c4ff> <c400>\n";
10057
		$stream .= "<c500> <c5ff> <c500>\n";
10058
		$stream .= "<c600> <c6ff> <c600>\n";
10059
		$stream .= "<c700> <c7ff> <c700>\n";
10060
		$stream .= "endbfrange\n";
10061
		$stream .= "56 beginbfrange\n";
10062
		$stream .= "<c800> <c8ff> <c800>\n";
10063
		$stream .= "<c900> <c9ff> <c900>\n";
10064
		$stream .= "<ca00> <caff> <ca00>\n";
10065
		$stream .= "<cb00> <cbff> <cb00>\n";
10066
		$stream .= "<cc00> <ccff> <cc00>\n";
10067
		$stream .= "<cd00> <cdff> <cd00>\n";
10068
		$stream .= "<ce00> <ceff> <ce00>\n";
10069
		$stream .= "<cf00> <cfff> <cf00>\n";
10070
		$stream .= "<d000> <d0ff> <d000>\n";
10071
		$stream .= "<d100> <d1ff> <d100>\n";
10072
		$stream .= "<d200> <d2ff> <d200>\n";
10073
		$stream .= "<d300> <d3ff> <d300>\n";
10074
		$stream .= "<d400> <d4ff> <d400>\n";
10075
		$stream .= "<d500> <d5ff> <d500>\n";
10076
		$stream .= "<d600> <d6ff> <d600>\n";
10077
		$stream .= "<d700> <d7ff> <d700>\n";
10078
		$stream .= "<d800> <d8ff> <d800>\n";
10079
		$stream .= "<d900> <d9ff> <d900>\n";
10080
		$stream .= "<da00> <daff> <da00>\n";
10081
		$stream .= "<db00> <dbff> <db00>\n";
10082
		$stream .= "<dc00> <dcff> <dc00>\n";
10083
		$stream .= "<dd00> <ddff> <dd00>\n";
10084
		$stream .= "<de00> <deff> <de00>\n";
10085
		$stream .= "<df00> <dfff> <df00>\n";
10086
		$stream .= "<e000> <e0ff> <e000>\n";
10087
		$stream .= "<e100> <e1ff> <e100>\n";
10088
		$stream .= "<e200> <e2ff> <e200>\n";
10089
		$stream .= "<e300> <e3ff> <e300>\n";
10090
		$stream .= "<e400> <e4ff> <e400>\n";
10091
		$stream .= "<e500> <e5ff> <e500>\n";
10092
		$stream .= "<e600> <e6ff> <e600>\n";
10093
		$stream .= "<e700> <e7ff> <e700>\n";
10094
		$stream .= "<e800> <e8ff> <e800>\n";
10095
		$stream .= "<e900> <e9ff> <e900>\n";
10096
		$stream .= "<ea00> <eaff> <ea00>\n";
10097
		$stream .= "<eb00> <ebff> <eb00>\n";
10098
		$stream .= "<ec00> <ecff> <ec00>\n";
10099
		$stream .= "<ed00> <edff> <ed00>\n";
10100
		$stream .= "<ee00> <eeff> <ee00>\n";
10101
		$stream .= "<ef00> <efff> <ef00>\n";
10102
		$stream .= "<f000> <f0ff> <f000>\n";
10103
		$stream .= "<f100> <f1ff> <f100>\n";
10104
		$stream .= "<f200> <f2ff> <f200>\n";
10105
		$stream .= "<f300> <f3ff> <f300>\n";
10106
		$stream .= "<f400> <f4ff> <f400>\n";
10107
		$stream .= "<f500> <f5ff> <f500>\n";
10108
		$stream .= "<f600> <f6ff> <f600>\n";
10109
		$stream .= "<f700> <f7ff> <f700>\n";
10110
		$stream .= "<f800> <f8ff> <f800>\n";
10111
		$stream .= "<f900> <f9ff> <f900>\n";
10112
		$stream .= "<fa00> <faff> <fa00>\n";
10113
		$stream .= "<fb00> <fbff> <fb00>\n";
10114
		$stream .= "<fc00> <fcff> <fc00>\n";
10115
		$stream .= "<fd00> <fdff> <fd00>\n";
10116
		$stream .= "<fe00> <feff> <fe00>\n";
10117
		$stream .= "<ff00> <ffff> <ff00>\n";
10118
		$stream .= "endbfrange\n";
10119
		$stream .= "endcmap\n";
10120
		$stream .= "CMapName currentdict /CMap defineresource pop\n";
10121
		$stream .= "end\n";
10122
		$stream .= "end";
10123
		// ToUnicode Object
10124
		$this->_newobj();
10125
		$stream = ($this->compress) ? gzcompress($stream) : $stream;
10126
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
10127
		$stream = $this->_getrawstream($stream);
10128
		$this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
10129
		// CIDFontType2
10130
		// A CIDFont whose glyph descriptions are based on TrueType font technology
10131
		$oid = $this->_newobj();
10132
		$out = '<< /Type /Font';
10133
		$out .= ' /Subtype /CIDFontType2';
10134
		$out .= ' /BaseFont /'.$fontname;
10135
		// A dictionary containing entries that define the character collection of the CIDFont.
10136
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
10137
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
10138
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
10139
		$out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
10140
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
10141
		$out .= ' /DW '.$font['dw']; // default width
10142
		$out .= "\n".$this->_putfontwidths($font, 0);
10143
		if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
10144
			$out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
10145
		}
10146
		$out .= ' >>';
10147
		$out .= "\n".'endobj';
10148
		$this->_out($out);
10149
		// Font descriptor
10150
		// A font descriptor describing the CIDFont default metrics other than its glyph widths
10151
		$this->_newobj();
10152
		$out = '<< /Type /FontDescriptor';
10153
		$out .= ' /FontName /'.$fontname;
10154
		foreach ($font['desc'] as $key => $value) {
10155
			if(is_float($value)) {
10156
				$value = sprintf('%.3F', $value);
10157
			}
10158
			$out .= ' /'.$key.' '.$value;
10159
		}
10160
		$fontdir = false;
10161
		if (!$this->empty_string($font['file'])) {
10162
			// A stream containing a TrueType font
10163
			$out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
10164
			$fontdir = $this->FontFiles[$font['file']]['fontdir'];
10165
		}
10166
		$out .= ' >>';
10167
		$out .= "\n".'endobj';
10168
		$this->_out($out);
10169
		if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
10170
			$this->_newobj();
10171
			// Embed CIDToGIDMap
10172
			// A specification of the mapping from CIDs to glyph indices
10173
			// search and get CTG font file to embedd
10174
			$ctgfile = strtolower($font['ctg']);
10175
			// search and get ctg font file to embedd
10176
			$fontfile = '';
10177
			// search files on various directories
10178
			if (($fontdir !== false) AND file_exists($fontdir.$ctgfile)) {
10179
				$fontfile = $fontdir.$ctgfile;
10180
			} elseif (file_exists($this->_getfontpath().$ctgfile)) {
10181
				$fontfile = $this->_getfontpath().$ctgfile;
10182
			} elseif (file_exists($ctgfile)) {
10183
				$fontfile = $ctgfile;
10184
			}
10185
			if ($this->empty_string($fontfile)) {
10186
				$this->Error('Font file not found: '.$ctgfile);
10187
			}
10188
			$stream = $this->_getrawstream(file_get_contents($fontfile));
10189
			$out = '<< /Length '.strlen($stream).'';
10190
			if (substr($fontfile, -2) == '.z') { // check file extension
10191
				// Decompresses data encoded using the public-domain
10192
				// zlib/deflate compression method, reproducing the
10193
				// original text or binary data
10194
				$out .= ' /Filter /FlateDecode';
10195
			}
10196
			$out .= ' >>';
10197
			$out .= ' stream'."\n".$stream."\n".'endstream';
10198
			$out .= "\n".'endobj';
10199
			$this->_out($out);
10200
		}
10201
	}
10202
 
10203
	/**
10204
	 * Output CID-0 fonts.
10205
	 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
10206
	 * @param array $font font data
10207
	 * @access protected
10208
	 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
10209
	 * @since 3.2.000 (2008-06-23)
10210
	 */
10211
	protected function _putcidfont0($font) {
10212
		$cidoffset = 0;
10213
		if (!isset($font['cw'][1])) {
10214
			$cidoffset = 31;
10215
		}
10216
		if (isset($font['cidinfo']['uni2cid'])) {
10217
			// convert unicode to cid.
10218
			$uni2cid = $font['cidinfo']['uni2cid'];
10219
			$cw = array();
10220
			foreach ($font['cw'] as $uni => $width) {
10221
				if (isset($uni2cid[$uni])) {
10222
					$cw[($uni2cid[$uni] + $cidoffset)] = $width;
10223
				} elseif ($uni < 256) {
10224
					$cw[$uni] = $width;
10225
				} // else unknown character
10226
			}
10227
			$font = array_merge($font, array('cw' => $cw));
10228
		}
10229
		$name = $font['name'];
10230
		$enc = $font['enc'];
10231
		if ($enc) {
10232
			$longname = $name.'-'.$enc;
10233
		} else {
10234
			$longname = $name;
10235
		}
10236
		$out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
10237
		$out .= '<</Type /Font';
10238
		$out .= ' /Subtype /Type0';
10239
		$out .= ' /BaseFont /'.$longname;
10240
		$out .= ' /Name /F'.$font['i'];
10241
		if ($enc) {
10242
			$out .= ' /Encoding /'.$enc;
10243
		}
10244
		$out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
10245
		$out .= ' >>';
10246
		$out .= "\n".'endobj';
10247
		$this->_out($out);
10248
		$oid = $this->_newobj();
10249
		$out = '<</Type /Font';
10250
		$out .= ' /Subtype /CIDFontType0';
10251
		$out .= ' /BaseFont /'.$name;
10252
		$cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
10253
		$cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
10254
		$cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
10255
		$out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
10256
		$out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
10257
		$out .= ' /DW '.$font['dw'];
10258
		$out .= "\n".$this->_putfontwidths($font, $cidoffset);
10259
		$out .= ' >>';
10260
		$out .= "\n".'endobj';
10261
		$this->_out($out);
10262
		$this->_newobj();
10263
		$s = '<</Type /FontDescriptor /FontName /'.$name;
10264
		foreach ($font['desc'] as $k => $v) {
10265
			if ($k != 'Style') {
10266
				if(is_float($v)) {
10267
					$v = sprintf('%.3F', $v);
10268
				}
10269
				$s .= ' /'.$k.' '.$v.'';
10270
			}
10271
		}
10272
		$s .= '>>';
10273
		$s .= "\n".'endobj';
10274
		$this->_out($s);
10275
	}
10276
 
10277
	/**
10278
	 * Output images.
10279
	 * @access protected
10280
	 */
10281
	protected function _putimages() {
10282
		$filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
10283
		foreach ($this->imagekeys as $file) {
10284
			$info = $this->getImageBuffer($file);
10285
			$oid = $this->_newobj();
10286
			$this->xobjects['I'.$info['i']] = array('n' => $oid);
10287
			$this->setImageSubBuffer($file, 'n', $this->n);
10288
			$out = '<</Type /XObject';
10289
			$out .= ' /Subtype /Image';
10290
			$out .= ' /Width '.$info['w'];
10291
			$out .= ' /Height '.$info['h'];
10292
			if (array_key_exists('masked', $info)) {
10293
				$out .= ' /SMask '.($this->n - 1).' 0 R';
10294
			}
10295
			if ($info['cs'] == 'Indexed') {
10296
				$out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
10297
			} else {
10298
				$out .= ' /ColorSpace /'.$info['cs'];
10299
				if ($info['cs'] == 'DeviceCMYK') {
10300
					$out .= ' /Decode [1 0 1 0 1 0 1 0]';
10301
				}
10302
			}
10303
			$out .= ' /BitsPerComponent '.$info['bpc'];
10304
			if (isset($info['f'])) {
10305
				$out .= ' /Filter /'.$info['f'];
10306
			}
10307
			if (isset($info['parms'])) {
10308
				$out .= ' '.$info['parms'];
10309
			}
10310
			if (isset($info['trns']) AND is_array($info['trns'])) {
10311
				$trns='';
10312
				$count_info = count($info['trns']);
10313
				for ($i=0; $i < $count_info; ++$i) {
10314
					$trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
10315
				}
10316
				$out .= ' /Mask ['.$trns.']';
10317
			}
10318
			$stream = $this->_getrawstream($info['data']);
10319
			$out .= ' /Length '.strlen($stream).' >>';
10320
			$out .= ' stream'."\n".$stream."\n".'endstream';
10321
			$out .= "\n".'endobj';
10322
			$this->_out($out);
10323
			//Palette
10324
			if ($info['cs'] == 'Indexed') {
10325
				$this->_newobj();
10326
				$pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
10327
				$pal = $this->_getrawstream($pal);
10328
				$this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
10329
			}
10330
		}
10331
	}
10332
 
10333
	/**
10334
	 * Output Form XObjects Templates.
10335
	 * @author Nicola Asuni
10336
	 * @since 5.8.017 (2010-08-24)
10337
	 * @access protected
10338
	 * @see startTemplate(), endTemplate(), printTemplate()
10339
	 */
10340
	protected function _putxobjects() {
10341
		foreach ($this->xobjects as $key => $data) {
10342
			if (isset($data['outdata'])) {
10343
				$stream = trim($data['outdata']);
10344
				$out = $this->_getobj($data['n'])."\n";
10345
				$out .= '<<';
10346
				$out .= ' /Type /XObject';
10347
				$out .= ' /Subtype /Form';
10348
				$out .= ' /FormType 1';
10349
				if ($this->compress) {
10350
					$stream = gzcompress($stream);
10351
					$out .= ' /Filter /FlateDecode';
10352
				}
10353
				$out .= sprintf(' /BBox [%.2F %.2F %.2F %.2F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
10354
				$out .= ' /Matrix [1 0 0 1 0 0]';
10355
				$out .= ' /Resources <<';
10356
				$out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
10357
				// fonts
10358
				if (!empty($data['fonts'])) {
10359
					$out .= ' /Font <<';
10360
					foreach ($data['fonts'] as $fontkey => $fontid) {
10361
						$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
10362
					}
10363
					$out .= ' >>';
10364
				}
10365
				// images or nested xobjects
10366
				if (!empty($data['images']) OR !empty($data['xobjects'])) {
10367
					$out .= ' /XObject <<';
10368
					foreach ($data['images'] as $imgid) {
10369
						$out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
10370
					}
10371
					foreach ($data['xobjects'] as $sub_id => $sub_objid) {
10372
						$out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
10373
					}
10374
					$out .= ' >>';
10375
				}
10376
				$out .= ' >>';
10377
				$stream = $this->_getrawstream($stream);
10378
				$out .= ' /Length '.strlen($stream);
10379
				$out .= ' >>';
10380
				$out .= ' stream'."\n".$stream."\n".'endstream';
10381
				$out .= "\n".'endobj';
10382
				$this->_out($out);
10383
			}
10384
		}
10385
	}
10386
 
10387
	/**
10388
	 * Output Spot Colors Resources.
10389
	 * @access protected
10390
	 * @since 4.0.024 (2008-09-12)
10391
	 */
10392
	protected function _putspotcolors() {
10393
		foreach ($this->spot_colors as $name => $color) {
10394
			$this->_newobj();
10395
			$this->spot_colors[$name]['n'] = $this->n;
10396
			$out = '[/Separation /'.str_replace(' ', '#20', $name);
10397
			$out .= ' /DeviceCMYK <<';
10398
			$out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
10399
			$out .= ' '.sprintf('/C1 [%.4F %.4F %.4F %.4F] ', ($color['c'] / 100), ($color['m'] / 100), ($color['y'] / 100), ($color['k'] / 100));
10400
			$out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
10401
			$out .= "\n".'endobj';
10402
			$this->_out($out);
10403
		}
10404
	}
10405
 
10406
	/**
10407
	 * Return XObjects Dictionary.
10408
	 * @return string XObjects dictionary
10409
	 * @access protected
10410
	 * @since 5.8.014 (2010-08-23)
10411
	 */
10412
	protected function _getxobjectdict() {
10413
		$out = '';
10414
		foreach ($this->xobjects as $id => $objid) {
10415
			$out .= ' /'.$id.' '.$objid['n'].' 0 R';
10416
		}
10417
		return $out;
10418
	}
10419
 
10420
	/**
10421
	 * Output Resources Dictionary.
10422
	 * @access protected
10423
	 */
10424
	protected function _putresourcedict() {
10425
		$out = $this->_getobj(2)."\n";
10426
		$out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
10427
		$out .= ' /Font <<';
10428
		foreach ($this->fontkeys as $fontkey) {
10429
			$font = $this->getFontBuffer($fontkey);
10430
			$out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
10431
		}
10432
		$out .= ' >>';
10433
		$out .= ' /XObject <<';
10434
		$out .= $this->_getxobjectdict();
10435
		$out .= ' >>';
10436
		// visibility
10437
		$out .= ' /Properties <</OC1 '.$this->n_ocg_print.' 0 R /OC2 '.$this->n_ocg_view.' 0 R>>';
10438
		// transparency
10439
		$out .= ' /ExtGState <<';
10440
		foreach ($this->extgstates as $k => $extgstate) {
10441
			if (isset($extgstate['name'])) {
10442
				$out .= ' /'.$extgstate['name'];
10443
			} else {
10444
				$out .= ' /GS'.$k;
10445
			}
10446
			$out .= ' '.$extgstate['n'].' 0 R';
10447
		}
10448
		$out .= ' >>';
10449
		// gradient patterns
10450
		if (isset($this->gradients) AND (count($this->gradients) > 0)) {
10451
			$out .= ' /Pattern <<';
10452
			foreach ($this->gradients as $id => $grad) {
10453
				$out .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
10454
			}
10455
			$out .= ' >>';
10456
		}
10457
		// gradient shadings
10458
		if (isset($this->gradients) AND (count($this->gradients) > 0)) {
10459
			$out .= ' /Shading <<';
10460
			foreach ($this->gradients as $id => $grad) {
10461
				$out .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
10462
			}
10463
			$out .= ' >>';
10464
		}
10465
		// spot colors
10466
		if (isset($this->spot_colors) AND (count($this->spot_colors) > 0)) {
10467
			$out .= ' /ColorSpace <<';
10468
			foreach ($this->spot_colors as $color) {
10469
				$out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
10470
			}
10471
			$out .= ' >>';
10472
		}
10473
		$out .= ' >>';
10474
		$out .= "\n".'endobj';
10475
		$this->_out($out);
10476
	}
10477
 
10478
	/**
10479
	 * Output Resources.
10480
	 * @access protected
10481
	 */
10482
	protected function _putresources() {
10483
		$this->_putextgstates();
10484
		$this->_putocg();
10485
		$this->_putfonts();
10486
		$this->_putimages();
10487
		$this->_putxobjects();
10488
		$this->_putspotcolors();
10489
		$this->_putshaders();
10490
		$this->_putresourcedict();
10491
		$this->_putbookmarks();
10492
		$this->_putEmbeddedFiles();
10493
		$this->_putannotsobjs();
10494
		$this->_putjavascript();
10495
		$this->_putencryption();
10496
	}
10497
 
10498
	/**
10499
	 * Adds some Metadata information (Document Information Dictionary)
10500
	 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
10501
	 * @return int object id
10502
	 * @access protected
10503
	 */
10504
	protected function _putinfo() {
10505
		$oid = $this->_newobj();
10506
		$out = '<<';
10507
		// store current isunicode value
10508
		$prev_isunicode = $this->isunicode;
10509
		if ($this->docinfounicode) {
10510
			$this->isunicode = true;
10511
		}
10512
		if (!$this->empty_string($this->title)) {
10513
			// The document's title.
10514
			$out .= ' /Title '.$this->_textstring($this->title, $oid);
10515
		}
10516
		if (!$this->empty_string($this->author)) {
10517
			// The name of the person who created the document.
10518
			$out .= ' /Author '.$this->_textstring($this->author, $oid);
10519
		}
10520
		if (!$this->empty_string($this->subject)) {
10521
			// The subject of the document.
10522
			$out .= ' /Subject '.$this->_textstring($this->subject, $oid);
10523
		}
10524
		if (!$this->empty_string($this->keywords)) {
10525
			// Keywords associated with the document.
10526
			$out .= ' /Keywords '.$this->_textstring($this->keywords.' TCPDF', $oid);
10527
		}
10528
		if (!$this->empty_string($this->creator)) {
10529
			// 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.
10530
			$out .= ' /Creator '.$this->_textstring($this->creator, $oid);
10531
		}
10532
		// restore previous isunicode value
10533
		$this->isunicode = $prev_isunicode;
10534
		// default producer
10535
		$out .= ' /Producer '.$this->_textstring("\x54\x43\x50\x44\x46\x20".$this->tcpdf_version."\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29", $oid);
10536
		// The date and time the document was created, in human-readable form
10537
		$out .= ' /CreationDate '.$this->_datestring();
10538
		// The date and time the document was most recently modified, in human-readable form
10539
		$out .= ' /ModDate '.$this->_datestring();
10540
		// A name object indicating whether the document has been modified to include trapping information
10541
		$out .= ' /Trapped /False';
10542
		$out .= ' >>';
10543
		$out .= "\n".'endobj';
10544
		$this->_out($out);
10545
		return $oid;
10546
	}
10547
 
10548
	/**
10549
	 * Output Catalog.
10550
	 * @return int object id
10551
	 * @access protected
10552
	 */
10553
	protected function _putcatalog() {
10554
		$oid = $this->_newobj();
10555
		$out = '<< /Type /Catalog';
10556
		$out .= ' /Pages 1 0 R';
10557
		if ($this->ZoomMode == 'fullpage') {
10558
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
10559
		} elseif ($this->ZoomMode == 'fullwidth') {
10560
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
10561
		} elseif ($this->ZoomMode == 'real') {
10562
			$out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
10563
		} elseif (!is_string($this->ZoomMode)) {
10564
			$out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %.2F]',($this->ZoomMode / 100));
10565
		}
10566
		if (isset($this->LayoutMode) AND (!$this->empty_string($this->LayoutMode))) {
10567
			$out .= ' /PageLayout /'.$this->LayoutMode;
10568
		}
10569
		if (isset($this->PageMode) AND (!$this->empty_string($this->PageMode))) {
10570
			$out .= ' /PageMode /'.$this->PageMode;
10571
		}
10572
		if (isset($this->l['a_meta_language'])) {
10573
			$out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
10574
		}
10575
		$out .= ' /Names <<';
10576
		if ((!empty($this->javascript)) OR (!empty($this->js_objects))) {
10577
			$out .= ' /JavaScript '.($this->n_js).' 0 R';
10578
		}
10579
		$out .= ' >>';
10580
		if (count($this->outlines) > 0) {
10581
			$out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
10582
			$out .= ' /PageMode /UseOutlines';
10583
		}
10584
		$out .= ' '.$this->_putviewerpreferences();
10585
		$p = $this->n_ocg_print.' 0 R';
10586
		$v = $this->n_ocg_view.' 0 R';
10587
		$as = '<< /Event /Print /OCGs ['.$p.' '.$v.'] /Category [/Print] >> << /Event /View /OCGs ['.$p.' '.$v.'] /Category [/View] >>';
10588
		$out .= ' /OCProperties << /OCGs ['.$p.' '.$v.'] /D << /ON ['.$p.'] /OFF ['.$v.'] /AS ['.$as.'] >> >>';
10589
		// AcroForm
10590
		if (!empty($this->form_obj_id) OR ($this->sign AND isset($this->signature_data['cert_type']))) {
10591
			$out .= ' /AcroForm <<';
10592
			$objrefs = '';
10593
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
10594
				$objrefs .= $this->sig_obj_id.' 0 R';
10595
			}
10596
			if (!empty($this->form_obj_id)) {
10597
				foreach($this->form_obj_id as $objid) {
10598
					$objrefs .= ' '.$objid.' 0 R';
10599
				}
10600
			}
10601
			$out .= ' /Fields ['.$objrefs.']';
10602
			if (!empty($this->form_obj_id) AND !$this->sign) {
10603
				// It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
10604
				$out .= ' /NeedAppearances true';
10605
			}
10606
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
10607
				if ($this->signature_data['cert_type'] > 0) {
10608
					$out .= ' /SigFlags 3';
10609
				} else {
10610
					$out .= ' /SigFlags 1';
10611
				}
10612
			}
10613
			//$out .= ' /CO ';
10614
			if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
10615
				$out .= ' /DR <<';
10616
				$out .= ' /Font <<';
10617
				foreach ($this->annotation_fonts as $fontkey => $fontid) {
10618
					$out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
10619
				}
10620
				$out .= ' >> >>';
10621
			}
10622
			$font = $this->getFontBuffer('helvetica');
10623
			$out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
10624
			$out .= ' /Q '.(($this->rtl)?'2':'0');
10625
			//$out .= ' /XFA ';
10626
			$out .= ' >>';
10627
			// signatures
10628
			if ($this->sign AND isset($this->signature_data['cert_type'])) {
10629
				if ($this->signature_data['cert_type'] > 0) {
10630
					$out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
10631
				} else {
10632
					$out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
10633
				}
10634
			}
10635
		}
10636
		$out .= ' >>';
10637
		$out .= "\n".'endobj';
10638
		$this->_out($out);
10639
		return $oid;
10640
	}
10641
 
10642
	/**
10643
	 * Output viewer preferences.
10644
	 * @return string for viewer preferences
10645
	 * @author Nicola asuni
10646
	 * @since 3.1.000 (2008-06-09)
10647
	 * @access protected
10648
	 */
10649
	protected function _putviewerpreferences() {
10650
		$out = '/ViewerPreferences <<';
10651
		if ($this->rtl) {
10652
			$out .= ' /Direction /R2L';
10653
		} else {
10654
			$out .= ' /Direction /L2R';
10655
		}
10656
		if (isset($this->viewer_preferences['HideToolbar']) AND ($this->viewer_preferences['HideToolbar'])) {
10657
			$out .= ' /HideToolbar true';
10658
		}
10659
		if (isset($this->viewer_preferences['HideMenubar']) AND ($this->viewer_preferences['HideMenubar'])) {
10660
			$out .= ' /HideMenubar true';
10661
		}
10662
		if (isset($this->viewer_preferences['HideWindowUI']) AND ($this->viewer_preferences['HideWindowUI'])) {
10663
			$out .= ' /HideWindowUI true';
10664
		}
10665
		if (isset($this->viewer_preferences['FitWindow']) AND ($this->viewer_preferences['FitWindow'])) {
10666
			$out .= ' /FitWindow true';
10667
		}
10668
		if (isset($this->viewer_preferences['CenterWindow']) AND ($this->viewer_preferences['CenterWindow'])) {
10669
			$out .= ' /CenterWindow true';
10670
		}
10671
		if (isset($this->viewer_preferences['DisplayDocTitle']) AND ($this->viewer_preferences['DisplayDocTitle'])) {
10672
			$out .= ' /DisplayDocTitle true';
10673
		}
10674
		if (isset($this->viewer_preferences['NonFullScreenPageMode'])) {
10675
			$out .= ' /NonFullScreenPageMode /'.$this->viewer_preferences['NonFullScreenPageMode'];
10676
		}
10677
		if (isset($this->viewer_preferences['ViewArea'])) {
10678
			$out .= ' /ViewArea /'.$this->viewer_preferences['ViewArea'];
10679
		}
10680
		if (isset($this->viewer_preferences['ViewClip'])) {
10681
			$out .= ' /ViewClip /'.$this->viewer_preferences['ViewClip'];
10682
		}
10683
		if (isset($this->viewer_preferences['PrintArea'])) {
10684
			$out .= ' /PrintArea /'.$this->viewer_preferences['PrintArea'];
10685
		}
10686
		if (isset($this->viewer_preferences['PrintClip'])) {
10687
			$out .= ' /PrintClip /'.$this->viewer_preferences['PrintClip'];
10688
		}
10689
		if (isset($this->viewer_preferences['PrintScaling'])) {
10690
			$out .= ' /PrintScaling /'.$this->viewer_preferences['PrintScaling'];
10691
		}
10692
		if (isset($this->viewer_preferences['Duplex']) AND (!$this->empty_string($this->viewer_preferences['Duplex']))) {
10693
			$out .= ' /Duplex /'.$this->viewer_preferences['Duplex'];
10694
		}
10695
		if (isset($this->viewer_preferences['PickTrayByPDFSize'])) {
10696
			if ($this->viewer_preferences['PickTrayByPDFSize']) {
10697
				$out .= ' /PickTrayByPDFSize true';
10698
			} else {
10699
				$out .= ' /PickTrayByPDFSize false';
10700
			}
10701
		}
10702
		if (isset($this->viewer_preferences['PrintPageRange'])) {
10703
			$PrintPageRangeNum = '';
10704
			foreach ($this->viewer_preferences['PrintPageRange'] as $k => $v) {
10705
				$PrintPageRangeNum .= ' '.($v - 1).'';
10706
			}
10707
			$out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10708
		}
10709
		if (isset($this->viewer_preferences['NumCopies'])) {
10710
			$out .= ' /NumCopies '.intval($this->viewer_preferences['NumCopies']);
10711
		}
10712
		$out .= ' >>';
10713
		return $out;
10714
	}
10715
 
10716
	/**
10717
	 * Output PDF header.
10718
	 * @access protected
10719
	 */
10720
	protected function _putheader() {
10721
		$this->_out('%PDF-'.$this->PDFVersion);
10722
	}
10723
 
10724
	/**
10725
	 * Output end of document (EOF).
10726
	 * @access protected
10727
	 */
10728
	protected function _enddoc() {
10729
		$this->state = 1;
10730
		$this->_putheader();
10731
		$this->_putpages();
10732
		$this->_putresources();
10733
		// Signature
10734
		if ($this->sign AND isset($this->signature_data['cert_type'])) {
10735
			// widget annotation for signature
10736
			$out = $this->_getobj($this->sig_obj_id)."\n";
10737
			$out .= '<< /Type /Annot';
10738
			$out .= ' /Subtype /Widget';
10739
			$out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10740
			$out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10741
			$out .= ' /F 4';
10742
			$out .= ' /FT /Sig';
10743
			$out .= ' /T '.$this->_textstring('Signature', $this->sig_obj_id);
10744
			$out .= ' /Ff 0';
10745
			$out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10746
			$out .= ' >>';
10747
			$out .= "\n".'endobj';
10748
			$this->_out($out);
10749
			// signature
10750
			$this->_putsignature();
10751
		}
10752
		// Info
10753
		$objid_info = $this->_putinfo();
10754
		// Catalog
10755
		$objid_catalog = $this->_putcatalog();
10756
		// Cross-ref
10757
		$o = $this->bufferlen;
10758
		// XREF section
10759
		$this->_out('xref');
10760
		$this->_out('0 '.($this->n + 1));
10761
		$this->_out('0000000000 65535 f ');
10762
		for ($i=1; $i <= $this->n; ++$i) {
10763
			$this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10764
		}
10765
		// TRAILER
10766
		$out = 'trailer <<';
10767
		$out .= ' /Size '.($this->n + 1);
10768
		$out .= ' /Root '.$objid_catalog.' 0 R';
10769
		$out .= ' /Info '.$objid_info.' 0 R';
10770
		if ($this->encrypted) {
10771
			$out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10772
		}
10773
		$out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10774
		$out .= ' >>';
10775
		$this->_out($out);
10776
		$this->_out('startxref');
10777
		$this->_out($o);
10778
		$this->_out('%%EOF');
10779
		$this->state = 3; // end-of-doc
10780
		if ($this->diskcache) {
10781
			// remove temporary files used for images
10782
			foreach ($this->imagekeys as $key) {
10783
				// remove temporary files
10784
				unlink($this->images[$key]);
10785
			}
10786
			foreach ($this->fontkeys as $key) {
10787
				// remove temporary files
10788
				unlink($this->fonts[$key]);
10789
			}
10790
		}
10791
	}
10792
 
10793
	/**
10794
	 * Initialize a new page.
10795
	 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10796
	 * @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().
10797
	 * @access protected
10798
	 * @see getPageSizeFromFormat(), setPageFormat()
10799
	 */
10800
	protected function _beginpage($orientation='', $format='') {
10801
		++$this->page;
10802
		$this->setPageBuffer($this->page, '');
10803
		// initialize array for graphics tranformation positions inside a page buffer
10804
		$this->transfmrk[$this->page] = array();
10805
		$this->state = 2;
10806
		if ($this->empty_string($orientation)) {
10807
			if (isset($this->CurOrientation)) {
10808
				$orientation = $this->CurOrientation;
10809
			} elseif ($this->fwPt > $this->fhPt) {
10810
				// landscape
10811
				$orientation = 'L';
10812
			} else {
10813
				// portrait
10814
				$orientation = 'P';
10815
			}
10816
		}
10817
		if ($this->empty_string($format)) {
10818
			$this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10819
			$this->setPageOrientation($orientation);
10820
		} else {
10821
			$this->setPageFormat($format, $orientation);
10822
		}
10823
		if ($this->rtl) {
10824
			$this->x = $this->w - $this->rMargin;
10825
		} else {
10826
			$this->x = $this->lMargin;
10827
		}
10828
		$this->y = $this->tMargin;
10829
		if (isset($this->newpagegroup[$this->page])) {
10830
			// start a new group
10831
			$n = sizeof($this->pagegroups) + 1;
10832
			$alias = '{nb'.$n.'}';
10833
			$this->pagegroups[$alias] = 1;
10834
			$this->currpagegroup = $alias;
10835
		} elseif ($this->currpagegroup) {
10836
			++$this->pagegroups[$this->currpagegroup];
10837
		}
10838
	}
10839
 
10840
	/**
10841
	 * Mark end of page.
10842
	 * @access protected
10843
	 */
10844
	protected function _endpage() {
10845
		$this->setVisibility('all');
10846
		$this->state = 1;
10847
	}
10848
 
10849
	/**
10850
	 * Begin a new object and return the object number.
10851
	 * @return int object number
10852
	 * @access protected
10853
	 */
10854
	protected function _newobj() {
10855
		$this->_out($this->_getobj());
10856
		return $this->n;
10857
	}
10858
 
10859
	/**
10860
	 * Return the starting object string for the selected object ID.
10861
	 * @param int $objid Object ID (leave empty to get a new ID).
10862
	 * @return string the starting object string
10863
	 * @access protected
10864
	 * @since 5.8.009 (2010-08-20)
10865
	 */
10866
	protected function _getobj($objid='') {
10867
		if ($objid === '') {
10868
			++$this->n;
10869
			$objid = $this->n;
10870
		}
10871
		$this->offsets[$objid] = $this->bufferlen;
10872
		return $objid.' 0 obj';
10873
	}
10874
 
10875
	/**
10876
	 * Underline text.
10877
	 * @param int $x X coordinate
10878
	 * @param int $y Y coordinate
10879
	 * @param string $txt text to underline
10880
	 * @access protected
10881
	 */
10882
	protected function _dounderline($x, $y, $txt) {
10883
		$w = $this->GetStringWidth($txt);
10884
		return $this->_dounderlinew($x, $y, $w);
10885
	}
10886
 
10887
	/**
10888
	 * Underline for rectangular text area.
10889
	 * @param int $x X coordinate
10890
	 * @param int $y Y coordinate
10891
	 * @param int $w width to underline
10892
	 * @access protected
10893
	 * @since 4.8.008 (2009-09-29)
10894
	 */
10895
	protected function _dounderlinew($x, $y, $w) {
10896
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10897
		return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10898
	}
10899
 
10900
	/**
10901
	 * Line through text.
10902
	 * @param int $x X coordinate
10903
	 * @param int $y Y coordinate
10904
	 * @param string $txt text to linethrough
10905
	 * @access protected
10906
	 */
10907
	protected function _dolinethrough($x, $y, $txt) {
10908
		$w = $this->GetStringWidth($txt);
10909
		return $this->_dolinethroughw($x, $y, $w);
10910
	}
10911
 
10912
	/**
10913
	 * Line through for rectangular text area.
10914
	 * @param int $x X coordinate
10915
	 * @param int $y Y coordinate
10916
	 * @param string $txt text to linethrough
10917
	 * @access protected
10918
	 * @since 4.9.008 (2009-09-29)
10919
	 */
10920
	protected function _dolinethroughw($x, $y, $w) {
10921
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10922
		return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10923
	}
10924
 
10925
	/**
10926
	 * Overline text.
10927
	 * @param int $x X coordinate
10928
	 * @param int $y Y coordinate
10929
	 * @param string $txt text to overline
10930
	 * @access protected
10931
	 * @since 4.9.015 (2010-04-19)
10932
	 */
10933
	protected function _dooverline($x, $y, $txt) {
10934
		$w = $this->GetStringWidth($txt);
10935
		return $this->_dooverlinew($x, $y, $w);
10936
	}
10937
 
10938
	/**
10939
	 * Overline for rectangular text area.
10940
	 * @param int $x X coordinate
10941
	 * @param int $y Y coordinate
10942
	 * @param int $w width to overline
10943
	 * @access protected
10944
	 * @since 4.9.015 (2010-04-19)
10945
	 */
10946
	protected function _dooverlinew($x, $y, $w) {
10947
		$linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10948
		return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10949
 
10950
	}
10951
 
10952
	/**
10953
	 * Read a 4-byte (32 bit) integer from file.
10954
	 * @param string $f file name.
10955
	 * @return 4-byte integer
10956
	 * @access protected
10957
	 */
10958
	protected function _freadint($f) {
10959
		$a = unpack('Ni', fread($f, 4));
10960
		return $a['i'];
10961
	}
10962
 
10963
	/**
10964
	 * Add "\" before "\", "(" and ")"
10965
	 * @param string $s string to escape.
10966
	 * @return string escaped string.
10967
	 * @access protected
10968
	 */
10969
	protected function _escape($s) {
10970
		// the chr(13) substitution fixes the Bugs item #1421290.
10971
		return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
10972
	}
10973
 
10974
	/**
10975
	 * Format a data string for meta information
10976
	 * @param string $s data string to escape.
10977
	 * @param int $n object ID
10978
	 * @return string escaped string.
10979
	 * @access protected
10980
	 */
10981
	protected function _datastring($s, $n=0) {
10982
		if ($n == 0) {
10983
			$n = $this->n;
10984
		}
10985
		$s = $this->_encrypt_data($n, $s);
10986
		return '('. $this->_escape($s).')';
10987
	}
10988
 
10989
	/**
10990
	 * Returns a formatted date for meta information
10991
	 * @param int $n object ID
10992
	 * @return string escaped date string.
10993
	 * @access protected
10994
	 * @since 4.6.028 (2009-08-25)
10995
	 */
10996
	protected function _datestring($n=0) {
10997
		$current_time = substr_replace(date('YmdHisO'), '\'', (0 - 2), 0).'\'';
10998
		return $this->_datastring('D:'.$current_time, $n);
10999
	}
11000
 
11001
	/**
11002
	 * Format a text string for meta information
11003
	 * @param string $s string to escape.
11004
	 * @param int $n object ID
11005
	 * @return string escaped string.
11006
	 * @access protected
11007
	 */
11008
	protected function _textstring($s, $n=0) {
11009
		if ($this->isunicode) {
11010
			//Convert string to UTF-16BE
11011
			$s = $this->UTF8ToUTF16BE($s, true);
11012
		}
11013
		return $this->_datastring($s, $n);
11014
	}
11015
 
11016
	/**
11017
	 * THIS METHOD IS DEPRECATED
11018
	 * Format a text string
11019
	 * @param string $s string to escape.
11020
	 * @return string escaped string.
11021
	 * @access protected
11022
	 * @deprecated
11023
	 */
11024
	protected function _escapetext($s) {
11025
		if ($this->isunicode) {
11026
			if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
11027
				$s = $this->UTF8ToLatin1($s);
11028
			} else {
11029
				//Convert string to UTF-16BE and reverse RTL language
11030
				$s = $this->utf8StrRev($s, false, $this->tmprtl);
11031
			}
11032
		}
11033
		return $this->_escape($s);
11034
	}
11035
 
11036
	/**
11037
	 * get raw output stream.
11038
	 * @param string $s string to output.
11039
	 * @param int $n object reference for encryption mode
11040
	 * @access protected
11041
	 * @author Nicola Asuni
11042
	 * @since 5.5.000 (2010-06-22)
11043
	 */
11044
	protected function _getrawstream($s, $n=0) {
11045
		if ($n <= 0) {
11046
			// default to current object
11047
			$n = $this->n;
11048
		}
11049
		return $this->_encrypt_data($n, $s);
11050
	}
11051
 
11052
	/**
11053
	 * Format output stream (DEPRECATED).
11054
	 * @param string $s string to output.
11055
	 * @param int $n object reference for encryption mode
11056
	 * @access protected
11057
	 * @deprecated
11058
	 */
11059
	protected function _getstream($s, $n=0) {
11060
		return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
11061
	}
11062
 
11063
	/**
11064
	 * Output a stream (DEPRECATED).
11065
	 * @param string $s string to output.
11066
	 * @param int $n object reference for encryption mode
11067
	 * @access protected
11068
	 * @deprecated
11069
	 */
11070
	protected function _putstream($s, $n=0) {
11071
		$this->_out($this->_getstream($s, $n));
11072
	}
11073
 
11074
	/**
11075
	 * Output a string to the document.
11076
	 * @param string $s string to output.
11077
	 * @access protected
11078
	 */
11079
	protected function _out($s) {
11080
		if ($this->state == 2) {
11081
			if ($this->inxobj) {
11082
				// we are inside an XObject template
11083
				$this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
11084
			} elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
11085
				// puts data before page footer
11086
				$pagebuff = $this->getPageBuffer($this->page);
11087
				$page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
11088
				$footer = substr($pagebuff, -$this->footerlen[$this->page]);
11089
				$this->setPageBuffer($this->page, $page.$s."\n".$footer);
11090
				// update footer position
11091
				$this->footerpos[$this->page] += strlen($s."\n");
11092
			} else {
11093
				$this->setPageBuffer($this->page, $s."\n", true);
11094
			}
11095
		} else {
11096
			$this->setBuffer($s."\n");
11097
		}
11098
	}
11099
 
11100
	/**
11101
	 * Converts UTF-8 strings to codepoints array.<br>
11102
	 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
11103
	 * Based on: http://www.faqs.org/rfcs/rfc3629.html
11104
	 * <pre>
11105
	 *    Char. number range  |        UTF-8 octet sequence
11106
	 *       (hexadecimal)    |              (binary)
11107
	 *    --------------------+-----------------------------------------------
11108
	 *    0000 0000-0000 007F | 0xxxxxxx
11109
	 *    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
11110
	 *    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
11111
	 *    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
11112
	 *    ---------------------------------------------------------------------
11113
	 *
11114
	 *   ABFN notation:
11115
	 *   ---------------------------------------------------------------------
11116
	 *   UTF8-octets = *( UTF8-char )
11117
	 *   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
11118
	 *   UTF8-1      = %x00-7F
11119
	 *   UTF8-2      = %xC2-DF UTF8-tail
11120
	 *
11121
	 *   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
11122
	 *                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
11123
	 *   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
11124
	 *                 %xF4 %x80-8F 2( UTF8-tail )
11125
	 *   UTF8-tail   = %x80-BF
11126
	 *   ---------------------------------------------------------------------
11127
	 * </pre>
11128
	 * @param string $str string to process.
11129
	 * @return array containing codepoints (UTF-8 characters values)
11130
	 * @access protected
11131
	 * @author Nicola Asuni
11132
	 * @since 1.53.0.TC005 (2005-01-05)
11133
	 */
11134
	protected function UTF8StringToArray($str) {
11135
		// build a unique string key
11136
		$strkey = md5($str);
11137
		if (isset($this->cache_UTF8StringToArray[$strkey])) {
11138
			// return cached value
11139
			$chrarray = $this->cache_UTF8StringToArray[$strkey]['s'];
11140
			if (!isset($this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']])) {
11141
				if ($this->isunicode) {
11142
					foreach ($chrarray as $chr) {
11143
						// store this char for font subsetting
11144
						$this->CurrentFont['subsetchars'][$chr] = true;
11145
					}
11146
					// update font subsetchars
11147
					$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
11148
				}
11149
				$this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
11150
			}
11151
			return $chrarray;
11152
		}
11153
		// check cache size
11154
		if ($this->cache_size_UTF8StringToArray >= $this->cache_maxsize_UTF8StringToArray) {
11155
			// remove first element
11156
			array_shift($this->cache_UTF8StringToArray);
11157
		}
11158
		// new cache array for selected string
11159
		$this->cache_UTF8StringToArray[$strkey] = array('s' => array(), 'f' => array());
11160
		++$this->cache_size_UTF8StringToArray;
11161
		if (!$this->isunicode) {
11162
			// split string into array of equivalent codes
11163
			$strarr = array();
11164
			$strlen = strlen($str);
11165
			for ($i=0; $i < $strlen; ++$i) {
11166
				$strarr[] = ord($str{$i});
11167
			}
11168
			// insert new value on cache
11169
			$this->cache_UTF8StringToArray[$strkey]['s'] = $strarr;
11170
			$this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
11171
			return $strarr;
11172
		}
11173
		$unichar = -1; // last unicode char
11174
		$unicode = array(); // array containing unicode values
11175
		$bytes  = array(); // array containing single character byte sequences
11176
		$numbytes = 1; // number of octetc needed to represent the UTF-8 character
11177
		$str .= ''; // force $str to be a string
11178
		$length = strlen($str);
11179
		for ($i = 0; $i < $length; ++$i) {
11180
			$char = ord($str{$i}); // get one string character at time
11181
			if (count($bytes) == 0) { // get starting octect
11182
				if ($char <= 0x7F) {
11183
					$unichar = $char; // use the character "as is" because is ASCII
11184
					$numbytes = 1;
11185
				} elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
11186
					$bytes[] = ($char - 0xC0) << 0x06;
11187
					$numbytes = 2;
11188
				} elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
11189
					$bytes[] = ($char - 0xE0) << 0x0C;
11190
					$numbytes = 3;
11191
				} elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
11192
					$bytes[] = ($char - 0xF0) << 0x12;
11193
					$numbytes = 4;
11194
				} else {
11195
					// use replacement character for other invalid sequences
11196
					$unichar = 0xFFFD;
11197
					$bytes = array();
11198
					$numbytes = 1;
11199
				}
11200
			} elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
11201
				$bytes[] = $char - 0x80;
11202
				if (count($bytes) == $numbytes) {
11203
					// compose UTF-8 bytes to a single unicode value
11204
					$char = $bytes[0];
11205
					for ($j = 1; $j < $numbytes; ++$j) {
11206
						$char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
11207
					}
11208
					if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
11209
						/* The definition of UTF-8 prohibits encoding character numbers between
11210
						U+D800 and U+DFFF, which are reserved for use with the UTF-16
11211
						encoding form (as surrogate pairs) and do not directly represent
11212
						characters. */
11213
						$unichar = 0xFFFD; // use replacement character
11214
					} else {
11215
						$unichar = $char; // add char to array
11216
					}
11217
					// reset data for next char
11218
					$bytes = array();
11219
					$numbytes = 1;
11220
				}
11221
			} else {
11222
				// use replacement character for other invalid sequences
11223
				$unichar = 0xFFFD;
11224
				$bytes = array();
11225
				$numbytes = 1;
11226
			}
11227
			if ($unichar >= 0) {
11228
				// insert unicode value into array
11229
				$unicode[] = $unichar;
11230
				// store this char for font subsetting
11231
				$this->CurrentFont['subsetchars'][$unichar] = true;
11232
				$unichar = -1;
11233
			}
11234
		}
11235
		// update font subsetchars
11236
		$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
11237
		// insert new value on cache
11238
		$this->cache_UTF8StringToArray[$strkey]['s'] = $unicode;
11239
		$this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
11240
		return $unicode;
11241
	}
11242
 
11243
	/**
11244
	 * Converts UTF-8 strings to UTF16-BE.<br>
11245
	 * @param string $str string to process.
11246
	 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
11247
	 * @return string
11248
	 * @access protected
11249
	 * @author Nicola Asuni
11250
	 * @since 1.53.0.TC005 (2005-01-05)
11251
	 * @uses UTF8StringToArray(), arrUTF8ToUTF16BE()
11252
	 */
11253
	protected function UTF8ToUTF16BE($str, $setbom=true) {
11254
		if (!$this->isunicode) {
11255
			return $str; // string is not in unicode
11256
		}
11257
		$unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
11258
		return $this->arrUTF8ToUTF16BE($unicode, $setbom);
11259
	}
11260
 
11261
	/**
11262
	 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
11263
	 * @param string $str string to process.
11264
	 * @return string
11265
	 * @author Andrew Whitehead, Nicola Asuni
11266
	 * @access protected
11267
	 * @since 3.2.000 (2008-06-23)
11268
	 */
11269
	protected function UTF8ToLatin1($str) {
11270
		if (!$this->isunicode) {
11271
			return $str; // string is not in unicode
11272
		}
11273
		$outstr = ''; // string to be returned
11274
		$unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
11275
		foreach ($unicode as $char) {
11276
			if ($char < 256) {
11277
				$outstr .= chr($char);
11278
			} elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
11279
				// map from UTF-8
11280
				$outstr .= chr($this->unicode->uni_utf8tolatin[$char]);
11281
			} elseif ($char == 0xFFFD) {
11282
				// skip
11283
			} else {
11284
				$outstr .= '?';
11285
			}
11286
		}
11287
		return $outstr;
11288
	}
11289
 
11290
	/**
11291
	 * Converts UTF-8 characters array to array of Latin1 characters<br>
11292
	 * @param array $unicode array containing UTF-8 unicode values
11293
	 * @return array
11294
	 * @author Nicola Asuni
11295
	 * @access protected
11296
	 * @since 4.8.023 (2010-01-15)
11297
	 */
11298
	protected function UTF8ArrToLatin1($unicode) {
11299
		if ((!$this->isunicode) OR $this->isUnicodeFont()) {
11300
			return $unicode;
11301
		}
11302
		$outarr = array(); // array to be returned
11303
		foreach ($unicode as $char) {
11304
			if ($char < 256) {
11305
				$outarr[] = $char;
11306
			} elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
11307
				// map from UTF-8
11308
				$outarr[] = $this->unicode->uni_utf8tolatin[$char];
11309
			} elseif ($char == 0xFFFD) {
11310
				// skip
11311
			} else {
11312
				$outarr[] = 63; // '?' character
11313
			}
11314
		}
11315
		return $outarr;
11316
	}
11317
 
11318
	/**
11319
	 * Converts array of UTF-8 characters to UTF16-BE string.<br>
11320
	 * Based on: http://www.faqs.org/rfcs/rfc2781.html
11321
	 * <pre>
11322
	 *   Encoding UTF-16:
11323
	 *
11324
	 *   Encoding of a single character from an ISO 10646 character value to
11325
	 *    UTF-16 proceeds as follows. Let U be the character number, no greater
11326
	 *    than 0x10FFFF.
11327
	 *
11328
	 *    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
11329
	 *       terminate.
11330
	 *
11331
	 *    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
11332
	 *       U' must be less than or equal to 0xFFFFF. That is, U' can be
11333
	 *       represented in 20 bits.
11334
	 *
11335
	 *    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
11336
	 *       0xDC00, respectively. These integers each have 10 bits free to
11337
	 *       encode the character value, for a total of 20 bits.
11338
	 *
11339
	 *    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
11340
	 *       bits of W1 and the 10 low-order bits of U' to the 10 low-order
11341
	 *       bits of W2. Terminate.
11342
	 *
11343
	 *    Graphically, steps 2 through 4 look like:
11344
	 *    U' = yyyyyyyyyyxxxxxxxxxx
11345
	 *    W1 = 110110yyyyyyyyyy
11346
	 *    W2 = 110111xxxxxxxxxx
11347
	 * </pre>
11348
	 * @param array $unicode array containing UTF-8 unicode values
11349
	 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
11350
	 * @return string
11351
	 * @access protected
11352
	 * @author Nicola Asuni
11353
	 * @since 2.1.000 (2008-01-08)
11354
	 * @see UTF8ToUTF16BE()
11355
	 */
11356
	protected function arrUTF8ToUTF16BE($unicode, $setbom=true) {
11357
		$outstr = ''; // string to be returned
11358
		if ($setbom) {
11359
			$outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
11360
		}
11361
		foreach ($unicode as $char) {
11362
			if ($char == 0x200b) {
11363
				// skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
11364
			} elseif ($char == 0xFFFD) {
11365
				$outstr .= "\xFF\xFD"; // replacement character
11366
			} elseif ($char < 0x10000) {
11367
				$outstr .= chr($char >> 0x08);
11368
				$outstr .= chr($char & 0xFF);
11369
			} else {
11370
				$char -= 0x10000;
11371
				$w1 = 0xD800 | ($char >> 0x10);
11372
				$w2 = 0xDC00 | ($char & 0x3FF);
11373
				$outstr .= chr($w1 >> 0x08);
11374
				$outstr .= chr($w1 & 0xFF);
11375
				$outstr .= chr($w2 >> 0x08);
11376
				$outstr .= chr($w2 & 0xFF);
11377
			}
11378
		}
11379
		return $outstr;
11380
	}
11381
	// ====================================================
11382
 
11383
	/**
11384
	 * Set header font.
11385
	 * @param array $font font
11386
	 * @access public
11387
	 * @since 1.1
11388
	 */
11389
	public function setHeaderFont($font) {
11390
		$this->header_font = $font;
11391
	}
11392
 
11393
	/**
11394
	 * Get header font.
11395
	 * @return array()
11396
	 * @access public
11397
	 * @since 4.0.012 (2008-07-24)
11398
	 */
11399
	public function getHeaderFont() {
11400
		return $this->header_font;
11401
	}
11402
 
11403
	/**
11404
	 * Set footer font.
11405
	 * @param array $font font
11406
	 * @access public
11407
	 * @since 1.1
11408
	 */
11409
	public function setFooterFont($font) {
11410
		$this->footer_font = $font;
11411
	}
11412
 
11413
	/**
11414
	 * Get Footer font.
11415
	 * @return array()
11416
	 * @access public
11417
	 * @since 4.0.012 (2008-07-24)
11418
	 */
11419
	public function getFooterFont() {
11420
		return $this->footer_font;
11421
	}
11422
 
11423
	/**
11424
	 * Set language array.
11425
	 * @param array $language
11426
	 * @access public
11427
	 * @since 1.1
11428
	 */
11429
	public function setLanguageArray($language) {
11430
		$this->l = $language;
11431
		if (isset($this->l['a_meta_dir'])) {
11432
			$this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
11433
		} else {
11434
			$this->rtl = false;
11435
		}
11436
	}
11437
 
11438
	/**
11439
	 * Returns the PDF data.
11440
	 * @access public
11441
	 */
11442
	public function getPDFData() {
11443
		if ($this->state < 3) {
11444
			$this->Close();
11445
		}
11446
		return $this->buffer;
11447
	}
11448
 
11449
	/**
11450
	 * Output anchor link.
11451
	 * @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;)
11452
	 * @param string $name link name
11453
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
11454
	 * @param boolean $firstline if true prints only the first line and return the remaining string.
11455
	 * @param array $color array of RGB text color
11456
	 * @param string $style font style (U, D, B, I)
11457
	 * @param boolean $firstblock if true the string is the starting of a line.
11458
	 * @return the number of cells used or the remaining text if $firstline = true;
11459
	 * @access public
11460
	 */
11461
	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
11462
		if (!$this->empty_string($url) AND ($url{0} == '#')) {
11463
			// convert url to internal link
11464
			$lnkdata = explode(',', $url);
11465
			if (isset($lnkdata[0])) {
11466
				$page = intval(substr($lnkdata[0], 1));
11467
				if (empty($page) OR ($page <= 0)) {
11468
					$page = $this->page;
11469
				}
11470
				if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
11471
					$lnky = floatval($lnkdata[1]);
11472
				} else {
11473
					$lnky = 0;
11474
				}
11475
				$url = $this->AddLink();
11476
				$this->SetLink($url, $lnky, $page);
11477
			}
11478
		}
11479
		// store current settings
11480
		$prevcolor = $this->fgcolor;
11481
		$prevstyle = $this->FontStyle;
11482
		if (empty($color)) {
11483
			$this->SetTextColorArray($this->htmlLinkColorArray);
11484
		} else {
11485
			$this->SetTextColorArray($color);
11486
		}
11487
		if ($style == -1) {
11488
			$this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
11489
		} else {
11490
			$this->SetFont('', $this->FontStyle.$style);
11491
		}
11492
		$ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
11493
		// restore settings
11494
		$this->SetFont('', $prevstyle);
11495
		$this->SetTextColorArray($prevcolor);
11496
		return $ret;
11497
	}
11498
 
11499
	/**
11500
	 * Returns an array (RGB or CMYK) from an html color name or a six-digit (i.e. #3FE5AA) or three-digit (i.e. #7FF) hexadecimal color representation.
11501
	 * @param string $hcolor html color
11502
	 * @return array RGB or CMYK color, or false in case of error.
11503
	 * @access public
11504
	 */
11505
	public function convertHTMLColorToDec($hcolor='#FFFFFF') {
11506
		$returncolor = false;
11507
		$color = preg_replace('/[\s]*/', '', $hcolor); // remove extra spaces
11508
		$color = strtolower($color);
11509
		if (($dotpos = strpos($color, '.')) !== false) {
11510
			// remove class parent (i.e.: color.red)
11511
			$color = substr($color, ($dotpos + 1));
11512
		}
11513
		if (strlen($color) == 0) {
11514
			return false;
11515
		}
11516
		// RGB ARRAY
11517
		if (substr($color, 0, 3) == 'rgb') {
11518
			$codes = substr($color, 4);
11519
			$codes = str_replace(')', '', $codes);
11520
			$returncolor = explode(',', $codes);
11521
			foreach ($returncolor as $key => $val) {
11522
				if (strpos($val, '%') > 0) {
11523
					// percentage
11524
					$returncolor[$key] = (255 * intval($val) / 100);
11525
				} else {
11526
					$returncolor[$key] = intval($val);
11527
				}
11528
				// normalize value
11529
				$returncolor[$key] = max(0, min(255, $returncolor[$key]));
11530
			}
11531
			return $returncolor;
11532
		}
11533
		// CMYK ARRAY
11534
		if (substr($color, 0, 4) == 'cmyk') {
11535
			$codes = substr($color, 5);
11536
			$codes = str_replace(')', '', $codes);
11537
			$returncolor = explode(',', $codes);
11538
			foreach ($returncolor as $key => $val) {
11539
				if (strpos($val, '%') !== false) {
11540
					// percentage
11541
					$returncolor[$key] = (100 * intval($val) / 100);
11542
				} else {
11543
					$returncolor[$key] = intval($val);
11544
				}
11545
				// normalize value
11546
				$returncolor[$key] = max(0, min(100, $returncolor[$key]));
11547
			}
11548
			return $returncolor;
11549
		}
11550
		// COLOR NAME
11551
		if (substr($color, 0, 1) != '#') {
11552
			// decode color name
11553
			if (isset($this->webcolor[$color])) {
11554
				// web color
11555
				$color_code = $this->webcolor[$color];
11556
			} elseif (isset($this->spot_colors[$hcolor])) {
11557
				// custom defined spot color
11558
				return array($this->spot_colors[$hcolor]['c'], $this->spot_colors[$hcolor]['m'], $this->spot_colors[$hcolor]['y'], $this->spot_colors[$hcolor]['k'], $hcolor);
11559
			} elseif (isset($this->spotcolor[$color])) {
11560
				// spot color from configuration file
11561
				return $this->spotcolor[$color];
11562
			} else {
11563
				return false;
11564
			}
11565
		} else {
11566
			$color_code = substr($color, 1);
11567
		}
11568
		// RGB VALUE
11569
		switch (strlen($color_code)) {
11570
			case 3: {
11571
				// three-digit hexadecimal representation
11572
				$r = substr($color_code, 0, 1);
11573
				$g = substr($color_code, 1, 1);
11574
				$b = substr($color_code, 2, 1);
11575
				$returncolor = array();
11576
				$returncolor['R'] = max(0, min(255, hexdec($r.$r)));
11577
				$returncolor['G'] = max(0, min(255, hexdec($g.$g)));
11578
				$returncolor['B'] = max(0, min(255, hexdec($b.$b)));
11579
				break;
11580
			}
11581
			case 6: {
11582
				// six-digit hexadecimal representation
11583
				$returncolor = array();
11584
				$returncolor['R'] = max(0, min(255, hexdec(substr($color_code, 0, 2))));
11585
				$returncolor['G'] = max(0, min(255, hexdec(substr($color_code, 2, 2))));
11586
				$returncolor['B'] = max(0, min(255, hexdec(substr($color_code, 4, 2))));
11587
				break;
11588
			}
11589
		}
11590
		return $returncolor;
11591
	}
11592
 
11593
	/**
11594
	 * Converts pixels to User's Units.
11595
	 * @param int $px pixels
11596
	 * @return float value in user's unit
11597
	 * @access public
11598
	 * @see setImageScale(), getImageScale()
11599
	 */
11600
	public function pixelsToUnits($px) {
11601
		return ($px / ($this->imgscale * $this->k));
11602
	}
11603
 
11604
	/**
11605
	 * Reverse function for htmlentities.
11606
	 * Convert entities in UTF-8.
11607
	 * @param string $text_to_convert Text to convert.
11608
	 * @return string converted text string
11609
	 * @access public
11610
	 */
11611
	public function unhtmlentities($text_to_convert) {
11612
		return html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
11613
	}
11614
 
11615
	// ENCRYPTION METHODS ----------------------------------
11616
 
11617
	/**
11618
	 * Returns a string containing random data to be used as a seed for encryption methods.
11619
	 * @param string $seed starting seed value
11620
	 * @return string containing random data
11621
	 * @author Nicola Asuni
11622
	 * @since 5.9.006 (2010-10-19)
11623
	 * @access protected
11624
	 */
11625
	protected function getRandomSeed($seed='') {
11626
		$seed .= microtime();
11627
		if (function_exists('openssl_random_pseudo_bytes')) {
11628
			$seed .= openssl_random_pseudo_bytes(512);
11629
		}
11630
		$seed .= uniqid('', true);
11631
		$seed .= rand();
11632
		$seed .= getmypid();
11633
		$seed .= __FILE__;
11634
		$seed .= $this->bufferlen;
11635
		if (isset($_SERVER['REMOTE_ADDR'])) {
11636
			$seed .= $_SERVER['REMOTE_ADDR'];
11637
		}
11638
		if (isset($_SERVER['HTTP_USER_AGENT'])) {
11639
			$seed .= $_SERVER['HTTP_USER_AGENT'];
11640
		}
11641
		if (isset($_SERVER['HTTP_ACCEPT'])) {
11642
			$seed .= $_SERVER['HTTP_ACCEPT'];
11643
		}
11644
		if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
11645
			$seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
11646
		}
11647
		if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
11648
			$seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
11649
		}
11650
		if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
11651
			$seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
11652
		}
11653
		$seed .= rand();
11654
		$seed .= uniqid('', true);
11655
		$seed .= microtime();
11656
		return $seed;
11657
	}
11658
 
11659
	/**
11660
	 * Compute encryption key depending on object number where the encrypted data is stored.
11661
	 * This is used for all strings and streams without crypt filter specifier.
11662
	 * @param int $n object number
11663
	 * @return int object key
11664
	 * @access protected
11665
	 * @author Nicola Asuni
11666
	 * @since 2.0.000 (2008-01-02)
11667
	 */
11668
	protected function _objectkey($n) {
11669
		$objkey = $this->encryptdata['key'].pack('VXxx', $n);
11670
		if ($this->encryptdata['mode'] == 2) { // AES-128
11671
			// AES padding
11672
			$objkey .= "\x73\x41\x6C\x54"; // sAlT
11673
		}
11674
		$objkey = substr($this->_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
11675
		$objkey = substr($objkey, 0, 16);
11676
		return $objkey;
11677
	}
11678
 
11679
	/**
11680
	 * Encrypt the input string.
11681
	 * @param int $n object number
11682
	 * @param string $s data string to encrypt
11683
	 * @return encrypted string
11684
	 * @access protected
11685
	 * @author Nicola Asuni
11686
	 * @since 5.0.005 (2010-05-11)
11687
	 */
11688
	protected function _encrypt_data($n, $s) {
11689
		if (!$this->encrypted) {
11690
			return $s;
11691
		}
11692
		switch ($this->encryptdata['mode']) {
11693
			case 0:   // RC4-40
11694
			case 1: { // RC4-128
11695
				$s = $this->_RC4($this->_objectkey($n), $s);
11696
				break;
11697
			}
11698
			case 2: { // AES-128
11699
				$s = $this->_AES($this->_objectkey($n), $s);
11700
				break;
11701
			}
11702
			case 3: { // AES-256
11703
				$s = $this->_AES($this->encryptdata['key'], $s);
11704
				break;
11705
			}
11706
		}
11707
		return $s;
11708
	}
11709
 
11710
	/**
11711
	 * Put encryption on PDF document.
11712
	 * @access protected
11713
	 * @author Nicola Asuni
11714
	 * @since 2.0.000 (2008-01-02)
11715
	 */
11716
	protected function _putencryption() {
11717
		if (!$this->encrypted) {
11718
			return;
11719
		}
11720
		$this->encryptdata['objid'] = $this->_newobj();
11721
		$out = '<<';
11722
		if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
11723
			$this->encryptdata['Filter'] = 'Standard';
11724
		}
11725
		$out .= ' /Filter /'.$this->encryptdata['Filter'];
11726
		if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
11727
			$out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
11728
		}
11729
		if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
11730
			$this->encryptdata['V'] = 1;
11731
		}
11732
		// V is a code specifying the algorithm to be used in encrypting and decrypting the document
11733
		$out .= ' /V '.$this->encryptdata['V'];
11734
		if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
11735
			// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
11736
			$out .= ' /Length '.$this->encryptdata['Length'];
11737
		} else {
11738
			$out .= ' /Length 40';
11739
		}
11740
		if ($this->encryptdata['V'] >= 4) {
11741
			if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
11742
				$this->encryptdata['StmF'] = 'Identity';
11743
			}
11744
			if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
11745
				// The name of the crypt filter that shall be used when decrypting all strings in the document.
11746
				$this->encryptdata['StrF'] = 'Identity';
11747
			}
11748
			// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
11749
			if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
11750
				$out .= ' /CF <<';
11751
				$out .= ' /'.$this->encryptdata['StmF'].' <<';
11752
				$out .= ' /Type /CryptFilter';
11753
				if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
11754
					// The method used
11755
					$out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
11756
					if ($this->encryptdata['pubkey']) {
11757
						$out .= ' /Recipients [';
11758
						foreach ($this->encryptdata['Recipients'] as $rec) {
11759
							$out .= ' <'.$rec.'>';
11760
						}
11761
						$out .= ' ]';
11762
						if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
11763
							$out .= ' /EncryptMetadata false';
11764
						} else {
11765
							$out .= ' /EncryptMetadata true';
11766
						}
11767
					}
11768
				} else {
11769
					$out .= ' /CFM /None';
11770
				}
11771
				if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
11772
					// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
11773
					$out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
11774
				} else {
11775
					$out .= ' /AuthEvent /DocOpen';
11776
				}
11777
				if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
11778
					// The bit length of the encryption key.
11779
					$out .= ' /Length '.$this->encryptdata['CF']['Length'];
11780
				}
11781
				$out .= ' >> >>';
11782
			}
11783
			// The name of the crypt filter that shall be used by default when decrypting streams.
11784
			$out .= ' /StmF /'.$this->encryptdata['StmF'];
11785
			// The name of the crypt filter that shall be used when decrypting all strings in the document.
11786
			$out .= ' /StrF /'.$this->encryptdata['StrF'];
11787
			if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
11788
				// The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
11789
				$out .= ' /EFF /'.$this->encryptdata[''];
11790
			}
11791
		}
11792
		// Additional encryption dictionary entries for the standard security handler
11793
		if ($this->encryptdata['pubkey']) {
11794
			if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
11795
				$out .= ' /Recipients [';
11796
				foreach ($this->encryptdata['Recipients'] as $rec) {
11797
					$out .= ' <'.$rec.'>';
11798
				}
11799
				$out .= ' ]';
11800
			}
11801
		} else {
11802
			$out .= ' /R';
11803
			if ($this->encryptdata['V'] == 5) { // AES-256
11804
				$out .= ' 5';
11805
				$out .= ' /OE ('.$this->_escape($this->encryptdata['OE']).')';
11806
				$out .= ' /UE ('.$this->_escape($this->encryptdata['UE']).')';
11807
				$out .= ' /Perms ('.$this->_escape($this->encryptdata['perms']).')';
11808
			} elseif ($this->encryptdata['V'] == 4) { // AES-128
11809
				$out .= ' 4';
11810
			} elseif ($this->encryptdata['V'] < 2) { // RC-40
11811
				$out .= ' 2';
11812
			} else { // RC-128
11813
				$out .= ' 3';
11814
			}
11815
			$out .= ' /O ('.$this->_escape($this->encryptdata['O']).')';
11816
			$out .= ' /U ('.$this->_escape($this->encryptdata['U']).')';
11817
			$out .= ' /P '.$this->encryptdata['P'];
11818
			if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
11819
				$out .= ' /EncryptMetadata false';
11820
			} else {
11821
				$out .= ' /EncryptMetadata true';
11822
			}
11823
		}
11824
		$out .= ' >>';
11825
		$out .= "\n".'endobj';
11826
		$this->_out($out);
11827
	}
11828
 
11829
	/**
11830
	 * Returns the input text encrypted using RC4 algorithm and the specified key.
11831
	 * RC4 is the standard encryption algorithm used in PDF format
11832
	 * @param string $key encryption key
11833
	 * @param String $text input text to be encrypted
11834
	 * @return String encrypted text
11835
	 * @access protected
11836
	 * @since 2.0.000 (2008-01-02)
11837
	 * @author Klemen Vodopivec, Nicola Asuni
11838
	 */
11839
	protected function _RC4($key, $text) {
11840
		if (function_exists('mcrypt_decrypt') AND ($out = @mcrypt_decrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
11841
			// try to use mcrypt function if exist
11842
			return $out;
11843
		}
11844
		if ($this->last_enc_key != $key) {
11845
			$k = str_repeat($key, ((256 / strlen($key)) + 1));
11846
			$rc4 = range(0, 255);
11847
			$j = 0;
11848
			for ($i = 0; $i < 256; ++$i) {
11849
				$t = $rc4[$i];
11850
				$j = ($j + $t + ord($k{$i})) % 256;
11851
				$rc4[$i] = $rc4[$j];
11852
				$rc4[$j] = $t;
11853
			}
11854
			$this->last_enc_key = $key;
11855
			$this->last_enc_key_c = $rc4;
11856
		} else {
11857
			$rc4 = $this->last_enc_key_c;
11858
		}
11859
		$len = strlen($text);
11860
		$a = 0;
11861
		$b = 0;
11862
		$out = '';
11863
		for ($i = 0; $i < $len; ++$i) {
11864
			$a = ($a + 1) % 256;
11865
			$t = $rc4[$a];
11866
			$b = ($b + $t) % 256;
11867
			$rc4[$a] = $rc4[$b];
11868
			$rc4[$b] = $t;
11869
			$k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
11870
			$out .= chr(ord($text{$i}) ^ $k);
11871
		}
11872
		return $out;
11873
	}
11874
 
11875
	/**
11876
	 * Returns the input text exrypted using AES algorithm and the specified key.
11877
	 * This method requires mcrypt.
11878
	 * @param string $key encryption key
11879
	 * @param String $text input text to be encrypted
11880
	 * @return String encrypted text
11881
	 * @access protected
11882
	 * @author Nicola Asuni
11883
	 * @since 5.0.005 (2010-05-11)
11884
	 */
11885
	protected function _AES($key, $text) {
11886
		// padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
11887
		$padding = 16 - (strlen($text) % 16);
11888
		$text .= str_repeat(chr($padding), $padding);
11889
		$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
11890
		$text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
11891
		$text = $iv.$text;
11892
		return $text;
11893
	}
11894
 
11895
	/**
11896
	 * Encrypts a string using MD5 and returns it's value as a binary string.
11897
	 * @param string $str input string
11898
	 * @return String MD5 encrypted binary string
11899
	 * @access protected
11900
	 * @since 2.0.000 (2008-01-02)
11901
	 * @author Klemen Vodopivec
11902
	 */
11903
	protected function _md5_16($str) {
11904
		return pack('H*', md5($str));
11905
	}
11906
 
11907
	/**
11908
	 * Compute U value (used for encryption)
11909
	 * @return string U value
11910
	 * @access protected
11911
	 * @since 2.0.000 (2008-01-02)
11912
	 * @author Nicola Asuni
11913
	 */
11914
	protected function _Uvalue() {
11915
		if ($this->encryptdata['mode'] == 0) { // RC4-40
11916
			return $this->_RC4($this->encryptdata['key'], $this->enc_padding);
11917
		} elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
11918
			$tmp = $this->_md5_16($this->enc_padding.$this->encryptdata['fileid']);
11919
			$enc = $this->_RC4($this->encryptdata['key'], $tmp);
11920
			$len = strlen($tmp);
11921
			for ($i = 1; $i <= 19; ++$i) {
11922
				$ek = '';
11923
				for ($j = 0; $j < $len; ++$j) {
11924
					$ek .= chr(ord($this->encryptdata['key']{$j}) ^ $i);
11925
				}
11926
				$enc = $this->_RC4($ek, $enc);
11927
			}
11928
			$enc .= str_repeat("\x00", 16);
11929
			return substr($enc, 0, 32);
11930
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
11931
			$seed = $this->_md5_16($this->getRandomSeed());
11932
			// User Validation Salt
11933
			$this->encryptdata['UVS'] = substr($seed, 0, 8);
11934
			// User Key Salt
11935
			$this->encryptdata['UKS'] = substr($seed, 8, 16);
11936
			return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
11937
		}
11938
	}
11939
 
11940
	/**
11941
	 * Compute UE value (used for encryption)
11942
	 * @return string UE value
11943
	 * @access protected
11944
	 * @since 5.9.006 (2010-10-19)
11945
	 * @author Nicola Asuni
11946
	 */
11947
	protected function _UEvalue() {
11948
		$hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
11949
		$iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
11950
		return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
11951
	}
11952
 
11953
	/**
11954
	 * Compute O value (used for encryption)
11955
	 * @return string O value
11956
	 * @access protected
11957
	 * @since 2.0.000 (2008-01-02)
11958
	 * @author Nicola Asuni
11959
	 */
11960
	protected function _Ovalue() {
11961
		if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
11962
			$tmp = $this->_md5_16($this->encryptdata['owner_password']);
11963
			if ($this->encryptdata['mode'] > 0) {
11964
				for ($i = 0; $i < 50; ++$i) {
11965
					$tmp = $this->_md5_16($tmp);
11966
				}
11967
			}
11968
			$owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
11969
			$enc = $this->_RC4($owner_key, $this->encryptdata['user_password']);
11970
			if ($this->encryptdata['mode'] > 0) {
11971
				$len = strlen($owner_key);
11972
				for ($i = 1; $i <= 19; ++$i) {
11973
					$ek = '';
11974
					for ($j = 0; $j < $len; ++$j) {
11975
						$ek .= chr(ord($owner_key{$j}) ^ $i);
11976
					}
11977
					$enc = $this->_RC4($ek, $enc);
11978
				}
11979
			}
11980
			return $enc;
11981
		} elseif ($this->encryptdata['mode'] == 3) { // AES-256
11982
			$seed = $this->_md5_16($this->getRandomSeed());
11983
			// Owner Validation Salt
11984
			$this->encryptdata['OVS'] = substr($seed, 0, 8);
11985
			// Owner Key Salt
11986
			$this->encryptdata['OKS'] = substr($seed, 8, 16);
11987
			return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
11988
		}
11989
	}
11990
 
11991
	/**
11992
	 * Compute OE value (used for encryption)
11993
	 * @return string OE value
11994
	 * @access protected
11995
	 * @since 5.9.006 (2010-10-19)
11996
	 * @author Nicola Asuni
11997
	 */
11998
	protected function _OEvalue() {
11999
		$hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
12000
		$iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
12001
		return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
12002
	}
12003
 
12004
	/**
12005
	 * Convert password for AES-256 encryption mode
12006
	 * @return string password
12007
	 * @access protected
12008
	 * @since 5.9.006 (2010-10-19)
12009
	 * @author Nicola Asuni
12010
	 */
12011
	protected function _fixAES256Password($password) {
12012
		$psw = ''; // password to be returned
12013
		$psw_array = $this->utf8Bidi($this->UTF8StringToArray($password), $password, $this->rtl);
12014
		foreach ($psw_array as $c) {
12015
			$psw .= $this->unichr($c);
12016
		}
12017
		return substr($psw, 0, 127);
12018
	}
12019
 
12020
	/**
12021
	 * Compute encryption key
12022
	 * @access protected
12023
	 * @since 2.0.000 (2008-01-02)
12024
	 * @author Nicola Asuni
12025
	 */
12026
	protected function _generateencryptionkey() {
12027
		$keybytelen = ($this->encryptdata['Length'] / 8);
12028
		if (!$this->encryptdata['pubkey']) { // standard mode
12029
			if ($this->encryptdata['mode'] == 3) { // AES-256
12030
				// generate 256 bit random key
12031
				$this->encryptdata['key'] = substr(hash('sha256', $this->getRandomSeed(), true), 0, $keybytelen);
12032
				// truncate passwords
12033
				$this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
12034
				$this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
12035
				// Compute U value
12036
				$this->encryptdata['U'] = $this->_Uvalue();
12037
				// Compute UE value
12038
				$this->encryptdata['UE'] = $this->_UEvalue();
12039
				// Compute O value
12040
				$this->encryptdata['O'] = $this->_Ovalue();
12041
				// Compute OE value
12042
				$this->encryptdata['OE'] = $this->_OEvalue();
12043
				// Compute P value
12044
				$this->encryptdata['P'] = $this->encryptdata['protection'];
12045
				// Computing the encryption dictionary's Perms (permissions) value
12046
				$perms = $this->getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
12047
				$perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
12048
				if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
12049
					$perms .= 'F';
12050
				} else {
12051
					$perms .= 'T';
12052
				}
12053
				$perms .= 'adb'; // bytes 9-11
12054
				$perms .= 'nick'; // bytes 12-15
12055
				$iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
12056
				$this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
12057
			} else { // RC4-40, RC4-128, AES-128
12058
				// Pad passwords
12059
				$this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].$this->enc_padding, 0, 32);
12060
				$this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].$this->enc_padding, 0, 32);
12061
				// Compute O value
12062
				$this->encryptdata['O'] = $this->_Ovalue();
12063
				// get default permissions (reverse byte order)
12064
				$permissions = $this->getEncPermissionsString($this->encryptdata['protection']);
12065
				// Compute encryption key
12066
				$tmp = $this->_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
12067
				if ($this->encryptdata['mode'] > 0) {
12068
					for ($i = 0; $i < 50; ++$i) {
12069
						$tmp = $this->_md5_16(substr($tmp, 0, $keybytelen));
12070
					}
12071
				}
12072
				$this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
12073
				// Compute U value
12074
				$this->encryptdata['U'] = $this->_Uvalue();
12075
				// Compute P value
12076
				$this->encryptdata['P'] = $this->encryptdata['protection'];
12077
			}
12078
		} else { // Public-Key mode
12079
			// random 20-byte seed
12080
			$seed = sha1($this->getRandomSeed(), true);
12081
			$recipient_bytes = '';
12082
			foreach ($this->encryptdata['pubkeys'] as $pubkey) {
12083
				// for each public certificate
12084
				if (isset($pubkey['p'])) {
12085
					$pkprotection = $this->getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
12086
				} else {
12087
					$pkprotection = $this->encryptdata['protection'];
12088
				}
12089
				// get default permissions (reverse byte order)
12090
				$pkpermissions = $this->getEncPermissionsString($pkprotection);
12091
				// envelope data
12092
				$envelope = $seed.$pkpermissions;
12093
				// write the envelope data to a temporary file
12094
				$tempkeyfile = tempnam(K_PATH_CACHE, 'tmpkey_');
12095
				$f = fopen($tempkeyfile, 'wb');
12096
				if (!$f) {
12097
					$this->Error('Unable to create temporary key file: '.$tempkeyfile);
12098
				}
12099
				$envelope_lenght = strlen($envelope);
12100
				fwrite($f, $envelope, $envelope_lenght);
12101
				fclose($f);
12102
				$tempencfile = tempnam(K_PATH_CACHE, 'tmpenc_');
12103
				if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_DETACHED | PKCS7_BINARY)) {
12104
					$this->Error('Unable to encrypt the file: '.$tempkeyfile);
12105
				}
12106
				unlink($tempkeyfile);
12107
				// read encryption signature
12108
				$signature = file_get_contents($tempencfile, false, null, $envelope_lenght);
12109
				unlink($tempencfile);
12110
				// extract signature
12111
				$signature = substr($signature, strpos($signature, 'Content-Disposition'));
12112
				$tmparr = explode("\n\n", $signature);
12113
				$signature = trim($tmparr[1]);
12114
				unset($tmparr);
12115
				// decode signature
12116
				$signature = base64_decode($signature);
12117
				// convert signature to hex
12118
				$hexsignature = current(unpack('H*', $signature));
12119
				// store signature on recipients array
12120
				$this->encryptdata['Recipients'][] = $hexsignature;
12121
				// The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
12122
				$recipient_bytes .= $signature;
12123
			}
12124
			// calculate encryption key
12125
			if ($this->encryptdata['mode'] == 3) { // AES-256
12126
				$this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
12127
			} else { // RC4-40, RC4-128, AES-128
12128
				$this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
12129
			}
12130
		}
12131
	}
12132
 
12133
	/**
12134
	 * Return the premission code used on encryption (P value).
12135
	 * @param Array $permissions the set of permissions (specify the ones you want to block).
12136
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
12137
	 * @access protected
12138
	 * @since 5.0.005 (2010-05-12)
12139
	 * @author Nicola Asuni
12140
	 */
12141
	protected function getUserPermissionCode($permissions, $mode=0) {
12142
		$options = array(
12143
			'owner' => 2, // bit 2 -- inverted logic: cleared by default
12144
			'print' => 4, // bit 3
12145
			'modify' => 8, // bit 4
12146
			'copy' => 16, // bit 5
12147
			'annot-forms' => 32, // bit 6
12148
			'fill-forms' => 256, // bit 9
12149
			'extract' => 512, // bit 10
12150
			'assemble' => 1024,// bit 11
12151
			'print-high' => 2048 // bit 12
12152
			);
12153
		$protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
12154
		foreach ($permissions as $permission) {
12155
			if (!isset($options[$permission])) {
12156
				$this->Error('Incorrect permission: '.$permission);
12157
			}
12158
			if (($mode > 0) OR ($options[$permission] <= 32)) {
12159
				// set only valid permissions
12160
				if ($options[$permission] == 2) {
12161
					// the logic for bit 2 is inverted (cleared by default)
12162
					$protection += $options[$permission];
12163
				} else {
12164
					$protection -= $options[$permission];
12165
				}
12166
			}
12167
		}
12168
		return $protection;
12169
	}
12170
 
12171
	/**
12172
	 * Set document protection
12173
	 * Remark: the protection against modification is for people who have the full Acrobat product.
12174
	 * 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.
12175
	 * 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.
12176
	 * @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>
12177
	 * @param String $user_pass user password. Empty by default.
12178
	 * @param String $owner_pass owner password. If not specified, a random value is used.
12179
	 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
12180
	 * @param String $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../tcpdf.crt', 'p' => array('print')))
12181
	 * @access public
12182
	 * @since 2.0.000 (2008-01-02)
12183
	 * @author Nicola Asuni
12184
	 */
12185
	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) {
12186
		$this->encryptdata['protection'] = $this->getUserPermissionCode($permissions, $mode);
12187
		if (($pubkeys !== null) AND (is_array($pubkeys))) {
12188
			// public-key mode
12189
			$this->encryptdata['pubkeys'] = $pubkeys;
12190
			if ($mode == 0) {
12191
				// public-Key Security requires at least 128 bit
12192
				$mode = 1;
12193
			}
12194
			if (!function_exists('openssl_pkcs7_encrypt')) {
12195
				$this->Error('Public-Key Security requires openssl library.');
12196
			}
12197
			// Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
12198
			$this->encryptdata['pubkey'] = true;
12199
			$this->encryptdata['Filter'] = 'Adobe.PubSec';
12200
			$this->encryptdata['StmF'] = 'DefaultCryptFilter';
12201
			$this->encryptdata['StrF'] = 'DefaultCryptFilter';
12202
		} else {
12203
			// standard mode (password mode)
12204
			$this->encryptdata['pubkey'] = false;
12205
			$this->encryptdata['Filter'] = 'Standard';
12206
			$this->encryptdata['StmF'] = 'StdCF';
12207
			$this->encryptdata['StrF'] = 'StdCF';
12208
		}
12209
		if ($mode > 1) { // AES
12210
			if (!extension_loaded('mcrypt')) {
12211
				$this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
12212
			}
12213
			if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
12214
				$this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
12215
			}
12216
			if (($mode == 3) AND !function_exists('hash')) {
12217
				// the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
12218
				$this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
12219
			}
12220
		}
12221
		if ($owner_pass === null) {
12222
			$owner_pass = md5($this->getRandomSeed());
12223
		}
12224
		$this->encryptdata['user_password'] = $user_pass;
12225
		$this->encryptdata['owner_password'] = $owner_pass;
12226
		$this->encryptdata['mode'] = $mode;
12227
		switch ($mode) {
12228
			case 0: { // RC4 40 bit
12229
				$this->encryptdata['V'] = 1;
12230
				$this->encryptdata['Length'] = 40;
12231
				$this->encryptdata['CF']['CFM'] = 'V2';
12232
				break;
12233
			}
12234
			case 1: { // RC4 128 bit
12235
				$this->encryptdata['V'] = 2;
12236
				$this->encryptdata['Length'] = 128;
12237
				$this->encryptdata['CF']['CFM'] = 'V2';
12238
				if ($this->encryptdata['pubkey']) {
12239
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
12240
					$this->encryptdata['Recipients'] = array();
12241
				}
12242
				break;
12243
			}
12244
			case 2: { // AES 128 bit
12245
				$this->encryptdata['V'] = 4;
12246
				$this->encryptdata['Length'] = 128;
12247
				$this->encryptdata['CF']['CFM'] = 'AESV2';
12248
				$this->encryptdata['CF']['Length'] = 128;
12249
				if ($this->encryptdata['pubkey']) {
12250
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
12251
					$this->encryptdata['Recipients'] = array();
12252
				}
12253
				break;
12254
			}
12255
			case 3: { // AES 256 bit
12256
				$this->encryptdata['V'] = 5;
12257
				$this->encryptdata['Length'] = 256;
12258
				$this->encryptdata['CF']['CFM'] = 'AESV3';
12259
				$this->encryptdata['CF']['Length'] = 256;
12260
				if ($this->encryptdata['pubkey']) {
12261
					$this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
12262
					$this->encryptdata['Recipients'] = array();
12263
				}
12264
				break;
12265
			}
12266
		}
12267
		$this->encrypted = true;
12268
		$this->encryptdata['fileid'] = $this->convertHexStringToString($this->file_id);
12269
		$this->_generateencryptionkey();
12270
	}
12271
 
12272
	/**
12273
	 * Convert hexadecimal string to string
12274
	 * @param string $bs byte-string to convert
12275
	 * @return String
12276
	 * @access protected
12277
	 * @since 5.0.005 (2010-05-12)
12278
	 * @author Nicola Asuni
12279
	 */
12280
	protected function convertHexStringToString($bs) {
12281
		$string = ''; // string to be returned
12282
		$bslenght = strlen($bs);
12283
		if (($bslenght % 2) != 0) {
12284
			// padding
12285
			$bs .= '0';
12286
			++$bslenght;
12287
		}
12288
		for ($i = 0; $i < $bslenght; $i += 2) {
12289
			$string .= chr(hexdec($bs{$i}.$bs{($i + 1)}));
12290
		}
12291
		return $string;
12292
	}
12293
 
12294
	/**
12295
	 * Convert string to hexadecimal string (byte string)
12296
	 * @param string $s string to convert
12297
	 * @return byte string
12298
	 * @access protected
12299
	 * @since 5.0.010 (2010-05-17)
12300
	 * @author Nicola Asuni
12301
	 */
12302
	protected function convertStringToHexString($s) {
12303
		$bs = '';
12304
		$chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
12305
		foreach ($chars as $c) {
12306
			$bs .= sprintf('%02s', dechex(ord($c)));
12307
		}
12308
		return $bs;
12309
	}
12310
 
12311
	/**
12312
	 * Convert encryption P value to a string of bytes, low-order byte first.
12313
	 * @param string $protection 32bit encryption permission value (P value)
12314
	 * @return String
12315
	 * @access protected
12316
	 * @since 5.0.005 (2010-05-12)
12317
	 * @author Nicola Asuni
12318
	 */
12319
	protected function getEncPermissionsString($protection) {
12320
		$binprot = sprintf('%032b', $protection);
12321
		$str = chr(bindec(substr($binprot, 24, 8)));
12322
		$str .= chr(bindec(substr($binprot, 16, 8)));
12323
		$str .= chr(bindec(substr($binprot, 8, 8)));
12324
		$str .= chr(bindec(substr($binprot, 0, 8)));
12325
		return $str;
12326
	}
12327
 
12328
	// END OF ENCRYPTION FUNCTIONS -------------------------
12329
 
12330
	// START TRANSFORMATIONS SECTION -----------------------
12331
 
12332
	/**
12333
	 * Starts a 2D tranformation saving current graphic state.
12334
	 * This function must be called before scaling, mirroring, translation, rotation and skewing.
12335
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
12336
	 * @access public
12337
	 * @since 2.1.000 (2008-01-07)
12338
	 * @see StartTransform(), StopTransform()
12339
	 */
12340
	public function StartTransform() {
12341
		$this->_out('q');
12342
		if ($this->inxobj) {
12343
			// we are inside an XObject template
12344
			$this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
12345
		} else {
12346
			$this->transfmrk[$this->page][] = $this->pagelen[$this->page];
12347
		}
12348
		++$this->transfmatrix_key;
12349
		$this->transfmatrix[$this->transfmatrix_key] = array();
12350
	}
12351
 
12352
	/**
12353
	 * Stops a 2D tranformation restoring previous graphic state.
12354
	 * This function must be called after scaling, mirroring, translation, rotation and skewing.
12355
	 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
12356
	 * @access public
12357
	 * @since 2.1.000 (2008-01-07)
12358
	 * @see StartTransform(), StopTransform()
12359
	 */
12360
	public function StopTransform() {
12361
		$this->_out('Q');
12362
		if (isset($this->transfmatrix[$this->transfmatrix_key])) {
12363
			array_pop($this->transfmatrix[$this->transfmatrix_key]);
12364
			--$this->transfmatrix_key;
12365
		}
12366
		if ($this->inxobj) {
12367
			// we are inside an XObject template
12368
			array_pop($this->xobjects[$this->xobjid]['transfmrk']);
12369
		} else {
12370
			array_pop($this->transfmrk[$this->page]);
12371
		}
12372
	}
12373
	/**
12374
	 * Horizontal Scaling.
12375
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
12376
	 * @param int $x abscissa of the scaling center. Default is current x position
12377
	 * @param int $y ordinate of the scaling center. Default is current y position
12378
	 * @access public
12379
	 * @since 2.1.000 (2008-01-07)
12380
	 * @see StartTransform(), StopTransform()
12381
	 */
12382
	public function ScaleX($s_x, $x='', $y='') {
12383
		$this->Scale($s_x, 100, $x, $y);
12384
	}
12385
 
12386
	/**
12387
	 * Vertical Scaling.
12388
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
12389
	 * @param int $x abscissa of the scaling center. Default is current x position
12390
	 * @param int $y ordinate of the scaling center. Default is current y position
12391
	 * @access public
12392
	 * @since 2.1.000 (2008-01-07)
12393
	 * @see StartTransform(), StopTransform()
12394
	 */
12395
	public function ScaleY($s_y, $x='', $y='') {
12396
		$this->Scale(100, $s_y, $x, $y);
12397
	}
12398
 
12399
	/**
12400
	 * Vertical and horizontal proportional Scaling.
12401
	 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
12402
	 * @param int $x abscissa of the scaling center. Default is current x position
12403
	 * @param int $y ordinate of the scaling center. Default is current y position
12404
	 * @access public
12405
	 * @since 2.1.000 (2008-01-07)
12406
	 * @see StartTransform(), StopTransform()
12407
	 */
12408
	public function ScaleXY($s, $x='', $y='') {
12409
		$this->Scale($s, $s, $x, $y);
12410
	}
12411
 
12412
	/**
12413
	 * Vertical and horizontal non-proportional Scaling.
12414
	 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
12415
	 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
12416
	 * @param int $x abscissa of the scaling center. Default is current x position
12417
	 * @param int $y ordinate of the scaling center. Default is current y position
12418
	 * @access public
12419
	 * @since 2.1.000 (2008-01-07)
12420
	 * @see StartTransform(), StopTransform()
12421
	 */
12422
	public function Scale($s_x, $s_y, $x='', $y='') {
12423
		if ($x === '') {
12424
			$x = $this->x;
12425
		}
12426
		if ($y === '') {
12427
			$y = $this->y;
12428
		}
12429
		if (($s_x == 0) OR ($s_y == 0)) {
12430
			$this->Error('Please do not use values equal to zero for scaling');
12431
		}
12432
		$y = ($this->h - $y) * $this->k;
12433
		$x *= $this->k;
12434
		//calculate elements of transformation matrix
12435
		$s_x /= 100;
12436
		$s_y /= 100;
12437
		$tm = array();
12438
		$tm[0] = $s_x;
12439
		$tm[1] = 0;
12440
		$tm[2] = 0;
12441
		$tm[3] = $s_y;
12442
		$tm[4] = $x * (1 - $s_x);
12443
		$tm[5] = $y * (1 - $s_y);
12444
		//scale the coordinate system
12445
		$this->Transform($tm);
12446
	}
12447
 
12448
	/**
12449
	 * Horizontal Mirroring.
12450
	 * @param int $x abscissa of the point. Default is current x position
12451
	 * @access public
12452
	 * @since 2.1.000 (2008-01-07)
12453
	 * @see StartTransform(), StopTransform()
12454
	 */
12455
	public function MirrorH($x='') {
12456
		$this->Scale(-100, 100, $x);
12457
	}
12458
 
12459
	/**
12460
	 * Verical Mirroring.
12461
	 * @param int $y ordinate of the point. Default is current y position
12462
	 * @access public
12463
	 * @since 2.1.000 (2008-01-07)
12464
	 * @see StartTransform(), StopTransform()
12465
	 */
12466
	public function MirrorV($y='') {
12467
		$this->Scale(100, -100, '', $y);
12468
	}
12469
 
12470
	/**
12471
	 * Point reflection mirroring.
12472
	 * @param int $x abscissa of the point. Default is current x position
12473
	 * @param int $y ordinate of the point. Default is current y position
12474
	 * @access public
12475
	 * @since 2.1.000 (2008-01-07)
12476
	 * @see StartTransform(), StopTransform()
12477
	 */
12478
	public function MirrorP($x='',$y='') {
12479
		$this->Scale(-100, -100, $x, $y);
12480
	}
12481
 
12482
	/**
12483
	 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
12484
	 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
12485
	 * @param int $x abscissa of the point. Default is current x position
12486
	 * @param int $y ordinate of the point. Default is current y position
12487
	 * @access public
12488
	 * @since 2.1.000 (2008-01-07)
12489
	 * @see StartTransform(), StopTransform()
12490
	 */
12491
	public function MirrorL($angle=0, $x='',$y='') {
12492
		$this->Scale(-100, 100, $x, $y);
12493
		$this->Rotate(-2*($angle-90), $x, $y);
12494
	}
12495
 
12496
	/**
12497
	 * Translate graphic object horizontally.
12498
	 * @param int $t_x movement to the right (or left for RTL)
12499
	 * @access public
12500
	 * @since 2.1.000 (2008-01-07)
12501
	 * @see StartTransform(), StopTransform()
12502
	 */
12503
	public function TranslateX($t_x) {
12504
		$this->Translate($t_x, 0);
12505
	}
12506
 
12507
	/**
12508
	 * Translate graphic object vertically.
12509
	 * @param int $t_y movement to the bottom
12510
	 * @access public
12511
	 * @since 2.1.000 (2008-01-07)
12512
	 * @see StartTransform(), StopTransform()
12513
	 */
12514
	public function TranslateY($t_y) {
12515
		$this->Translate(0, $t_y);
12516
	}
12517
 
12518
	/**
12519
	 * Translate graphic object horizontally and vertically.
12520
	 * @param int $t_x movement to the right
12521
	 * @param int $t_y movement to the bottom
12522
	 * @access public
12523
	 * @since 2.1.000 (2008-01-07)
12524
	 * @see StartTransform(), StopTransform()
12525
	 */
12526
	public function Translate($t_x, $t_y) {
12527
		//calculate elements of transformation matrix
12528
		$tm = array();
12529
		$tm[0] = 1;
12530
		$tm[1] = 0;
12531
		$tm[2] = 0;
12532
		$tm[3] = 1;
12533
		$tm[4] = $t_x * $this->k;
12534
		$tm[5] = -$t_y * $this->k;
12535
		//translate the coordinate system
12536
		$this->Transform($tm);
12537
	}
12538
 
12539
	/**
12540
	 * Rotate object.
12541
	 * @param float $angle angle in degrees for counter-clockwise rotation
12542
	 * @param int $x abscissa of the rotation center. Default is current x position
12543
	 * @param int $y ordinate of the rotation center. Default is current y position
12544
	 * @access public
12545
	 * @since 2.1.000 (2008-01-07)
12546
	 * @see StartTransform(), StopTransform()
12547
	 */
12548
	public function Rotate($angle, $x='', $y='') {
12549
		if ($x === '') {
12550
			$x = $this->x;
12551
		}
12552
		if ($y === '') {
12553
			$y = $this->y;
12554
		}
12555
		$y = ($this->h - $y) * $this->k;
12556
		$x *= $this->k;
12557
		//calculate elements of transformation matrix
12558
		$tm = array();
12559
		$tm[0] = cos(deg2rad($angle));
12560
		$tm[1] = sin(deg2rad($angle));
12561
		$tm[2] = -$tm[1];
12562
		$tm[3] = $tm[0];
12563
		$tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
12564
		$tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
12565
		//rotate the coordinate system around ($x,$y)
12566
		$this->Transform($tm);
12567
	}
12568
 
12569
	/**
12570
	 * Skew horizontally.
12571
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
12572
	 * @param int $x abscissa of the skewing center. default is current x position
12573
	 * @param int $y ordinate of the skewing center. default is current y position
12574
	 * @access public
12575
	 * @since 2.1.000 (2008-01-07)
12576
	 * @see StartTransform(), StopTransform()
12577
	 */
12578
	public function SkewX($angle_x, $x='', $y='') {
12579
		$this->Skew($angle_x, 0, $x, $y);
12580
	}
12581
 
12582
	/**
12583
	 * Skew vertically.
12584
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
12585
	 * @param int $x abscissa of the skewing center. default is current x position
12586
	 * @param int $y ordinate of the skewing center. default is current y position
12587
	 * @access public
12588
	 * @since 2.1.000 (2008-01-07)
12589
	 * @see StartTransform(), StopTransform()
12590
	 */
12591
	public function SkewY($angle_y, $x='', $y='') {
12592
		$this->Skew(0, $angle_y, $x, $y);
12593
	}
12594
 
12595
	/**
12596
	 * Skew.
12597
	 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
12598
	 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
12599
	 * @param int $x abscissa of the skewing center. default is current x position
12600
	 * @param int $y ordinate of the skewing center. default is current y position
12601
	 * @access public
12602
	 * @since 2.1.000 (2008-01-07)
12603
	 * @see StartTransform(), StopTransform()
12604
	 */
12605
	public function Skew($angle_x, $angle_y, $x='', $y='') {
12606
		if ($x === '') {
12607
			$x = $this->x;
12608
		}
12609
		if ($y === '') {
12610
			$y = $this->y;
12611
		}
12612
		if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
12613
			$this->Error('Please use values between -90 and +90 degrees for Skewing.');
12614
		}
12615
		$x *= $this->k;
12616
		$y = ($this->h - $y) * $this->k;
12617
		//calculate elements of transformation matrix
12618
		$tm = array();
12619
		$tm[0] = 1;
12620
		$tm[1] = tan(deg2rad($angle_y));
12621
		$tm[2] = tan(deg2rad($angle_x));
12622
		$tm[3] = 1;
12623
		$tm[4] = -$tm[2] * $y;
12624
		$tm[5] = -$tm[1] * $x;
12625
		//skew the coordinate system
12626
		$this->Transform($tm);
12627
	}
12628
 
12629
	/**
12630
	 * Apply graphic transformations.
12631
	 * @param array $tm transformation matrix
12632
	 * @access protected
12633
	 * @since 2.1.000 (2008-01-07)
12634
	 * @see StartTransform(), StopTransform()
12635
	 */
12636
	protected function Transform($tm) {
12637
		$this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
12638
		// add tranformation matrix
12639
		$this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
12640
		// update transformation mark
12641
		if ($this->inxobj) {
12642
			// we are inside an XObject template
12643
			if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
12644
				$key = key($this->xobjects[$this->xobjid]['transfmrk']);
12645
				$this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
12646
			}
12647
		} elseif (end($this->transfmrk[$this->page]) !== false) {
12648
			$key = key($this->transfmrk[$this->page]);
12649
			$this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
12650
		}
12651
	}
12652
 
12653
	// END TRANSFORMATIONS SECTION -------------------------
12654
 
12655
	// START GRAPHIC FUNCTIONS SECTION ---------------------
12656
	// The following section is based on the code provided by David Hernandez Sanz
12657
 
12658
	/**
12659
	 * 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.
12660
	 * @param float $width The width.
12661
	 * @access public
12662
	 * @since 1.0
12663
	 * @see Line(), Rect(), Cell(), MultiCell()
12664
	 */
12665
	public function SetLineWidth($width) {
12666
		//Set line width
12667
		$this->LineWidth = $width;
12668
		$this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
12669
		if ($this->page > 0) {
12670
			$this->_out($this->linestyleWidth);
12671
		}
12672
	}
12673
 
12674
	/**
12675
	 * Returns the current the line width.
12676
	 * @return int Line width
12677
	 * @access public
12678
	 * @since 2.1.000 (2008-01-07)
12679
	 * @see Line(), SetLineWidth()
12680
	 */
12681
	public function GetLineWidth() {
12682
		return $this->LineWidth;
12683
	}
12684
 
12685
	/**
12686
	 * Set line style.
12687
	 * @param array $style Line style. Array with keys among the following:
12688
	 * <ul>
12689
	 *	 <li>width (float): Width of the line in user units.</li>
12690
	 *	 <li>cap (string): Type of cap to put on the line. Possible values are:
12691
	 * butt, round, square. The difference between "square" and "butt" is that
12692
	 * "square" projects a flat end past the end of the line.</li>
12693
	 *	 <li>join (string): Type of join. Possible values are: miter, round,
12694
	 * bevel.</li>
12695
	 *	 <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
12696
	 * series of length values, which are the lengths of the on and off dashes.
12697
	 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
12698
	 * 1 off, 2 on, 1 off, ...</li>
12699
	 *	 <li>phase (integer): Modifier on the dash pattern which is used to shift
12700
	 * the point at which the pattern starts.</li>
12701
	 *	 <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K).</li>
12702
	 * </ul>
12703
	 * @param boolean $ret if true do not send the command.
12704
	 * @return string the PDF command
12705
	 * @access public
12706
	 * @since 2.1.000 (2008-01-08)
12707
	 */
12708
	public function SetLineStyle($style, $ret=false) {
12709
		$s = ''; // string to be returned
12710
		if (!is_array($style)) {
12711
			return;
12712
		}
12713
		extract($style);
12714
		if (isset($width)) {
12715
			$this->LineWidth = $width;
12716
			$this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
12717
			$s .= $this->linestyleWidth.' ';
12718
		}
12719
		if (isset($cap)) {
12720
			$ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
12721
			if (isset($ca[$cap])) {
12722
				$this->linestyleCap = $ca[$cap].' J';
12723
				$s .= $this->linestyleCap.' ';
12724
			}
12725
		}
12726
		if (isset($join)) {
12727
			$ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
12728
			if (isset($ja[$join])) {
12729
				$this->linestyleJoin = $ja[$join].' j';
12730
				$s .= $this->linestyleJoin.' ';
12731
			}
12732
		}
12733
		if (isset($dash)) {
12734
			$dash_string = '';
12735
			if ($dash) {
12736
				if (preg_match('/^.+,/', $dash) > 0) {
12737
					$tab = explode(',', $dash);
12738
				} else {
12739
					$tab = array($dash);
12740
				}
12741
				$dash_string = '';
12742
				foreach ($tab as $i => $v) {
12743
					if ($i) {
12744
						$dash_string .= ' ';
12745
					}
12746
					$dash_string .= sprintf('%.2F', $v);
12747
				}
12748
			}
12749
			if (!isset($phase) OR !$dash) {
12750
				$phase = 0;
12751
			}
12752
			$this->linestyleDash = sprintf('[%s] %.2F d', $dash_string, $phase);
12753
			$s .= $this->linestyleDash.' ';
12754
		}
12755
		if (isset($color)) {
12756
			$s .= $this->SetDrawColorArray($color, true).' ';
12757
		}
12758
		if (!$ret) {
12759
			$this->_out($s);
12760
		}
12761
		return $s;
12762
	}
12763
 
12764
	/**
12765
	 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
12766
	 * @param float $x Abscissa of point.
12767
	 * @param float $y Ordinate of point.
12768
	 * @access protected
12769
	 * @since 2.1.000 (2008-01-08)
12770
	 */
12771
	protected function _outPoint($x, $y) {
12772
		$this->_out(sprintf('%.2F %.2F m', $x * $this->k, ($this->h - $y) * $this->k));
12773
	}
12774
 
12775
	/**
12776
	 * Append a straight line segment from the current point to the point (x, y).
12777
	 * The new current point shall be (x, y).
12778
	 * @param float $x Abscissa of end point.
12779
	 * @param float $y Ordinate of end point.
12780
	 * @access protected
12781
	 * @since 2.1.000 (2008-01-08)
12782
	 */
12783
	protected function _outLine($x, $y) {
12784
		$this->_out(sprintf('%.2F %.2F l', $x * $this->k, ($this->h - $y) * $this->k));
12785
	}
12786
 
12787
	/**
12788
	 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
12789
	 * @param float $x Abscissa of upper-left corner.
12790
	 * @param float $y Ordinate of upper-left corner.
12791
	 * @param float $w Width.
12792
	 * @param float $h Height.
12793
	 * @param string $op options
12794
	 * @access protected
12795
	 * @since 2.1.000 (2008-01-08)
12796
	 */
12797
	protected function _outRect($x, $y, $w, $h, $op) {
12798
		$this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x * $this->k, ($this->h - $y) * $this->k, $w * $this->k, -$h * $this->k, $op));
12799
	}
12800
 
12801
	/**
12802
	 * Append a cubic B�zier 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 B�zier control points.
12803
	 * The new current point shall be (x3, y3).
12804
	 * @param float $x1 Abscissa of control point 1.
12805
	 * @param float $y1 Ordinate of control point 1.
12806
	 * @param float $x2 Abscissa of control point 2.
12807
	 * @param float $y2 Ordinate of control point 2.
12808
	 * @param float $x3 Abscissa of end point.
12809
	 * @param float $y3 Ordinate of end point.
12810
	 * @access protected
12811
	 * @since 2.1.000 (2008-01-08)
12812
	 */
12813
	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
12814
		$this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F 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));
12815
	}
12816
 
12817
	/**
12818
	 * Append a cubic B�zier 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 B�zier control points.
12819
	 * The new current point shall be (x3, y3).
12820
	 * @param float $x2 Abscissa of control point 2.
12821
	 * @param float $y2 Ordinate of control point 2.
12822
	 * @param float $x3 Abscissa of end point.
12823
	 * @param float $y3 Ordinate of end point.
12824
	 * @access protected
12825
	 * @since 4.9.019 (2010-04-26)
12826
	 */
12827
	protected function _outCurveV($x2, $y2, $x3, $y3) {
12828
		$this->_out(sprintf('%.2F %.2F %.2F %.2F v', $x2 * $this->k, ($this->h - $y2) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
12829
	}
12830
 
12831
	/**
12832
	 * Append a cubic B�zier 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 B�zier control points.
12833
	 * The new current point shall be (x3, y3).
12834
	 * @param float $x1 Abscissa of control point 1.
12835
	 * @param float $y1 Ordinate of control point 1.
12836
	 * @param float $x2 Abscissa of control point 2.
12837
	 * @param float $y2 Ordinate of control point 2.
12838
	 * @param float $x3 Abscissa of end point.
12839
	 * @param float $y3 Ordinate of end point.
12840
	 * @access protected
12841
	 * @since 2.1.000 (2008-01-08)
12842
	 */
12843
	protected function _outCurveY($x1, $y1, $x3, $y3) {
12844
		$this->_out(sprintf('%.2F %.2F %.2F %.2F y', $x1 * $this->k, ($this->h - $y1) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
12845
	}
12846
 
12847
	/**
12848
	 * Draws a line between two points.
12849
	 * @param float $x1 Abscissa of first point.
12850
	 * @param float $y1 Ordinate of first point.
12851
	 * @param float $x2 Abscissa of second point.
12852
	 * @param float $y2 Ordinate of second point.
12853
	 * @param array $style Line style. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
12854
	 * @access public
12855
	 * @since 1.0
12856
	 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
12857
	 */
12858
	public function Line($x1, $y1, $x2, $y2, $style=array()) {
12859
		if (is_array($style)) {
12860
			$this->SetLineStyle($style);
12861
		}
12862
		$this->_outPoint($x1, $y1);
12863
		$this->_outLine($x2, $y2);
12864
		$this->_out('S');
12865
	}
12866
 
12867
	/**
12868
	 * Draws a rectangle.
12869
	 * @param float $x Abscissa of upper-left corner.
12870
	 * @param float $y Ordinate of upper-left corner.
12871
	 * @param float $w Width.
12872
	 * @param float $h Height.
12873
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12874
	 * @param array $border_style Border style of rectangle. Array with keys among the following:
12875
	 * <ul>
12876
	 *	 <li>all: Line style of all borders. Array like for {@link SetLineStyle SetLineStyle}.</li>
12877
	 *	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for {@link SetLineStyle SetLineStyle}.</li>
12878
	 * </ul>
12879
	 * If a key is not present or is null, not draws the border. Default value: default line style (empty array).
12880
	 * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
12881
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
12882
	 * @access public
12883
	 * @since 1.0
12884
	 * @see SetLineStyle()
12885
	 */
12886
	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
12887
		if (!(false === strpos($style, 'F')) AND !empty($fill_color)) {
12888
			$this->SetFillColorArray($fill_color);
12889
		}
12890
		$op = $this->getPathPaintOperator($style);
12891
		if ((!$border_style) OR (isset($border_style['all']))) {
12892
			if (isset($border_style['all']) AND $border_style['all']) {
12893
				$this->SetLineStyle($border_style['all']);
12894
				$border_style = array();
12895
			}
12896
		}
12897
		$this->_outRect($x, $y, $w, $h, $op);
12898
		if ($border_style) {
12899
			$border_style2 = array();
12900
			foreach ($border_style as $line => $value) {
12901
				$length = strlen($line);
12902
				for ($i = 0; $i < $length; ++$i) {
12903
					$border_style2[$line[$i]] = $value;
12904
				}
12905
			}
12906
			$border_style = $border_style2;
12907
			if (isset($border_style['L']) AND $border_style['L']) {
12908
				$this->Line($x, $y, $x, $y + $h, $border_style['L']);
12909
			}
12910
			if (isset($border_style['T']) AND $border_style['T']) {
12911
				$this->Line($x, $y, $x + $w, $y, $border_style['T']);
12912
			}
12913
			if (isset($border_style['R']) AND $border_style['R']) {
12914
				$this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
12915
			}
12916
			if (isset($border_style['B']) AND $border_style['B']) {
12917
				$this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
12918
			}
12919
		}
12920
	}
12921
 
12922
	/**
12923
	 * Draws a Bezier curve.
12924
	 * The Bezier curve is a tangent to the line between the control points at
12925
	 * either end of the curve.
12926
	 * @param float $x0 Abscissa of start point.
12927
	 * @param float $y0 Ordinate of start point.
12928
	 * @param float $x1 Abscissa of control point 1.
12929
	 * @param float $y1 Ordinate of control point 1.
12930
	 * @param float $x2 Abscissa of control point 2.
12931
	 * @param float $y2 Ordinate of control point 2.
12932
	 * @param float $x3 Abscissa of end point.
12933
	 * @param float $y3 Ordinate of end point.
12934
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12935
	 * @param array $line_style Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
12936
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
12937
	 * @access public
12938
	 * @see SetLineStyle()
12939
	 * @since 2.1.000 (2008-01-08)
12940
	 */
12941
	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
12942
		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12943
			$this->SetFillColorArray($fill_color);
12944
		}
12945
		$op = $this->getPathPaintOperator($style);
12946
		if ($line_style) {
12947
			$this->SetLineStyle($line_style);
12948
		}
12949
		$this->_outPoint($x0, $y0);
12950
		$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
12951
		$this->_out($op);
12952
	}
12953
 
12954
	/**
12955
	 * Draws a poly-Bezier curve.
12956
	 * Each Bezier curve segment is a tangent to the line between the control points at
12957
	 * either end of the curve.
12958
	 * @param float $x0 Abscissa of start point.
12959
	 * @param float $y0 Ordinate of start point.
12960
	 * @param float $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
12961
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12962
	 * @param array $line_style Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
12963
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
12964
	 * @access public
12965
	 * @see SetLineStyle()
12966
	 * @since 3.0008 (2008-05-12)
12967
	 */
12968
	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
12969
		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12970
			$this->SetFillColorArray($fill_color);
12971
		}
12972
		$op = $this->getPathPaintOperator($style);
12973
		if ($op == 'f') {
12974
			$line_style = array();
12975
		}
12976
		if ($line_style) {
12977
			$this->SetLineStyle($line_style);
12978
		}
12979
		$this->_outPoint($x0, $y0);
12980
		foreach ($segments as $segment) {
12981
			list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
12982
			$this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
12983
		}
12984
		$this->_out($op);
12985
	}
12986
 
12987
	/**
12988
	 * Draws an ellipse.
12989
	 * An ellipse is formed from n Bezier curves.
12990
	 * @param float $x0 Abscissa of center point.
12991
	 * @param float $y0 Ordinate of center point.
12992
	 * @param float $rx Horizontal radius.
12993
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0.
12994
	 * @param float $angle: Angle oriented (anti-clockwise). Default value: 0.
12995
	 * @param float $astart: Angle start of draw line. Default value: 0.
12996
	 * @param float $afinish: Angle finish of draw line. Default value: 360.
12997
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12998
	 * @param array $line_style Line style of ellipse. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
12999
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
13000
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
13001
	 * @author Nicola Asuni
13002
	 * @access public
13003
	 * @since 2.1.000 (2008-01-08)
13004
	 */
13005
	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
13006
		if ($this->empty_string($ry) OR ($ry == 0)) {
13007
			$ry = $rx;
13008
		}
13009
		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
13010
			$this->SetFillColorArray($fill_color);
13011
		}
13012
		$op = $this->getPathPaintOperator($style);
13013
		if ($op == 'f') {
13014
			$line_style = array();
13015
		}
13016
		if ($line_style) {
13017
			$this->SetLineStyle($line_style);
13018
		}
13019
		$this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc);
13020
		$this->_out($op);
13021
	}
13022
 
13023
	/**
13024
	 * Append an elliptical arc to the current path.
13025
	 * An ellipse is formed from n Bezier curves.
13026
	 * @param float $xc Abscissa of center point.
13027
	 * @param float $yc Ordinate of center point.
13028
	 * @param float $rx Horizontal radius.
13029
	 * @param float $ry Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0.
13030
	 * @param float $xang: Angle between the X-axis and the major axis of the ellipse. Default value: 0.
13031
	 * @param float $angs: Angle start of draw line. Default value: 0.
13032
	 * @param float $angf: Angle finish of draw line. Default value: 360.
13033
	 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
13034
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
13035
	 * @param boolean $startpoint if true output a starting point
13036
	 * @param boolean $ccw if true draws in counter-clockwise
13037
	 * @return array bounding box coordinates (x min, y min, x max, y max)
13038
	 * @author Nicola Asuni
13039
	 * @access protected
13040
	 * @since 4.9.019 (2010-04-26)
13041
	 */
13042
	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true) {
13043
		$k = $this->k;
13044
		if ($nc < 2) {
13045
			$nc = 2;
13046
		}
13047
		$xmin = 2147483647;
13048
		$ymin = 2147483647;
13049
		$xmax = 0;
13050
		$ymax = 0;
13051
		if ($pie) {
13052
			// center of the arc
13053
			$this->_outPoint($xc, $yc);
13054
		}
13055
		$xang = deg2rad((float) $xang);
13056
		$angs = deg2rad((float) $angs);
13057
		$angf = deg2rad((float) $angf);
13058
		$as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
13059
		$af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
13060
		if ($as < 0) {
13061
			$as += (2 * M_PI);
13062
		}
13063
		if ($af < 0) {
13064
			$af += (2 * M_PI);
13065
		}
13066
		if ($ccw AND ($as > $af)) {
13067
			// reverse rotation
13068
			$as -= (2 * M_PI);
13069
		} elseif (!$ccw AND ($as < $af)) {
13070
			// reverse rotation
13071
			$af -= (2 * M_PI);
13072
		}
13073
		$total_angle = ($af - $as);
13074
		if ($nc < 2) {
13075
			$nc = 2;
13076
		}
13077
		// total arcs to draw
13078
		$nc *= (2 * abs($total_angle) / M_PI);
13079
		$nc = round($nc) + 1;
13080
		// angle of each arc
13081
		$arcang = $total_angle / $nc;
13082
		// center point in PDF coordiantes
13083
		$x0 = $xc;
13084
		$y0 = ($this->h - $yc);
13085
		// starting angle
13086
		$ang = $as;
13087
		$alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
13088
		$cos_xang = cos($xang);
13089
		$sin_xang = sin($xang);
13090
		$cos_ang = cos($ang);
13091
		$sin_ang = sin($ang);
13092
		// first arc point
13093
		$px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
13094
		$py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
13095
		// first Bezier control point
13096
		$qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
13097
		$qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
13098
		if ($pie) {
13099
			// line from center to arc starting point
13100
			$this->_outLine($px1, $this->h - $py1);
13101
		} elseif ($startpoint) {
13102
			// arc starting point
13103
			$this->_outPoint($px1, $this->h - $py1);
13104
		}
13105
		// draw arcs
13106
		for ($i = 1; $i <= $nc; ++$i) {
13107
			// starting angle
13108
			$ang = $as + ($i * $arcang);
13109
			$cos_xang = cos($xang);
13110
			$sin_xang = sin($xang);
13111
			$cos_ang = cos($ang);
13112
			$sin_ang = sin($ang);
13113
			// second arc point
13114
			$px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
13115
			$py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
13116
			// second Bezier control point
13117
			$qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
13118
			$qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
13119
			// draw arc
13120
			$cx1 = ($px1 + $qx1);
13121
			$cy1 = ($this->h - ($py1 + $qy1));
13122
			$cx2 = ($px2 - $qx2);
13123
			$cy2 = ($this->h - ($py2 - $qy2));
13124
			$cx3 = $px2;
13125
			$cy3 = ($this->h - $py2);
13126
			$this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
13127
			// get bounding box coordinates
13128
			$xmin = min($xmin, $cx1, $cx2, $cx3);
13129
			$ymin = min($ymin, $cy1, $cy2, $cy3);
13130
			$xmax = max($xmax, $cx1, $cx2, $cx3);
13131
			$ymax = max($ymax, $cy1, $cy2, $cy3);
13132
			// move to next point
13133
			$px1 = $px2;
13134
			$py1 = $py2;
13135
			$qx1 = $qx2;
13136
			$qy1 = $qy2;
13137
		}
13138
		if ($pie) {
13139
			$this->_outLine($xc, $yc);
13140
			// get bounding box coordinates
13141
			$xmin = min($xmin, $xc);
13142
			$ymin = min($ymin, $yc);
13143
			$xmax = max($xmax, $xc);
13144
			$ymax = max($ymax, $yc);
13145
		}
13146
		return array($xmin, $ymin, $xmax, $ymax);
13147
	}
13148
 
13149
	/**
13150
	 * Draws a circle.
13151
	 * A circle is formed from n Bezier curves.
13152
	 * @param float $x0 Abscissa of center point.
13153
	 * @param float $y0 Ordinate of center point.
13154
	 * @param float $r Radius.
13155
	 * @param float $angstr: Angle start of draw line. Default value: 0.
13156
	 * @param float $angend: Angle finish of draw line. Default value: 360.
13157
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13158
	 * @param array $line_style Line style of circle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
13159
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
13160
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
13161
	 * @access public
13162
	 * @since 2.1.000 (2008-01-08)
13163
	 */
13164
	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
13165
		$this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
13166
	}
13167
 
13168
	/**
13169
	 * Draws a polygonal line
13170
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
13171
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13172
	 * @param array $line_style Line style of polygon. Array with keys among the following:
13173
	 * <ul>
13174
	 *	 <li>all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}.</li>
13175
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}.</li>
13176
	 * </ul>
13177
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
13178
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
13179
	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
13180
	 * @access public
13181
	 * @since 4.8.003 (2009-09-15)
13182
	 */
13183
	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
13184
		$this->Polygon($p, $style, $line_style, $fill_color, false);
13185
	}
13186
 
13187
	/**
13188
	 * Draws a polygon.
13189
	 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
13190
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13191
	 * @param array $line_style Line style of polygon. Array with keys among the following:
13192
	 * <ul>
13193
	 *	 <li>all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}.</li>
13194
	 *	 <li>0 to ($np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}.</li>
13195
	 * </ul>
13196
	 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
13197
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
13198
	 * @param boolean $closed if true the polygon is closes, otherwise will remain open
13199
	 * @access public
13200
	 * @since 2.1.000 (2008-01-08)
13201
	 */
13202
	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
13203
		$nc = count($p); // number of coordinates
13204
		$np = $nc / 2; // number of points
13205
		if ($closed) {
13206
			// close polygon by adding the first 2 points at the end (one line)
13207
			for ($i = 0; $i < 4; ++$i) {
13208
				$p[$nc + $i] = $p[$i];
13209
			}
13210
			// copy style for the last added line
13211
			if (isset($line_style[0])) {
13212
				$line_style[$np] = $line_style[0];
13213
			}
13214
			$nc += 4;
13215
		}
13216
		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
13217
			$this->SetFillColorArray($fill_color);
13218
		}
13219
		$op = $this->getPathPaintOperator($style);
13220
		if ($op == 'f') {
13221
			$line_style = array();
13222
		}
13223
		$draw = true;
13224
		if ($line_style) {
13225
			if (isset($line_style['all'])) {
13226
				$this->SetLineStyle($line_style['all']);
13227
			} else {
13228
				$draw = false;
13229
				if ($op == 'B') {
13230
					// draw fill
13231
					$op = 'f';
13232
					$this->_outPoint($p[0], $p[1]);
13233
					for ($i = 2; $i < $nc; $i = $i + 2) {
13234
						$this->_outLine($p[$i], $p[$i + 1]);
13235
					}
13236
					$this->_out($op);
13237
				}
13238
				// draw outline
13239
				$this->_outPoint($p[0], $p[1]);
13240
				for ($i = 2; $i < $nc; $i = $i + 2) {
13241
					$line_num = ($i / 2) - 1;
13242
					if (isset($line_style[$line_num])) {
13243
						if ($line_style[$line_num] != 0) {
13244
							if (is_array($line_style[$line_num])) {
13245
								$this->_out('S');
13246
								$this->SetLineStyle($line_style[$line_num]);
13247
								$this->_outPoint($p[$i - 2], $p[$i - 1]);
13248
								$this->_outLine($p[$i], $p[$i + 1]);
13249
								$this->_out('S');
13250
								$this->_outPoint($p[$i], $p[$i + 1]);
13251
							} else {
13252
								$this->_outLine($p[$i], $p[$i + 1]);
13253
							}
13254
						}
13255
					} else {
13256
						$this->_outLine($p[$i], $p[$i + 1]);
13257
					}
13258
				}
13259
				$this->_out($op);
13260
			}
13261
		}
13262
		if ($draw) {
13263
			$this->_outPoint($p[0], $p[1]);
13264
			for ($i = 2; $i < $nc; $i = $i + 2) {
13265
				$this->_outLine($p[$i], $p[$i + 1]);
13266
			}
13267
			$this->_out($op);
13268
		}
13269
	}
13270
 
13271
	/**
13272
	 * Draws a regular polygon.
13273
	 * @param float $x0 Abscissa of center point.
13274
	 * @param float $y0 Ordinate of center point.
13275
	 * @param float $r: Radius of inscribed circle.
13276
	 * @param integer $ns Number of sides.
13277
	 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
13278
	 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
13279
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13280
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
13281
	 * <ul>
13282
	 *	 <li>all: Line style of all sides. Array like for {@link SetLineStyle SetLineStyle}.</li>
13283
	 *	 <li>0 to ($ns - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}.</li>
13284
	 * </ul>
13285
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
13286
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
13287
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
13288
	 * <ul>
13289
	 *	 <li>D or empty string: Draw (default).</li>
13290
	 *	 <li>F: Fill.</li>
13291
	 *	 <li>DF or FD: Draw and fill.</li>
13292
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
13293
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
13294
	 * </ul>
13295
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
13296
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
13297
	 * @access public
13298
	 * @since 2.1.000 (2008-01-08)
13299
	 */
13300
	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()) {
13301
		if (3 > $ns) {
13302
			$ns = 3;
13303
		}
13304
		if ($draw_circle) {
13305
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
13306
		}
13307
		$p = array();
13308
		for ($i = 0; $i < $ns; ++$i) {
13309
			$a = $angle + ($i * 360 / $ns);
13310
			$a_rad = deg2rad((float) $a);
13311
			$p[] = $x0 + ($r * sin($a_rad));
13312
			$p[] = $y0 + ($r * cos($a_rad));
13313
		}
13314
		$this->Polygon($p, $style, $line_style, $fill_color);
13315
	}
13316
 
13317
	/**
13318
	 * Draws a star polygon
13319
	 * @param float $x0 Abscissa of center point.
13320
	 * @param float $y0 Ordinate of center point.
13321
	 * @param float $r Radius of inscribed circle.
13322
	 * @param integer $nv Number of vertices.
13323
	 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
13324
	 * @param float $angle: Angle oriented (anti-clockwise). Default value: 0.
13325
	 * @param boolean $draw_circle: Draw inscribed circle or not. Default value is false.
13326
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13327
	 * @param array $line_style Line style of polygon sides. Array with keys among the following:
13328
	 * <ul>
13329
	 *	 <li>all: Line style of all sides. Array like for
13330
	 * {@link SetLineStyle SetLineStyle}.</li>
13331
	 *	 <li>0 to (n - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}.</li>
13332
	 * </ul>
13333
	 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
13334
	 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
13335
	 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
13336
	 * <ul>
13337
	 *	 <li>D or empty string: Draw (default).</li>
13338
	 *	 <li>F: Fill.</li>
13339
	 *	 <li>DF or FD: Draw and fill.</li>
13340
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
13341
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
13342
	 * </ul>
13343
	 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
13344
	 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
13345
	 * @access public
13346
	 * @since 2.1.000 (2008-01-08)
13347
	 */
13348
	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()) {
13349
		if ($nv < 2) {
13350
			$nv = 2;
13351
		}
13352
		if ($draw_circle) {
13353
			$this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
13354
		}
13355
		$p2 = array();
13356
		$visited = array();
13357
		for ($i = 0; $i < $nv; ++$i) {
13358
			$a = $angle + ($i * 360 / $nv);
13359
			$a_rad = deg2rad((float) $a);
13360
			$p2[] = $x0 + ($r * sin($a_rad));
13361
			$p2[] = $y0 + ($r * cos($a_rad));
13362
			$visited[] = false;
13363
		}
13364
		$p = array();
13365
		$i = 0;
13366
		do {
13367
			$p[] = $p2[$i * 2];
13368
			$p[] = $p2[($i * 2) + 1];
13369
			$visited[$i] = true;
13370
			$i += $ng;
13371
			$i %= $nv;
13372
		} while (!$visited[$i]);
13373
		$this->Polygon($p, $style, $line_style, $fill_color);
13374
	}
13375
 
13376
	/**
13377
	 * Draws a rounded rectangle.
13378
	 * @param float $x Abscissa of upper-left corner.
13379
	 * @param float $y Ordinate of upper-left corner.
13380
	 * @param float $w Width.
13381
	 * @param float $h Height.
13382
	 * @param float $r the radius of the circle used to round off the corners of the rectangle.
13383
	 * @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 left, top right, bottom right and bottom left. Default value: all rounded corner ("1111").
13384
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13385
	 * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
13386
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
13387
	 * @access public
13388
	 * @since 2.1.000 (2008-01-08)
13389
	 */
13390
	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
13391
		$this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
13392
	}
13393
 
13394
	/**
13395
	 * Draws a rounded rectangle.
13396
	 * @param float $x Abscissa of upper-left corner.
13397
	 * @param float $y Ordinate of upper-left corner.
13398
	 * @param float $w Width.
13399
	 * @param float $h Height.
13400
	 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
13401
	 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
13402
	 * @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 left, top right, bottom right and bottom left. Default value: all rounded corner ("1111").
13403
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
13404
	 * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
13405
	 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
13406
	 * @access public
13407
	 * @since 4.9.019 (2010-04-22)
13408
	 */
13409
	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
13410
		if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
13411
			// Not rounded
13412
			$this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
13413
			return;
13414
		}
13415
		// Rounded
13416
		if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
13417
			$this->SetFillColorArray($fill_color);
13418
		}
13419
		$op = $this->getPathPaintOperator($style);
13420
		if ($op == 'f') {
13421
			$border_style = array();
13422
		}
13423
		if ($border_style) {
13424
			$this->SetLineStyle($border_style);
13425
		}
13426
		$MyArc = 4 / 3 * (sqrt(2) - 1);
13427
		$this->_outPoint($x + $rx, $y);
13428
		$xc = $x + $w - $rx;
13429
		$yc = $y + $ry;
13430
		$this->_outLine($xc, $y);
13431
		if ($round_corner[0]) {
13432
			$this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
13433
		} else {
13434
			$this->_outLine($x + $w, $y);
13435
		}
13436
		$xc = $x + $w - $rx;
13437
		$yc = $y + $h - $ry;
13438
		$this->_outLine($x + $w, $yc);
13439
		if ($round_corner[1]) {
13440
			$this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
13441
		} else {
13442
			$this->_outLine($x + $w, $y + $h);
13443
		}
13444
		$xc = $x + $rx;
13445
		$yc = $y + $h - $ry;
13446
		$this->_outLine($xc, $y + $h);
13447
		if ($round_corner[2]) {
13448
			$this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
13449
		} else {
13450
			$this->_outLine($x, $y + $h);
13451
		}
13452
		$xc = $x + $rx;
13453
		$yc = $y + $ry;
13454
		$this->_outLine($x, $yc);
13455
		if ($round_corner[3]) {
13456
			$this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
13457
		} else {
13458
			$this->_outLine($x, $y);
13459
			$this->_outLine($x + $rx, $y);
13460
		}
13461
		$this->_out($op);
13462
	}
13463
 
13464
	/**
13465
	 * Draws a grahic arrow.
13466
	 * @param float $x0 Abscissa of first point.
13467
	 * @param float $y0 Ordinate of first point.
13468
	 * @param float $x0 Abscissa of second point.
13469
	 * @param float $y1 Ordinate of second point.
13470
	 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
13471
	 * @param float $arm_size length of arrowhead arms
13472
	 * @param int $arm_angle angle between an arm and the shaft
13473
	 * @author Piotr Galecki, Nicola Asuni, Andy Meier
13474
	 * @since 4.6.018 (2009-07-10)
13475
	 */
13476
	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
13477
		// getting arrow direction angle
13478
		// 0 deg angle is when both arms go along X axis. angle grows clockwise.
13479
		$dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
13480
		if ($dir_angle < 0) {
13481
			$dir_angle += (2 * M_PI);
13482
		}
13483
		$arm_angle = deg2rad($arm_angle);
13484
		$sx1 = $x1;
13485
		$sy1 = $y1;
13486
		if ($head_style > 0) {
13487
			// calculate the stopping point for the arrow shaft
13488
			$sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
13489
			$sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
13490
		}
13491
		// main arrow line / shaft
13492
		$this->Line($x0, $y0, $sx1, $sy1);
13493
		// left arrowhead arm tip
13494
		$x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
13495
		$y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
13496
		// right arrowhead arm tip
13497
		$x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
13498
		$y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
13499
		$mode = 'D';
13500
		$style = array();
13501
		switch ($head_style) {
13502
			case 0: {
13503
				// draw only arrowhead arms
13504
				$mode = 'D';
13505
				$style = array(1, 1, 0);
13506
				break;
13507
			}
13508
			case 1: {
13509
				// draw closed arrowhead, but no fill
13510
				$mode = 'D';
13511
				break;
13512
			}
13513
			case 2: {
13514
				// closed and filled arrowhead
13515
				$mode = 'DF';
13516
				break;
13517
			}
13518
			case 3: {
13519
				// filled arrowhead
13520
				$mode = 'F';
13521
				break;
13522
			}
13523
		}
13524
		$this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
13525
	}
13526
 
13527
	// END GRAPHIC FUNCTIONS SECTION -----------------------
13528
 
13529
	// BIDIRECTIONAL TEXT SECTION --------------------------
13530
 
13531
	/**
13532
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
13533
	 * @param string $str string to manipulate.
13534
	 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
13535
	 * @param bool $forcertl if true forces RTL text direction
13536
	 * @return string
13537
	 * @access protected
13538
	 * @author Nicola Asuni
13539
	 * @since 2.1.000 (2008-01-08)
13540
	 */
13541
	protected function utf8StrRev($str, $setbom=false, $forcertl=false) {
13542
		return $this->utf8StrArrRev($this->UTF8StringToArray($str), $str, $setbom, $forcertl);
13543
	}
13544
 
13545
	/**
13546
	 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
13547
	 * @param array $arr array of unicode values.
13548
	 * @param string $str string to manipulate (or empty value).
13549
	 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
13550
	 * @param bool $forcertl if true forces RTL text direction
13551
	 * @return string
13552
	 * @access protected
13553
	 * @author Nicola Asuni
13554
	 * @since 4.9.000 (2010-03-27)
13555
	 */
13556
	protected function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false) {
13557
		return $this->arrUTF8ToUTF16BE($this->utf8Bidi($arr, $str, $forcertl), $setbom);
13558
	}
13559
 
13560
	/**
13561
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
13562
	 * @param array $ta array of characters composing the string.
13563
	 * @param string $str string to process
13564
	 * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR
13565
	 * @return array of unicode chars
13566
	 * @author Nicola Asuni
13567
	 * @access protected
13568
	 * @since 2.4.000 (2008-03-06)
13569
	 */
13570
	protected function utf8Bidi($ta, $str='', $forcertl=false) {
13571
		// paragraph embedding level
13572
		$pel = 0;
13573
		// max level
13574
		$maxlevel = 0;
13575
		if ($this->empty_string($str)) {
13576
			// create string from array
13577
			$str = $this->UTF8ArrSubString($ta);
13578
		}
13579
		// check if string contains arabic text
13580
		if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $str)) {
13581
			$arabic = true;
13582
		} else {
13583
			$arabic = false;
13584
		}
13585
		// check if string contains RTL text
13586
		if (!($forcertl OR $arabic OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $str))) {
13587
			return $ta;
13588
		}
13589
 
13590
		// get number of chars
13591
		$numchars = count($ta);
13592
 
13593
		if ($forcertl == 'R') {
13594
			$pel = 1;
13595
		} elseif ($forcertl == 'L') {
13596
			$pel = 0;
13597
		} else {
13598
			// P2. In each paragraph, find the first character of type L, AL, or R.
13599
			// P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
13600
			for ($i=0; $i < $numchars; ++$i) {
13601
				$type = $this->unicode->uni_type[$ta[$i]];
13602
				if ($type == 'L') {
13603
					$pel = 0;
13604
					break;
13605
				} elseif (($type == 'AL') OR ($type == 'R')) {
13606
					$pel = 1;
13607
					break;
13608
				}
13609
			}
13610
		}
13611
 
13612
		// Current Embedding Level
13613
		$cel = $pel;
13614
		// directional override status
13615
		$dos = 'N';
13616
		$remember = array();
13617
		// start-of-level-run
13618
		$sor = $pel % 2 ? 'R' : 'L';
13619
		$eor = $sor;
13620
 
13621
		// Array of characters data
13622
		$chardata = Array();
13623
 
13624
		// X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
13625
		// In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
13626
		for ($i=0; $i < $numchars; ++$i) {
13627
			if ($ta[$i] == $this->unicode->uni_RLE) {
13628
				// X2. With each RLE, compute the least greater odd embedding level.
13629
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
13630
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13631
				$next_level = $cel + ($cel % 2) + 1;
13632
				if ($next_level < 62) {
13633
					$remember[] = array('num' => $this->unicode->uni_RLE, 'cel' => $cel, 'dos' => $dos);
13634
					$cel = $next_level;
13635
					$dos = 'N';
13636
					$sor = $eor;
13637
					$eor = $cel % 2 ? 'R' : 'L';
13638
				}
13639
			} elseif ($ta[$i] == $this->unicode->uni_LRE) {
13640
				// X3. With each LRE, compute the least greater even embedding level.
13641
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
13642
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13643
				$next_level = $cel + 2 - ($cel % 2);
13644
				if ( $next_level < 62 ) {
13645
					$remember[] = array('num' => $this->unicode->uni_LRE, 'cel' => $cel, 'dos' => $dos);
13646
					$cel = $next_level;
13647
					$dos = 'N';
13648
					$sor = $eor;
13649
					$eor = $cel % 2 ? 'R' : 'L';
13650
				}
13651
			} elseif ($ta[$i] == $this->unicode->uni_RLO) {
13652
				// X4. With each RLO, compute the least greater odd embedding level.
13653
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
13654
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13655
				$next_level = $cel + ($cel % 2) + 1;
13656
				if ($next_level < 62) {
13657
					$remember[] = array('num' => $this->unicode->uni_RLO, 'cel' => $cel, 'dos' => $dos);
13658
					$cel = $next_level;
13659
					$dos = 'R';
13660
					$sor = $eor;
13661
					$eor = $cel % 2 ? 'R' : 'L';
13662
				}
13663
			} elseif ($ta[$i] == $this->unicode->uni_LRO) {
13664
				// X5. With each LRO, compute the least greater even embedding level.
13665
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
13666
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13667
				$next_level = $cel + 2 - ($cel % 2);
13668
				if ( $next_level < 62 ) {
13669
					$remember[] = array('num' => $this->unicode->uni_LRO, 'cel' => $cel, 'dos' => $dos);
13670
					$cel = $next_level;
13671
					$dos = 'L';
13672
					$sor = $eor;
13673
					$eor = $cel % 2 ? 'R' : 'L';
13674
				}
13675
			} elseif ($ta[$i] == $this->unicode->uni_PDF) {
13676
				// X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
13677
				if (count($remember)) {
13678
					$last = count($remember ) - 1;
13679
					if (($remember[$last]['num'] == $this->unicode->uni_RLE) OR
13680
						($remember[$last]['num'] == $this->unicode->uni_LRE) OR
13681
						($remember[$last]['num'] == $this->unicode->uni_RLO) OR
13682
						($remember[$last]['num'] == $this->unicode->uni_LRO)) {
13683
						$match = array_pop($remember);
13684
						$cel = $match['cel'];
13685
						$dos = $match['dos'];
13686
						$sor = $eor;
13687
						$eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
13688
					}
13689
				}
13690
			} elseif (($ta[$i] != $this->unicode->uni_RLE) AND
13691
							 ($ta[$i] != $this->unicode->uni_LRE) AND
13692
							 ($ta[$i] != $this->unicode->uni_RLO) AND
13693
							 ($ta[$i] != $this->unicode->uni_LRO) AND
13694
							 ($ta[$i] != $this->unicode->uni_PDF)) {
13695
				// X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
13696
				//	a. Set the level of the current character to the current embedding level.
13697
				//	b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
13698
				if ($dos != 'N') {
13699
					$chardir = $dos;
13700
				} else {
13701
					if (isset($this->unicode->uni_type[$ta[$i]])) {
13702
						$chardir = $this->unicode->uni_type[$ta[$i]];
13703
					} else {
13704
						$chardir = 'L';
13705
					}
13706
				}
13707
				// stores string characters and other information
13708
				$chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
13709
			}
13710
		} // end for each char
13711
 
13712
		// X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
13713
		// X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
13714
		// X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
13715
 
13716
		// 3.3.3 Resolving Weak Types
13717
		// Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
13718
		// Nonspacing marks are now resolved based on the previous characters.
13719
		$numchars = count($chardata);
13720
 
13721
		// W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
13722
		$prevlevel = -1; // track level changes
13723
		$levcount = 0; // counts consecutive chars at the same level
13724
		for ($i=0; $i < $numchars; ++$i) {
13725
			if ($chardata[$i]['type'] == 'NSM') {
13726
				if ($levcount) {
13727
					$chardata[$i]['type'] = $chardata[$i]['sor'];
13728
				} elseif ($i > 0) {
13729
					$chardata[$i]['type'] = $chardata[($i-1)]['type'];
13730
				}
13731
			}
13732
			if ($chardata[$i]['level'] != $prevlevel) {
13733
				$levcount = 0;
13734
			} else {
13735
				++$levcount;
13736
			}
13737
			$prevlevel = $chardata[$i]['level'];
13738
		}
13739
 
13740
		// W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
13741
		$prevlevel = -1;
13742
		$levcount = 0;
13743
		for ($i=0; $i < $numchars; ++$i) {
13744
			if ($chardata[$i]['char'] == 'EN') {
13745
				for ($j=$levcount; $j >= 0; $j--) {
13746
					if ($chardata[$j]['type'] == 'AL') {
13747
						$chardata[$i]['type'] = 'AN';
13748
					} elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
13749
						break;
13750
					}
13751
				}
13752
			}
13753
			if ($chardata[$i]['level'] != $prevlevel) {
13754
				$levcount = 0;
13755
			} else {
13756
				++$levcount;
13757
			}
13758
			$prevlevel = $chardata[$i]['level'];
13759
		}
13760
 
13761
		// W3. Change all ALs to R.
13762
		for ($i=0; $i < $numchars; ++$i) {
13763
			if ($chardata[$i]['type'] == 'AL') {
13764
				$chardata[$i]['type'] = 'R';
13765
			}
13766
		}
13767
 
13768
		// W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
13769
		$prevlevel = -1;
13770
		$levcount = 0;
13771
		for ($i=0; $i < $numchars; ++$i) {
13772
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13773
				if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
13774
					$chardata[$i]['type'] = 'EN';
13775
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
13776
					$chardata[$i]['type'] = 'EN';
13777
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
13778
					$chardata[$i]['type'] = 'AN';
13779
				}
13780
			}
13781
			if ($chardata[$i]['level'] != $prevlevel) {
13782
				$levcount = 0;
13783
			} else {
13784
				++$levcount;
13785
			}
13786
			$prevlevel = $chardata[$i]['level'];
13787
		}
13788
 
13789
		// W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
13790
		$prevlevel = -1;
13791
		$levcount = 0;
13792
		for ($i=0; $i < $numchars; ++$i) {
13793
			if ($chardata[$i]['type'] == 'ET') {
13794
				if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
13795
					$chardata[$i]['type'] = 'EN';
13796
				} else {
13797
					$j = $i+1;
13798
					while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
13799
						if ($chardata[$j]['type'] == 'EN') {
13800
							$chardata[$i]['type'] = 'EN';
13801
							break;
13802
						} elseif ($chardata[$j]['type'] != 'ET') {
13803
							break;
13804
						}
13805
						++$j;
13806
					}
13807
				}
13808
			}
13809
			if ($chardata[$i]['level'] != $prevlevel) {
13810
				$levcount = 0;
13811
			} else {
13812
				++$levcount;
13813
			}
13814
			$prevlevel = $chardata[$i]['level'];
13815
		}
13816
 
13817
		// W6. Otherwise, separators and terminators change to Other Neutral.
13818
		$prevlevel = -1;
13819
		$levcount = 0;
13820
		for ($i=0; $i < $numchars; ++$i) {
13821
			if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
13822
				$chardata[$i]['type'] = 'ON';
13823
			}
13824
			if ($chardata[$i]['level'] != $prevlevel) {
13825
				$levcount = 0;
13826
			} else {
13827
				++$levcount;
13828
			}
13829
			$prevlevel = $chardata[$i]['level'];
13830
		}
13831
 
13832
		//W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
13833
		$prevlevel = -1;
13834
		$levcount = 0;
13835
		for ($i=0; $i < $numchars; ++$i) {
13836
			if ($chardata[$i]['char'] == 'EN') {
13837
				for ($j=$levcount; $j >= 0; $j--) {
13838
					if ($chardata[$j]['type'] == 'L') {
13839
						$chardata[$i]['type'] = 'L';
13840
					} elseif ($chardata[$j]['type'] == 'R') {
13841
						break;
13842
					}
13843
				}
13844
			}
13845
			if ($chardata[$i]['level'] != $prevlevel) {
13846
				$levcount = 0;
13847
			} else {
13848
				++$levcount;
13849
			}
13850
			$prevlevel = $chardata[$i]['level'];
13851
		}
13852
 
13853
		// N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
13854
		$prevlevel = -1;
13855
		$levcount = 0;
13856
		for ($i=0; $i < $numchars; ++$i) {
13857
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13858
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
13859
					$chardata[$i]['type'] = 'L';
13860
				} elseif (($chardata[$i]['type'] == 'N') AND
13861
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
13862
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
13863
					$chardata[$i]['type'] = 'R';
13864
				} elseif ($chardata[$i]['type'] == 'N') {
13865
					// N2. Any remaining neutrals take the embedding direction
13866
					$chardata[$i]['type'] = $chardata[$i]['sor'];
13867
				}
13868
			} elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13869
				// first char
13870
				if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
13871
					$chardata[$i]['type'] = 'L';
13872
				} elseif (($chardata[$i]['type'] == 'N') AND
13873
				 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
13874
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
13875
					$chardata[$i]['type'] = 'R';
13876
				} elseif ($chardata[$i]['type'] == 'N') {
13877
					// N2. Any remaining neutrals take the embedding direction
13878
					$chardata[$i]['type'] = $chardata[$i]['sor'];
13879
				}
13880
			} elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
13881
				//last char
13882
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
13883
					$chardata[$i]['type'] = 'L';
13884
				} elseif (($chardata[$i]['type'] == 'N') AND
13885
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
13886
				 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
13887
					$chardata[$i]['type'] = 'R';
13888
				} elseif ($chardata[$i]['type'] == 'N') {
13889
					// N2. Any remaining neutrals take the embedding direction
13890
					$chardata[$i]['type'] = $chardata[$i]['sor'];
13891
				}
13892
			} elseif ($chardata[$i]['type'] == 'N') {
13893
				// N2. Any remaining neutrals take the embedding direction
13894
				$chardata[$i]['type'] = $chardata[$i]['sor'];
13895
			}
13896
			if ($chardata[$i]['level'] != $prevlevel) {
13897
				$levcount = 0;
13898
			} else {
13899
				++$levcount;
13900
			}
13901
			$prevlevel = $chardata[$i]['level'];
13902
		}
13903
 
13904
		// I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
13905
		// I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
13906
		for ($i=0; $i < $numchars; ++$i) {
13907
			$odd = $chardata[$i]['level'] % 2;
13908
			if ($odd) {
13909
				if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
13910
					$chardata[$i]['level'] += 1;
13911
				}
13912
			} else {
13913
				if ($chardata[$i]['type'] == 'R') {
13914
					$chardata[$i]['level'] += 1;
13915
				} elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
13916
					$chardata[$i]['level'] += 2;
13917
				}
13918
			}
13919
			$maxlevel = max($chardata[$i]['level'],$maxlevel);
13920
		}
13921
 
13922
		// L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
13923
		//	1. Segment separators,
13924
		//	2. Paragraph separators,
13925
		//	3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
13926
		//	4. Any sequence of white space characters at the end of the line.
13927
		for ($i=0; $i < $numchars; ++$i) {
13928
			if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
13929
				$chardata[$i]['level'] = $pel;
13930
			} elseif ($chardata[$i]['type'] == 'WS') {
13931
				$j = $i+1;
13932
				while ($j < $numchars) {
13933
					if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
13934
						(($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
13935
						$chardata[$i]['level'] = $pel;
13936
						break;
13937
					} elseif ($chardata[$j]['type'] != 'WS') {
13938
						break;
13939
					}
13940
					++$j;
13941
				}
13942
			}
13943
		}
13944
 
13945
		// Arabic Shaping
13946
		// Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
13947
		if ($arabic) {
13948
			$endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
13949
			$alfletter = array(1570,1571,1573,1575);
13950
			$chardata2 = $chardata;
13951
			$laaletter = false;
13952
			$charAL = array();
13953
			$x = 0;
13954
			for ($i=0; $i < $numchars; ++$i) {
13955
				if (($this->unicode->uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
13956
					$charAL[$x] = $chardata[$i];
13957
					$charAL[$x]['i'] = $i;
13958
					$chardata[$i]['x'] = $x;
13959
					++$x;
13960
				}
13961
			}
13962
			$numAL = $x;
13963
			for ($i=0; $i < $numchars; ++$i) {
13964
				$thischar = $chardata[$i];
13965
				if ($i > 0) {
13966
					$prevchar = $chardata[($i-1)];
13967
				} else {
13968
					$prevchar = false;
13969
				}
13970
				if (($i+1) < $numchars) {
13971
					$nextchar = $chardata[($i+1)];
13972
				} else {
13973
					$nextchar = false;
13974
				}
13975
				if ($this->unicode->uni_type[$thischar['char']] == 'AL') {
13976
					$x = $thischar['x'];
13977
					if ($x > 0) {
13978
						$prevchar = $charAL[($x-1)];
13979
					} else {
13980
						$prevchar = false;
13981
					}
13982
					if (($x+1) < $numAL) {
13983
						$nextchar = $charAL[($x+1)];
13984
					} else {
13985
						$nextchar = false;
13986
					}
13987
					// if laa letter
13988
					if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
13989
						$arabicarr = $this->unicode->uni_laa_array;
13990
						$laaletter = true;
13991
						if ($x > 1) {
13992
							$prevchar = $charAL[($x-2)];
13993
						} else {
13994
							$prevchar = false;
13995
						}
13996
					} else {
13997
						$arabicarr = $this->unicode->uni_arabicsubst;
13998
						$laaletter = false;
13999
					}
14000
					if (($prevchar !== false) AND ($nextchar !== false) AND
14001
						(($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
14002
						(($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
14003
						($prevchar['type'] == $thischar['type']) AND
14004
						($nextchar['type'] == $thischar['type']) AND
14005
						($nextchar['char'] != 1567)) {
14006
						if (in_array($prevchar['char'], $endedletter)) {
14007
							if (isset($arabicarr[$thischar['char']][2])) {
14008
								// initial
14009
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
14010
							}
14011
						} else {
14012
							if (isset($arabicarr[$thischar['char']][3])) {
14013
								// medial
14014
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
14015
							}
14016
						}
14017
					} elseif (($nextchar !== false) AND
14018
						(($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
14019
						($nextchar['type'] == $thischar['type']) AND
14020
						($nextchar['char'] != 1567)) {
14021
						if (isset($arabicarr[$chardata[$i]['char']][2])) {
14022
							// initial
14023
							$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
14024
						}
14025
					} elseif ((($prevchar !== false) AND
14026
						(($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
14027
						($prevchar['type'] == $thischar['type'])) OR
14028
						(($nextchar !== false) AND ($nextchar['char'] == 1567))) {
14029
						// final
14030
						if (($i > 1) AND ($thischar['char'] == 1607) AND
14031
							($chardata[$i-1]['char'] == 1604) AND
14032
							($chardata[$i-2]['char'] == 1604)) {
14033
							//Allah Word
14034
							// mark characters to delete with false
14035
							$chardata2[$i-2]['char'] = false;
14036
							$chardata2[$i-1]['char'] = false;
14037
							$chardata2[$i]['char'] = 65010;
14038
						} else {
14039
							if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
14040
								if (isset($arabicarr[$thischar['char']][0])) {
14041
									// isolated
14042
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
14043
								}
14044
							} else {
14045
								if (isset($arabicarr[$thischar['char']][1])) {
14046
									// final
14047
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
14048
								}
14049
							}
14050
						}
14051
					} elseif (isset($arabicarr[$thischar['char']][0])) {
14052
						// isolated
14053
						$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
14054
					}
14055
					// if laa letter
14056
					if ($laaletter) {
14057
						// mark characters to delete with false
14058
						$chardata2[($charAL[($x-1)]['i'])]['char'] = false;
14059
					}
14060
				} // end if AL (Arabic Letter)
14061
			} // end for each char
14062
			/*
14063
			 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
14064
			 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
14065
			 */
14066
			$cw = &$this->CurrentFont['cw'];
14067
			for ($i = 0; $i < ($numchars-1); ++$i) {
14068
				if (($chardata2[$i]['char'] == 1617) AND (isset($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])]))) {
14069
					// check if the subtitution font is defined on current font
14070
					if (isset($cw[($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])])])) {
14071
						$chardata2[$i]['char'] = false;
14072
						$chardata2[$i+1]['char'] = $this->unicode->uni_diacritics[($chardata2[$i+1]['char'])];
14073
					}
14074
				}
14075
			}
14076
			// remove marked characters
14077
			foreach ($chardata2 as $key => $value) {
14078
				if ($value['char'] === false) {
14079
					unset($chardata2[$key]);
14080
				}
14081
			}
14082
			$chardata = array_values($chardata2);
14083
			$numchars = count($chardata);
14084
			unset($chardata2);
14085
			unset($arabicarr);
14086
			unset($laaletter);
14087
			unset($charAL);
14088
		}
14089
 
14090
		// L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
14091
		for ($j=$maxlevel; $j > 0; $j--) {
14092
			$ordarray = Array();
14093
			$revarr = Array();
14094
			$onlevel = false;
14095
			for ($i=0; $i < $numchars; ++$i) {
14096
				if ($chardata[$i]['level'] >= $j) {
14097
					$onlevel = true;
14098
					if (isset($this->unicode->uni_mirror[$chardata[$i]['char']])) {
14099
						// L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
14100
						$chardata[$i]['char'] = $this->unicode->uni_mirror[$chardata[$i]['char']];
14101
					}
14102
					$revarr[] = $chardata[$i];
14103
				} else {
14104
					if ($onlevel) {
14105
						$revarr = array_reverse($revarr);
14106
						$ordarray = array_merge($ordarray, $revarr);
14107
						$revarr = Array();
14108
						$onlevel = false;
14109
					}
14110
					$ordarray[] = $chardata[$i];
14111
				}
14112
			}
14113
			if ($onlevel) {
14114
				$revarr = array_reverse($revarr);
14115
				$ordarray = array_merge($ordarray, $revarr);
14116
			}
14117
			$chardata = $ordarray;
14118
		}
14119
 
14120
		$ordarray = array();
14121
		for ($i=0; $i < $numchars; ++$i) {
14122
			$ordarray[] = $chardata[$i]['char'];
14123
			// store char values for subsetting
14124
			$this->CurrentFont['subsetchars'][$chardata[$i]['char']] = true;
14125
		}
14126
		// update font subsetchars
14127
		$this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
14128
		return $ordarray;
14129
	}
14130
 
14131
	// END OF BIDIRECTIONAL TEXT SECTION -------------------
14132
 
14133
	/**
14134
	 * Adds a bookmark.
14135
	 * @param string $txt bookmark description.
14136
	 * @param int $level bookmark level (minimum value is 0).
14137
	 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
14138
	 * @param int $page target page number (leave empty for current page).
14139
	 * @access public
14140
	 * @author Olivier Plathey, Nicola Asuni
14141
	 * @since 2.1.002 (2008-02-12)
14142
	 */
14143
	public function Bookmark($txt, $level=0, $y=-1, $page='') {
14144
		if ($level < 0) {
14145
			$level = 0;
14146
		}
14147
		if (isset($this->outlines[0])) {
14148
			$lastoutline = end($this->outlines);
14149
			$maxlevel = $lastoutline['l'] + 1;
14150
		} else {
14151
			$maxlevel = 0;
14152
		}
14153
		if ($level > $maxlevel) {
14154
			$level = $maxlevel;
14155
		}
14156
		if ($y == -1) {
14157
			$y = $this->GetY();
14158
		}
14159
		if (empty($page)) {
14160
			$page = $this->PageNo();
14161
			if (empty($page)) {
14162
				return;
14163
			}
14164
		}
14165
		$this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => $y, 'p' => $page);
14166
	}
14167
 
14168
	/**
14169
	 * Create a bookmark PDF string.
14170
	 * @access protected
14171
	 * @author Olivier Plathey, Nicola Asuni
14172
	 * @since 2.1.002 (2008-02-12)
14173
	 */
14174
	protected function _putbookmarks() {
14175
		$nb = count($this->outlines);
14176
		if ($nb == 0) {
14177
			return;
14178
		}
14179
		// get sorting columns
14180
		$outline_p = array();
14181
		$outline_y = array();
14182
		foreach ($this->outlines as $key => $row) {
14183
			$outline_p[$key] = $row['p'];
14184
			$outline_k[$key] = $key;
14185
		}
14186
		// sort outlines by page and original position
14187
		array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
14188
		$lru = array();
14189
		$level = 0;
14190
		foreach ($this->outlines as $i => $o) {
14191
			if ($o['l'] > 0) {
14192
				$parent = $lru[($o['l'] - 1)];
14193
				//Set parent and last pointers
14194
				$this->outlines[$i]['parent'] = $parent;
14195
				$this->outlines[$parent]['last'] = $i;
14196
				if ($o['l'] > $level) {
14197
					//Level increasing: set first pointer
14198
					$this->outlines[$parent]['first'] = $i;
14199
				}
14200
			} else {
14201
				$this->outlines[$i]['parent'] = $nb;
14202
			}
14203
			if (($o['l'] <= $level) AND ($i > 0)) {
14204
				//Set prev and next pointers
14205
				$prev = $lru[$o['l']];
14206
				$this->outlines[$prev]['next'] = $i;
14207
				$this->outlines[$i]['prev'] = $prev;
14208
			}
14209
			$lru[$o['l']] = $i;
14210
			$level = $o['l'];
14211
		}
14212
		//Outline items
14213
		$n = $this->n + 1;
14214
		$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';
14215
		foreach ($this->outlines as $i => $o) {
14216
			if (isset($this->page_obj_id[($o['p'])])) {
14217
				$oid = $this->_newobj();
14218
				// covert HTML title to string
14219
				$title = preg_replace($nltags, "\n", $o['t']);
14220
				$title = preg_replace("/[\r]+/si", '', $title);
14221
				$title = preg_replace("/[\n]+/si", "\n", $title);
14222
				$title = strip_tags($title);
14223
				$title = $this->stringTrim($title);
14224
				$out = '<</Title '.$this->_textstring($title, $oid);
14225
				$out .= ' /Parent '.($n + $o['parent']).' 0 R';
14226
				if (isset($o['prev'])) {
14227
					$out .= ' /Prev '.($n + $o['prev']).' 0 R';
14228
				}
14229
				if (isset($o['next'])) {
14230
					$out .= ' /Next '.($n + $o['next']).' 0 R';
14231
				}
14232
				if (isset($o['first'])) {
14233
					$out .= ' /First '.($n + $o['first']).' 0 R';
14234
				}
14235
				if (isset($o['last'])) {
14236
					$out .= ' /Last '.($n + $o['last']).' 0 R';
14237
				}
14238
				$out .= ' '.sprintf('/Dest [%u 0 R /XYZ 0 %.2F null]', $this->page_obj_id[($o['p'])], ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
14239
				$out .= ' /Count 0 >>';
14240
				$out .= "\n".'endobj';
14241
				$this->_out($out);
14242
			}
14243
		}
14244
		//Outline root
14245
		$this->OutlineRoot = $this->_newobj();
14246
		$this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
14247
	}
14248
 
14249
	// --- JAVASCRIPT ------------------------------------------------------
14250
 
14251
	/**
14252
	 * Adds a javascript
14253
	 * @param string $script Javascript code
14254
	 * @access public
14255
	 * @author Johannes G�ntert, Nicola Asuni
14256
	 * @since 2.1.002 (2008-02-12)
14257
	 */
14258
	public function IncludeJS($script) {
14259
		$this->javascript .= $script;
14260
	}
14261
 
14262
	/**
14263
	 * Adds a javascript object and return object ID
14264
	 * @param string $script Javascript code
14265
	 * @param boolean $onload if true executes this object when opening the document
14266
	 * @return int internal object ID
14267
	 * @access public
14268
	 * @author Nicola Asuni
14269
	 * @since 4.8.000 (2009-09-07)
14270
	 */
14271
	public function addJavascriptObject($script, $onload=false) {
14272
		++$this->n;
14273
		$this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
14274
		return $this->n;
14275
	}
14276
 
14277
	/**
14278
	 * Create a javascript PDF string.
14279
	 * @access protected
14280
	 * @author Johannes G�ntert, Nicola Asuni
14281
	 * @since 2.1.002 (2008-02-12)
14282
	 */
14283
	protected function _putjavascript() {
14284
		if (empty($this->javascript) AND empty($this->js_objects)) {
14285
			return;
14286
		}
14287
		if (strpos($this->javascript, 'this.addField') > 0) {
14288
			if (!$this->ur['enabled']) {
14289
				//$this->setUserRights();
14290
			}
14291
			// the following two lines are used to avoid form fields duplication after saving
14292
			// The addField method only works when releasing user rights (UR3)
14293
			$jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%.2F,%.2F,%.2F,%.2F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
14294
			$jsb = "getField('tcpdfdocsaved').value='saved';";
14295
			$this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
14296
		}
14297
		$this->n_js = $this->_newobj();
14298
		$out = ' << /Names [';
14299
		if (!empty($this->javascript)) {
14300
			$out .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
14301
		}
14302
		if (!empty($this->js_objects)) {
14303
			foreach ($this->js_objects as $key => $val) {
14304
				if ($val['onload']) {
14305
					$out .= ' (JS'.$key.') '.$key.' 0 R';
14306
				}
14307
			}
14308
		}
14309
		$out .= ' ] >>';
14310
		$out .= "\n".'endobj';
14311
		$this->_out($out);
14312
		// default Javascript object
14313
		if (!empty($this->javascript)) {
14314
			$obj_id = $this->_newobj();
14315
			$out = '<< /S /JavaScript';
14316
			$out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
14317
			$out .= ' >>';
14318
			$out .= "\n".'endobj';
14319
			$this->_out($out);
14320
		}
14321
		// additional Javascript objects
14322
		if (!empty($this->js_objects)) {
14323
			foreach ($this->js_objects as $key => $val) {
14324
				$out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
14325
				$this->_out($out);
14326
			}
14327
		}
14328
	}
14329
 
14330
	/**
14331
	 * Convert color to javascript color.
14332
	 * @param string $color color name or #RRGGBB
14333
	 * @access protected
14334
	 * @author Denis Van Nuffelen, Nicola Asuni
14335
	 * @since 2.1.002 (2008-02-12)
14336
	 */
14337
	protected function _JScolor($color) {
14338
		static $aColors = array('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
14339
		if (substr($color,0,1) == '#') {
14340
			return sprintf("['RGB',%.3F,%.3F,%.3F]", hexdec(substr($color,1,2))/255, hexdec(substr($color,3,2))/255, hexdec(substr($color,5,2))/255);
14341
		}
14342
		if (!in_array($color,$aColors)) {
14343
			$this->Error('Invalid color: '.$color);
14344
		}
14345
		return 'color.'.$color;
14346
	}
14347
 
14348
	/**
14349
	 * Adds a javascript form field.
14350
	 * @param string $type field type
14351
	 * @param string $name field name
14352
	 * @param int $x horizontal position
14353
	 * @param int $y vertical position
14354
	 * @param int $w width
14355
	 * @param int $h height
14356
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14357
	 * @access protected
14358
	 * @author Denis Van Nuffelen, Nicola Asuni
14359
	 * @since 2.1.002 (2008-02-12)
14360
	 */
14361
	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
14362
		if ($this->rtl) {
14363
			$x = $x - $w;
14364
		}
14365
		// the followind avoid fields duplication after saving the document
14366
		$this->javascript .= "if(getField('tcpdfdocsaved').value != 'saved') {";
14367
		$k = $this->k;
14368
		$this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%.2F,%.2F,%.2F,%.2F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
14369
		$this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
14370
		while (list($key, $val) = each($prop)) {
14371
			if (strcmp(substr($key, -5), 'Color') == 0) {
14372
				$val = $this->_JScolor($val);
14373
			} else {
14374
				$val = "'".$val."'";
14375
			}
14376
			$this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
14377
		}
14378
		if ($this->rtl) {
14379
			$this->x -= $w;
14380
		} else {
14381
			$this->x += $w;
14382
		}
14383
		$this->javascript .= '}';
14384
	}
14385
 
14386
	// --- FORM FIELDS -----------------------------------------------------
14387
 
14388
	/**
14389
	 * Convert JavaScript form fields properties array to Annotation Properties array.
14390
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14391
	 * @return array of annotation properties
14392
	 * @access protected
14393
	 * @author Nicola Asuni
14394
	 * @since 4.8.000 (2009-09-06)
14395
	 */
14396
	protected function getAnnotOptFromJSProp($prop) {
14397
		if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
14398
			// the annotation options area lready defined
14399
			return $prop['aopt'];
14400
		}
14401
		$opt = array(); // value to be returned
14402
		// alignment: Controls how the text is laid out within the text field.
14403
		if (isset($prop['alignment'])) {
14404
			switch ($prop['alignment']) {
14405
				case 'left': {
14406
					$opt['q'] = 0;
14407
					break;
14408
				}
14409
				case 'center': {
14410
					$opt['q'] = 1;
14411
					break;
14412
				}
14413
				case 'right': {
14414
					$opt['q'] = 2;
14415
					break;
14416
				}
14417
				default: {
14418
					$opt['q'] = ($this->rtl)?2:0;
14419
					break;
14420
				}
14421
			}
14422
		}
14423
		// lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
14424
		if (isset($prop['lineWidth'])) {
14425
			$linewidth = intval($prop['lineWidth']);
14426
		} else {
14427
			$linewidth = 1;
14428
		}
14429
		// borderStyle: The border style for a field.
14430
		if (isset($prop['borderStyle'])) {
14431
			switch ($prop['borderStyle']) {
14432
				case 'border.d':
14433
				case 'dashed': {
14434
					$opt['border'] = array(0, 0, $linewidth, array(3, 2));
14435
					$opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
14436
					break;
14437
				}
14438
				case 'border.b':
14439
				case 'beveled': {
14440
					$opt['border'] = array(0, 0, $linewidth);
14441
					$opt['bs'] = array('w'=>$linewidth, 's'=>'B');
14442
					break;
14443
				}
14444
				case 'border.i':
14445
				case 'inset': {
14446
					$opt['border'] = array(0, 0, $linewidth);
14447
					$opt['bs'] = array('w'=>$linewidth, 's'=>'I');
14448
					break;
14449
				}
14450
				case 'border.u':
14451
				case 'underline': {
14452
					$opt['border'] = array(0, 0, $linewidth);
14453
					$opt['bs'] = array('w'=>$linewidth, 's'=>'U');
14454
					break;
14455
				}
14456
				default:
14457
				case 'border.s':
14458
				case 'solid': {
14459
					$opt['border'] = array(0, 0, $linewidth);
14460
					$opt['bs'] = array('w'=>$linewidth, 's'=>'S');
14461
					break;
14462
				}
14463
			}
14464
		}
14465
		if (isset($prop['border']) AND is_array($prop['border'])) {
14466
			$opt['border'] = $prop['border'];
14467
		}
14468
		if (!isset($opt['mk'])) {
14469
			$opt['mk'] = array();
14470
		}
14471
		if (!isset($opt['mk']['if'])) {
14472
			$opt['mk']['if'] = array();
14473
		}
14474
		$opt['mk']['if']['a'] = array(0.5, 0.5);
14475
		// buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
14476
		if (isset($prop['buttonAlignX'])) {
14477
			$opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
14478
		}
14479
		// buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
14480
		if (isset($prop['buttonAlignY'])) {
14481
			$opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
14482
		}
14483
		// buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
14484
		if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
14485
			$opt['mk']['if']['fb'] = true;
14486
		}
14487
		// buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
14488
		if (isset($prop['buttonScaleHow'])) {
14489
			switch ($prop['buttonScaleHow']) {
14490
				case 'scaleHow.proportional': {
14491
					$opt['mk']['if']['s'] = 'P';
14492
					break;
14493
				}
14494
				case 'scaleHow.anamorphic': {
14495
					$opt['mk']['if']['s'] = 'A';
14496
					break;
14497
				}
14498
			}
14499
		}
14500
		// buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
14501
		if (isset($prop['buttonScaleWhen'])) {
14502
			switch ($prop['buttonScaleWhen']) {
14503
				case 'scaleWhen.always': {
14504
					$opt['mk']['if']['sw'] = 'A';
14505
					break;
14506
				}
14507
				case 'scaleWhen.never': {
14508
					$opt['mk']['if']['sw'] = 'N';
14509
					break;
14510
				}
14511
				case 'scaleWhen.tooBig': {
14512
					$opt['mk']['if']['sw'] = 'B';
14513
					break;
14514
				}
14515
				case 'scaleWhen.tooSmall': {
14516
					$opt['mk']['if']['sw'] = 'S';
14517
					break;
14518
				}
14519
			}
14520
		}
14521
		// buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
14522
		if (isset($prop['buttonPosition'])) {
14523
			switch ($prop['buttonPosition']) {
14524
				case 0:
14525
				case 'position.textOnly': {
14526
					$opt['mk']['tp'] = 0;
14527
					break;
14528
				}
14529
				case 1:
14530
				case 'position.iconOnly': {
14531
					$opt['mk']['tp'] = 1;
14532
					break;
14533
				}
14534
				case 2:
14535
				case 'position.iconTextV': {
14536
					$opt['mk']['tp'] = 2;
14537
					break;
14538
				}
14539
				case 3:
14540
				case 'position.textIconV': {
14541
					$opt['mk']['tp'] = 3;
14542
					break;
14543
				}
14544
				case 4:
14545
				case 'position.iconTextH': {
14546
					$opt['mk']['tp'] = 4;
14547
					break;
14548
				}
14549
				case 5:
14550
				case 'position.textIconH': {
14551
					$opt['mk']['tp'] = 5;
14552
					break;
14553
				}
14554
				case 6:
14555
				case 'position.overlay': {
14556
					$opt['mk']['tp'] = 6;
14557
					break;
14558
				}
14559
			}
14560
		}
14561
		// fillColor: Specifies the background color for a field.
14562
		if (isset($prop['fillColor'])) {
14563
			if (is_array($prop['fillColor'])) {
14564
				$opt['mk']['bg'] = $prop['fillColor'];
14565
			} else {
14566
				$opt['mk']['bg'] = $this->convertHTMLColorToDec($prop['fillColor']);
14567
			}
14568
		}
14569
		// strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
14570
		if (isset($prop['strokeColor'])) {
14571
			if (is_array($prop['strokeColor'])) {
14572
				$opt['mk']['bc'] = $prop['strokeColor'];
14573
			} else {
14574
				$opt['mk']['bc'] = $this->convertHTMLColorToDec($prop['strokeColor']);
14575
			}
14576
		}
14577
		// rotation: The rotation of a widget in counterclockwise increments.
14578
		if (isset($prop['rotation'])) {
14579
			$opt['mk']['r'] = $prop['rotation'];
14580
		}
14581
		// charLimit: Limits the number of characters that a user can type into a text field.
14582
		if (isset($prop['charLimit'])) {
14583
			$opt['maxlen'] = intval($prop['charLimit']);
14584
		}
14585
		if (!isset($ff)) {
14586
			$ff = 0;
14587
		}
14588
		// readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
14589
		if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
14590
			$ff += 1 << 0;
14591
		}
14592
		// required: Specifies whether a field requires a value.
14593
		if (isset($prop['required']) AND ($prop['required'] == 'true')) {
14594
			$ff += 1 << 1;
14595
		}
14596
		// multiline: Controls how text is wrapped within the field.
14597
		if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
14598
			$ff += 1 << 12;
14599
		}
14600
		// password: Specifies whether the field should display asterisks when data is entered in the field.
14601
		if (isset($prop['password']) AND ($prop['password'] == 'true')) {
14602
			$ff += 1 << 13;
14603
		}
14604
		// NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
14605
		if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
14606
			$ff += 1 << 14;
14607
		}
14608
		// Radio: If set, the field is a set of radio buttons.
14609
		if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
14610
			$ff += 1 << 15;
14611
		}
14612
		// Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
14613
		if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
14614
			$ff += 1 << 16;
14615
		}
14616
		// Combo: If set, the field is a combo box; if clear, the field is a list box.
14617
		if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
14618
			$ff += 1 << 17;
14619
		}
14620
		// editable: Controls whether a combo box is editable.
14621
		if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
14622
			$ff += 1 << 18;
14623
		}
14624
		// Sort: If set, the field's option items shall be sorted alphabetically.
14625
		if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
14626
			$ff += 1 << 19;
14627
		}
14628
		// fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
14629
		if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
14630
			$ff += 1 << 20;
14631
		}
14632
		// multipleSelection: If true, indicates that a list box allows a multiple selection of items.
14633
		if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
14634
			$ff += 1 << 21;
14635
		}
14636
		// doNotSpellCheck: If true, spell checking is not performed on this editable text field.
14637
		if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
14638
			$ff += 1 << 22;
14639
		}
14640
		// doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
14641
		if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
14642
			$ff += 1 << 23;
14643
		}
14644
		// comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
14645
		if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
14646
			$ff += 1 << 24;
14647
		}
14648
		// radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
14649
		if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
14650
			$ff += 1 << 25;
14651
		}
14652
		// richText: If true, the field allows rich text formatting.
14653
		if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
14654
			$ff += 1 << 25;
14655
		}
14656
		// commitOnSelChange: Controls whether a field value is committed after a selection change.
14657
		if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
14658
			$ff += 1 << 26;
14659
		}
14660
		$opt['ff'] = $ff;
14661
		// defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
14662
		if (isset($prop['defaultValue'])) {
14663
			$opt['dv'] = $prop['defaultValue'];
14664
		}
14665
		$f = 4; // default value for annotation flags
14666
		// readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
14667
		if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
14668
			$f += 1 << 6;
14669
		}
14670
		// display: Controls whether the field is hidden or visible on screen and in print.
14671
		if (isset($prop['display'])) {
14672
			if ($prop['display'] == 'display.visible') {
14673
				//
14674
			} elseif ($prop['display'] == 'display.hidden') {
14675
				$f += 1 << 1;
14676
			} elseif ($prop['display'] == 'display.noPrint') {
14677
				$f -= 1 << 2;
14678
			} elseif ($prop['display'] == 'display.noView') {
14679
				$f += 1 << 5;
14680
			}
14681
		}
14682
		$opt['f'] = $f;
14683
		// currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
14684
		if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
14685
			$opt['i'] = $prop['currentValueIndices'];
14686
		}
14687
		// value: The value of the field data that the user has entered.
14688
		if (isset($prop['value'])) {
14689
			if (is_array($prop['value'])) {
14690
				$opt['opt'] = array();
14691
				foreach ($prop['value'] AS $key => $optval) {
14692
					// exportValues: An array of strings representing the export values for the field.
14693
					if (isset($prop['exportValues'][$key])) {
14694
						$opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
14695
					} else {
14696
						$opt['opt'][$key] = $prop['value'][$key];
14697
					}
14698
				}
14699
			} else {
14700
				$opt['v'] = $prop['value'];
14701
			}
14702
		}
14703
		// richValue: This property specifies the text contents and formatting of a rich text field.
14704
		if (isset($prop['richValue'])) {
14705
			$opt['rv'] = $prop['richValue'];
14706
		}
14707
		// submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
14708
		if (isset($prop['submitName'])) {
14709
			$opt['tm'] = $prop['submitName'];
14710
		}
14711
		// name: Fully qualified field name.
14712
		if (isset($prop['name'])) {
14713
			$opt['t'] = $prop['name'];
14714
		}
14715
		// userName: The user name (short description string) of the field.
14716
		if (isset($prop['userName'])) {
14717
			$opt['tu'] = $prop['userName'];
14718
		}
14719
		// highlight: Defines how a button reacts when a user clicks it.
14720
		if (isset($prop['highlight'])) {
14721
			switch ($prop['highlight']) {
14722
				case 'none':
14723
				case 'highlight.n': {
14724
					$opt['h'] = 'N';
14725
					break;
14726
				}
14727
				case 'invert':
14728
				case 'highlight.i': {
14729
					$opt['h'] = 'i';
14730
					break;
14731
				}
14732
				case 'push':
14733
				case 'highlight.p': {
14734
					$opt['h'] = 'P';
14735
					break;
14736
				}
14737
				case 'outline':
14738
				case 'highlight.o': {
14739
					$opt['h'] = 'O';
14740
					break;
14741
				}
14742
			}
14743
		}
14744
		// Unsupported options:
14745
		// - calcOrderIndex: Changes the calculation order of fields in the document.
14746
		// - delay: Delays the redrawing of a field's appearance.
14747
		// - defaultStyle: This property defines the default style attributes for the form field.
14748
		// - style: Allows the user to set the glyph style of a check box or radio button.
14749
		// - textColor, textFont, textSize
14750
		return $opt;
14751
	}
14752
 
14753
	/**
14754
	 * Set default properties for form fields.
14755
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14756
	 * @access public
14757
	 * @author Nicola Asuni
14758
	 * @since 4.8.000 (2009-09-06)
14759
	 */
14760
	public function setFormDefaultProp($prop=array()) {
14761
		$this->default_form_prop = $prop;
14762
	}
14763
 
14764
	/**
14765
	 * Return the default properties for form fields.
14766
	 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14767
	 * @access public
14768
	 * @author Nicola Asuni
14769
	 * @since 4.8.000 (2009-09-06)
14770
	 */
14771
	public function getFormDefaultProp() {
14772
		return $this->default_form_prop;
14773
	}
14774
 
14775
	/**
14776
	 * Creates a text field
14777
	 * @param string $name field name
14778
	 * @param float $w Width of the rectangle
14779
	 * @param float $h Height of the rectangle
14780
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14781
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
14782
	 * @param float $x Abscissa of the upper-left corner of the rectangle
14783
	 * @param float $y Ordinate of the upper-left corner of the rectangle
14784
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
14785
	 * @access public
14786
	 * @author Nicola Asuni
14787
	 * @since 4.8.000 (2009-09-07)
14788
	 */
14789
	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14790
		if ($x === '') {
14791
			$x = $this->x;
14792
		}
14793
		if ($y === '') {
14794
			$y = $this->y;
14795
		}
14796
		// check page for no-write regions and adapt page margins if necessary
14797
		$this->checkPageRegions($h, $x, $y);
14798
		if ($js) {
14799
			$this->_addfield('text', $name, $x, $y, $w, $h, $prop);
14800
			return;
14801
		}
14802
		// get default style
14803
		$prop = array_merge($this->getFormDefaultProp(), $prop);
14804
		// get annotation data
14805
		$popt = $this->getAnnotOptFromJSProp($prop);
14806
		// set default appearance stream
14807
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14808
		$fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14809
		$popt['da'] = $fontstyle;
14810
		$popt['ap'] = array();
14811
		$popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14812
		// merge options
14813
		$opt = array_merge($popt, $opt);
14814
		// remove some conflicting options
14815
		unset($opt['bs']);
14816
		// set remaining annotation data
14817
		$opt['Subtype'] = 'Widget';
14818
		$opt['ft'] = 'Tx';
14819
		$opt['t'] = $name;
14820
		/*
14821
		Additional annotation's parameters (check _putannotsobj() method):
14822
		//$opt['f']
14823
		//$opt['ap']
14824
		//$opt['as']
14825
		//$opt['bs']
14826
		//$opt['be']
14827
		//$opt['c']
14828
		//$opt['border']
14829
		//$opt['h']
14830
		//$opt['mk']
14831
		//$opt['mk']['r']
14832
		//$opt['mk']['bc']
14833
		//$opt['mk']['bg']
14834
		//$opt['mk']['ca']
14835
		//$opt['mk']['rc']
14836
		//$opt['mk']['ac']
14837
		//$opt['mk']['i']
14838
		//$opt['mk']['ri']
14839
		//$opt['mk']['ix']
14840
		//$opt['mk']['if']
14841
		//$opt['mk']['if']['sw']
14842
		//$opt['mk']['if']['s']
14843
		//$opt['mk']['if']['a']
14844
		//$opt['mk']['if']['fb']
14845
		//$opt['mk']['tp']
14846
		//$opt['tu']
14847
		//$opt['tm']
14848
		//$opt['ff']
14849
		//$opt['v']
14850
		//$opt['dv']
14851
		//$opt['a']
14852
		//$opt['aa']
14853
		//$opt['q']
14854
		*/
14855
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
14856
		if ($this->rtl) {
14857
			$this->x -= $w;
14858
		} else {
14859
			$this->x += $w;
14860
		}
14861
	}
14862
 
14863
	/**
14864
	 * Creates a RadioButton field
14865
	 * @param string $name field name
14866
	 * @param int $w width
14867
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14868
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
14869
	 * @param string $onvalue value to be returned if selected.
14870
	 * @param boolean $checked define the initial state.
14871
	 * @param float $x Abscissa of the upper-left corner of the rectangle
14872
	 * @param float $y Ordinate of the upper-left corner of the rectangle
14873
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
14874
	 * @access public
14875
	 * @author Nicola Asuni
14876
	 * @since 4.8.000 (2009-09-07)
14877
	 */
14878
	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
14879
		if ($x === '') {
14880
			$x = $this->x;
14881
		}
14882
		if ($y === '') {
14883
			$y = $this->y;
14884
		}
14885
		// check page for no-write regions and adapt page margins if necessary
14886
		$this->checkPageRegions($w, $x, $y);
14887
		if ($js) {
14888
			$this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
14889
			return;
14890
		}
14891
		if ($this->empty_string($onvalue)) {
14892
			$onvalue = 'On';
14893
		}
14894
		if ($checked) {
14895
			$defval = $onvalue;
14896
		} else {
14897
			$defval = 'Off';
14898
		}
14899
		// set data for parent group
14900
		if (!isset($this->radiobutton_groups[$this->page])) {
14901
			$this->radiobutton_groups[$this->page] = array();
14902
		}
14903
		if (!isset($this->radiobutton_groups[$this->page][$name])) {
14904
			$this->radiobutton_groups[$this->page][$name] = array();
14905
			++$this->n;
14906
			$this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
14907
			$this->radio_groups[] = $this->n;
14908
			$kid = ($this->n + 2);
14909
		} else {
14910
			$kid = ($this->n + 1);
14911
		}
14912
		// save object ID to be added on Kids entry on parent object
14913
		$this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
14914
		// get default style
14915
		$prop = array_merge($this->getFormDefaultProp(), $prop);
14916
		$prop['NoToggleToOff'] = 'true';
14917
		$prop['Radio'] = 'true';
14918
		$prop['borderStyle'] = 'inset';
14919
		// get annotation data
14920
		$popt = $this->getAnnotOptFromJSProp($prop);
14921
		// set additional default values
14922
		$font = 'zapfdingbats';
14923
		$this->AddFont($font);
14924
		$tmpfont = $this->getFontBuffer($font);
14925
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
14926
		$fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
14927
		$popt['da'] = $fontstyle;
14928
		$popt['ap'] = array();
14929
		$popt['ap']['n'] = array();
14930
		$popt['ap']['n'][$onvalue] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14931
		$popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14932
		if (!isset($popt['mk'])) {
14933
			$popt['mk'] = array();
14934
		}
14935
		$popt['mk']['ca'] = '(l)';
14936
		// merge options
14937
		$opt = array_merge($popt, $opt);
14938
		// set remaining annotation data
14939
		$opt['Subtype'] = 'Widget';
14940
		$opt['ft'] = 'Btn';
14941
		if ($checked) {
14942
			$opt['v'] = array('/'.$onvalue);
14943
			$opt['as'] = $onvalue;
14944
		} else {
14945
			$opt['as'] = 'Off';
14946
		}
14947
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
14948
		if ($this->rtl) {
14949
			$this->x -= $w;
14950
		} else {
14951
			$this->x += $w;
14952
		}
14953
	}
14954
 
14955
	/**
14956
	 * Creates a List-box field
14957
	 * @param string $name field name
14958
	 * @param int $w width
14959
	 * @param int $h height
14960
	 * @param array $values array containing the list of values.
14961
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
14962
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
14963
	 * @param float $x Abscissa of the upper-left corner of the rectangle
14964
	 * @param float $y Ordinate of the upper-left corner of the rectangle
14965
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
14966
	 * @access public
14967
	 * @author Nicola Asuni
14968
	 * @since 4.8.000 (2009-09-07)
14969
	 */
14970
	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14971
		if ($x === '') {
14972
			$x = $this->x;
14973
		}
14974
		if ($y === '') {
14975
			$y = $this->y;
14976
		}
14977
		// check page for no-write regions and adapt page margins if necessary
14978
		$this->checkPageRegions($h, $x, $y);
14979
		if ($js) {
14980
			$this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
14981
			$s = '';
14982
			foreach ($values as $value) {
14983
				$s .= "'".addslashes($value)."',";
14984
			}
14985
			$this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
14986
			return;
14987
		}
14988
		// get default style
14989
		$prop = array_merge($this->getFormDefaultProp(), $prop);
14990
		// get annotation data
14991
		$popt = $this->getAnnotOptFromJSProp($prop);
14992
		// set additional default values
14993
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14994
		$fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14995
		$popt['da'] = $fontstyle;
14996
		$popt['ap'] = array();
14997
		$popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14998
		// merge options
14999
		$opt = array_merge($popt, $opt);
15000
		// set remaining annotation data
15001
		$opt['Subtype'] = 'Widget';
15002
		$opt['ft'] = 'Ch';
15003
		$opt['t'] = $name;
15004
		$opt['opt'] = $values;
15005
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
15006
		if ($this->rtl) {
15007
			$this->x -= $w;
15008
		} else {
15009
			$this->x += $w;
15010
		}
15011
	}
15012
 
15013
	/**
15014
	 * Creates a Combo-box field
15015
	 * @param string $name field name
15016
	 * @param int $w width
15017
	 * @param int $h height
15018
	 * @param array $values array containing the list of values.
15019
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
15020
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
15021
	 * @param float $x Abscissa of the upper-left corner of the rectangle
15022
	 * @param float $y Ordinate of the upper-left corner of the rectangle
15023
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
15024
	 * @access public
15025
	 * @author Nicola Asuni
15026
	 * @since 4.8.000 (2009-09-07)
15027
	 */
15028
	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
15029
		if ($x === '') {
15030
			$x = $this->x;
15031
		}
15032
		if ($y === '') {
15033
			$y = $this->y;
15034
		}
15035
		// check page for no-write regions and adapt page margins if necessary
15036
		$this->checkPageRegions($h, $x, $y);
15037
		if ($js) {
15038
			$this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
15039
			$s = '';
15040
			foreach ($values as $value) {
15041
				$s .= "'".addslashes($value)."',";
15042
			}
15043
			$this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
15044
			return;
15045
		}
15046
		// get default style
15047
		$prop = array_merge($this->getFormDefaultProp(), $prop);
15048
		$prop['Combo'] = true;
15049
		// get annotation data
15050
		$popt = $this->getAnnotOptFromJSProp($prop);
15051
		// set additional default options
15052
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
15053
		$fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
15054
		$popt['da'] = $fontstyle;
15055
		$popt['ap'] = array();
15056
		$popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
15057
		// merge options
15058
		$opt = array_merge($popt, $opt);
15059
		// set remaining annotation data
15060
		$opt['Subtype'] = 'Widget';
15061
		$opt['ft'] = 'Ch';
15062
		$opt['t'] = $name;
15063
		$opt['opt'] = $values;
15064
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
15065
		if ($this->rtl) {
15066
			$this->x -= $w;
15067
		} else {
15068
			$this->x += $w;
15069
		}
15070
	}
15071
 
15072
	/**
15073
	 * Creates a CheckBox field
15074
	 * @param string $name field name
15075
	 * @param int $w width
15076
	 * @param boolean $checked define the initial state.
15077
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
15078
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
15079
	 * @param string $onvalue value to be returned if selected.
15080
	 * @param float $x Abscissa of the upper-left corner of the rectangle
15081
	 * @param float $y Ordinate of the upper-left corner of the rectangle
15082
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
15083
	 * @access public
15084
	 * @author Nicola Asuni
15085
	 * @since 4.8.000 (2009-09-07)
15086
	 */
15087
	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
15088
		if ($x === '') {
15089
			$x = $this->x;
15090
		}
15091
		if ($y === '') {
15092
			$y = $this->y;
15093
		}
15094
		// check page for no-write regions and adapt page margins if necessary
15095
		$this->checkPageRegions($w, $x, $y);
15096
		if ($js) {
15097
			$this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
15098
			return;
15099
		}
15100
		if (!isset($prop['value'])) {
15101
			$prop['value'] = array('Yes');
15102
		}
15103
		// get default style
15104
		$prop = array_merge($this->getFormDefaultProp(), $prop);
15105
		$prop['borderStyle'] = 'inset';
15106
		// get annotation data
15107
		$popt = $this->getAnnotOptFromJSProp($prop);
15108
		// set additional default options
15109
		$font = 'zapfdingbats';
15110
		$this->AddFont($font);
15111
		$tmpfont = $this->getFontBuffer($font);
15112
		$this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
15113
		$fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
15114
		$popt['da'] = $fontstyle;
15115
		$popt['ap'] = array();
15116
		$popt['ap']['n'] = array();
15117
		$popt['ap']['n']['Yes'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
15118
		$popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
15119
		// merge options
15120
		$opt = array_merge($popt, $opt);
15121
		// set remaining annotation data
15122
		$opt['Subtype'] = 'Widget';
15123
		$opt['ft'] = 'Btn';
15124
		$opt['t'] = $name;
15125
		$opt['opt'] = array($onvalue);
15126
		if ($checked) {
15127
			$opt['v'] = array('/0');
15128
			$opt['as'] = 'Yes';
15129
		} else {
15130
			$opt['v'] = array('/Off');
15131
			$opt['as'] = 'Off';
15132
		}
15133
		$this->Annotation($x, $y, $w, $w, $name, $opt, 0);
15134
		if ($this->rtl) {
15135
			$this->x -= $w;
15136
		} else {
15137
			$this->x += $w;
15138
		}
15139
	}
15140
 
15141
	/**
15142
	 * Creates a button field
15143
	 * @param string $name field name
15144
	 * @param int $w width
15145
	 * @param int $h height
15146
	 * @param string $caption caption.
15147
	 * @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.
15148
	 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
15149
	 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
15150
	 * @param float $x Abscissa of the upper-left corner of the rectangle
15151
	 * @param float $y Ordinate of the upper-left corner of the rectangle
15152
	 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
15153
	 * @access public
15154
	 * @author Nicola Asuni
15155
	 * @since 4.8.000 (2009-09-07)
15156
	 */
15157
	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
15158
		if ($x === '') {
15159
			$x = $this->x;
15160
		}
15161
		if ($y === '') {
15162
			$y = $this->y;
15163
		}
15164
		// check page for no-write regions and adapt page margins if necessary
15165
		$this->checkPageRegions($h, $x, $y);
15166
		if ($js) {
15167
			$this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
15168
			$this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
15169
			$this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
15170
			$this->javascript .= 'f'.$name.".highlight='push';\n";
15171
			$this->javascript .= 'f'.$name.".print=false;\n";
15172
			return;
15173
		}
15174
		// get default style
15175
		$prop = array_merge($this->getFormDefaultProp(), $prop);
15176
		$prop['Pushbutton'] = 'true';
15177
		$prop['highlight'] = 'push';
15178
		$prop['display'] = 'display.noPrint';
15179
		// get annotation data
15180
		$popt = $this->getAnnotOptFromJSProp($prop);
15181
		$this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
15182
		$fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
15183
		$popt['da'] = $fontstyle;
15184
		$popt['ap'] = array();
15185
		$popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
15186
		// set additional default options
15187
		if (!isset($popt['mk'])) {
15188
			$popt['mk'] = array();
15189
		}
15190
		$ann_obj_id = ($this->n + 1);
15191
		if (!empty($action) AND !is_array($action)) {
15192
			$ann_obj_id = ($this->n + 2);
15193
		}
15194
		$popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
15195
		$popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
15196
		$popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
15197
		// merge options
15198
		$opt = array_merge($popt, $opt);
15199
		// set remaining annotation data
15200
		$opt['Subtype'] = 'Widget';
15201
		$opt['ft'] = 'Btn';
15202
		$opt['t'] = $caption;
15203
		$opt['v'] = $name;
15204
		if (!empty($action)) {
15205
			if (is_array($action)) {
15206
				// form action options as on section 12.7.5 of PDF32000_2008.
15207
				$opt['aa'] = '/D <<';
15208
				$bmode = array('SubmitForm', 'ResetForm', 'ImportData');
15209
				foreach ($action AS $key => $val) {
15210
					if (($key == 'S') AND in_array($val, $bmode)) {
15211
						$opt['aa'] .= ' /S /'.$val;
15212
					} elseif (($key == 'F') AND (!empty($val))) {
15213
						$opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
15214
					} elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
15215
						$opt['aa'] .= ' /Fields [';
15216
						foreach ($val AS $field) {
15217
							$opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
15218
						}
15219
						$opt['aa'] .= ']';
15220
					} elseif (($key == 'Flags')) {
15221
						$ff = 0;
15222
						if (is_array($val)) {
15223
							foreach ($val AS $flag) {
15224
								switch ($flag) {
15225
									case 'Include/Exclude': {
15226
										$ff += 1 << 0;
15227
										break;
15228
									}
15229
									case 'IncludeNoValueFields': {
15230
										$ff += 1 << 1;
15231
										break;
15232
									}
15233
									case 'ExportFormat': {
15234
										$ff += 1 << 2;
15235
										break;
15236
									}
15237
									case 'GetMethod': {
15238
										$ff += 1 << 3;
15239
										break;
15240
									}
15241
									case 'SubmitCoordinates': {
15242
										$ff += 1 << 4;
15243
										break;
15244
									}
15245
									case 'XFDF': {
15246
										$ff += 1 << 5;
15247
										break;
15248
									}
15249
									case 'IncludeAppendSaves': {
15250
										$ff += 1 << 6;
15251
										break;
15252
									}
15253
									case 'IncludeAnnotations': {
15254
										$ff += 1 << 7;
15255
										break;
15256
									}
15257
									case 'SubmitPDF': {
15258
										$ff += 1 << 8;
15259
										break;
15260
									}
15261
									case 'CanonicalFormat': {
15262
										$ff += 1 << 9;
15263
										break;
15264
									}
15265
									case 'ExclNonUserAnnots': {
15266
										$ff += 1 << 10;
15267
										break;
15268
									}
15269
									case 'ExclFKey': {
15270
										$ff += 1 << 11;
15271
										break;
15272
									}
15273
									case 'EmbedForm': {
15274
										$ff += 1 << 13;
15275
										break;
15276
									}
15277
								}
15278
							}
15279
						} else {
15280
							$ff = intval($val);
15281
						}
15282
						$opt['aa'] .= ' /Flags '.$ff;
15283
					}
15284
				}
15285
				$opt['aa'] .= ' >>';
15286
			} else {
15287
				// Javascript action or raw action command
15288
				$js_obj_id = $this->addJavascriptObject($action);
15289
				$opt['aa'] = '/D '.$js_obj_id.' 0 R';
15290
			}
15291
		}
15292
		$this->Annotation($x, $y, $w, $h, $name, $opt, 0);
15293
		if ($this->rtl) {
15294
			$this->x -= $w;
15295
		} else {
15296
			$this->x += $w;
15297
		}
15298
	}
15299
 
15300
	// --- END FORMS FIELDS ------------------------------------------------
15301
 
15302
	/**
15303
	 * Add certification signature (DocMDP or UR3)
15304
	 * You can set only one signature type
15305
	 * @access protected
15306
	 * @author Nicola Asuni
15307
	 * @since 4.6.008 (2009-05-07)
15308
	 */
15309
	protected function _putsignature() {
15310
		if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
15311
			return;
15312
		}
15313
		$out = $this->_getobj($this->sig_obj_id + 1)."\n";
15314
		$out .= '<< /Type /Sig';
15315
		$out .= ' /Filter /Adobe.PPKLite';
15316
		$out .= ' /SubFilter /adbe.pkcs7.detached';
15317
		$out .= ' '.$this->byterange_string;
15318
		$out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
15319
		$out .= ' /Reference ['; // array of signature reference dictionaries
15320
		$out .= ' << /Type /SigRef';
15321
		if ($this->signature_data['cert_type'] > 0) {
15322
			$out .= ' /TransformMethod /DocMDP';
15323
			$out .= ' /TransformParams <<';
15324
			$out .= ' /Type /TransformParams';
15325
			$out .= ' /V /1.2';
15326
			$out .= ' /P '.$this->signature_data['cert_type'];
15327
		} else {
15328
			$out .= ' /TransformMethod /UR3';
15329
			$out .= ' /TransformParams <<';
15330
			$out .= ' /Type /TransformParams';
15331
			$out .= ' /V /2.2';
15332
			if (!$this->empty_string($this->ur['document'])) {
15333
				$out .= ' /Document['.$this->ur['document'].']';
15334
			}
15335
			if (!$this->empty_string($this->ur['form'])) {
15336
				$out .= ' /Form['.$this->ur['form'].']';
15337
			}
15338
			if (!$this->empty_string($this->ur['signature'])) {
15339
				$out .= ' /Signature['.$this->ur['signature'].']';
15340
			}
15341
			if (!$this->empty_string($this->ur['annots'])) {
15342
				$out .= ' /Annots['.$this->ur['annots'].']';
15343
			}
15344
			if (!$this->empty_string($this->ur['ef'])) {
15345
				$out .= ' /EF['.$this->ur['ef'].']';
15346
			}
15347
			if (!$this->empty_string($this->ur['formex'])) {
15348
				$out .= ' /FormEX['.$this->ur['formex'].']';
15349
			}
15350
		}
15351
		$out .= ' >>'; // close TransformParams
15352
		// optional digest data (values must be calculated and replaced later)
15353
		//$out .= ' /Data ********** 0 R';
15354
		//$out .= ' /DigestMethod/MD5';
15355
		//$out .= ' /DigestLocation[********** 34]';
15356
		//$out .= ' /DigestValue<********************************>';
15357
		$out .= ' >>';
15358
		$out .= ' ]'; // end of reference
15359
		if (isset($this->signature_data['info']['Name']) AND !$this->empty_string($this->signature_data['info']['Name'])) {
15360
			$out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name']);
15361
		}
15362
		if (isset($this->signature_data['info']['Location']) AND !$this->empty_string($this->signature_data['info']['Location'])) {
15363
			$out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location']);
15364
		}
15365
		if (isset($this->signature_data['info']['Reason']) AND !$this->empty_string($this->signature_data['info']['Reason'])) {
15366
			$out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason']);
15367
		}
15368
		if (isset($this->signature_data['info']['ContactInfo']) AND !$this->empty_string($this->signature_data['info']['ContactInfo'])) {
15369
			$out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo']);
15370
		}
15371
		$out .= ' /M '.$this->_datestring();
15372
		$out .= ' >>';
15373
		$out .= "\n".'endobj';
15374
		$this->_out($out);
15375
	}
15376
 
15377
	/**
15378
	 * Set User's Rights for PDF Reader
15379
	 * WARNING: This is experimental and currently do not work.
15380
	 * Check the PDF Reference 8.7.1 Transform Methods,
15381
	 * Table 8.105 Entries in the UR transform parameters dictionary
15382
	 * @param boolean $enable if true enable user's rights on PDF reader
15383
	 * @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.
15384
	 * @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.
15385
	 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
15386
	 * @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.
15387
	 * @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
15388
	 Names specifying additional embedded-files-related usage rights for the document.
15389
	 * @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.
15390
	 * @access public
15391
	 * @author Nicola Asuni
15392
	 * @since 2.9.000 (2008-03-26)
15393
	 */
15394
	public function setUserRights(
15395
			$enable=true,
15396
			$document='/FullSave',
15397
			$annots='/Create/Delete/Modify/Copy/Import/Export',
15398
			$form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
15399
			$signature='/Modify',
15400
			$ef='/Create/Delete/Modify/Import',
15401
			$formex='') {
15402
		$this->ur['enabled'] = $enable;
15403
		$this->ur['document'] = $document;
15404
		$this->ur['annots'] = $annots;
15405
		$this->ur['form'] = $form;
15406
		$this->ur['signature'] = $signature;
15407
		$this->ur['ef'] = $ef;
15408
		$this->ur['formex'] = $formex;
15409
		if (!$this->sign) {
15410
			$this->setSignature('', '', '', '', 0, array());
15411
		}
15412
	}
15413
 
15414
	/**
15415
	 * Enable document signature (requires the OpenSSL Library).
15416
	 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
15417
	 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
15418
	 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
15419
	 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
15420
	 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
15421
	 * @param mixed $private_key private key (string or filename prefixed with 'file://')
15422
	 * @param string $private_key_password password
15423
	 * @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.
15424
	 * @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.
15425
	 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
15426
	 * @access public
15427
	 * @author Nicola Asuni
15428
	 * @since 4.6.005 (2009-04-24)
15429
	 */
15430
	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
15431
		// to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
15432
		// to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
15433
		// to convert pfx certificate to pem: openssl
15434
		//     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
15435
		$this->sign = true;
15436
		++$this->n;
15437
		$this->sig_obj_id = $this->n; // signature widget
15438
		++$this->n; // signature object ($this->sig_obj_id + 1)
15439
		$this->signature_data = array();
15440
		if (strlen($signing_cert) == 0) {
15441
			$signing_cert = 'file://'.dirname(__FILE__).'/tcpdf.crt';
15442
			$private_key_password = 'tcpdfdemo';
15443
		}
15444
		if (strlen($private_key) == 0) {
15445
			$private_key = $signing_cert;
15446
		}
15447
		$this->signature_data['signcert'] = $signing_cert;
15448
		$this->signature_data['privkey'] = $private_key;
15449
		$this->signature_data['password'] = $private_key_password;
15450
		$this->signature_data['extracerts'] = $extracerts;
15451
		$this->signature_data['cert_type'] = $cert_type;
15452
		$this->signature_data['info'] = $info;
15453
	}
15454
 
15455
	/**
15456
	 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
15457
	 * @param float $x Abscissa of the upper-left corner.
15458
	 * @param float $y Ordinate of the upper-left corner.
15459
	 * @param float $w Width of the signature area.
15460
	 * @param float $h Height of the signature area.
15461
	 * @param int $page option page number (if < 0 the current page is used).
15462
	 * @access public
15463
	 * @author Nicola Asuni
15464
	 * @since 5.3.011 (2010-06-17)
15465
	 */
15466
	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
15467
		if (($page < 1) OR ($page > $this->numpages)) {
15468
			$this->signature_appearance['page'] = $this->page;
15469
		} else {
15470
			$this->signature_appearance['page'] = intval($page);
15471
		}
15472
		$a = $x * $this->k;
15473
		$b = $this->pagedim[($this->signature_appearance['page'])]['h'] - (($y + $h) * $this->k);
15474
		$c = $w * $this->k;
15475
		$d = $h * $this->k;
15476
		$this->signature_appearance['rect'] = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
15477
	}
15478
 
15479
	/**
15480
	 * Create a new page group.
15481
	 * NOTE: call this function before calling AddPage()
15482
	 * @param int $page starting group page (leave empty for next page).
15483
	 * @access public
15484
	 * @since 3.0.000 (2008-03-27)
15485
	 */
15486
	public function startPageGroup($page='') {
15487
		if (empty($page)) {
15488
			$page = $this->page + 1;
15489
		}
15490
		$this->newpagegroup[$page] = true;
15491
	}
15492
 
15493
	/**
15494
	 * Defines an alias for the total number of pages.
15495
	 * It will be substituted as the document is closed.
15496
	 * @param string $alias The alias.
15497
	 * @access public
15498
	 * @since 1.4
15499
	 * @see getAliasNbPages(), PageNo(), Footer()
15500
	 */
15501
	public function AliasNbPages($alias='{nb}') {
15502
		$this->AliasNbPages = $alias;
15503
	}
15504
 
15505
	/**
15506
	 * Returns the string alias used for the total number of pages.
15507
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
15508
	 * @return string
15509
	 * @access public
15510
	 * @since 4.0.018 (2008-08-08)
15511
	 * @see AliasNbPages(), PageNo(), Footer()
15512
	 */
15513
	public function getAliasNbPages() {
15514
		if ($this->isUnicodeFont()) {
15515
			return '{'.$this->AliasNbPages.'}';
15516
		}
15517
		return $this->AliasNbPages;
15518
	}
15519
 
15520
	/**
15521
	 * Defines an alias for the page number.
15522
	 * It will be substituted as the document is closed.
15523
	 * @param string $alias The alias.
15524
	 * @access public
15525
	 * @since 4.5.000 (2009-01-02)
15526
	 * @see getAliasNbPages(), PageNo(), Footer()
15527
	 */
15528
	public function AliasNumPage($alias='{pnb}') {
15529
		//Define an alias for total number of pages
15530
		$this->AliasNumPage = $alias;
15531
	}
15532
 
15533
	/**
15534
	 * Returns the string alias used for the page number.
15535
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
15536
	 * @return string
15537
	 * @access public
15538
	 * @since 4.5.000 (2009-01-02)
15539
	 * @see AliasNbPages(), PageNo(), Footer()
15540
	 */
15541
	public function getAliasNumPage() {
15542
		if ($this->isUnicodeFont()) {
15543
			return '{'.$this->AliasNumPage.'}';
15544
		}
15545
		return $this->AliasNumPage;
15546
	}
15547
 
15548
	/**
15549
	 * Return the current page in the group.
15550
	 * @return current page in the group
15551
	 * @access public
15552
	 * @since 3.0.000 (2008-03-27)
15553
	 */
15554
	public function getGroupPageNo() {
15555
		return $this->pagegroups[$this->currpagegroup];
15556
	}
15557
 
15558
	/**
15559
	 * Returns the current group page number formatted as a string.
15560
	 * @access public
15561
	 * @since 4.3.003 (2008-11-18)
15562
	 * @see PaneNo(), formatPageNumber()
15563
	 */
15564
	public function getGroupPageNoFormatted() {
15565
		return $this->formatPageNumber($this->getGroupPageNo());
15566
	}
15567
 
15568
	/**
15569
	 * Return the alias of the current page group
15570
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
15571
	 * (will be replaced by the total number of pages in this group).
15572
	 * @return alias of the current page group
15573
	 * @access public
15574
	 * @since 3.0.000 (2008-03-27)
15575
	 */
15576
	public function getPageGroupAlias() {
15577
		if ($this->isUnicodeFont()) {
15578
			return '{'.$this->currpagegroup.'}';
15579
		}
15580
		return $this->currpagegroup;
15581
	}
15582
 
15583
	/**
15584
	 * Return the alias for the page number on the current page group
15585
	 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
15586
	 * (will be replaced by the total number of pages in this group).
15587
	 * @return alias of the current page group
15588
	 * @access public
15589
	 * @since 4.5.000 (2009-01-02)
15590
	 */
15591
	public function getPageNumGroupAlias() {
15592
		if ($this->isUnicodeFont()) {
15593
			return '{'.str_replace('{nb', '{pnb', $this->currpagegroup).'}';
15594
		}
15595
		return str_replace('{nb', '{pnb', $this->currpagegroup);
15596
	}
15597
 
15598
	/**
15599
	 * Format the page numbers.
15600
	 * This method can be overriden for custom formats.
15601
	 * @param int $num page number
15602
	 * @access protected
15603
	 * @since 4.2.005 (2008-11-06)
15604
	 */
15605
	protected function formatPageNumber($num) {
15606
		return number_format((float)$num, 0, '', '.');
15607
	}
15608
 
15609
	/**
15610
	 * Format the page numbers on the Table Of Content.
15611
	 * This method can be overriden for custom formats.
15612
	 * @param int $num page number
15613
	 * @access protected
15614
	 * @since 4.5.001 (2009-01-04)
15615
	 * @see addTOC(), addHTMLTOC()
15616
	 */
15617
	protected function formatTOCPageNumber($num) {
15618
		return number_format((float)$num, 0, '', '.');
15619
	}
15620
 
15621
	/**
15622
	 * Returns the current page number formatted as a string.
15623
	 * @access public
15624
	 * @since 4.2.005 (2008-11-06)
15625
	 * @see PaneNo(), formatPageNumber()
15626
	 */
15627
	public function PageNoFormatted() {
15628
		return $this->formatPageNumber($this->PageNo());
15629
	}
15630
 
15631
	/**
15632
	 * Put visibility settings.
15633
	 * @access protected
15634
	 * @since 3.0.000 (2008-03-27)
15635
	 */
15636
	protected function _putocg() {
15637
		$this->n_ocg_print = $this->_newobj();
15638
		$this->_out('<< /Type /OCG /Name '.$this->_textstring('print', $this->n_ocg_print).' /Usage << /Print <</PrintState /ON>> /View <</ViewState /OFF>> >> >>'."\n".'endobj');
15639
		$this->n_ocg_view = $this->_newobj();
15640
		$this->_out('<< /Type /OCG /Name '.$this->_textstring('view', $this->n_ocg_view).' /Usage << /Print <</PrintState /OFF>> /View <</ViewState /ON>> >> >>'."\n".'endobj');
15641
	}
15642
 
15643
	/**
15644
	 * Set the visibility of the successive elements.
15645
	 * This can be useful, for instance, to put a background
15646
	 * image or color that will show on screen but won't print.
15647
	 * @param string $v visibility mode. Legal values are: all, print, screen.
15648
	 * @access public
15649
	 * @since 3.0.000 (2008-03-27)
15650
	 */
15651
	public function setVisibility($v) {
15652
		if ($this->openMarkedContent) {
15653
			// close existing open marked-content
15654
			$this->_out('EMC');
15655
			$this->openMarkedContent = false;
15656
		}
15657
		switch($v) {
15658
			case 'print': {
15659
				$this->_out('/OC /OC1 BDC');
15660
				$this->openMarkedContent = true;
15661
				break;
15662
			}
15663
			case 'screen': {
15664
				$this->_out('/OC /OC2 BDC');
15665
				$this->openMarkedContent = true;
15666
				break;
15667
			}
15668
			case 'all': {
15669
				$this->_out('');
15670
				break;
15671
			}
15672
			default: {
15673
				$this->Error('Incorrect visibility: '.$v);
15674
				break;
15675
			}
15676
		}
15677
		$this->visibility = $v;
15678
	}
15679
 
15680
	/**
15681
	 * Add transparency parameters to the current extgstate
15682
	 * @param array $params parameters
15683
	 * @return the number of extgstates
15684
	 * @access protected
15685
	 * @since 3.0.000 (2008-03-27)
15686
	 */
15687
	protected function addExtGState($parms) {
15688
		$n = count($this->extgstates) + 1;
15689
		// check if this ExtGState already exist
15690
		for ($i = 1; $i < $n; ++$i) {
15691
			if ($this->extgstates[$i]['parms'] == $parms) {
15692
				// return reference to existing ExtGState
15693
				return $i;
15694
			}
15695
		}
15696
		$this->extgstates[$n]['parms'] = $parms;
15697
		return $n;
15698
	}
15699
 
15700
	/**
15701
	 * Add an extgstate
15702
	 * @param array $gs extgstate
15703
	 * @access protected
15704
	 * @since 3.0.000 (2008-03-27)
15705
	 */
15706
	protected function setExtGState($gs) {
15707
		$this->_out(sprintf('/GS%d gs', $gs));
15708
	}
15709
 
15710
	/**
15711
	 * Put extgstates for object transparency
15712
	 * @param array $gs extgstate
15713
	 * @access protected
15714
	 * @since 3.0.000 (2008-03-27)
15715
	 */
15716
	protected function _putextgstates() {
15717
		$ne = count($this->extgstates);
15718
		for ($i = 1; $i <= $ne; ++$i) {
15719
			$this->extgstates[$i]['n'] = $this->_newobj();
15720
			$out = '<< /Type /ExtGState';
15721
			foreach ($this->extgstates[$i]['parms'] as $k => $v) {
15722
				if (is_float($v)) {
15723
					$v = sprintf('%.2F', $v);
15724
				}
15725
				$out .= ' /'.$k.' '.$v;
15726
			}
15727
			$out .= ' >>';
15728
			$out .= "\n".'endobj';
15729
			$this->_out($out);
15730
		}
15731
	}
15732
 
15733
	/**
15734
	 * Set alpha for stroking (CA) and non-stroking (ca) operations.
15735
	 * @param float $alpha real value from 0 (transparent) to 1 (opaque)
15736
	 * @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
15737
	 * @access public
15738
	 * @since 3.0.000 (2008-03-27)
15739
	 */
15740
	public function setAlpha($alpha, $bm='Normal') {
15741
		$gs = $this->addExtGState(array('ca' => $alpha, 'CA' => $alpha, 'BM' => '/'.$bm, 'AIS' => 'false'));
15742
		$this->setExtGState($gs);
15743
	}
15744
 
15745
	/**
15746
	 * Set the default JPEG compression quality (1-100)
15747
	 * @param int $quality JPEG quality, integer between 1 and 100
15748
	 * @access public
15749
	 * @since 3.0.000 (2008-03-27)
15750
	 */
15751
	public function setJPEGQuality($quality) {
15752
		if (($quality < 1) OR ($quality > 100)) {
15753
			$quality = 75;
15754
		}
15755
		$this->jpeg_quality = intval($quality);
15756
	}
15757
 
15758
	/**
15759
	 * Set the default number of columns in a row for HTML tables.
15760
	 * @param int $cols number of columns
15761
	 * @access public
15762
	 * @since 3.0.014 (2008-06-04)
15763
	 */
15764
	public function setDefaultTableColumns($cols=4) {
15765
		$this->default_table_columns = intval($cols);
15766
	}
15767
 
15768
	/**
15769
	 * Set the height of the cell (line height) respect the font height.
15770
	 * @param int $h cell proportion respect font height (typical value = 1.25).
15771
	 * @access public
15772
	 * @since 3.0.014 (2008-06-04)
15773
	 */
15774
	public function setCellHeightRatio($h) {
15775
		$this->cell_height_ratio = $h;
15776
	}
15777
 
15778
	/**
15779
	 * return the height of cell repect font height.
15780
	 * @access public
15781
	 * @since 4.0.012 (2008-07-24)
15782
	 */
15783
	public function getCellHeightRatio() {
15784
		return $this->cell_height_ratio;
15785
	}
15786
 
15787
	/**
15788
	 * Set the PDF version (check PDF reference for valid values).
15789
	 * Default value is 1.t
15790
	 * @access public
15791
	 * @since 3.1.000 (2008-06-09)
15792
	 */
15793
	public function setPDFVersion($version='1.7') {
15794
		$this->PDFVersion = $version;
15795
	}
15796
 
15797
	/**
15798
	 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
15799
	 * (see Section 8.1 of PDF reference, "Viewer Preferences").
15800
	 * <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>
15801
	 * @param array $preferences array of options.
15802
	 * @author Nicola Asuni
15803
	 * @access public
15804
	 * @since 3.1.000 (2008-06-09)
15805
	 */
15806
	public function setViewerPreferences($preferences) {
15807
		$this->viewer_preferences = $preferences;
15808
	}
15809
 
15810
	/**
15811
	 * Paints color transition registration bars
15812
	 * @param float $x abscissa of the top left corner of the rectangle.
15813
	 * @param float $y ordinate of the top left corner of the rectangle.
15814
	 * @param float $w width of the rectangle.
15815
	 * @param float $h height of the rectangle.
15816
	 * @param boolean $transition if true prints tcolor transitions to white.
15817
	 * @param boolean $vertical if true prints bar vertically.
15818
	 * @param string $colors colors to print, one letter per color separated by comma (for example 'A,W,R,G,B,C,M,Y,K'): A=black, W=white, R=red, G=green, B=blue, C=cyan, M=magenta, Y=yellow, K=black.
15819
	 * @author Nicola Asuni
15820
	 * @since 4.9.000 (2010-03-26)
15821
	 * @access public
15822
	 */
15823
	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
15824
		$bars = explode(',', $colors);
15825
		$numbars = count($bars); // number of bars to print
15826
		// set bar measures
15827
		if ($vertical) {
15828
			$coords = array(0, 0, 0, 1);
15829
			$wb = $w / $numbars; // bar width
15830
			$hb = $h; // bar height
15831
			$xd = $wb; // delta x
15832
			$yd = 0; // delta y
15833
		} else {
15834
			$coords = array(1, 0, 0, 0);
15835
			$wb = $w; // bar width
15836
			$hb = $h / $numbars; // bar height
15837
			$xd = 0; // delta x
15838
			$yd = $hb; // delta y
15839
		}
15840
		$xb = $x;
15841
		$yb = $y;
15842
		foreach ($bars as $col) {
15843
			switch ($col) {
15844
				// set transition colors
15845
				case 'A': { // BLACK
15846
					$col_a = array(255);
15847
					$col_b = array(0);
15848
					break;
15849
				}
15850
				case 'W': { // WHITE
15851
					$col_a = array(0);
15852
					$col_b = array(255);
15853
					break;
15854
				}
15855
				case 'R': { // R
15856
					$col_a = array(255,255,255);
15857
					$col_b = array(255,0,0);
15858
					break;
15859
				}
15860
				case 'G': { // G
15861
					$col_a = array(255,255,255);
15862
					$col_b = array(0,255,0);
15863
					break;
15864
				}
15865
				case 'B': { // B
15866
					$col_a = array(255,255,255);
15867
					$col_b = array(0,0,255);
15868
					break;
15869
				}
15870
				case 'C': { // C
15871
					$col_a = array(0,0,0,0);
15872
					$col_b = array(100,0,0,0);
15873
					break;
15874
				}
15875
				case 'M': { // M
15876
					$col_a = array(0,0,0,0);
15877
					$col_b = array(0,100,0,0);
15878
					break;
15879
				}
15880
				case 'Y': { // Y
15881
					$col_a = array(0,0,0,0);
15882
					$col_b = array(0,0,100,0);
15883
					break;
15884
				}
15885
				case 'K': { // K
15886
					$col_a = array(0,0,0,0);
15887
					$col_b = array(0,0,0,100);
15888
					break;
15889
				}
15890
				default: { // GRAY
15891
					$col_a = array(255);
15892
					$col_b = array(0);
15893
					break;
15894
				}
15895
			}
15896
			if ($transition) {
15897
				// color gradient
15898
				$this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
15899
			} else {
15900
				// color rectangle
15901
				$this->SetFillColorArray($col_b);
15902
				$this->Rect($xb, $yb, $wb, $hb, 'F', array());
15903
			}
15904
			$xb += $xd;
15905
			$yb += $yd;
15906
		}
15907
	}
15908
 
15909
	/**
15910
	 * Paints crop mark
15911
	 * @param float $x abscissa of the crop mark center.
15912
	 * @param float $y ordinate of the crop mark center.
15913
	 * @param float $w width of the crop mark.
15914
	 * @param float $h height of the crop mark.
15915
	 * @param string $type type of crop mark, one sybol per type separated by comma: A = top left, B = top right, C = bottom left, D = bottom right.
15916
	 * @param array $color crop mark color (default black).
15917
	 * @author Nicola Asuni
15918
	 * @since 4.9.000 (2010-03-26)
15919
	 * @access public
15920
	 */
15921
	public function cropMark($x, $y, $w, $h, $type='A,B,C,D', $color=array(0,0,0)) {
15922
		$this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
15923
		$crops = explode(',', $type);
15924
		$numcrops = count($crops); // number of crop marks to print
15925
		$dw = $w / 4; // horizontal space to leave before the intersection point
15926
		$dh = $h / 4; // vertical space to leave before the intersection point
15927
		foreach ($crops as $crop) {
15928
			switch ($crop) {
15929
				case 'A': {
15930
					$x1 = $x;
15931
					$y1 = $y - $h;
15932
					$x2 = $x;
15933
					$y2 = $y - $dh;
15934
					$x3 = $x - $w;
15935
					$y3 = $y;
15936
					$x4 = $x - $dw;
15937
					$y4 = $y;
15938
					break;
15939
				}
15940
				case 'B': {
15941
					$x1 = $x;
15942
					$y1 = $y - $h;
15943
					$x2 = $x;
15944
					$y2 = $y - $dh;
15945
					$x3 = $x + $dw;
15946
					$y3 = $y;
15947
					$x4 = $x + $w;
15948
					$y4 = $y;
15949
					break;
15950
				}
15951
				case 'C': {
15952
					$x1 = $x - $w;
15953
					$y1 = $y;
15954
					$x2 = $x - $dw;
15955
					$y2 = $y;
15956
					$x3 = $x;
15957
					$y3 = $y + $dh;
15958
					$x4 = $x;
15959
					$y4 = $y + $h;
15960
					break;
15961
				}
15962
				case 'D': {
15963
					$x1 = $x + $dw;
15964
					$y1 = $y;
15965
					$x2 = $x + $w;
15966
					$y2 = $y;
15967
					$x3 = $x;
15968
					$y3 = $y + $dh;
15969
					$x4 = $x;
15970
					$y4 = $y + $h;
15971
					break;
15972
				}
15973
			}
15974
			$this->Line($x1, $y1, $x2, $y2);
15975
			$this->Line($x3, $y3, $x4, $y4);
15976
		}
15977
	}
15978
 
15979
	/**
15980
	 * Paints a registration mark
15981
	 * @param float $x abscissa of the registration mark center.
15982
	 * @param float $y ordinate of the registration mark center.
15983
	 * @param float $r radius of the crop mark.
15984
	 * @param boolean $double if true print two concentric crop marks.
15985
	 * @param array $cola crop mark color (default black).
15986
	 * @param array $colb second crop mark color.
15987
	 * @author Nicola Asuni
15988
	 * @since 4.9.000 (2010-03-26)
15989
	 * @access public
15990
	 */
15991
	public function registrationMark($x, $y, $r, $double=false, $cola=array(0,0,0), $colb=array(255,255,255)) {
15992
		$line_style = array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
15993
		$this->SetFillColorArray($cola);
15994
		$this->PieSector($x, $y, $r, 90, 180, 'F');
15995
		$this->PieSector($x, $y, $r, 270, 360, 'F');
15996
		$this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
15997
		if ($double) {
15998
			$r2 = $r * 0.5;
15999
			$this->SetFillColorArray($colb);
16000
			$this->PieSector($x, $y, $r2, 90, 180, 'F');
16001
			$this->PieSector($x, $y, $r2, 270, 360, 'F');
16002
			$this->SetFillColorArray($cola);
16003
			$this->PieSector($x, $y, $r2, 0, 90, 'F');
16004
			$this->PieSector($x, $y, $r2, 180, 270, 'F');
16005
			$this->Circle($x, $y, $r2, 0, 360, 'C', $line_style, array(), 8);
16006
		}
16007
	}
16008
 
16009
	/**
16010
	 * Paints a linear colour gradient.
16011
	 * @param float $x abscissa of the top left corner of the rectangle.
16012
	 * @param float $y ordinate of the top left corner of the rectangle.
16013
	 * @param float $w width of the rectangle.
16014
	 * @param float $h height of the rectangle.
16015
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
16016
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
16017
	 * @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).
16018
	 * @author Andreas W�rmser, Nicola Asuni
16019
	 * @since 3.1.000 (2008-06-09)
16020
	 * @access public
16021
	 */
16022
	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
16023
		$this->Clip($x, $y, $w, $h);
16024
		$this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
16025
	}
16026
 
16027
	/**
16028
	 * Paints a radial colour gradient.
16029
	 * @param float $x abscissa of the top left corner of the rectangle.
16030
	 * @param float $y ordinate of the top left corner of the rectangle.
16031
	 * @param float $w width of the rectangle.
16032
	 * @param float $h height of the rectangle.
16033
	 * @param array $col1 first color (Grayscale, RGB or CMYK components).
16034
	 * @param array $col2 second color (Grayscale, RGB or CMYK components).
16035
	 * @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.
16036
	 * @author Andreas W�rmser, Nicola Asuni
16037
	 * @since 3.1.000 (2008-06-09)
16038
	 * @access public
16039
	 */
16040
	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
16041
		$this->Clip($x, $y, $w, $h);
16042
		$this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
16043
	}
16044
 
16045
	/**
16046
	 * Paints a coons patch mesh.
16047
	 * @param float $x abscissa of the top left corner of the rectangle.
16048
	 * @param float $y ordinate of the top left corner of the rectangle.
16049
	 * @param float $w width of the rectangle.
16050
	 * @param float $h height of the rectangle.
16051
	 * @param array $col1 first color (lower left corner) (RGB components).
16052
	 * @param array $col2 second color (lower right corner) (RGB components).
16053
	 * @param array $col3 third color (upper right corner) (RGB components).
16054
	 * @param array $col4 fourth color (upper left corner) (RGB components).
16055
	 * @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>
16056
	 * @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
16057
	 * @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
16058
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
16059
	 * @author Andreas W�rmser, Nicola Asuni
16060
	 * @since 3.1.000 (2008-06-09)
16061
	 * @access public
16062
	 */
16063
	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) {
16064
		$this->Clip($x, $y, $w, $h);
16065
		$n = count($this->gradients) + 1;
16066
		$this->gradients[$n] = array();
16067
		$this->gradients[$n]['type'] = 6; //coons patch mesh
16068
		$this->gradients[$n]['coords'] = array();
16069
		$this->gradients[$n]['antialias'] = $antialias;
16070
		$this->gradients[$n]['colors'] = array();
16071
		$this->gradients[$n]['transparency'] = false;
16072
		//check the coords array if it is the simple array or the multi patch array
16073
		if (!isset($coords[0]['f'])) {
16074
			//simple array -> convert to multi patch array
16075
			if (!isset($col1[1])) {
16076
				$col1[1] = $col1[2] = $col1[0];
16077
			}
16078
			if (!isset($col2[1])) {
16079
				$col2[1] = $col2[2] = $col2[0];
16080
			}
16081
			if (!isset($col3[1])) {
16082
				$col3[1] = $col3[2] = $col3[0];
16083
			}
16084
			if (!isset($col4[1])) {
16085
				$col4[1] = $col4[2] = $col4[0];
16086
			}
16087
			$patch_array[0]['f'] = 0;
16088
			$patch_array[0]['points'] = $coords;
16089
			$patch_array[0]['colors'][0]['r'] = $col1[0];
16090
			$patch_array[0]['colors'][0]['g'] = $col1[1];
16091
			$patch_array[0]['colors'][0]['b'] = $col1[2];
16092
			$patch_array[0]['colors'][1]['r'] = $col2[0];
16093
			$patch_array[0]['colors'][1]['g'] = $col2[1];
16094
			$patch_array[0]['colors'][1]['b'] = $col2[2];
16095
			$patch_array[0]['colors'][2]['r'] = $col3[0];
16096
			$patch_array[0]['colors'][2]['g'] = $col3[1];
16097
			$patch_array[0]['colors'][2]['b'] = $col3[2];
16098
			$patch_array[0]['colors'][3]['r'] = $col4[0];
16099
			$patch_array[0]['colors'][3]['g'] = $col4[1];
16100
			$patch_array[0]['colors'][3]['b'] = $col4[2];
16101
		} else {
16102
			//multi patch array
16103
			$patch_array = $coords;
16104
		}
16105
		$bpcd = 65535; //16 bits per coordinate
16106
		//build the data stream
16107
		$this->gradients[$n]['stream'] = '';
16108
		$count_patch = count($patch_array);
16109
		for ($i=0; $i < $count_patch; ++$i) {
16110
			$this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
16111
			$count_points = count($patch_array[$i]['points']);
16112
			for ($j=0; $j < $count_points; ++$j) {
16113
				//each point as 16 bit
16114
				$patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
16115
				if ($patch_array[$i]['points'][$j] < 0) {
16116
					$patch_array[$i]['points'][$j] = 0;
16117
				}
16118
				if ($patch_array[$i]['points'][$j] > $bpcd) {
16119
					$patch_array[$i]['points'][$j] = $bpcd;
16120
				}
16121
				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
16122
				$this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
16123
			}
16124
			$count_cols = count($patch_array[$i]['colors']);
16125
			for ($j=0; $j < $count_cols; ++$j) {
16126
				//each color component as 8 bit
16127
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
16128
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
16129
				$this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
16130
			}
16131
		}
16132
		//paint the gradient
16133
		$this->_out('/Sh'.$n.' sh');
16134
		//restore previous Graphic State
16135
		$this->_out('Q');
16136
	}
16137
 
16138
	/**
16139
	 * Set a rectangular clipping area.
16140
	 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
16141
	 * @param float $y ordinate of the top left corner of the rectangle.
16142
	 * @param float $w width of the rectangle.
16143
	 * @param float $h height of the rectangle.
16144
	 * @author Andreas W�rmser, Nicola Asuni
16145
	 * @since 3.1.000 (2008-06-09)
16146
	 * @access protected
16147
	 */
16148
	protected function Clip($x, $y, $w, $h) {
16149
		if ($this->rtl) {
16150
			$x = $this->w - $x - $w;
16151
		}
16152
		//save current Graphic State
16153
		$s = 'q';
16154
		//set clipping area
16155
		$s .= sprintf(' %.2F %.2F %.2F %.2F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
16156
		//set up transformation matrix for gradient
16157
		$s .= sprintf(' %.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
16158
		$this->_out($s);
16159
	}
16160
 
16161
	/**
16162
	 * Output gradient.
16163
	 * @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)
16164
	 * @param array $coords array of coordinates.
16165
	 * @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).
16166
	 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
16167
	 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
16168
	 * @author Nicola Asuni
16169
	 * @since 3.1.000 (2008-06-09)
16170
	 * @access public
16171
	 */
16172
	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
16173
		$n = count($this->gradients) + 1;
16174
		$this->gradients[$n] = array();
16175
		$this->gradients[$n]['type'] = $type;
16176
		$this->gradients[$n]['coords'] = $coords;
16177
		$this->gradients[$n]['antialias'] = $antialias;
16178
		$this->gradients[$n]['colors'] = array();
16179
		$this->gradients[$n]['transparency'] = false;
16180
		// color space
16181
		$numcolspace = count($stops[0]['color']);
16182
		$bcolor = array_values($background);
16183
		switch($numcolspace) {
16184
			case 4: { // CMYK
16185
				$this->gradients[$n]['colspace'] = 'DeviceCMYK';
16186
				if (!empty($background)) {
16187
					$this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F %.3F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
16188
				}
16189
				break;
16190
			}
16191
			case 3: { // RGB
16192
				$this->gradients[$n]['colspace'] = 'DeviceRGB';
16193
				if (!empty($background)) {
16194
					$this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
16195
				}
16196
				break;
16197
			}
16198
			case 1: { // Gray scale
16199
				$this->gradients[$n]['colspace'] = 'DeviceGray';
16200
				if (!empty($background)) {
16201
					$this->gradients[$n]['background'] = sprintf('%.3F', $bcolor[0]/255);
16202
				}
16203
				break;
16204
			}
16205
		}
16206
		$num_stops = count($stops);
16207
		$last_stop_id = $num_stops - 1;
16208
		foreach ($stops as $key => $stop) {
16209
			$this->gradients[$n]['colors'][$key] = array();
16210
			// offset represents a location along the gradient vector
16211
			if (isset($stop['offset'])) {
16212
				$this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
16213
			} else {
16214
				if ($key == 0) {
16215
					$this->gradients[$n]['colors'][$key]['offset'] = 0;
16216
				} elseif ($key == $last_stop_id) {
16217
					$this->gradients[$n]['colors'][$key]['offset'] = 1;
16218
				} else {
16219
					$offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
16220
					$this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
16221
				}
16222
			}
16223
			if (isset($stop['opacity'])) {
16224
				$this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
16225
				if ($stop['opacity'] < 1) {
16226
					$this->gradients[$n]['transparency'] = true;
16227
				}
16228
			} else {
16229
				$this->gradients[$n]['colors'][$key]['opacity'] = 1;
16230
			}
16231
			// exponent for the exponential interpolation function
16232
			if (isset($stop['exponent'])) {
16233
				$this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
16234
			} else {
16235
				$this->gradients[$n]['colors'][$key]['exponent'] = 1;
16236
			}
16237
			// set colors
16238
			$color = array_values($stop['color']);
16239
			switch($numcolspace) {
16240
				case 4: { // CMYK
16241
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F %.3F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
16242
					break;
16243
				}
16244
				case 3: { // RGB
16245
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
16246
					break;
16247
				}
16248
				case 1: { // Gray scale
16249
					$this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F', $color[0]/255);
16250
					break;
16251
				}
16252
			}
16253
		}
16254
		if ($this->gradients[$n]['transparency']) {
16255
			// paint luminosity gradient
16256
			$this->_out('/TGS'.$n.' gs');
16257
		}
16258
		//paint the gradient
16259
		$this->_out('/Sh'.$n.' sh');
16260
		//restore previous Graphic State
16261
		$this->_out('Q');
16262
	}
16263
 
16264
	/**
16265
	 * Output gradient shaders.
16266
	 * @author Nicola Asuni
16267
	 * @since 3.1.000 (2008-06-09)
16268
	 * @access protected
16269
	 */
16270
	function _putshaders() {
16271
		$idt = count($this->gradients); //index for transparency gradients
16272
		foreach ($this->gradients as $id => $grad) {
16273
			if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
16274
				$fc = $this->_newobj();
16275
				$out = '<<';
16276
				$out .= ' /FunctionType 3';
16277
				$out .= ' /Domain [0 1]';
16278
				$functions = '';
16279
				$bounds = '';
16280
				$encode = '';
16281
				$i = 1;
16282
				$num_cols = count($grad['colors']);
16283
				$lastcols = $num_cols - 1;
16284
				for ($i = 1; $i < $num_cols; ++$i) {
16285
					$functions .= ($fc + $i).' 0 R ';
16286
					if ($i < $lastcols) {
16287
						$bounds .= sprintf('%.3F ', $grad['colors'][$i]['offset']);
16288
					}
16289
					$encode .= '0 1 ';
16290
				}
16291
				$out .= ' /Functions ['.trim($functions).']';
16292
				$out .= ' /Bounds ['.trim($bounds).']';
16293
				$out .= ' /Encode ['.trim($encode).']';
16294
				$out .= ' >>';
16295
				$out .= "\n".'endobj';
16296
				$this->_out($out);
16297
				for ($i = 1; $i < $num_cols; ++$i) {
16298
					$this->_newobj();
16299
					$out = '<<';
16300
					$out .= ' /FunctionType 2';
16301
					$out .= ' /Domain [0 1]';
16302
					$out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
16303
					$out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
16304
					$out .= ' /N '.$grad['colors'][$i]['exponent'];
16305
					$out .= ' >>';
16306
					$out .= "\n".'endobj';
16307
					$this->_out($out);
16308
				}
16309
				// set transparency fuctions
16310
				if ($grad['transparency']) {
16311
					$ft = $this->_newobj();
16312
					$out = '<<';
16313
					$out .= ' /FunctionType 3';
16314
					$out .= ' /Domain [0 1]';
16315
					$functions = '';
16316
					$i = 1;
16317
					$num_cols = count($grad['colors']);
16318
					for ($i = 1; $i < $num_cols; ++$i) {
16319
						$functions .= ($ft + $i).' 0 R ';
16320
					}
16321
					$out .= ' /Functions ['.trim($functions).']';
16322
					$out .= ' /Bounds ['.trim($bounds).']';
16323
					$out .= ' /Encode ['.trim($encode).']';
16324
					$out .= ' >>';
16325
					$out .= "\n".'endobj';
16326
					$this->_out($out);
16327
					for ($i = 1; $i < $num_cols; ++$i) {
16328
						$this->_newobj();
16329
						$out = '<<';
16330
						$out .= ' /FunctionType 2';
16331
						$out .= ' /Domain [0 1]';
16332
						$out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
16333
						$out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
16334
						$out .= ' /N '.$grad['colors'][$i]['exponent'];
16335
						$out .= ' >>';
16336
						$out .= "\n".'endobj';
16337
						$this->_out($out);
16338
					}
16339
				}
16340
			}
16341
			// set shading object
16342
			$this->_newobj();
16343
			$out = '<< /ShadingType '.$grad['type'];
16344
			if (isset($grad['colspace'])) {
16345
				$out .= ' /ColorSpace /'.$grad['colspace'];
16346
			} else {
16347
				$out .= ' /ColorSpace /DeviceRGB';
16348
			}
16349
			if (isset($grad['background']) AND !empty($grad['background'])) {
16350
				$out .= ' /Background ['.$grad['background'].']';
16351
			}
16352
			if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
16353
				$out .= ' /AntiAlias true';
16354
			}
16355
			if ($grad['type'] == 2) {
16356
				$out .= ' '.sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
16357
				$out .= ' /Domain [0 1]';
16358
				$out .= ' /Function '.$fc.' 0 R';
16359
				$out .= ' /Extend [true true]';
16360
				$out .= ' >>';
16361
			} elseif ($grad['type'] == 3) {
16362
				//x0, y0, r0, x1, y1, r1
16363
				//at this this time radius of inner circle is 0
16364
				$out .= ' '.sprintf('/Coords [%.3F %.3F 0 %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
16365
				$out .= ' /Domain [0 1]';
16366
				$out .= ' /Function '.$fc.' 0 R';
16367
				$out .= ' /Extend [true true]';
16368
				$out .= ' >>';
16369
			} elseif ($grad['type'] == 6) {
16370
				$out .= ' /BitsPerCoordinate 16';
16371
				$out .= ' /BitsPerComponent 8';
16372
				$out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
16373
				$out .= ' /BitsPerFlag 8';
16374
				$stream = $this->_getrawstream($grad['stream']);
16375
				$out .= ' /Length '.strlen($stream);
16376
				$out .= ' >>';
16377
				$out .= ' stream'."\n".$stream."\n".'endstream';
16378
			}
16379
			$out .= "\n".'endobj';
16380
			$this->_out($out);
16381
			if ($grad['transparency']) {
16382
				$shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
16383
				$shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
16384
			}
16385
			$this->gradients[$id]['id'] = $this->n;
16386
			// set pattern object
16387
			$this->_newobj();
16388
			$out = '<< /Type /Pattern /PatternType 2';
16389
			$out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
16390
			$out .= ' >>';
16391
			$out .= "\n".'endobj';
16392
			$this->_out($out);
16393
			$this->gradients[$id]['pattern'] = $this->n;
16394
			// set shading and pattern for transparency mask
16395
			if ($grad['transparency']) {
16396
				// luminosity pattern
16397
				$idgs = $id + $idt;
16398
				$this->_newobj();
16399
				$this->_out($shading_transparency);
16400
				$this->gradients[$idgs]['id'] = $this->n;
16401
				$this->_newobj();
16402
				$out = '<< /Type /Pattern /PatternType 2';
16403
				$out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
16404
				$out .= ' >>';
16405
				$out .= "\n".'endobj';
16406
				$this->_out($out);
16407
				$this->gradients[$idgs]['pattern'] = $this->n;
16408
				// luminosity XObject
16409
				$oid = $this->_newobj();
16410
				$this->xobjects['LX'.$oid] = array('n' => $oid);
16411
				$filter = '';
16412
				$stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
16413
				if ($this->compress) {
16414
					$filter = ' /Filter /FlateDecode';
16415
					$stream = gzcompress($stream);
16416
				}
16417
				$stream = $this->_getrawstream($stream);
16418
				$out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
16419
				$out .= ' /Length '.strlen($stream);
16420
				$rect = sprintf('%.2F %.2F', $this->wPt, $this->hPt);
16421
				$out .= ' /BBox [0 0 '.$rect.']';
16422
				$out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
16423
				$out .= ' /Resources <<';
16424
				$out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
16425
				$out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
16426
				$out .= ' >>';
16427
				$out .= ' >> ';
16428
				$out .= ' stream'."\n".$stream."\n".'endstream';
16429
				$out .= "\n".'endobj';
16430
				$this->_out($out);
16431
				// SMask
16432
				$this->_newobj();
16433
				$out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
16434
				$this->_out($out);
16435
				// ExtGState
16436
				$this->_newobj();
16437
				$out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
16438
				$this->_out($out);
16439
				$this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
16440
			}
16441
		}
16442
	}
16443
 
16444
	/**
16445
	 * Draw the sector of a circle.
16446
	 * It can be used for instance to render pie charts.
16447
	 * @param float $xc abscissa of the center.
16448
	 * @param float $yc ordinate of the center.
16449
	 * @param float $r radius.
16450
	 * @param float $a start angle (in degrees).
16451
	 * @param float $b end angle (in degrees).
16452
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
16453
	 * @param float $cw: indicates whether to go clockwise (default: true).
16454
	 * @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.
16455
	 * @author Maxime Delorme, Nicola Asuni
16456
	 * @since 3.1.000 (2008-06-09)
16457
	 * @access public
16458
	 */
16459
	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
16460
		$this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
16461
	}
16462
 
16463
	/**
16464
	 * Draw the sector of an ellipse.
16465
	 * It can be used for instance to render pie charts.
16466
	 * @param float $xc abscissa of the center.
16467
	 * @param float $yc ordinate of the center.
16468
	 * @param float $rx the x-axis radius.
16469
	 * @param float $ry the y-axis radius.
16470
	 * @param float $a start angle (in degrees).
16471
	 * @param float $b end angle (in degrees).
16472
	 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
16473
	 * @param float $cw: indicates whether to go clockwise.
16474
	 * @param float $o: origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
16475
	 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
16476
	 * @author Maxime Delorme, Nicola Asuni
16477
	 * @since 3.1.000 (2008-06-09)
16478
	 * @access public
16479
	 */
16480
	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
16481
		if ($this->rtl) {
16482
			$xc = $this->w - $xc;
16483
		}
16484
		$op = $this->getPathPaintOperator($style);
16485
		if ($op == 'f') {
16486
			$line_style = array();
16487
		}
16488
		if ($cw) {
16489
			$d = $b;
16490
			$b = 360 - $a + $o;
16491
			$a = 360 - $d + $o;
16492
		} else {
16493
			$b += $o;
16494
			$a += $o;
16495
		}
16496
		$this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
16497
		$this->_out($op);
16498
	}
16499
 
16500
	/**
16501
	 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
16502
	 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
16503
	 * Only vector drawing is supported, not text or bitmap.
16504
	 * 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).
16505
	 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
16506
	 * @param float $x Abscissa of the upper-left corner.
16507
	 * @param float $y Ordinate of the upper-left corner.
16508
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
16509
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
16510
	 * @param mixed $link URL or identifier returned by AddLink().
16511
	 * @param boolean useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
16512
	 * @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>
16513
	 * @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>
16514
	 * @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)))
16515
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
16516
	 * @param boolean $fixoutvals if true remove values outside the bounding box.
16517
	 * @author Valentin Schmidt, Nicola Asuni
16518
	 * @since 3.1.000 (2008-06-09)
16519
	 * @access public
16520
	 */
16521
	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
16522
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
16523
			// convert EPS to raster image using GD or ImageMagick libraries
16524
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
16525
		}
16526
		if ($x === '') {
16527
			$x = $this->x;
16528
		}
16529
		if ($y === '') {
16530
			$y = $this->y;
16531
		}
16532
		// check page for no-write regions and adapt page margins if necessary
16533
		$this->checkPageRegions($h, $x, $y);
16534
		$k = $this->k;
16535
		if ($file{0} === '@') { // image from string
16536
			$data = substr($file, 1);
16537
		} else { // EPS/AI file
16538
			$data = file_get_contents($file);
16539
		}
16540
		if ($data === false) {
16541
			$this->Error('EPS file not found: '.$file);
16542
		}
16543
		$regs = array();
16544
		// EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
16545
		preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
16546
		if (count($regs) > 1) {
16547
			$version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
16548
			if (strpos($version_str, 'Adobe Illustrator') !== false) {
16549
				$versexp = explode(' ', $version_str);
16550
				$version = (float)array_pop($versexp);
16551
				if ($version >= 9) {
16552
					$this->Error('This version of Adobe Illustrator file is not supported: '.$file);
16553
				}
16554
			}
16555
		}
16556
		// strip binary bytes in front of PS-header
16557
		$start = strpos($data, '%!PS-Adobe');
16558
		if ($start > 0) {
16559
			$data = substr($data, $start);
16560
		}
16561
		// find BoundingBox params
16562
		preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
16563
		if (count($regs) > 1) {
16564
			list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
16565
		} else {
16566
			$this->Error('No BoundingBox found in EPS/AI file: '.$file);
16567
		}
16568
		$start = strpos($data, '%%EndSetup');
16569
		if ($start === false) {
16570
			$start = strpos($data, '%%EndProlog');
16571
		}
16572
		if ($start === false) {
16573
			$start = strpos($data, '%%BoundingBox');
16574
		}
16575
		$data = substr($data, $start);
16576
		$end = strpos($data, '%%PageTrailer');
16577
		if ($end===false) {
16578
			$end = strpos($data, 'showpage');
16579
		}
16580
		if ($end) {
16581
			$data = substr($data, 0, $end);
16582
		}
16583
		// calculate image width and height on document
16584
		if (($w <= 0) AND ($h <= 0)) {
16585
			$w = ($x2 - $x1) / $k;
16586
			$h = ($y2 - $y1) / $k;
16587
		} elseif ($w <= 0) {
16588
			$w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
16589
		} elseif ($h <= 0) {
16590
			$h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
16591
		}
16592
		// fit the image on available space
16593
		$this->fitBlock($w, $h, $x, $y, $fitonpage);
16594
		if ($this->rasterize_vector_images) {
16595
			// convert EPS to raster image using GD or ImageMagick libraries
16596
			return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
16597
		}
16598
		// set scaling factors
16599
		$scale_x = $w / (($x2 - $x1) / $k);
16600
		$scale_y = $h / (($y2 - $y1) / $k);
16601
		// set alignment
16602
		$this->img_rb_y = $y + $h;
16603
		// set alignment
16604
		if ($this->rtl) {
16605
			if ($palign == 'L') {
16606
				$ximg = $this->lMargin;
16607
			} elseif ($palign == 'C') {
16608
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16609
			} elseif ($palign == 'R') {
16610
				$ximg = $this->w - $this->rMargin - $w;
16611
			} else {
16612
				$ximg = $x - $w;
16613
			}
16614
			$this->img_rb_x = $ximg;
16615
		} else {
16616
			if ($palign == 'L') {
16617
				$ximg = $this->lMargin;
16618
			} elseif ($palign == 'C') {
16619
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16620
			} elseif ($palign == 'R') {
16621
				$ximg = $this->w - $this->rMargin - $w;
16622
			} else {
16623
				$ximg = $x;
16624
			}
16625
			$this->img_rb_x = $ximg + $w;
16626
		}
16627
		if ($useBoundingBox) {
16628
			$dx = $ximg * $k - $x1;
16629
			$dy = $y * $k - $y1;
16630
		} else {
16631
			$dx = $ximg * $k;
16632
			$dy = $y * $k;
16633
		}
16634
		// save the current graphic state
16635
		$this->_out('q'.$this->epsmarker);
16636
		// translate
16637
		$this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
16638
		// scale
16639
		if (isset($scale_x)) {
16640
			$this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
16641
		}
16642
		// handle pc/unix/mac line endings
16643
		$lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
16644
		$u=0;
16645
		$cnt = count($lines);
16646
		for ($i=0; $i < $cnt; ++$i) {
16647
			$line = $lines[$i];
16648
			if (($line == '') OR ($line{0} == '%')) {
16649
				continue;
16650
			}
16651
			$len = strlen($line);
16652
			// check for spot color names
16653
			$color_name = '';
16654
			if (strcasecmp('x', substr(trim($line), -1)) == 0) {
16655
				if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
16656
					// extract spot color name
16657
					$color_name = $matches[0];
16658
					// remove color name from string
16659
					$line = str_replace(' '.$color_name, '', $line);
16660
					// remove pharentesis from color name
16661
					$color_name = substr($color_name, 1, -1);
16662
				}
16663
			}
16664
			$chunks = explode(' ', $line);
16665
			$cmd = trim(array_pop($chunks));
16666
			// RGB
16667
			if (($cmd == 'Xa') OR ($cmd == 'XA')) {
16668
				$b = array_pop($chunks);
16669
				$g = array_pop($chunks);
16670
				$r = array_pop($chunks);
16671
				$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!
16672
				continue;
16673
			}
16674
			$skip = false;
16675
			if ($fixoutvals) {
16676
				// check for values outside the bounding box
16677
				switch ($cmd) {
16678
					case 'm':
16679
					case 'l':
16680
					case 'L': {
16681
						// skip values outside bounding box
16682
						foreach ($chunks as $key => $val) {
16683
							if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
16684
								$skip = true;
16685
							} elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
16686
								$skip = true;
16687
							}
16688
						}
16689
					}
16690
				}
16691
			}
16692
			switch ($cmd) {
16693
				case 'm':
16694
				case 'l':
16695
				case 'v':
16696
				case 'y':
16697
				case 'c':
16698
				case 'k':
16699
				case 'K':
16700
				case 'g':
16701
				case 'G':
16702
				case 's':
16703
				case 'S':
16704
				case 'J':
16705
				case 'j':
16706
				case 'w':
16707
				case 'M':
16708
				case 'd':
16709
				case 'n': {
16710
					if ($skip) {
16711
						break;
16712
					}
16713
					$this->_out($line);
16714
					break;
16715
				}
16716
				case 'x': {// custom fill color
16717
					if (empty($color_name)) {
16718
						// CMYK color
16719
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
16720
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
16721
					} else {
16722
						// Spot Color (CMYK + tint)
16723
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
16724
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
16725
						$color_cmd = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
16726
						$this->_out($color_cmd);
16727
					}
16728
					break;
16729
				}
16730
				case 'X': { // custom stroke color
16731
					if (empty($color_name)) {
16732
						// CMYK color
16733
						list($col_c, $col_m, $col_y, $col_k) = $chunks;
16734
						$this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
16735
					} else {
16736
						// Spot Color (CMYK + tint)
16737
						list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
16738
						$this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
16739
						$color_cmd = sprintf('/CS%d CS %.3F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
16740
						$this->_out($color_cmd);
16741
					}
16742
					break;
16743
				}
16744
				case 'Y':
16745
				case 'N':
16746
				case 'V':
16747
				case 'L':
16748
				case 'C': {
16749
					if ($skip) {
16750
						break;
16751
					}
16752
					$line{$len-1} = strtolower($cmd);
16753
					$this->_out($line);
16754
					break;
16755
				}
16756
				case 'b':
16757
				case 'B': {
16758
					$this->_out($cmd . '*');
16759
					break;
16760
				}
16761
				case 'f':
16762
				case 'F': {
16763
					if ($u > 0) {
16764
						$isU = false;
16765
						$max = min(($i + 5), $cnt);
16766
						for ($j = ($i + 1); $j < $max; ++$j) {
16767
							$isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
16768
						}
16769
						if ($isU) {
16770
							$this->_out('f*');
16771
						}
16772
					} else {
16773
						$this->_out('f*');
16774
					}
16775
					break;
16776
				}
16777
				case '*u': {
16778
					++$u;
16779
					break;
16780
				}
16781
				case '*U': {
16782
					--$u;
16783
					break;
16784
				}
16785
			}
16786
		}
16787
		// restore previous graphic state
16788
		$this->_out($this->epsmarker.'Q');
16789
		if (!empty($border)) {
16790
			$bx = $this->x;
16791
			$by = $this->y;
16792
			$this->x = $ximg;
16793
			if ($this->rtl) {
16794
				$this->x += $w;
16795
			}
16796
			$this->y = $y;
16797
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
16798
			$this->x = $bx;
16799
			$this->y = $by;
16800
		}
16801
		if ($link) {
16802
			$this->Link($ximg, $y, $w, $h, $link, 0);
16803
		}
16804
		// set pointer to align the next text/objects
16805
		switch($align) {
16806
			case 'T':{
16807
				$this->y = $y;
16808
				$this->x = $this->img_rb_x;
16809
				break;
16810
			}
16811
			case 'M':{
16812
				$this->y = $y + round($h/2);
16813
				$this->x = $this->img_rb_x;
16814
				break;
16815
			}
16816
			case 'B':{
16817
				$this->y = $this->img_rb_y;
16818
				$this->x = $this->img_rb_x;
16819
				break;
16820
			}
16821
			case 'N':{
16822
				$this->SetY($this->img_rb_y);
16823
				break;
16824
			}
16825
			default:{
16826
				break;
16827
			}
16828
		}
16829
		$this->endlinex = $this->img_rb_x;
16830
	}
16831
 
16832
	/**
16833
	 * Set document barcode.
16834
	 * @param string $bc barcode
16835
	 * @access public
16836
	 */
16837
	public function setBarcode($bc='') {
16838
		$this->barcode = $bc;
16839
	}
16840
 
16841
	/**
16842
	 * Get current barcode.
16843
	 * @return string
16844
	 * @access public
16845
	 * @since 4.0.012 (2008-07-24)
16846
	 */
16847
	public function getBarcode() {
16848
		return $this->barcode;
16849
	}
16850
 
16851
	/**
16852
	 * Print a Linear Barcode.
16853
	 * @param string $code code to print
16854
	 * @param string $type type of barcode (see barcodes.php for supported formats).
16855
	 * @param int $x x position in user units (empty string = current x position)
16856
	 * @param int $y y position in user units (empty string = current y position)
16857
	 * @param int $w width in user units (empty string = remaining page width)
16858
	 * @param int $h height in user units (empty string = remaining page height)
16859
	 * @param float $xres width of the smallest bar in user units (empty string = default value = 0.4mm)
16860
	 * @param array $style array of options:<ul>
16861
	 * <li>boolean $style['border'] if true prints a border</li>
16862
	 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
16863
	 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
16864
	 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
16865
	 * <li>array $style['fgcolor'] color array for bars and text</li>
16866
	 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
16867
	 * <li>boolean $style['text'] if true prints text below the barcode</li>
16868
	 * <li>string $style['label'] override default label</li>
16869
	 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
16870
	 * <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>
16871
	 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
16872
	 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
16873
	 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
16874
	 * <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>
16875
	 * <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>
16876
	 * @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>
16877
	 * @author Nicola Asuni
16878
	 * @since 3.1.000 (2008-06-09)
16879
	 * @access public
16880
	 */
16881
	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
16882
		if ($this->empty_string(trim($code))) {
16883
			return;
16884
		}
16885
		require_once(dirname(__FILE__).'/barcodes.php');
16886
		// save current graphic settings
16887
		$gvars = $this->getGraphicVars();
16888
		// create new barcode object
16889
		$barcodeobj = new TCPDFBarcode($code, $type);
16890
		$arrcode = $barcodeobj->getBarcodeArray();
16891
		if ($arrcode === false) {
16892
			$this->Error('Error in 1D barcode string');
16893
		}
16894
		// set default values
16895
		if (!isset($style['position'])) {
16896
			$style['position'] = '';
16897
		} elseif ($style['position'] == 'S') {
16898
			// keep this for backward compatibility
16899
			$style['position'] = '';
16900
			$style['stretch'] = true;
16901
		}
16902
		if (!isset($style['fitwidth'])) {
16903
			if (!isset($style['stretch'])) {
16904
				$style['fitwidth'] = true;
16905
			} else {
16906
				$style['fitwidth'] = false;
16907
			}
16908
		}
16909
		if ($style['fitwidth']) {
16910
			// disable stretch
16911
			$style['stretch'] = false;
16912
		}
16913
		if (!isset($style['stretch'])) {
16914
			if (($w === '') OR ($w <= 0)) {
16915
				$style['stretch'] = false;
16916
			} else {
16917
				$style['stretch'] = true;
16918
			}
16919
		}
16920
		if (!isset($style['fgcolor'])) {
16921
			$style['fgcolor'] = array(0,0,0); // default black
16922
		}
16923
		if (!isset($style['bgcolor'])) {
16924
			$style['bgcolor'] = false; // default transparent
16925
		}
16926
		if (!isset($style['border'])) {
16927
			$style['border'] = false;
16928
		}
16929
		$fontsize = 0;
16930
		if (!isset($style['text'])) {
16931
			$style['text'] = false;
16932
		}
16933
		if ($style['text'] AND isset($style['font'])) {
16934
			if (isset($style['fontsize'])) {
16935
				$fontsize = $style['fontsize'];
16936
			}
16937
			$this->SetFont($style['font'], '', $fontsize);
16938
		}
16939
		if (!isset($style['stretchtext'])) {
16940
			$style['stretchtext'] = 4;
16941
		}
16942
		if ($x === '') {
16943
			$x = $this->x;
16944
		}
16945
		if ($y === '') {
16946
			$y = $this->y;
16947
		}
16948
		// check page for no-write regions and adapt page margins if necessary
16949
		$this->checkPageRegions($h, $x, $y);
16950
		if (($w === '') OR ($w <= 0)) {
16951
			if ($this->rtl) {
16952
				$w = $x - $this->lMargin;
16953
			} else {
16954
				$w = $this->w - $this->rMargin - $x;
16955
			}
16956
		}
16957
		// padding
16958
		if (!isset($style['padding'])) {
16959
			$padding = 0;
16960
		} elseif ($style['padding'] === 'auto') {
16961
			$padding = 10 * ($w / ($arrcode['maxw'] + 20));
16962
		} else {
16963
			$padding = floatval($style['padding']);
16964
		}
16965
		// horizontal padding
16966
		if (!isset($style['hpadding'])) {
16967
			$hpadding = $padding;
16968
		} elseif ($style['hpadding'] === 'auto') {
16969
			$hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
16970
		} else {
16971
			$hpadding = floatval($style['hpadding']);
16972
		}
16973
		// vertical padding
16974
		if (!isset($style['vpadding'])) {
16975
			$vpadding = $padding;
16976
		} elseif ($style['vpadding'] === 'auto') {
16977
			$vpadding = ($hpadding / 2);
16978
		} else {
16979
			$vpadding = floatval($style['vpadding']);
16980
		}
16981
		// calculate xres (single bar width)
16982
		$max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
16983
		if ($style['stretch']) {
16984
			$xres = $max_xres;
16985
		} else {
16986
			if ($this->empty_string($xres)) {
16987
				$xres = (0.141 * $this->k); // default bar width = 0.4 mm
16988
			}
16989
			if ($xres > $max_xres) {
16990
				// correct xres to fit on $w
16991
				$xres = $max_xres;
16992
			}
16993
			if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
16994
				OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
16995
				$hpadding = 10 * $xres;
16996
				if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
16997
					$vpadding = ($hpadding / 2);
16998
				}
16999
			}
17000
		}
17001
		if ($style['fitwidth']) {
17002
			$wold = $w;
17003
			$w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
17004
			if (isset($style['cellfitalign'])) {
17005
				switch ($style['cellfitalign']) {
17006
					case 'L': {
17007
						if ($this->rtl) {
17008
							$x -= ($wold - $w);
17009
						}
17010
						break;
17011
					}
17012
					case 'R': {
17013
						if (!$this->rtl) {
17014
							$x += ($wold - $w);
17015
						}
17016
						break;
17017
					}
17018
					case 'C': {
17019
						if ($this->rtl) {
17020
							$x -= (($wold - $w) / 2);
17021
						} else {
17022
							$x += (($wold - $w) / 2);
17023
						}
17024
						break;
17025
					}
17026
					default : {
17027
						break;
17028
					}
17029
				}
17030
			}
17031
		}
17032
		$text_height = ($this->cell_height_ratio * $fontsize / $this->k);
17033
		// height
17034
		if (($h === '') OR ($h <= 0)) {
17035
			// set default height
17036
			$h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
17037
		}
17038
		$barh = $h - $text_height - (2 * $vpadding);
17039
		if ($barh <=0) {
17040
			// try to reduce font or padding to fit barcode on available height
17041
			if ($text_height > $h) {
17042
				$fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
17043
				$text_height = ($this->cell_height_ratio * $fontsize / $this->k);
17044
				$this->SetFont($style['font'], '', $fontsize);
17045
			}
17046
			if ($vpadding > 0) {
17047
				$vpadding = (($h - $text_height) / 4);
17048
			}
17049
			$barh = $h - $text_height - (2 * $vpadding);
17050
		}
17051
		// fit the barcode on available space
17052
		$this->fitBlock($w, $h, $x, $y, false);
17053
		// set alignment
17054
		$this->img_rb_y = $y + $h;
17055
		// set alignment
17056
		if ($this->rtl) {
17057
			if ($style['position'] == 'L') {
17058
				$xpos = $this->lMargin;
17059
			} elseif ($style['position'] == 'C') {
17060
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17061
			} elseif ($style['position'] == 'R') {
17062
				$xpos = $this->w - $this->rMargin - $w;
17063
			} else {
17064
				$xpos = $x - $w;
17065
			}
17066
			$this->img_rb_x = $xpos;
17067
		} else {
17068
			if ($style['position'] == 'L') {
17069
				$xpos = $this->lMargin;
17070
			} elseif ($style['position'] == 'C') {
17071
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17072
			} elseif ($style['position'] == 'R') {
17073
				$xpos = $this->w - $this->rMargin - $w;
17074
			} else {
17075
				$xpos = $x;
17076
			}
17077
			$this->img_rb_x = $xpos + $w;
17078
		}
17079
		$xpos_rect = $xpos;
17080
		if (!isset($style['align'])) {
17081
			$style['align'] = 'C';
17082
		}
17083
		switch ($style['align']) {
17084
			case 'L': {
17085
				$xpos = $xpos_rect + $hpadding;
17086
				break;
17087
			}
17088
			case 'R': {
17089
				$xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
17090
				break;
17091
			}
17092
			case 'C':
17093
			default : {
17094
				$xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
17095
				break;
17096
			}
17097
		}
17098
		$xpos_text = $xpos;
17099
		// barcode is always printed in LTR direction
17100
		$tempRTL = $this->rtl;
17101
		$this->rtl = false;
17102
		// print background color
17103
		if ($style['bgcolor']) {
17104
			$this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
17105
		} elseif ($style['border']) {
17106
			$this->Rect($xpos_rect, $y, $w, $h, 'D');
17107
		}
17108
		// set foreground color
17109
		$this->SetDrawColorArray($style['fgcolor']);
17110
		$this->SetTextColorArray($style['fgcolor']);
17111
		// print bars
17112
		foreach ($arrcode['bcode'] as $k => $v) {
17113
			$bw = ($v['w'] * $xres);
17114
			if ($v['t']) {
17115
				// draw a vertical bar
17116
				$ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
17117
				$this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
17118
			}
17119
			$xpos += $bw;
17120
		}
17121
		// print text
17122
		if ($style['text']) {
17123
			if (isset($style['label']) AND !$this->empty_string($style['label'])) {
17124
				$label = $style['label'];
17125
			} else {
17126
				$label = $code;
17127
			}
17128
			$txtwidth = ($arrcode['maxw'] * $xres);
17129
			if ($this->GetStringWidth($label) > $txtwidth) {
17130
				$style['stretchtext'] = 2;
17131
			}
17132
			// print text
17133
			$this->x = $xpos_text;
17134
			$this->y = $y + $vpadding + $barh;
17135
			$cellpadding = $this->cell_padding;
17136
			$this->SetCellPadding(0);
17137
			$this->Cell($txtwidth, '', $label, 0, 0, 'C', 0, '', $style['stretchtext'], false, 'T', 'T');
17138
			$this->cell_padding = $cellpadding;
17139
		}
17140
		// restore original direction
17141
		$this->rtl = $tempRTL;
17142
		// restore previous settings
17143
		$this->setGraphicVars($gvars);
17144
		// set pointer to align the next text/objects
17145
		switch($align) {
17146
			case 'T':{
17147
				$this->y = $y;
17148
				$this->x = $this->img_rb_x;
17149
				break;
17150
			}
17151
			case 'M':{
17152
				$this->y = $y + round($h / 2);
17153
				$this->x = $this->img_rb_x;
17154
				break;
17155
			}
17156
			case 'B':{
17157
				$this->y = $this->img_rb_y;
17158
				$this->x = $this->img_rb_x;
17159
				break;
17160
			}
17161
			case 'N':{
17162
				$this->SetY($this->img_rb_y);
17163
				break;
17164
			}
17165
			default:{
17166
				break;
17167
			}
17168
		}
17169
		$this->endlinex = $this->img_rb_x;
17170
	}
17171
 
17172
	/**
17173
	 * This function is DEPRECATED, please use the new write1DBarcode() function.
17174
	 * @param int $x x position in user units
17175
	 * @param int $y y position in user units
17176
	 * @param int $w width in user units
17177
	 * @param int $h height position in user units
17178
	 * @param string $type type of barcode
17179
	 * @param string $style barcode style
17180
	 * @param string $font font for text
17181
	 * @param int $xres x resolution
17182
	 * @param string $code code to print
17183
	 * @deprecated deprecated since version 3.1.000 (2008-06-10)
17184
	 * @access public
17185
	 * @see write1DBarcode()
17186
	 */
17187
	public function writeBarcode($x, $y, $w, $h, $type, $style, $font, $xres, $code) {
17188
		// convert old settings for the new write1DBarcode() function.
17189
		$xres = 1 / $xres;
17190
		$newstyle = array(
17191
			'position' => '',
17192
			'align' => '',
17193
			'stretch' => false,
17194
			'fitwidth' => false,
17195
			'cellfitalign' => '',
17196
			'border' => false,
17197
			'padding' => 0,
17198
			'fgcolor' => array(0,0,0),
17199
			'bgcolor' => false,
17200
			'text' => true,
17201
			'font' => $font,
17202
			'fontsize' => 8,
17203
			'stretchtext' => 4
17204
		);
17205
		if ($style & 1) {
17206
			$newstyle['border'] = true;
17207
		}
17208
		if ($style & 2) {
17209
			$newstyle['bgcolor'] = false;
17210
		}
17211
		if ($style & 4) {
17212
			$newstyle['position'] = 'C';
17213
		} elseif ($style & 8) {
17214
			$newstyle['position'] = 'L';
17215
		} elseif ($style & 16) {
17216
			$newstyle['position'] = 'R';
17217
		}
17218
		if ($style & 128) {
17219
			$newstyle['text'] = true;
17220
		}
17221
		if ($style & 256) {
17222
			$newstyle['stretchtext'] = 4;
17223
		}
17224
		$this->write1DBarcode($code, $type, $x, $y, $w, $h, $xres, $newstyle, '');
17225
	}
17226
 
17227
	/**
17228
	 * Print 2D Barcode.
17229
	 * @param string $code code to print
17230
	 * @param string $type type of barcode (see 2dbarcodes.php for supported formats).
17231
	 * @param int $x x position in user units
17232
	 * @param int $y y position in user units
17233
	 * @param int $w width in user units
17234
	 * @param int $h height in user units
17235
	 * @param array $style array of options:<ul>
17236
	 * <li>boolean $style['border'] if true prints a border around the barcode</li>
17237
	 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
17238
	 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
17239
	 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
17240
	 * <li>int $style['module_width'] width of a single module in points</li>
17241
	 * <li>int $style['module_height'] height of a single module in points</li>
17242
	 * <li>array $style['fgcolor'] color array for bars and text</li>
17243
	 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
17244
	 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
17245
	 * <li>$style['module_height'] height of a single module in points</li></ul>
17246
	 * @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>
17247
	 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
17248
	 * @author Nicola Asuni
17249
	 * @since 4.5.037 (2009-04-07)
17250
	 * @access public
17251
	 */
17252
	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
17253
		if ($this->empty_string(trim($code))) {
17254
			return;
17255
		}
17256
		require_once(dirname(__FILE__).'/2dbarcodes.php');
17257
		// save current graphic settings
17258
		$gvars = $this->getGraphicVars();
17259
		// create new barcode object
17260
		$barcodeobj = new TCPDF2DBarcode($code, $type);
17261
		$arrcode = $barcodeobj->getBarcodeArray();
17262
		if (($arrcode === false) OR empty($arrcode)) {
17263
			$this->Error('Error in 2D barcode string');
17264
		}
17265
		// set default values
17266
		if (!isset($style['position'])) {
17267
			$style['position'] = '';
17268
		}
17269
		if (!isset($style['fgcolor'])) {
17270
			$style['fgcolor'] = array(0,0,0); // default black
17271
		}
17272
		if (!isset($style['bgcolor'])) {
17273
			$style['bgcolor'] = false; // default transparent
17274
		}
17275
		if (!isset($style['border'])) {
17276
			$style['border'] = false;
17277
		}
17278
		// padding
17279
		if (!isset($style['padding'])) {
17280
			$style['padding'] = 0;
17281
		} elseif ($style['padding'] === 'auto') {
17282
			$style['padding'] = 4;
17283
		}
17284
		if (!isset($style['hpadding'])) {
17285
			$style['hpadding'] = $style['padding'];
17286
		} elseif ($style['hpadding'] === 'auto') {
17287
			$style['hpadding'] = 4;
17288
		}
17289
		if (!isset($style['vpadding'])) {
17290
			$style['vpadding'] = $style['padding'];
17291
		} elseif ($style['vpadding'] === 'auto') {
17292
			$style['vpadding'] = 4;
17293
		}
17294
		// cell (module) dimension
17295
		if (!isset($style['module_width'])) {
17296
			$style['module_width'] = 1; // width of a single module in points
17297
		}
17298
		if (!isset($style['module_height'])) {
17299
			$style['module_height'] = 1; // height of a single module in points
17300
		}
17301
		if ($x === '') {
17302
			$x = $this->x;
17303
		}
17304
		if ($y === '') {
17305
			$y = $this->y;
17306
		}
17307
		// check page for no-write regions and adapt page margins if necessary
17308
		$this->checkPageRegions($h, $x, $y);
17309
		// number of barcode columns and rows
17310
		$rows = $arrcode['num_rows'];
17311
		$cols = $arrcode['num_cols'];
17312
		// module width and height
17313
		$mw = $style['module_width'];
17314
		$mh = $style['module_height'];
17315
		// get max dimensions
17316
		if ($this->rtl) {
17317
			$maxw = $x - $this->lMargin;
17318
		} else {
17319
			$maxw = $this->w - $this->rMargin - $x;
17320
		}
17321
		$maxh = ($this->h - $this->tMargin - $this->bMargin);
17322
		$ratioHW = ($rows * $mh) / ($cols * $mw);
17323
		$ratioWH = ($cols * $mw) / ($rows * $mh);
17324
		if (!$distort) {
17325
			if (($maxw * $ratioHW) > $maxh) {
17326
				$maxw = $maxh * $ratioWH;
17327
			}
17328
			if (($maxh * $ratioWH) > $maxw) {
17329
				$maxh = $maxw * $ratioHW;
17330
			}
17331
		}
17332
		// set maximum dimesions
17333
		if ($w > $maxw) {
17334
			$w = $maxw;
17335
		}
17336
		if ($h > $maxh) {
17337
			$h = $maxh;
17338
		}
17339
		$hpad = (2 * $style['hpadding']);
17340
		$vpad = (2 * $style['vpadding']);
17341
		// set dimensions
17342
		if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
17343
			$w = ($cols + $hpad) * ($mw / $this->k);
17344
			$h = ($rows + $vpad) * ($mh / $this->k);
17345
		} elseif (($w === '') OR ($w <= 0)) {
17346
			$w = $h * $ratioWH;
17347
		} elseif (($h === '') OR ($h <= 0)) {
17348
			$h = $w * $ratioHW;
17349
		}
17350
		// barcode size (excluding padding)
17351
		$bw = ($w * $cols) / ($cols + $hpad);
17352
		$bh = ($h * $rows) / ($rows + $vpad);
17353
		// dimension of single barcode cell unit
17354
		$cw = $bw / $cols;
17355
		$ch = $bh / $rows;
17356
		if (!$distort) {
17357
			if (($cw / $ch) > ($mw / $mh)) {
17358
				// correct horizontal distortion
17359
				$cw = $ch * $mw / $mh;
17360
				$bw = $cw * $cols;
17361
				$style['hpadding'] = ($w - $bw) / (2 * $cw);
17362
			} else {
17363
				// correct vertical distortion
17364
				$ch = $cw * $mh / $mw;
17365
				$bh = $ch * $rows;
17366
				$style['vpadding'] = ($h - $bh) / (2 * $ch);
17367
			}
17368
		}
17369
		// fit the barcode on available space
17370
		$this->fitBlock($w, $h, $x, $y, false);
17371
		// set alignment
17372
		$this->img_rb_y = $y + $h;
17373
		// set alignment
17374
		if ($this->rtl) {
17375
			if ($style['position'] == 'L') {
17376
				$xpos = $this->lMargin;
17377
			} elseif ($style['position'] == 'C') {
17378
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17379
			} elseif ($style['position'] == 'R') {
17380
				$xpos = $this->w - $this->rMargin - $w;
17381
			} else {
17382
				$xpos = $x - $w;
17383
			}
17384
			$this->img_rb_x = $xpos;
17385
		} else {
17386
			if ($style['position'] == 'L') {
17387
				$xpos = $this->lMargin;
17388
			} elseif ($style['position'] == 'C') {
17389
				$xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17390
			} elseif ($style['position'] == 'R') {
17391
				$xpos = $this->w - $this->rMargin - $w;
17392
			} else {
17393
				$xpos = $x;
17394
			}
17395
			$this->img_rb_x = $xpos + $w;
17396
		}
17397
		$xstart = $xpos + ($style['hpadding'] * $cw);
17398
		$ystart = $y + ($style['vpadding'] * $ch);
17399
		// barcode is always printed in LTR direction
17400
		$tempRTL = $this->rtl;
17401
		$this->rtl = false;
17402
		// print background color
17403
		if ($style['bgcolor']) {
17404
			$this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
17405
		} elseif ($style['border']) {
17406
			$this->Rect($xpos, $y, $w, $h, 'D');
17407
		}
17408
		// set foreground color
17409
		$this->SetDrawColorArray($style['fgcolor']);
17410
		// print barcode cells
17411
		// for each row
17412
		for ($r = 0; $r < $rows; ++$r) {
17413
			$xr = $xstart;
17414
			// for each column
17415
			for ($c = 0; $c < $cols; ++$c) {
17416
				if ($arrcode['bcode'][$r][$c] == 1) {
17417
					// draw a single barcode cell
17418
					$this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
17419
				}
17420
				$xr += $cw;
17421
			}
17422
			$ystart += $ch;
17423
		}
17424
		// restore original direction
17425
		$this->rtl = $tempRTL;
17426
		// restore previous settings
17427
		$this->setGraphicVars($gvars);
17428
		// set pointer to align the next text/objects
17429
		switch($align) {
17430
			case 'T':{
17431
				$this->y = $y;
17432
				$this->x = $this->img_rb_x;
17433
				break;
17434
			}
17435
			case 'M':{
17436
				$this->y = $y + round($h/2);
17437
				$this->x = $this->img_rb_x;
17438
				break;
17439
			}
17440
			case 'B':{
17441
				$this->y = $this->img_rb_y;
17442
				$this->x = $this->img_rb_x;
17443
				break;
17444
			}
17445
			case 'N':{
17446
				$this->SetY($this->img_rb_y);
17447
				break;
17448
			}
17449
			default:{
17450
				break;
17451
			}
17452
		}
17453
		$this->endlinex = $this->img_rb_x;
17454
	}
17455
 
17456
	/**
17457
	 * Returns an array containing current margins:
17458
	 * <ul>
17459
			<li>$ret['left'] = left margin</li>
17460
			<li>$ret['right'] = right margin</li>
17461
			<li>$ret['top'] = top margin</li>
17462
			<li>$ret['bottom'] = bottom margin</li>
17463
			<li>$ret['header'] = header margin</li>
17464
			<li>$ret['footer'] = footer margin</li>
17465
			<li>$ret['cell'] = cell padding array</li>
17466
			<li>$ret['padding_left'] = cell left padding</li>
17467
			<li>$ret['padding_top'] = cell top padding</li>
17468
			<li>$ret['padding_right'] = cell right padding</li>
17469
			<li>$ret['padding_bottom'] = cell bottom padding</li>
17470
	 * </ul>
17471
	 * @return array containing all margins measures
17472
	 * @access public
17473
	 * @since 3.2.000 (2008-06-23)
17474
	 */
17475
	public function getMargins() {
17476
		$ret = array(
17477
			'left' => $this->lMargin,
17478
			'right' => $this->rMargin,
17479
			'top' => $this->tMargin,
17480
			'bottom' => $this->bMargin,
17481
			'header' => $this->header_margin,
17482
			'footer' => $this->footer_margin,
17483
			'cell' => $this->cell_padding,
17484
			'padding_left' => $this->cell_padding['L'],
17485
			'padding_top' => $this->cell_padding['T'],
17486
			'padding_right' => $this->cell_padding['R'],
17487
			'padding_bottom' => $this->cell_padding['B']
17488
		);
17489
		return $ret;
17490
	}
17491
 
17492
	/**
17493
	 * Returns an array containing original margins:
17494
	 * <ul>
17495
			<li>$ret['left'] = left margin</li>
17496
			<li>$ret['right'] = right margin</li>
17497
	 * </ul>
17498
	 * @return array containing all margins measures
17499
	 * @access public
17500
	 * @since 4.0.012 (2008-07-24)
17501
	 */
17502
	public function getOriginalMargins() {
17503
		$ret = array(
17504
			'left' => $this->original_lMargin,
17505
			'right' => $this->original_rMargin
17506
		);
17507
		return $ret;
17508
	}
17509
 
17510
	/**
17511
	 * Returns the current font size.
17512
	 * @return current font size
17513
	 * @access public
17514
	 * @since 3.2.000 (2008-06-23)
17515
	 */
17516
	public function getFontSize() {
17517
		return $this->FontSize;
17518
	}
17519
 
17520
	/**
17521
	 * Returns the current font size in points unit.
17522
	 * @return current font size in points unit
17523
	 * @access public
17524
	 * @since 3.2.000 (2008-06-23)
17525
	 */
17526
	public function getFontSizePt() {
17527
		return $this->FontSizePt;
17528
	}
17529
 
17530
	/**
17531
	 * Returns the current font family name.
17532
	 * @return string current font family name
17533
	 * @access public
17534
	 * @since 4.3.008 (2008-12-05)
17535
	 */
17536
	public function getFontFamily() {
17537
		return $this->FontFamily;
17538
	}
17539
 
17540
	/**
17541
	 * Returns the current font style.
17542
	 * @return string current font style
17543
	 * @access public
17544
	 * @since 4.3.008 (2008-12-05)
17545
	 */
17546
	public function getFontStyle() {
17547
		return $this->FontStyle;
17548
	}
17549
 
17550
	/**
17551
	 * Cleanup HTML code (requires HTML Tidy library).
17552
	 * @param string $html htmlcode to fix
17553
	 * @param string $default_css CSS commands to add
17554
	 * @param array $tagvs parameters for setHtmlVSpace method
17555
	 * @param array $tidy_options options for tidy_parse_string function
17556
	 * @return string XHTML code cleaned up
17557
	 * @author Nicola Asuni
17558
	 * @access public
17559
	 * @since 5.9.017 (2010-11-16)
17560
	 * @see setHtmlVSpace()
17561
	 */
17562
	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
17563
		// configure parameters for HTML Tidy
17564
		if ($tidy_options === '') {
17565
			$tidy_options = array (
17566
				'clean' => 1,
17567
				'drop-empty-paras' => 0,
17568
				'drop-proprietary-attributes' => 1,
17569
				'fix-backslash' => 1,
17570
				'hide-comments' => 1,
17571
				'join-styles' => 1,
17572
				'lower-literals' => 1,
17573
				'merge-divs' => 1,
17574
				'merge-spans' => 1,
17575
				'output-xhtml' => 1,
17576
				'word-2000' => 1,
17577
				'wrap' => 0,
17578
				'output-bom' => 0,
17579
				//'char-encoding' => 'utf8',
17580
				//'input-encoding' => 'utf8',
17581
				//'output-encoding' => 'utf8'
17582
			);
17583
		}
17584
		// clean up the HTML code
17585
		$tidy = tidy_parse_string($html, $tidy_options);
17586
		// fix the HTML
17587
		$tidy->cleanRepair();
17588
		// get the CSS part
17589
		$tidy_head = tidy_get_head($tidy);
17590
		$css = $tidy_head->value;
17591
		$css = preg_replace('/<style([^>]+)>/ims', '<style>', $css);
17592
		$css = preg_replace('/<\/style>(.*)<style>/ims', "\n", $css);
17593
		$css = str_replace('/*<![CDATA[*/', '', $css);
17594
		$css = str_replace('/*]]>*/', '', $css);
17595
		preg_match('/<style>(.*)<\/style>/ims', $css, $matches);
17596
		$css = strtolower($matches[1]);
17597
		// include default css
17598
		$css = '<style>'.$default_css.$css.'</style>';
17599
		// get the body part
17600
		$tidy_body = tidy_get_body($tidy);
17601
		$html = $tidy_body->value;
17602
		// fix some self-closing tags
17603
		$html = str_replace('<br>', '<br />', $html);
17604
		// remove some empty tag blocks
17605
		$html = preg_replace('/<div([^\>]*)><\/div>/', '', $html);
17606
		$html = preg_replace('/<p([^\>]*)><\/p>/', '', $html);
17607
		if ($tagvs !== '') {
17608
			// set vertical space for some XHTML tags
17609
			$this->setHtmlVSpace($tagvs);
17610
		}
17611
		// return the cleaned XHTML code + CSS
17612
		return $css.$html;
17613
	}
17614
 
17615
	/**
17616
	 * Extracts the CSS properties from a CSS string.
17617
	 * @param string $cssdata string containing CSS definitions.
17618
	 * @return An array where the keys are the CSS selectors and the values are the CSS properties.
17619
	 * @author Nicola Asuni
17620
	 * @since 5.1.000 (2010-05-25)
17621
	 * @access protected
17622
	 */
17623
	protected function extractCSSproperties($cssdata) {
17624
		if (empty($cssdata)) {
17625
			return array();
17626
		}
17627
		// remove comments
17628
		$cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
17629
		// remove newlines and multiple spaces
17630
		$cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
17631
		// remove some spaces
17632
		$cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
17633
		// remove empty blocks
17634
		$cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
17635
		// replace media type parenthesis
17636
		$cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1�', $cssdata);
17637
		$cssdata = preg_replace('/\}\}/si', '}�', $cssdata);
17638
		// trim string
17639
		$cssdata = trim($cssdata);
17640
		// find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
17641
		$cssblocks = array();
17642
		$matches = array();
17643
		if (preg_match_all('/@media[\s]+([^\�]*)�([^�]*)�/i', $cssdata, $matches) > 0) {
17644
			foreach ($matches[1] as $key => $type) {
17645
				$cssblocks[$type] = $matches[2][$key];
17646
			}
17647
			// remove media blocks
17648
			$cssdata = preg_replace('/@media[\s]+([^\�]*)�([^�]*)�/i', '', $cssdata);
17649
		}
17650
		// keep 'all' and 'print' media, other media types are discarded
17651
		if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
17652
			$cssdata .= $cssblocks['all'];
17653
		}
17654
		if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
17655
			$cssdata .= $cssblocks['print'];
17656
		}
17657
		// reset css blocks array
17658
		$cssblocks = array();
17659
		$matches = array();
17660
		// explode css data string into array
17661
		if (substr($cssdata, -1) == '}') {
17662
			// remove last parethesis
17663
			$cssdata = substr($cssdata, 0, -1);
17664
		}
17665
		$matches = explode('}', $cssdata);
17666
		foreach ($matches as $key => $block) {
17667
			// index 0 contains the CSS selector, index 1 contains CSS properties
17668
			$cssblocks[$key] = explode('{', $block);
17669
			if (!isset($cssblocks[$key][1])) {
17670
				// remove empty definitions
17671
				unset($cssblocks[$key]);
17672
			}
17673
		}
17674
		// split groups of selectors (comma-separated list of selectors)
17675
		foreach ($cssblocks as $key => $block) {
17676
			if (strpos($block[0], ',') > 0) {
17677
				$selectors = explode(',', $block[0]);
17678
				foreach ($selectors as $sel) {
17679
					$cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
17680
				}
17681
				unset($cssblocks[$key]);
17682
			}
17683
		}
17684
		// covert array to selector => properties
17685
		$cssdata = array();
17686
		foreach ($cssblocks as $block) {
17687
			$selector = $block[0];
17688
			// calculate selector's specificity
17689
			$matches = array();
17690
			$a = 0; // the declaration is not from is a 'style' attribute
17691
			$b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
17692
			$c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
17693
			$c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
17694
			$d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9\*]+/', ' '.$selector, $matches)); // number of element names
17695
			$d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
17696
			$specificity = $a.$b.$c.$d;
17697
			// add specificity to the beginning of the selector
17698
			$cssdata[$specificity.' '.$selector] = $block[1];
17699
		}
17700
		// sort selectors alphabetically to account for specificity
17701
		ksort($cssdata, SORT_STRING);
17702
		// return array
17703
		return $cssdata;
17704
	}
17705
 
17706
	/**
17707
	 * Returns true if the CSS selector is valid for the selected HTML tag
17708
	 * @param array $dom array of HTML tags and properties
17709
	 * @param int $key key of the current HTML tag
17710
	 * @param string $selector CSS selector string
17711
	 * @return true if the selector is valid, false otherwise
17712
	 * @access protected
17713
	 * @since 5.1.000 (2010-05-25)
17714
	 */
17715
	protected function isValidCSSSelectorForTag($dom, $key, $selector) {
17716
		$valid = false; // value to be returned
17717
		$tag = $dom[$key]['value'];
17718
		$class = array();
17719
		if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
17720
			$class = explode(' ', strtolower($dom[$key]['attribute']['class']));
17721
		}
17722
		$id = '';
17723
		if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
17724
			$id = strtolower($dom[$key]['attribute']['id']);
17725
		}
17726
		$selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
17727
		$matches = array();
17728
		if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
17729
			$parentop = array_pop($matches[1]);
17730
			$operator = $parentop[0];
17731
			$offset = $parentop[1];
17732
			$lasttag = array_pop($matches[2]);
17733
			$lasttag = strtolower(trim($lasttag[0]));
17734
			if (($lasttag == '*') OR ($lasttag == $tag)) {
17735
				// the last element on selector is our tag or 'any tag'
17736
				$attrib = array_pop($matches[3]);
17737
				$attrib = strtolower(trim($attrib[0]));
17738
				if (!empty($attrib)) {
17739
					// check if matches class, id, attribute, pseudo-class or pseudo-element
17740
					switch ($attrib{0}) {
17741
						case '.': { // class
17742
							if (in_array(substr($attrib, 1), $class)) {
17743
								$valid = true;
17744
							}
17745
							break;
17746
						}
17747
						case '#': { // ID
17748
							if (substr($attrib, 1) == $id) {
17749
								$valid = true;
17750
							}
17751
							break;
17752
						}
17753
						case '[': { // attribute
17754
							$attrmatch = array();
17755
							if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
17756
								$att = strtolower($attrmatch[1]);
17757
								$val = $attrmatch[3];
17758
								if (isset($dom[$key]['attribute'][$att])) {
17759
									switch ($attrmatch[2]) {
17760
										case '=': {
17761
											if ($dom[$key]['attribute'][$att] == $val) {
17762
												$valid = true;
17763
											}
17764
											break;
17765
										}
17766
										case '~=': {
17767
											if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
17768
												$valid = true;
17769
											}
17770
											break;
17771
										}
17772
										case '^=': {
17773
											if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
17774
												$valid = true;
17775
											}
17776
											break;
17777
										}
17778
										case '$=': {
17779
											if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
17780
												$valid = true;
17781
											}
17782
											break;
17783
										}
17784
										case '*=': {
17785
											if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
17786
												$valid = true;
17787
											}
17788
											break;
17789
										}
17790
										case '|=': {
17791
											if ($dom[$key]['attribute'][$att] == $val) {
17792
												$valid = true;
17793
											} elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
17794
												$valid = true;
17795
											}
17796
											break;
17797
										}
17798
										default: {
17799
											$valid = true;
17800
										}
17801
									}
17802
								}
17803
							}
17804
							break;
17805
						}
17806
						case ':': { // pseudo-class or pseudo-element
17807
							if ($attrib{1} == ':') { // pseudo-element
17808
								// pseudo-elements are not supported!
17809
								// (::first-line, ::first-letter, ::before, ::after)
17810
							} else { // pseudo-class
17811
								// pseudo-classes are not supported!
17812
								// (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
17813
							}
17814
							break;
17815
						}
17816
					} // end of switch
17817
				} else {
17818
					$valid = true;
17819
				}
17820
				if ($valid AND ($offset > 0)) {
17821
					$valid = false;
17822
					// check remaining selector part
17823
					$selector = substr($selector, 0, $offset);
17824
					switch ($operator) {
17825
						case ' ': { // descendant of an element
17826
							while ($dom[$key]['parent'] > 0) {
17827
								if ($this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
17828
									$valid = true;
17829
									break;
17830
								} else {
17831
									$key = $dom[$key]['parent'];
17832
								}
17833
							}
17834
							break;
17835
						}
17836
						case '>': { // child of an element
17837
							$valid = $this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
17838
							break;
17839
						}
17840
						case '+': { // immediately preceded by an element
17841
							for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
17842
								if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
17843
									$valid = $this->isValidCSSSelectorForTag($dom, $i, $selector);
17844
									break;
17845
								}
17846
							}
17847
							break;
17848
						}
17849
						case '~': { // preceded by an element
17850
							for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
17851
								if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
17852
									if ($this->isValidCSSSelectorForTag($dom, $i, $selector)) {
17853
										break;
17854
									}
17855
								}
17856
							}
17857
							break;
17858
						}
17859
					}
17860
				}
17861
			}
17862
		}
17863
		return $valid;
17864
	}
17865
 
17866
	/**
17867
	 * Returns the styles that apply for the selected HTML tag.
17868
	 * @param array $dom array of HTML tags and properties
17869
	 * @param int $key key of the current HTML tag
17870
	 * @param array $css array of CSS properties
17871
	 * @return string containing CSS properties
17872
	 * @access protected
17873
	 * @since 5.1.000 (2010-05-25)
17874
	 */
17875
	protected function getTagStyleFromCSS($dom, $key, $css) {
17876
		$tagstyle = ''; // style to be returned
17877
		// get all styles that apply
17878
		foreach($css as $selector => $style) {
17879
			// remove specificity
17880
			$selector = substr($selector, strpos($selector, ' '));
17881
			// check if this selector apply to current tag
17882
			if ($this->isValidCSSSelectorForTag($dom, $key, $selector)) {
17883
				// apply style
17884
				$tagstyle .= ';'.$style;
17885
			}
17886
		}
17887
		if (isset($dom[$key]['attribute']['style'])) {
17888
			// attach inline style (latest properties have high priority)
17889
			$tagstyle .= ';'.$dom[$key]['attribute']['style'];
17890
		}
17891
		// remove multiple semicolons
17892
		$tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
17893
		return $tagstyle;
17894
	}
17895
 
17896
	/**
17897
	 * Returns the border width from CSS property
17898
	 * @param string $width border width
17899
	 * @return int with in user units
17900
	 * @access protected
17901
	 * @since 5.7.000 (2010-08-02)
17902
	 */
17903
	protected function getCSSBorderWidth($width) {
17904
		if ($width == 'thin') {
17905
			$width = (2 / $this->k);
17906
		} elseif ($width == 'medium') {
17907
			$width = (4 / $this->k);
17908
		} elseif ($width == 'thick') {
17909
			$width = (6 / $this->k);
17910
		} else {
17911
			$width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
17912
		}
17913
		return $width;
17914
	}
17915
 
17916
	/**
17917
	 * Returns the border dash style from CSS property
17918
	 * @param string $style border style to convert
17919
	 * @return int sash style (return -1 in case of none or hidden border)
17920
	 * @access protected
17921
	 * @since 5.7.000 (2010-08-02)
17922
	 */
17923
	protected function getCSSBorderDashStyle($style) {
17924
		switch (strtolower($style)) {
17925
			case 'none':
17926
			case 'hidden': {
17927
				$dash = -1;
17928
				break;
17929
			}
17930
			case 'dotted': {
17931
				$dash = 1;
17932
				break;
17933
			}
17934
			case 'dashed': {
17935
				$dash = 3;
17936
				break;
17937
			}
17938
			case 'double':
17939
			case 'groove':
17940
			case 'ridge':
17941
			case 'inset':
17942
			case 'outset':
17943
			case 'solid':
17944
			default: {
17945
				$dash = 0;
17946
				break;
17947
			}
17948
		}
17949
		return $dash;
17950
	}
17951
 
17952
	/**
17953
	 * Returns the border style array from CSS border properties
17954
	 * @param string $cssborder border properties
17955
	 * @return array containing border properties
17956
	 * @access protected
17957
	 * @since 5.7.000 (2010-08-02)
17958
	 */
17959
	protected function getCSSBorderStyle($cssborder) {
17960
		$bprop = preg_split('/[\s]+/', trim($cssborder));
17961
		$border = array(); // value to be returned
17962
		switch (count($bprop)) {
17963
			case 3: {
17964
				$width = $bprop[0];
17965
				$style = $bprop[1];
17966
				$color = $bprop[2];
17967
				break;
17968
			}
17969
			case 2: {
17970
				$width = 'medium';
17971
				$style = $bprop[0];
17972
				$color = $bprop[1];
17973
				break;
17974
			}
17975
			case 1: {
17976
				$width = 'medium';
17977
				$style = $bprop[0];
17978
				$color = 'black';
17979
				break;
17980
			}
17981
			default: {
17982
				$width = 'medium';
17983
				$style = 'solid';
17984
				$color = 'black';
17985
				break;
17986
			}
17987
		}
17988
		if ($style == 'none') {
17989
			return array();
17990
		}
17991
		$border['cap'] = 'square';
17992
		$border['join'] = 'miter';
17993
		$border['dash'] = $this->getCSSBorderDashStyle($style);
17994
		if ($border['dash'] < 0) {
17995
			return array();
17996
		}
17997
		$border['width'] = $this->getCSSBorderWidth($width);
17998
		$border['color'] = $this->convertHTMLColorToDec($color);
17999
		return $border;
18000
	}
18001
 
18002
	/**
18003
	 * Get the internal Cell padding from CSS attribute.
18004
	 * @param string $csspadding padding properties
18005
	 * @param float $width width of the containing element
18006
	 * @return array of cell paddings
18007
	 * @access public
18008
	 * @since 5.9.000 (2010-10-04)
18009
	 */
18010
	public function getCSSPadding($csspadding, $width=0) {
18011
		$padding = preg_split('/[\s]+/', trim($csspadding));
18012
		$cell_padding = array(); // value to be returned
18013
		switch (count($padding)) {
18014
			case 4: {
18015
				$cell_padding['T'] = $padding[0];
18016
				$cell_padding['R'] = $padding[1];
18017
				$cell_padding['B'] = $padding[2];
18018
				$cell_padding['L'] = $padding[3];
18019
				break;
18020
			}
18021
			case 3: {
18022
				$cell_padding['T'] = $padding[0];
18023
				$cell_padding['R'] = $padding[1];
18024
				$cell_padding['B'] = $padding[2];
18025
				$cell_padding['L'] = $padding[1];
18026
				break;
18027
			}
18028
			case 2: {
18029
				$cell_padding['T'] = $padding[0];
18030
				$cell_padding['R'] = $padding[1];
18031
				$cell_padding['B'] = $padding[0];
18032
				$cell_padding['L'] = $padding[1];
18033
				break;
18034
			}
18035
			case 1: {
18036
				$cell_padding['T'] = $padding[0];
18037
				$cell_padding['R'] = $padding[0];
18038
				$cell_padding['B'] = $padding[0];
18039
				$cell_padding['L'] = $padding[0];
18040
				break;
18041
			}
18042
			default: {
18043
				return $this->cell_padding;
18044
			}
18045
		}
18046
		if ($width == 0) {
18047
			$width = $this->w - $this->lMargin - $this->rMargin;
18048
		}
18049
		$cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
18050
		$cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
18051
		$cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
18052
		$cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
18053
		return $cell_padding;
18054
	}
18055
 
18056
	/**
18057
	 * Get the internal Cell margin from CSS attribute.
18058
	 * @param string $cssmargin margin properties
18059
	 * @param float $width width of the containing element
18060
	 * @return array of cell margins
18061
	 * @access public
18062
	 * @since 5.9.000 (2010-10-04)
18063
	 */
18064
	public function getCSSMargin($cssmargin, $width=0) {
18065
		$margin = preg_split('/[\s]+/', trim($cssmargin));
18066
		$cell_margin = array(); // value to be returned
18067
		switch (count($margin)) {
18068
			case 4: {
18069
				$cell_margin['T'] = $margin[0];
18070
				$cell_margin['R'] = $margin[1];
18071
				$cell_margin['B'] = $margin[2];
18072
				$cell_margin['L'] = $margin[3];
18073
				break;
18074
			}
18075
			case 3: {
18076
				$cell_margin['T'] = $margin[0];
18077
				$cell_margin['R'] = $margin[1];
18078
				$cell_margin['B'] = $margin[2];
18079
				$cell_margin['L'] = $margin[1];
18080
				break;
18081
			}
18082
			case 2: {
18083
				$cell_margin['T'] = $margin[0];
18084
				$cell_margin['R'] = $margin[1];
18085
				$cell_margin['B'] = $margin[0];
18086
				$cell_margin['L'] = $margin[1];
18087
				break;
18088
			}
18089
			case 1: {
18090
				$cell_margin['T'] = $margin[0];
18091
				$cell_margin['R'] = $margin[0];
18092
				$cell_margin['B'] = $margin[0];
18093
				$cell_margin['L'] = $margin[0];
18094
				break;
18095
			}
18096
			default: {
18097
				return $this->cell_margin;
18098
			}
18099
		}
18100
		if ($width == 0) {
18101
			$width = $this->w - $this->lMargin - $this->rMargin;
18102
		}
18103
		$cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
18104
		$cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
18105
		$cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
18106
		$cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
18107
		return $cell_margin;
18108
	}
18109
 
18110
	/**
18111
	 * Get the border-spacing from CSS attribute.
18112
	 * @param string $cssbspace border-spacing CSS properties
18113
	 * @param float $width width of the containing element
18114
	 * @return array of border spacings
18115
	 * @access public
18116
	 * @since 5.9.010 (2010-10-27)
18117
	 */
18118
	public function getCSSBorderMargin($cssbspace, $width=0) {
18119
		$space = preg_split('/[\s]+/', trim($cssbspace));
18120
		$border_spacing = array(); // value to be returned
18121
		switch (count($space)) {
18122
			case 2: {
18123
				$border_spacing['H'] = $space[0];
18124
				$border_spacing['V'] = $space[1];
18125
				break;
18126
			}
18127
			case 1: {
18128
				$border_spacing['H'] = $space[0];
18129
				$border_spacing['V'] = $space[0];
18130
				break;
18131
			}
18132
			default: {
18133
				return array('H' => 0, 'V' => 0);
18134
			}
18135
		}
18136
		if ($width == 0) {
18137
			$width = $this->w - $this->lMargin - $this->rMargin;
18138
		}
18139
		$border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
18140
		$border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
18141
		return $border_spacing;
18142
	}
18143
 
18144
	/**
18145
	 * Returns the letter-spacing value from CSS value
18146
	 * @param string $spacing letter-spacing value
18147
	 * @param float $parent font spacing (tracking/kerning) value of the parent element
18148
	 * @return float quantity to increases or decreases the space between characters in a text.
18149
	 * @access protected
18150
	 * @since 5.9.000 (2010-10-02)
18151
	 */
18152
	protected function getCSSFontSpacing($spacing, $parent=0) {
18153
		$val = 0; // value to be returned
18154
		$spacing = trim($spacing);
18155
		switch ($spacing) {
18156
			case 'normal': {
18157
				$val = 0;
18158
				break;
18159
			}
18160
			case 'inherit': {
18161
				if ($parent == 'normal') {
18162
					$val = 0;
18163
				} else {
18164
					$val = $parent;
18165
				}
18166
				break;
18167
			}
18168
			default: {
18169
				$val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
18170
			}
18171
		}
18172
		return $val;
18173
	}
18174
 
18175
	/**
18176
	 * Returns the percentage of font stretching from CSS value
18177
	 * @param string $stretch stretch mode
18178
	 * @param float $parent stretch value of the parent element
18179
	 * @return float font stretching percentage
18180
	 * @access protected
18181
	 * @since 5.9.000 (2010-10-02)
18182
	 */
18183
	protected function getCSSFontStretching($stretch, $parent=100) {
18184
		$val = 100; // value to be returned
18185
		$stretch = trim($stretch);
18186
		switch ($stretch) {
18187
			case 'ultra-condensed': {
18188
				$val = 40;
18189
				break;
18190
			}
18191
			case 'extra-condensed': {
18192
				$val = 55;
18193
				break;
18194
			}
18195
			case 'condensed': {
18196
				$val = 70;
18197
				break;
18198
			}
18199
			case 'semi-condensed': {
18200
				$val = 85;
18201
				break;
18202
			}
18203
			case 'normal': {
18204
				$val = 100;
18205
				break;
18206
			}
18207
			case 'semi-expanded': {
18208
				$val = 115;
18209
				break;
18210
			}
18211
			case 'expanded': {
18212
				$val = 130;
18213
				break;
18214
			}
18215
			case 'extra-expanded': {
18216
				$val = 145;
18217
				break;
18218
			}
18219
			case 'ultra-expanded': {
18220
				$val = 160;
18221
				break;
18222
			}
18223
			case 'wider': {
18224
				$val = $parent + 10;
18225
				break;
18226
			}
18227
			case 'narrower': {
18228
				$val = $parent - 10;
18229
				break;
18230
			}
18231
			case 'inherit': {
18232
				if ($parent == 'normal') {
18233
					$val = 100;
18234
				} else {
18235
					$val = $parent;
18236
				}
18237
				break;
18238
			}
18239
			default: {
18240
				$val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
18241
			}
18242
		}
18243
		return $val;
18244
	}
18245
 
18246
	/**
18247
	 * Returns the HTML DOM array.
18248
	 * @param string $html html code
18249
	 * @return array
18250
	 * @access protected
18251
	 * @since 3.2.000 (2008-06-20)
18252
	 */
18253
	protected function getHtmlDomArray($html) {
18254
		// array of CSS styles ( selector => properties).
18255
		$css = array();
18256
		// get CSS array defined at previous call
18257
		$matches = array();
18258
		if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
18259
			if (isset($matches[1][0])) {
18260
				$css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
18261
			}
18262
			$html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
18263
		}
18264
		// extract external CSS files
18265
		$matches = array();
18266
		if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
18267
			foreach ($matches[1] as $key => $link) {
18268
				$type = array();
18269
				if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
18270
					$type = array();
18271
					preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
18272
					// get 'all' and 'print' media, other media types are discarded
18273
					// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
18274
					if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
18275
						$type = array();
18276
						if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
18277
							// read CSS data file
18278
							$cssdata = file_get_contents(trim($type[1]));
18279
							$css = array_merge($css, $this->extractCSSproperties($cssdata));
18280
						}
18281
					}
18282
				}
18283
			}
18284
		}
18285
		// extract style tags
18286
		$matches = array();
18287
		if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
18288
			foreach ($matches[1] as $key => $media) {
18289
				$type = array();
18290
				preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
18291
				// get 'all' and 'print' media, other media types are discarded
18292
				// (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
18293
				if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
18294
					$cssdata = $matches[2][$key];
18295
					$css = array_merge($css, $this->extractCSSproperties($cssdata));
18296
				}
18297
			}
18298
		}
18299
		// create a special tag to contain the CSS array (used for table content)
18300
		$csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
18301
		// remove head and style blocks
18302
		$html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
18303
		$html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
18304
		// define block tags
18305
		$blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
18306
		// define self-closing tags
18307
		$selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
18308
		// remove all unsupported tags (the line below lists all supported tags)
18309
		$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>');
18310
		//replace some blank characters
18311
		$html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
18312
		$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);
18313
		$html = preg_replace('@(\r\n|\r)@', "\n", $html);
18314
		$repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
18315
		$html = strtr($html, $repTable);
18316
		$offset = 0;
18317
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
18318
			$html_a = substr($html, 0, $offset);
18319
			$html_b = substr($html, $offset, ($pos - $offset + 6));
18320
			while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
18321
				// preserve newlines on <pre> tag
18322
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
18323
			}
18324
			while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
18325
				// preserve spaces on <pre> tag
18326
				$html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
18327
			}
18328
			$html = $html_a.$html_b.substr($html, $pos + 6);
18329
			$offset = strlen($html_a.$html_b);
18330
		}
18331
		$offset = 0;
18332
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
18333
			$html_a = substr($html, 0, $offset);
18334
			$html_b = substr($html, $offset, ($pos - $offset + 11));
18335
			while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
18336
				// preserve newlines on <textarea> tag
18337
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
18338
				$html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
18339
			}
18340
			$html = $html_a.$html_b.substr($html, $pos + 11);
18341
			$offset = strlen($html_a.$html_b);
18342
		}
18343
		$html = preg_replace('/([\s]*)<option/si', '<option', $html);
18344
		$html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
18345
		$offset = 0;
18346
		while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
18347
			$html_a = substr($html, 0, $offset);
18348
			$html_b = substr($html, $offset, ($pos - $offset + 9));
18349
			while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
18350
				$html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
18351
				$html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
18352
			}
18353
			$html = $html_a.$html_b.substr($html, $pos + 9);
18354
			$offset = strlen($html_a.$html_b);
18355
		}
18356
		if (preg_match("'</select'si", $html)) {
18357
			$html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
18358
			$html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
18359
		}
18360
		$html = str_replace("\n", ' ', $html);
18361
		// restore textarea newlines
18362
		$html = str_replace('<TBR>', "\n", $html);
18363
		// remove extra spaces from code
18364
		$html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
18365
		$html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
18366
		$html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
18367
		$html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
18368
		$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);
18369
		$html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
18370
		$html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
18371
		$html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
18372
		$html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
18373
		$html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
18374
		$html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
18375
		$html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
18376
		$html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
18377
		$html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
18378
		$html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
18379
		$html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
18380
		// trim string
18381
		$html = $this->stringTrim($html);
18382
		// pattern for generic tag
18383
		$tagpattern = '/(<[^>]+>)/';
18384
		// explodes the string
18385
		$a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
18386
		// count elements
18387
		$maxel = count($a);
18388
		$elkey = 0;
18389
		$key = 0;
18390
		// create an array of elements
18391
		$dom = array();
18392
		$dom[$key] = array();
18393
		// set inheritable properties fot the first void element
18394
		// 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
18395
		$dom[$key]['tag'] = false;
18396
		$dom[$key]['block'] = false;
18397
		$dom[$key]['value'] = '';
18398
		$dom[$key]['parent'] = 0;
18399
		$dom[$key]['fontname'] = $this->FontFamily;
18400
		$dom[$key]['fontstyle'] = $this->FontStyle;
18401
		$dom[$key]['fontsize'] = $this->FontSizePt;
18402
		$dom[$key]['font-stretch'] = 100;
18403
		$dom[$key]['letter-spacing'] = 0;
18404
		$dom[$key]['stroke'] = $this->textstrokewidth;
18405
		$dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
18406
		$dom[$key]['clip'] = ($this->textrendermode > 3);
18407
		$dom[$key]['line-height'] = $this->cell_height_ratio;
18408
		$dom[$key]['bgcolor'] = false;
18409
		$dom[$key]['fgcolor'] = $this->fgcolor; // color
18410
		$dom[$key]['strokecolor'] = $this->strokecolor;
18411
		$dom[$key]['align'] = '';
18412
		$dom[$key]['listtype'] = '';
18413
		$dom[$key]['text-indent'] = 0;
18414
		$dom[$key]['border'] = array();
18415
		$dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
18416
		$thead = false; // true when we are inside the THEAD tag
18417
		++$key;
18418
		$level = array();
18419
		array_push($level, 0); // root
18420
		while ($elkey < $maxel) {
18421
			$dom[$key] = array();
18422
			$element = $a[$elkey];
18423
			$dom[$key]['elkey'] = $elkey;
18424
			if (preg_match($tagpattern, $element)) {
18425
				// html tag
18426
				$element = substr($element, 1, -1);
18427
				// get tag name
18428
				preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
18429
				$tagname = strtolower($tag[1]);
18430
				// check if we are inside a table header
18431
				if ($tagname == 'thead') {
18432
					if ($element{0} == '/') {
18433
						$thead = false;
18434
					} else {
18435
						$thead = true;
18436
					}
18437
					++$elkey;
18438
					continue;
18439
				}
18440
				$dom[$key]['tag'] = true;
18441
				$dom[$key]['value'] = $tagname;
18442
				if (in_array($dom[$key]['value'], $blocktags)) {
18443
					$dom[$key]['block'] = true;
18444
				} else {
18445
					$dom[$key]['block'] = false;
18446
				}
18447
				if ($element{0} == '/') {
18448
					// *** closing html tag
18449
					$dom[$key]['opening'] = false;
18450
					$dom[$key]['parent'] = end($level);
18451
					array_pop($level);
18452
					$dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
18453
					$dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
18454
					$dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
18455
					$dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
18456
					$dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
18457
					$dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
18458
					$dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
18459
					$dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
18460
					$dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
18461
					$dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
18462
					$dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
18463
					$dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
18464
					$dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
18465
					$dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
18466
					if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
18467
						$dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
18468
					}
18469
					// set the number of columns in table tag
18470
					if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
18471
						$dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
18472
					}
18473
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18474
						$dom[($dom[$key]['parent'])]['content'] = $csstagarray;
18475
						for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
18476
							$dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
18477
						}
18478
						$key = $i;
18479
						// mark nested tables
18480
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
18481
						// remove thead sections from nested tables
18482
						$dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
18483
						$dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
18484
					}
18485
					// store header rows on a new table
18486
					if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
18487
						if ($this->empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
18488
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
18489
						}
18490
						for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
18491
							$dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
18492
						}
18493
						if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
18494
							$dom[($dom[$key]['parent'])]['attribute'] = array();
18495
						}
18496
						// header elements must be always contained in a single page
18497
						$dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
18498
					}
18499
					if (($dom[$key]['value'] == 'table') AND (!$this->empty_string($dom[($dom[$key]['parent'])]['thead']))) {
18500
						// remove the nobr attributes from the table header
18501
						$dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
18502
						$dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
18503
					}
18504
				} else {
18505
					// *** opening or self-closing html tag
18506
					$dom[$key]['opening'] = true;
18507
					$dom[$key]['parent'] = end($level);
18508
					if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
18509
						// self-closing tag
18510
						$dom[$key]['self'] = true;
18511
					} else {
18512
						// opening tag
18513
						array_push($level, $key);
18514
						$dom[$key]['self'] = false;
18515
					}
18516
					// copy some values from parent
18517
					$parentkey = 0;
18518
					if ($key > 0) {
18519
						$parentkey = $dom[$key]['parent'];
18520
						$dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
18521
						$dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
18522
						$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
18523
						$dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
18524
						$dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
18525
						$dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
18526
						$dom[$key]['fill'] = $dom[$parentkey]['fill'];
18527
						$dom[$key]['clip'] = $dom[$parentkey]['clip'];
18528
						$dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
18529
						$dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
18530
						$dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
18531
						$dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
18532
						$dom[$key]['align'] = $dom[$parentkey]['align'];
18533
						$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
18534
						$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
18535
						$dom[$key]['border'] = array();
18536
						$dom[$key]['dir'] = $dom[$parentkey]['dir'];
18537
					}
18538
					// get attributes
18539
					preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
18540
					$dom[$key]['attribute'] = array(); // reset attribute array
18541
					while (list($id, $name) = each($attr_array[1])) {
18542
						$dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
18543
					}
18544
					if (!empty($css)) {
18545
						// merge eternal CSS style to current style
18546
						$dom[$key]['attribute']['style'] = $this->getTagStyleFromCSS($dom, $key, $css);
18547
					}
18548
					// split style attributes
18549
					if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
18550
						// get style attributes
18551
						preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
18552
						$dom[$key]['style'] = array(); // reset style attribute array
18553
						while (list($id, $name) = each($style_array[1])) {
18554
							// in case of duplicate attribute the last replace the previous
18555
							$dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
18556
						}
18557
						// --- get some style attributes ---
18558
						// text direction
18559
						if (isset($dom[$key]['style']['direction'])) {
18560
							$dom[$key]['dir'] = $dom[$key]['style']['direction'];
18561
						}
18562
						// font family
18563
						if (isset($dom[$key]['style']['font-family'])) {
18564
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
18565
						}
18566
						// list-style-type
18567
						if (isset($dom[$key]['style']['list-style-type'])) {
18568
							$dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
18569
							if ($dom[$key]['listtype'] == 'inherit') {
18570
								$dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
18571
							}
18572
						}
18573
						// text-indent
18574
						if (isset($dom[$key]['style']['text-indent'])) {
18575
							$dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
18576
							if ($dom[$key]['text-indent'] == 'inherit') {
18577
								$dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
18578
							}
18579
						}
18580
						// font size
18581
						if (isset($dom[$key]['style']['font-size'])) {
18582
							$fsize = trim($dom[$key]['style']['font-size']);
18583
							switch ($fsize) {
18584
								// absolute-size
18585
								case 'xx-small': {
18586
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] - 4;
18587
									break;
18588
								}
18589
								case 'x-small': {
18590
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] - 3;
18591
									break;
18592
								}
18593
								case 'small': {
18594
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] - 2;
18595
									break;
18596
								}
18597
								case 'medium': {
18598
									$dom[$key]['fontsize'] = $dom[0]['fontsize'];
18599
									break;
18600
								}
18601
								case 'large': {
18602
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] + 2;
18603
									break;
18604
								}
18605
								case 'x-large': {
18606
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] + 4;
18607
									break;
18608
								}
18609
								case 'xx-large': {
18610
									$dom[$key]['fontsize'] = $dom[0]['fontsize'] + 6;
18611
									break;
18612
								}
18613
								// relative-size
18614
								case 'smaller': {
18615
									$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] - 3;
18616
									break;
18617
								}
18618
								case 'larger': {
18619
									$dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] + 3;
18620
									break;
18621
								}
18622
								default: {
18623
									$dom[$key]['fontsize'] = $this->getHTMLUnitToUnits($fsize, $dom[$parentkey]['fontsize'], 'pt', true);
18624
								}
18625
							}
18626
						}
18627
						// font-stretch
18628
						if (isset($dom[$key]['style']['font-stretch'])) {
18629
							$dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
18630
						}
18631
						// letter-spacing
18632
						if (isset($dom[$key]['style']['letter-spacing'])) {
18633
							$dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
18634
						}
18635
						// line-height
18636
						if (isset($dom[$key]['style']['line-height'])) {
18637
							$lineheight = trim($dom[$key]['style']['line-height']);
18638
							switch ($lineheight) {
18639
								// A normal line height. This is default
18640
								case 'normal': {
18641
									$dom[$key]['line-height'] = $dom[0]['line-height'];
18642
									break;
18643
								}
18644
								default: {
18645
									if (is_numeric($lineheight)) {
18646
										$lineheight = $lineheight * 100;
18647
									}
18648
									$dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
18649
								}
18650
							}
18651
						}
18652
						// font style
18653
						if (isset($dom[$key]['style']['font-weight']) AND (strtolower($dom[$key]['style']['font-weight']{0}) == 'b')) {
18654
							$dom[$key]['fontstyle'] .= 'B';
18655
						}
18656
						if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
18657
							$dom[$key]['fontstyle'] .= 'I';
18658
						}
18659
						// font color
18660
						if (isset($dom[$key]['style']['color']) AND (!$this->empty_string($dom[$key]['style']['color']))) {
18661
							$dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['color']);
18662
						} elseif ($dom[$key]['value'] == 'a') {
18663
							$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
18664
						}
18665
						// background color
18666
						if (isset($dom[$key]['style']['background-color']) AND (!$this->empty_string($dom[$key]['style']['background-color']))) {
18667
							$dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['background-color']);
18668
						}
18669
						// text-decoration
18670
						if (isset($dom[$key]['style']['text-decoration'])) {
18671
							$decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
18672
							foreach ($decors as $dec) {
18673
								$dec = trim($dec);
18674
								if (!$this->empty_string($dec)) {
18675
									if ($dec{0} == 'u') {
18676
										// underline
18677
										$dom[$key]['fontstyle'] .= 'U';
18678
									} elseif ($dec{0} == 'l') {
18679
										// line-trough
18680
										$dom[$key]['fontstyle'] .= 'D';
18681
									} elseif ($dec{0} == 'o') {
18682
										// overline
18683
										$dom[$key]['fontstyle'] .= 'O';
18684
									}
18685
								}
18686
							}
18687
						} elseif ($dom[$key]['value'] == 'a') {
18688
							$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
18689
						}
18690
						// check for width attribute
18691
						if (isset($dom[$key]['style']['width'])) {
18692
							$dom[$key]['width'] = $dom[$key]['style']['width'];
18693
						}
18694
						// check for height attribute
18695
						if (isset($dom[$key]['style']['height'])) {
18696
							$dom[$key]['height'] = $dom[$key]['style']['height'];
18697
						}
18698
						// check for text alignment
18699
						if (isset($dom[$key]['style']['text-align'])) {
18700
							$dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
18701
						}
18702
						// check for CSS border properties
18703
						if (isset($dom[$key]['style']['border'])) {
18704
							$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
18705
							if (!empty($borderstyle)) {
18706
								$dom[$key]['border']['LTRB'] = $borderstyle;
18707
							}
18708
						}
18709
						if (isset($dom[$key]['style']['border-color'])) {
18710
							$brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
18711
							if (isset($brd_colors[3])) {
18712
								$dom[$key]['border']['L']['color'] = $this->convertHTMLColorToDec($brd_colors[3]);
18713
							}
18714
							if (isset($brd_colors[1])) {
18715
								$dom[$key]['border']['R']['color'] = $this->convertHTMLColorToDec($brd_colors[1]);
18716
							}
18717
							if (isset($brd_colors[0])) {
18718
								$dom[$key]['border']['T']['color'] = $this->convertHTMLColorToDec($brd_colors[0]);
18719
							}
18720
							if (isset($brd_colors[2])) {
18721
								$dom[$key]['border']['B']['color'] = $this->convertHTMLColorToDec($brd_colors[2]);
18722
							}
18723
						}
18724
						if (isset($dom[$key]['style']['border-width'])) {
18725
							$brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
18726
							if (isset($brd_widths[3])) {
18727
								$dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
18728
							}
18729
							if (isset($brd_widths[1])) {
18730
								$dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
18731
							}
18732
							if (isset($brd_widths[0])) {
18733
								$dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
18734
							}
18735
							if (isset($brd_widths[2])) {
18736
								$dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
18737
							}
18738
						}
18739
						if (isset($dom[$key]['style']['border-style'])) {
18740
							$brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
18741
							if (isset($brd_styles[3])) {
18742
								$dom[$key]['border']['L']['cap'] = 'square';
18743
								$dom[$key]['border']['L']['join'] = 'miter';
18744
								$dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
18745
								if ($dom[$key]['border']['L']['dash'] < 0) {
18746
									$dom[$key]['border']['L'] = array();
18747
								}
18748
							}
18749
							if (isset($brd_styles[1])) {
18750
								$dom[$key]['border']['R']['cap'] = 'square';
18751
								$dom[$key]['border']['R']['join'] = 'miter';
18752
								$dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
18753
								if ($dom[$key]['border']['R']['dash'] < 0) {
18754
									$dom[$key]['border']['R'] = array();
18755
								}
18756
							}
18757
							if (isset($brd_styles[0])) {
18758
								$dom[$key]['border']['T']['cap'] = 'square';
18759
								$dom[$key]['border']['T']['join'] = 'miter';
18760
								$dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
18761
								if ($dom[$key]['border']['T']['dash'] < 0) {
18762
									$dom[$key]['border']['T'] = array();
18763
								}
18764
							}
18765
							if (isset($brd_styles[2])) {
18766
								$dom[$key]['border']['B']['cap'] = 'square';
18767
								$dom[$key]['border']['B']['join'] = 'miter';
18768
								$dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
18769
								if ($dom[$key]['border']['B']['dash'] < 0) {
18770
									$dom[$key]['border']['B'] = array();
18771
								}
18772
							}
18773
						}
18774
						$cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
18775
						foreach ($cellside as $bsk => $bsv) {
18776
							if (isset($dom[$key]['style']['border-'.$bsv])) {
18777
								$borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
18778
								if (!empty($borderstyle)) {
18779
									$dom[$key]['border'][$bsk] = $borderstyle;
18780
								}
18781
							}
18782
							if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
18783
								$dom[$key]['border'][$bsk]['color'] = $this->convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color']);
18784
							}
18785
							if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
18786
								$dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
18787
							}
18788
							if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
18789
								$dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
18790
								if ($dom[$key]['border'][$bsk]['dash'] < 0) {
18791
									$dom[$key]['border'][$bsk] = array();
18792
								}
18793
							}
18794
						}
18795
						// check for CSS padding properties
18796
						if (isset($dom[$key]['style']['padding'])) {
18797
							$dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
18798
						} else {
18799
							$dom[$key]['padding'] = $this->cell_padding;
18800
						}
18801
						foreach ($cellside as $psk => $psv) {
18802
							if (isset($dom[$key]['style']['padding-'.$psv])) {
18803
								$dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
18804
							}
18805
						}
18806
						// check for CSS margin properties
18807
						if (isset($dom[$key]['style']['margin'])) {
18808
							$dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
18809
						} else {
18810
							$dom[$key]['margin'] = $this->cell_margin;
18811
						}
18812
						foreach ($cellside as $psk => $psv) {
18813
							if (isset($dom[$key]['style']['margin-'.$psv])) {
18814
								$dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
18815
							}
18816
						}
18817
						// check for CSS border-spacing properties
18818
						if (isset($dom[$key]['style']['border-spacing'])) {
18819
							$dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
18820
						}
18821
						// page-break-inside
18822
						if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
18823
							$dom[$key]['attribute']['nobr'] = 'true';
18824
						}
18825
						// page-break-before
18826
						if (isset($dom[$key]['style']['page-break-before'])) {
18827
							if ($dom[$key]['style']['page-break-before'] == 'always') {
18828
								$dom[$key]['attribute']['pagebreak'] = 'true';
18829
							} elseif ($dom[$key]['style']['page-break-before'] == 'left') {
18830
								$dom[$key]['attribute']['pagebreak'] = 'left';
18831
							} elseif ($dom[$key]['style']['page-break-before'] == 'right') {
18832
								$dom[$key]['attribute']['pagebreak'] = 'right';
18833
							}
18834
						}
18835
						// page-break-after
18836
						if (isset($dom[$key]['style']['page-break-after'])) {
18837
							if ($dom[$key]['style']['page-break-after'] == 'always') {
18838
								$dom[$key]['attribute']['pagebreakafter'] = 'true';
18839
							} elseif ($dom[$key]['style']['page-break-after'] == 'left') {
18840
								$dom[$key]['attribute']['pagebreakafter'] = 'left';
18841
							} elseif ($dom[$key]['style']['page-break-after'] == 'right') {
18842
								$dom[$key]['attribute']['pagebreakafter'] = 'right';
18843
							}
18844
						}
18845
					}
18846
					if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
18847
						$borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
18848
						if (!empty($borderstyle)) {
18849
							$dom[$key]['border']['LTRB'] = $borderstyle;
18850
						}
18851
					}
18852
					// check for font tag
18853
					if ($dom[$key]['value'] == 'font') {
18854
						// font family
18855
						if (isset($dom[$key]['attribute']['face'])) {
18856
							$dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
18857
						}
18858
						// font size
18859
						if (isset($dom[$key]['attribute']['size'])) {
18860
							if ($key > 0) {
18861
								if ($dom[$key]['attribute']['size']{0} == '+') {
18862
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
18863
								} elseif ($dom[$key]['attribute']['size']{0} == '-') {
18864
									$dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
18865
								} else {
18866
									$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
18867
								}
18868
							} else {
18869
								$dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
18870
							}
18871
						}
18872
					}
18873
					// force natural alignment for lists
18874
					if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
18875
						AND (!isset($dom[$key]['align']) OR $this->empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
18876
						if ($this->rtl) {
18877
							$dom[$key]['align'] = 'R';
18878
						} else {
18879
							$dom[$key]['align'] = 'L';
18880
						}
18881
					}
18882
					if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
18883
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
18884
							$dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
18885
						}
18886
					}
18887
					if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
18888
						$dom[$key]['fontstyle'] .= 'B';
18889
					}
18890
					if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
18891
						$dom[$key]['fontstyle'] .= 'I';
18892
					}
18893
					if ($dom[$key]['value'] == 'u') {
18894
						$dom[$key]['fontstyle'] .= 'U';
18895
					}
18896
					if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
18897
						$dom[$key]['fontstyle'] .= 'D';
18898
					}
18899
					if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
18900
						$dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
18901
					}
18902
					if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
18903
						$dom[$key]['fontname'] = $this->default_monospaced_font;
18904
					}
18905
					if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
18906
						// headings h1, h2, h3, h4, h5, h6
18907
						if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
18908
							$headsize = (4 - intval($dom[$key]['value']{1})) * 2;
18909
							$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
18910
						}
18911
						if (!isset($dom[$key]['style']['font-weight'])) {
18912
							$dom[$key]['fontstyle'] .= 'B';
18913
						}
18914
					}
18915
					if (($dom[$key]['value'] == 'table')) {
18916
						$dom[$key]['rows'] = 0; // number of rows
18917
						$dom[$key]['trids'] = array(); // IDs of TR elements
18918
						$dom[$key]['thead'] = ''; // table header rows
18919
					}
18920
					if (($dom[$key]['value'] == 'tr')) {
18921
						$dom[$key]['cols'] = 0;
18922
						if ($thead) {
18923
							$dom[$key]['thead'] = true;
18924
							// rows on thead block are printed as a separate table
18925
						} else {
18926
							$dom[$key]['thead'] = false;
18927
							// store the number of rows on table element
18928
							++$dom[($dom[$key]['parent'])]['rows'];
18929
							// store the TR elements IDs on table element
18930
							array_push($dom[($dom[$key]['parent'])]['trids'], $key);
18931
						}
18932
					}
18933
					if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
18934
						if (isset($dom[$key]['attribute']['colspan'])) {
18935
							$colspan = intval($dom[$key]['attribute']['colspan']);
18936
						} else {
18937
							$colspan = 1;
18938
						}
18939
						$dom[$key]['attribute']['colspan'] = $colspan;
18940
						$dom[($dom[$key]['parent'])]['cols'] += $colspan;
18941
					}
18942
					// text direction
18943
					if (isset($dom[$key]['attribute']['dir'])) {
18944
						$dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
18945
					}
18946
					// set foreground color attribute
18947
					if (isset($dom[$key]['attribute']['color']) AND (!$this->empty_string($dom[$key]['attribute']['color']))) {
18948
						$dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['color']);
18949
					} elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
18950
						$dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
18951
					}
18952
					// set background color attribute
18953
					if (isset($dom[$key]['attribute']['bgcolor']) AND (!$this->empty_string($dom[$key]['attribute']['bgcolor']))) {
18954
						$dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['bgcolor']);
18955
					}
18956
					// set stroke color attribute
18957
					if (isset($dom[$key]['attribute']['strokecolor']) AND (!$this->empty_string($dom[$key]['attribute']['strokecolor']))) {
18958
						$dom[$key]['strokecolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['strokecolor']);
18959
					}
18960
					// check for width attribute
18961
					if (isset($dom[$key]['attribute']['width'])) {
18962
						$dom[$key]['width'] = $dom[$key]['attribute']['width'];
18963
					}
18964
					// check for height attribute
18965
					if (isset($dom[$key]['attribute']['height'])) {
18966
						$dom[$key]['height'] = $dom[$key]['attribute']['height'];
18967
					}
18968
					// check for text alignment
18969
					if (isset($dom[$key]['attribute']['align']) AND (!$this->empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
18970
						$dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
18971
					}
18972
					// check for text rendering mode (the following attributes do not exist in HTML)
18973
					if (isset($dom[$key]['attribute']['stroke'])) {
18974
						// font stroke width
18975
						$dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
18976
					}
18977
					if (isset($dom[$key]['attribute']['fill'])) {
18978
						// font fill
18979
						if ($dom[$key]['attribute']['fill'] == 'true') {
18980
							$dom[$key]['fill'] = true;
18981
						} else {
18982
							$dom[$key]['fill'] = false;
18983
						}
18984
					}
18985
					if (isset($dom[$key]['attribute']['clip'])) {
18986
						// clipping mode
18987
						if ($dom[$key]['attribute']['clip'] == 'true') {
18988
							$dom[$key]['clip'] = true;
18989
						} else {
18990
							$dom[$key]['clip'] = false;
18991
						}
18992
					}
18993
				} // end opening tag
18994
			} else {
18995
				// text
18996
				$dom[$key]['tag'] = false;
18997
				$dom[$key]['block'] = false;
18998
				$element = str_replace('$nbsp;', $this->unichr(160), $element);
18999
				$dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
19000
				$dom[$key]['parent'] = end($level);
19001
				$dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
19002
			}
19003
			++$elkey;
19004
			++$key;
19005
		}
19006
		return $dom;
19007
	}
19008
 
19009
	/**
19010
	 * Returns the string used to find spaces
19011
	 * @return string
19012
	 * @access protected
19013
	 * @author Nicola Asuni
19014
	 * @since 4.8.024 (2010-01-15)
19015
	 */
19016
	protected function getSpaceString() {
19017
		$spacestr = chr(32);
19018
		if ($this->isUnicodeFont()) {
19019
			$spacestr = chr(0).chr(32);
19020
		}
19021
		return $spacestr;
19022
	}
19023
 
19024
	/**
19025
	 * Prints a cell (rectangular area) with optional borders, background color and html text string.
19026
	 * 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 />
19027
	 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
19028
	 * @param float $w Cell width. If 0, the cell extends up to the right margin.
19029
	 * @param float $h Cell minimum height. The cell extends automatically if needed.
19030
	 * @param float $x upper-left corner X coordinate
19031
	 * @param float $y upper-left corner Y coordinate
19032
	 * @param string $html html text to print. Default value: empty string.
19033
	 * @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)))
19034
	 * @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>
19035
Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
19036
	 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
19037
	 * @param boolean $reseth if true reset the last cell height (default true).
19038
	 * @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>
19039
	 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
19040
	 * @access public
19041
	 * @uses MultiCell()
19042
	 * @see Multicell(), writeHTML()
19043
	 */
19044
	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
19045
		return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0);
19046
	}
19047
 
19048
	/**
19049
	 * Allows to preserve some HTML formatting (limited support).<br />
19050
	 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
19051
	 * 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
19052
	 * @param string $html text to display
19053
	 * @param boolean $ln if true add a new line after text (default = true)
19054
	 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
19055
	 * @param boolean $reseth if true reset the last cell height (default false).
19056
	 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
19057
	 * @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>
19058
	 * @access public
19059
	 */
19060
	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
19061
		$gvars = $this->getGraphicVars();
19062
		// store current values
19063
		$prev_cell_margin = $this->cell_margin;
19064
		$prev_cell_padding = $this->cell_padding;
19065
		$prevPage = $this->page;
19066
		$prevlMargin = $this->lMargin;
19067
		$prevrMargin = $this->rMargin;
19068
		$curfontname = $this->FontFamily;
19069
		$curfontstyle = $this->FontStyle;
19070
		$curfontsize = $this->FontSizePt;
19071
		$curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
19072
		$curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
19073
		$curfontstretcing = $this->font_stretching;
19074
		$curfontkerning = $this->font_spacing;
19075
		$this->newline = true;
19076
		$newline = true;
19077
		$startlinepage = $this->page;
19078
		$minstartliney = $this->y;
19079
		$maxbottomliney = 0;
19080
		$startlinex = $this->x;
19081
		$startliney = $this->y;
19082
		$yshift = 0;
19083
		$loop = 0;
19084
		$curpos = 0;
19085
		$this_method_vars = array();
19086
		$undo = false;
19087
		$fontaligned = false;
19088
		$reverse_dir = false; // true when the text direction is reversed
19089
		$this->premode = false;
19090
		if ($this->inxobj) {
19091
			// we are inside an XObject template
19092
			$pask = count($this->xobjects[$this->xobjid]['annotations']);
19093
		} elseif (isset($this->PageAnnots[$this->page])) {
19094
			$pask = count($this->PageAnnots[$this->page]);
19095
		} else {
19096
			$pask = 0;
19097
		}
19098
		if ($this->inxobj) {
19099
			// we are inside an XObject template
19100
			$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
19101
		} elseif (!$this->InFooter) {
19102
			if (isset($this->footerlen[$this->page])) {
19103
				$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
19104
			} else {
19105
				$this->footerpos[$this->page] = $this->pagelen[$this->page];
19106
			}
19107
			$startlinepos = $this->footerpos[$this->page];
19108
		} else {
19109
			// we are inside the footer
19110
			$startlinepos = $this->pagelen[$this->page];
19111
		}
19112
		$lalign = $align;
19113
		$plalign = $align;
19114
		if ($this->rtl) {
19115
			$w = $this->x - $this->lMargin;
19116
		} else {
19117
			$w = $this->w - $this->rMargin - $this->x;
19118
		}
19119
		$w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
19120
		if ($cell) {
19121
			if ($this->rtl) {
19122
				$this->x -= $this->cell_padding['R'];
19123
				$this->lMargin += $this->cell_padding['R'];
19124
			} else {
19125
				$this->x += $this->cell_padding['L'];
19126
				$this->rMargin += $this->cell_padding['L'];
19127
			}
19128
		}
19129
		if ($this->customlistindent >= 0) {
19130
			$this->listindent = $this->customlistindent;
19131
		} else {
19132
			$this->listindent = $this->GetStringWidth('0000');
19133
		}
19134
		$this->listindentlevel = 0;
19135
		// save previous states
19136
		$prev_cell_height_ratio = $this->cell_height_ratio;
19137
		$prev_listnum = $this->listnum;
19138
		$prev_listordered = $this->listordered;
19139
		$prev_listcount = $this->listcount;
19140
		$prev_lispacer = $this->lispacer;
19141
		$this->listnum = 0;
19142
		$this->listordered = array();
19143
		$this->listcount = array();
19144
		$this->lispacer = '';
19145
		if (($this->empty_string($this->lasth)) OR ($reseth)) {
19146
			// reset row height
19147
			$this->resetLastH();
19148
		}
19149
		$dom = $this->getHtmlDomArray($html);
19150
		$maxel = count($dom);
19151
		$key = 0;
19152
		while ($key < $maxel) {
19153
			if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
19154
				// check for pagebreak
19155
				if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
19156
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
19157
					$this->checkPageBreak($this->PageBreakTrigger + 1);
19158
				}
19159
				if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19160
					OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19161
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
19162
					$this->checkPageBreak($this->PageBreakTrigger + 1);
19163
				}
19164
			}
19165
			if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
19166
				if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
19167
					$dom[$key]['attribute']['nobr'] = false;
19168
				} else {
19169
					// store current object
19170
					$this->startTransaction();
19171
					// save this method vars
19172
					$this_method_vars['html'] = $html;
19173
					$this_method_vars['ln'] = $ln;
19174
					$this_method_vars['fill'] = $fill;
19175
					$this_method_vars['reseth'] = $reseth;
19176
					$this_method_vars['cell'] = $cell;
19177
					$this_method_vars['align'] = $align;
19178
					$this_method_vars['gvars'] = $gvars;
19179
					$this_method_vars['prevPage'] = $prevPage;
19180
					$this_method_vars['prev_cell_margin'] = $prev_cell_margin;
19181
					$this_method_vars['prev_cell_padding'] = $prev_cell_padding;
19182
					$this_method_vars['prevlMargin'] = $prevlMargin;
19183
					$this_method_vars['prevrMargin'] = $prevrMargin;
19184
					$this_method_vars['curfontname'] = $curfontname;
19185
					$this_method_vars['curfontstyle'] = $curfontstyle;
19186
					$this_method_vars['curfontsize'] = $curfontsize;
19187
					$this_method_vars['curfontascent'] = $curfontascent;
19188
					$this_method_vars['curfontdescent'] = $curfontdescent;
19189
					$this_method_vars['curfontstretcing'] = $curfontstretcing;
19190
					$this_method_vars['curfontkerning'] = $curfontkerning;
19191
					$this_method_vars['minstartliney'] = $minstartliney;
19192
					$this_method_vars['maxbottomliney'] = $maxbottomliney;
19193
					$this_method_vars['yshift'] = $yshift;
19194
					$this_method_vars['startlinepage'] = $startlinepage;
19195
					$this_method_vars['startlinepos'] = $startlinepos;
19196
					$this_method_vars['startlinex'] = $startlinex;
19197
					$this_method_vars['startliney'] = $startliney;
19198
					$this_method_vars['newline'] = $newline;
19199
					$this_method_vars['loop'] = $loop;
19200
					$this_method_vars['curpos'] = $curpos;
19201
					$this_method_vars['pask'] = $pask;
19202
					$this_method_vars['lalign'] = $lalign;
19203
					$this_method_vars['plalign'] = $plalign;
19204
					$this_method_vars['w'] = $w;
19205
					$this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
19206
					$this_method_vars['prev_listnum'] = $prev_listnum;
19207
					$this_method_vars['prev_listordered'] = $prev_listordered;
19208
					$this_method_vars['prev_listcount'] = $prev_listcount;
19209
					$this_method_vars['prev_lispacer'] = $prev_lispacer;
19210
					$this_method_vars['fontaligned'] = $fontaligned;
19211
					$this_method_vars['key'] = $key;
19212
					$this_method_vars['dom'] = $dom;
19213
				}
19214
			}
19215
			// print THEAD block
19216
			if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
19217
				if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !$this->empty_string($dom[$dom[$key]['parent']]['thead'])) {
19218
					$this->inthead = true;
19219
					// print table header (thead)
19220
					$this->writeHTML($this->thead, false, false, false, false, '');
19221
					// check if we are on a new page or on a new column
19222
					if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
19223
						// we are on a new page or on a new column and the total object height is less than the available vertical space.
19224
						// restore previous object
19225
						$this->rollbackTransaction(true);
19226
						// restore previous values
19227
						foreach ($this_method_vars as $vkey => $vval) {
19228
							$$vkey = $vval;
19229
						}
19230
						// disable table header
19231
						$tmp_thead = $this->thead;
19232
						$this->thead = '';
19233
						// add a page (or trig AcceptPageBreak() for multicolumn mode)
19234
						$pre_y = $this->y;
19235
						if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
19236
							// fix for multicolumn mode
19237
							$startliney = $this->y;
19238
						}
19239
						$this->start_transaction_page = $this->page;
19240
						$this->start_transaction_y = $this->y;
19241
						// restore table header
19242
						$this->thead = $tmp_thead;
19243
						// fix table border properties
19244
						if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
19245
							$tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
19246
						} elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
19247
							$tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
19248
						} else {
19249
							$tmp_cellspacing = 0;
19250
						}
19251
						$dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
19252
						$dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
19253
						$dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
19254
						$xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
19255
						$dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
19256
						$dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
19257
						// print table header (thead)
19258
						$this->writeHTML($this->thead, false, false, false, false, '');
19259
					}
19260
				}
19261
				// move $key index forward to skip THEAD block
19262
				while ( ($key < $maxel) AND (!(
19263
					($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
19264
					OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
19265
					++$key;
19266
				}
19267
			}
19268
			if ($dom[$key]['tag'] OR ($key == 0)) {
19269
				if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
19270
					$dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
19271
				}
19272
				// vertically align image in line
19273
				if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
19274
					// get image height
19275
					$imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
19276
					// check for automatic line break
19277
					$autolinebreak = false;
19278
					if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
19279
						$imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
19280
						if (($this->rtl AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
19281
							OR (!$this->rtl AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R'])))) {
19282
							// add automatic line break
19283
							$autolinebreak = true;
19284
							$this->Ln('', $cell);
19285
							// go back to evaluate this line break
19286
							--$key;
19287
						}
19288
					}
19289
					if (!$autolinebreak) {
19290
						if (!$this->InFooter) {
19291
							$pre_y = $this->y;
19292
							// check for page break
19293
							if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
19294
								// fix for multicolumn mode
19295
								$startliney = $this->y;
19296
							}
19297
						}
19298
						if ($this->page > $startlinepage) {
19299
							// fix line splitted over two pages
19300
							if (isset($this->footerlen[$startlinepage])) {
19301
								$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19302
							}
19303
							// line to be moved one page forward
19304
							$pagebuff = $this->getPageBuffer($startlinepage);
19305
							$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
19306
							$tstart = substr($pagebuff, 0, $startlinepos);
19307
							$tend = substr($this->getPageBuffer($startlinepage), $curpos);
19308
							// remove line from previous page
19309
							$this->setPageBuffer($startlinepage, $tstart.''.$tend);
19310
							$pagebuff = $this->getPageBuffer($this->page);
19311
							$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
19312
							$tend = substr($pagebuff, $this->cntmrk[$this->page]);
19313
							// add line start to current page
19314
							$yshift = $minstartliney - $this->y;
19315
							if ($fontaligned) {
19316
								$yshift += ($curfontsize / $this->k);
19317
							}
19318
							$try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
19319
							$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
19320
							// shift the annotations and links
19321
							if (isset($this->PageAnnots[$this->page])) {
19322
								$next_pask = count($this->PageAnnots[$this->page]);
19323
							} else {
19324
								$next_pask = 0;
19325
							}
19326
							if (isset($this->PageAnnots[$startlinepage])) {
19327
								foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
19328
									if ($pak >= $pask) {
19329
										$this->PageAnnots[$this->page][] = $pac;
19330
										unset($this->PageAnnots[$startlinepage][$pak]);
19331
										$npak = count($this->PageAnnots[$this->page]) - 1;
19332
										$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
19333
									}
19334
								}
19335
							}
19336
							$pask = $next_pask;
19337
							$startlinepos = $this->cntmrk[$this->page];
19338
							$startlinepage = $this->page;
19339
							$startliney = $this->y;
19340
							$this->newline = false;
19341
						}
19342
						$this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
19343
						$minstartliney = min($this->y, $minstartliney);
19344
						$maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
19345
					}
19346
				} elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
19347
					// account for different font size
19348
					$pfontname = $curfontname;
19349
					$pfontstyle = $curfontstyle;
19350
					$pfontsize = $curfontsize;
19351
					$fontname = isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname;
19352
					$fontstyle = isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle;
19353
					$fontsize = isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize;
19354
					$fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
19355
					$fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
19356
					if ( ($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
19357
						OR ($this->cell_height_ratio != $dom[$key]['line-height'])
19358
						OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
19359
						if ((!$this->newline) AND ($key < ($maxel - 1))
19360
							AND ( (is_numeric($fontsize) AND ($fontsize >= 0) AND is_numeric($curfontsize) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
19361
								OR ($this->cell_height_ratio != $dom[$key]['line-height']))
19362
								OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
19363
							if ($this->page > $startlinepage) {
19364
								// fix lines splitted over two pages
19365
								if (isset($this->footerlen[$startlinepage])) {
19366
									$curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19367
								}
19368
								// line to be moved one page forward
19369
								$pagebuff = $this->getPageBuffer($startlinepage);
19370
								$linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
19371
								$tstart = substr($pagebuff, 0, $startlinepos);
19372
								$tend = substr($this->getPageBuffer($startlinepage), $curpos);
19373
								// remove line start from previous page
19374
								$this->setPageBuffer($startlinepage, $tstart.''.$tend);
19375
								$pagebuff = $this->getPageBuffer($this->page);
19376
								$tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
19377
								$tend = substr($pagebuff, $this->cntmrk[$this->page]);
19378
								// add line start to current page
19379
								$yshift = $minstartliney - $this->y;
19380
								$try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
19381
								$this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
19382
								// shift the annotations and links
19383
								if (isset($this->PageAnnots[$this->page])) {
19384
									$next_pask = count($this->PageAnnots[$this->page]);
19385
								} else {
19386
									$next_pask = 0;
19387
								}
19388
								if (isset($this->PageAnnots[$startlinepage])) {
19389
									foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
19390
										if ($pak >= $pask) {
19391
											$this->PageAnnots[$this->page][] = $pac;
19392
											unset($this->PageAnnots[$startlinepage][$pak]);
19393
											$npak = count($this->PageAnnots[$this->page]) - 1;
19394
											$this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
19395
										}
19396
									}
19397
								}
19398
								$pask = $next_pask;
19399
								$startlinepos = $this->cntmrk[$this->page];
19400
								$startlinepage = $this->page;
19401
								$startliney = $this->y;
19402
							}
19403
							if (!isset($dom[$key]['line-height'])) {
19404
								$dom[$key]['line-height'] = $this->cell_height_ratio;
19405
							}
19406
							if (!$dom[$key]['block']) {
19407
								$this->y += (((($curfontsize * $this->cell_height_ratio ) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
19408
								if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
19409
									$minstartliney = min($this->y, $minstartliney);
19410
									$maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
19411
								}
19412
							}
19413
							$this->cell_height_ratio = $dom[$key]['line-height'];
19414
							$fontaligned = true;
19415
						}
19416
						$this->SetFont($fontname, $fontstyle, $fontsize);
19417
						// reset row height
19418
						$this->resetLastH();
19419
						$curfontname = $fontname;
19420
						$curfontstyle = $fontstyle;
19421
						$curfontsize = $fontsize;
19422
						$curfontascent = $fontascent;
19423
						$curfontdescent = $fontdescent;
19424
					}
19425
				}
19426
				// set text rendering mode
19427
				$textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
19428
				$textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
19429
				$textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
19430
				$this->setTextRenderingMode($textstroke, $textfill, $textclip);
19431
				if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
19432
					$this->setFontStretching($dom[$key]['font-stretch']);
19433
				}
19434
				if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
19435
					$this->setFontSpacing($dom[$key]['letter-spacing']);
19436
				}
19437
				if (($plalign == 'J') AND $dom[$key]['block']) {
19438
					$plalign = '';
19439
				}
19440
				// get current position on page buffer
19441
				$curpos = $this->pagelen[$startlinepage];
19442
				if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
19443
					$this->SetFillColorArray($dom[$key]['bgcolor']);
19444
					$wfill = true;
19445
				} else {
19446
					$wfill = $fill | false;
19447
				}
19448
				if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
19449
					$this->SetTextColorArray($dom[$key]['fgcolor']);
19450
				}
19451
				if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
19452
					$this->SetDrawColorArray($dom[$key]['strokecolor']);
19453
				}
19454
				if (isset($dom[$key]['align'])) {
19455
					$lalign = $dom[$key]['align'];
19456
				}
19457
				if ($this->empty_string($lalign)) {
19458
					$lalign = $align;
19459
				}
19460
			}
19461
			// align lines
19462
			if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
19463
				$newline = true;
19464
				$fontaligned = false;
19465
				// we are at the beginning of a new line
19466
				if (isset($startlinex)) {
19467
					$yshift = $minstartliney - $startliney;
19468
					if (($yshift > 0) OR ($this->page > $startlinepage)) {
19469
						$yshift = 0;
19470
					}
19471
					$t_x = 0;
19472
					// the last line must be shifted to be aligned as requested
19473
					$linew = abs($this->endlinex - $startlinex);
19474
					if ($this->inxobj) {
19475
						// we are inside an XObject template
19476
						$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
19477
						if (isset($opentagpos)) {
19478
							$midpos = $opentagpos;
19479
						} else {
19480
							$midpos = 0;
19481
						}
19482
						if ($midpos > 0) {
19483
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
19484
							$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
19485
						} else {
19486
							$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
19487
							$pend = '';
19488
						}
19489
					} else {
19490
						$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
19491
						if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19492
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19493
							$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
19494
						} elseif (isset($opentagpos)) {
19495
							$midpos = $opentagpos;
19496
						} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19497
							$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19498
							$midpos = $this->footerpos[$startlinepage];
19499
						} else {
19500
							$midpos = 0;
19501
						}
19502
						if ($midpos > 0) {
19503
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
19504
							$pend = substr($this->getPageBuffer($startlinepage), $midpos);
19505
						} else {
19506
							$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
19507
							$pend = '';
19508
						}
19509
					}
19510
					if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
19511
						// calculate shifting amount
19512
						$tw = $w;
19513
						if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
19514
							$tw += $this->cell_padding['R'];
19515
						}
19516
						if ($this->lMargin != $prevlMargin) {
19517
							$tw += ($prevlMargin - $this->lMargin);
19518
						}
19519
						if ($this->rMargin != $prevrMargin) {
19520
							$tw += ($prevrMargin - $this->rMargin);
19521
						}
19522
						$one_space_width = $this->GetStringWidth(chr(32));
19523
						$no = 0; // number of spaces on a line contained on a single block
19524
						if ($this->isRTLTextDir()) { // RTL
19525
							// remove left space if exist
19526
							$pos1 = $this->revstrpos($pmid, '[(');
19527
							if ($pos1 > 0) {
19528
								$pos1 = intval($pos1);
19529
								if ($this->isUnicodeFont()) {
19530
									$pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
19531
									$spacelen = 2;
19532
								} else {
19533
									$pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
19534
									$spacelen = 1;
19535
								}
19536
								if ($pos1 == $pos2) {
19537
									$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
19538
									if (substr($pmid, $pos1, 4) == '[()]') {
19539
										$linew -= $one_space_width;
19540
									} elseif ($pos1 == strpos($pmid, '[(')) {
19541
										$no = 1;
19542
									}
19543
								}
19544
							}
19545
						} else { // LTR
19546
							// remove right space if exist
19547
							$pos1 = $this->revstrpos($pmid, ')]');
19548
							if ($pos1 > 0) {
19549
								$pos1 = intval($pos1);
19550
								if ($this->isUnicodeFont()) {
19551
									$pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
19552
									$spacelen = 2;
19553
								} else {
19554
									$pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
19555
									$spacelen = 1;
19556
								}
19557
								if ($pos1 == $pos2) {
19558
									$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
19559
									$linew -= $one_space_width;
19560
								}
19561
							}
19562
						}
19563
						$mdiff = ($tw - $linew);
19564
						if ($plalign == 'C') {
19565
							if ($this->rtl) {
19566
								$t_x = -($mdiff / 2);
19567
							} else {
19568
								$t_x = ($mdiff / 2);
19569
							}
19570
						} elseif ($plalign == 'R') {
19571
							// right alignment on LTR document
19572
							$t_x = $mdiff;
19573
						} elseif ($plalign == 'L') {
19574
							// left alignment on RTL document
19575
							$t_x = -$mdiff;
19576
						} elseif (($plalign == 'J') AND ($plalign == $lalign)) {
19577
							// Justification
19578
							if ($this->isRTLTextDir()) {
19579
								// align text on the left
19580
								$t_x = -$mdiff;
19581
							}
19582
							$ns = 0; // number of spaces
19583
							$pmidtemp = $pmid;
19584
							// escape special characters
19585
							$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
19586
							$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
19587
							// search spaces
19588
							if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
19589
								$spacestr = $this->getSpaceString();
19590
								$maxkk = count($lnstring[1]) - 1;
19591
								for ($kk=0; $kk <= $maxkk; ++$kk) {
19592
									// restore special characters
19593
									$lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
19594
									$lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
19595
									// store number of spaces on the strings
19596
									$lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
19597
									// count total spaces on line
19598
									$ns += $lnstring[2][$kk];
19599
									$lnstring[3][$kk] = $ns;
19600
								}
19601
								if ($ns == 0) {
19602
									$ns = 1;
19603
								}
19604
								// calculate additional space to add to each existing space
19605
								$spacewidth = ($mdiff / ($ns - $no)) * $this->k;
19606
								$spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
19607
								if ($this->font_spacing != 0) {
19608
									// fixed spacing mode
19609
									$osw = -1000 * $this->font_spacing / $this->FontSize;
19610
									$spacewidthu += $osw;
19611
								}
19612
								$nsmax = $ns;
19613
								$ns = 0;
19614
								reset($lnstring);
19615
								$offset = 0;
19616
								$strcount = 0;
19617
								$prev_epsposbeg = 0;
19618
								$textpos = 0;
19619
								if ($this->isRTLTextDir()) {
19620
									$textpos = $this->wPt;
19621
								}
19622
								global $spacew;
19623
								while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
19624
									// check if we are inside a string section '[( ... )]'
19625
									$stroffset = strpos($pmid, '[(', $offset);
19626
									if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
19627
										// set offset to the end of string section
19628
										$offset = strpos($pmid, ')]', $stroffset);
19629
										while (($offset !== false) AND ($pmid{($offset - 1)} == '\\')) {
19630
											$offset = strpos($pmid, ')]', ($offset + 1));
19631
										}
19632
										if ($offset === false) {
19633
											$this->Error('HTML Justification: malformed PDF code.');
19634
										}
19635
										continue;
19636
									}
19637
									if ($this->isRTLTextDir()) {
19638
										$spacew = ($spacewidth * ($nsmax - $ns));
19639
									} else {
19640
										$spacew = ($spacewidth * $ns);
19641
									}
19642
									$offset = $strpiece[2][1] + strlen($strpiece[2][0]);
19643
									$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
19644
									$epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
19645
									if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
19646
										OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
19647
										// shift EPS images
19648
										$trx = sprintf('1 0 0 1 %.3F 0 cm', $spacew);
19649
										$epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
19650
										$pmid_b = substr($pmid, 0, $epsposbeg);
19651
										$pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
19652
										$pmid_e = substr($pmid, $epsposend);
19653
										$pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
19654
										$offset = $epsposend;
19655
										continue;
19656
 
19657
									}
19658
									$prev_epsposbeg = $epsposbeg;
19659
									$currentxpos = 0;
19660
									// shift blocks of code
19661
									switch ($strpiece[2][0]) {
19662
										case 'Td':
19663
										case 'cm':
19664
										case 'm':
19665
										case 'l': {
19666
											// get current X position
19667
											preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
19668
											$currentxpos = $xmatches[1];
19669
											$textpos = $currentxpos;
19670
											if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
19671
												$ns = $lnstring[3][$strcount];
19672
												if ($this->isRTLTextDir()) {
19673
													$spacew = ($spacewidth * ($nsmax - $ns));
19674
												}
19675
												++$strcount;
19676
											}
19677
											// justify block
19678
											$pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
19679
												create_function('$matches', 'global $spacew;
19680
												$newx = sprintf("%.2F",(floatval($matches[1]) + $spacew));
19681
												return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
19682
											break;
19683
										}
19684
										case 're': {
19685
											// justify block
19686
											if (!$this->empty_string($this->lispacer)) {
19687
												$this->lispacer = '';
19688
												continue;
19689
											}
19690
											preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
19691
											$currentxpos = $xmatches[1];
19692
											global $x_diff, $w_diff;
19693
											$x_diff = 0;
19694
											$w_diff = 0;
19695
											if ($this->isRTLTextDir()) { // RTL
19696
												if ($currentxpos < $textpos) {
19697
													$x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
19698
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
19699
												} else {
19700
													if ($strcount > 0) {
19701
														$x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
19702
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
19703
													}
19704
												}
19705
											} else { // LTR
19706
												if ($currentxpos > $textpos) {
19707
													if ($strcount > 0) {
19708
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
19709
													}
19710
													$w_diff = ($spacewidth * $lnstring[2][$strcount]);
19711
												} else {
19712
													if ($strcount > 1) {
19713
														$x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
19714
													}
19715
													if ($strcount > 0) {
19716
														$w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
19717
													}
19718
												}
19719
											}
19720
											$pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
19721
												create_function('$matches', 'global $x_diff, $w_diff;
19722
												$newx = sprintf("%.2F",(floatval($matches[1]) + $x_diff));
19723
												$neww = sprintf("%.2F",(floatval($matches[3]) + $w_diff));
19724
												return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
19725
											break;
19726
										}
19727
										case 'c': {
19728
											// get current X position
19729
											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);
19730
											$currentxpos = $xmatches[1];
19731
											// justify block
19732
											$pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x',
19733
												create_function('$matches', 'global $spacew;
19734
												$newx1 = sprintf("%.3F",(floatval($matches[1]) + $spacew));
19735
												$newx2 = sprintf("%.3F",(floatval($matches[3]) + $spacew));
19736
												$newx3 = sprintf("%.3F",(floatval($matches[5]) + $spacew));
19737
												return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
19738
											break;
19739
										}
19740
									}
19741
									// shift the annotations and links
19742
									$cxpos = ($currentxpos / $this->k);
19743
									$lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
19744
									if ($this->inxobj) {
19745
										// we are inside an XObject template
19746
										foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
19747
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
19748
												if ($cxpos > $lmpos) {
19749
													$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
19750
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19751
												} else {
19752
													$this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19753
												}
19754
												break;
19755
											}
19756
										}
19757
									} elseif (isset($this->PageAnnots[$this->page])) {
19758
										foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
19759
											if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
19760
												if ($cxpos > $lmpos) {
19761
													$this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
19762
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19763
												} else {
19764
													$this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19765
												}
19766
												break;
19767
											}
19768
										}
19769
									}
19770
								} // end of while
19771
								// remove markers
19772
								$pmid = str_replace('x*#!#*x', '', $pmid);
19773
								if ($this->isUnicodeFont()) {
19774
									// multibyte characters
19775
									$spacew = $spacewidthu;
19776
									if ($this->font_stretching != 100) {
19777
										// word spacing is affected by stretching
19778
										$spacew /= ($this->font_stretching / 100);
19779
									}
19780
									$pmidtemp = $pmid;
19781
									// escape special characters
19782
									$pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
19783
									$pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
19784
									$pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
19785
												create_function('$matches', 'global $spacew;
19786
												$matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
19787
												$matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
19788
												return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%.3F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
19789
									if ($this->inxobj) {
19790
										// we are inside an XObject template
19791
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
19792
									} else {
19793
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
19794
									}
19795
									$endlinepos = strlen($pstart."\n".$pmid."\n");
19796
								} else {
19797
									// non-unicode (single-byte characters)
19798
									if ($this->font_stretching != 100) {
19799
										// word spacing (Tw) is affected by stretching
19800
										$spacewidth /= ($this->font_stretching / 100);
19801
									}
19802
									$rs = sprintf('%.3F Tw', $spacewidth);
19803
									$pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
19804
									if ($this->inxobj) {
19805
										// we are inside an XObject template
19806
										$this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
19807
									} else {
19808
										$this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
19809
									}
19810
									$endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
19811
								}
19812
							}
19813
						} // end of J
19814
					} // end if $startlinex
19815
					if (($t_x != 0) OR ($yshift < 0)) {
19816
						// shift the line
19817
						$trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
19818
						$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
19819
						$endlinepos = strlen($pstart);
19820
						if ($this->inxobj) {
19821
							// we are inside an XObject template
19822
							$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
19823
							foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
19824
								if ($pak >= $pask) {
19825
									$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
19826
									$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
19827
								}
19828
							}
19829
						} else {
19830
							$this->setPageBuffer($startlinepage, $pstart.$pend);
19831
							// shift the annotations and links
19832
							if (isset($this->PageAnnots[$this->page])) {
19833
								foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
19834
									if ($pak >= $pask) {
19835
										$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
19836
										$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
19837
									}
19838
								}
19839
							}
19840
						}
19841
						$this->y -= $yshift;
19842
					}
19843
				}
19844
				$pbrk = $this->checkPageBreak($this->lasth);
19845
				$this->newline = false;
19846
				$startlinex = $this->x;
19847
				$startliney = $this->y;
19848
				if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
19849
					$startliney -= ((0.3 * $this->FontSizePt) / $this->k);
19850
				} elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
19851
					$startliney -= (($this->FontSizePt / 0.7) / $this->k);
19852
				} else {
19853
					$minstartliney = $startliney;
19854
					$maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
19855
				}
19856
				$startlinepage = $this->page;
19857
				if (isset($endlinepos) AND (!$pbrk)) {
19858
					$startlinepos = $endlinepos;
19859
				} else {
19860
					if ($this->inxobj) {
19861
						// we are inside an XObject template
19862
						$startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
19863
					} elseif (!$this->InFooter) {
19864
						if (isset($this->footerlen[$this->page])) {
19865
							$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
19866
						} else {
19867
							$this->footerpos[$this->page] = $this->pagelen[$this->page];
19868
						}
19869
						$startlinepos = $this->footerpos[$this->page];
19870
					} else {
19871
						$startlinepos = $this->pagelen[$this->page];
19872
					}
19873
				}
19874
				unset($endlinepos);
19875
				$plalign = $lalign;
19876
				if (isset($this->PageAnnots[$this->page])) {
19877
					$pask = count($this->PageAnnots[$this->page]);
19878
				} else {
19879
					$pask = 0;
19880
				}
19881
				if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table') AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
19882
					$this->SetFont($fontname, $fontstyle, $fontsize);
19883
					if ($wfill) {
19884
						$this->SetFillColorArray($this->bgcolor);
19885
					}
19886
				}
19887
			} // end newline
19888
			if (isset($opentagpos)) {
19889
				unset($opentagpos);
19890
			}
19891
			if ($dom[$key]['tag']) {
19892
				if ($dom[$key]['opening']) {
19893
					// get text indentation (if any)
19894
					if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
19895
						$this->textindent = $dom[$key]['text-indent'];
19896
						$this->newline = true;
19897
					}
19898
					// table
19899
					if ($dom[$key]['value'] == 'table') {
19900
						// available page width
19901
						if ($this->rtl) {
19902
							$wtmp = $this->x - $this->lMargin;
19903
						} else {
19904
							$wtmp = $this->w - $this->rMargin - $this->x;
19905
						}
19906
						// get cell spacing
19907
						if (isset($dom[$key]['attribute']['cellspacing'])) {
19908
							$clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
19909
							$cellspacing = array('H' => $clsp, 'V' => $clsp);
19910
						} elseif (isset($dom[$key]['border-spacing'])) {
19911
							$cellspacing = $dom[$key]['border-spacing'];
19912
						} else {
19913
							$cellspacing = array('H' => 0, 'V' => 0);
19914
						}
19915
						// table width
19916
						if (isset($dom[$key]['width'])) {
19917
							$table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
19918
						} else {
19919
							$table_width = $wtmp;
19920
						}
19921
						$table_width -= (2 * $cellspacing['H']);
19922
						if (!$this->inthead) {
19923
							$this->y += $cellspacing['V'];
19924
						}
19925
						if ($this->rtl) {
19926
							$cellspacingx = -$cellspacing['H'];
19927
						} else {
19928
							$cellspacingx = $cellspacing['H'];
19929
						}
19930
						// total table width without cellspaces
19931
						$table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
19932
						// minimum column width
19933
						$table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
19934
						// array of custom column widths
19935
						$table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
19936
					}
19937
					// table row
19938
					if ($dom[$key]['value'] == 'tr') {
19939
						// reset column counter
19940
						$colid = 0;
19941
					}
19942
					// table cell
19943
					if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
19944
						$trid = $dom[$key]['parent'];
19945
						$table_el = $dom[$trid]['parent'];
19946
						if (!isset($dom[$table_el]['cols'])) {
19947
							$dom[$table_el]['cols'] = $dom[$trid]['cols'];
19948
						}
19949
						// store border info
19950
						$tdborder = 0;
19951
						if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
19952
							$tdborder = $dom[$key]['border'];
19953
						}
19954
						$colspan = $dom[$key]['attribute']['colspan'];
19955
						$old_cell_padding = $this->cell_padding;
19956
						if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
19957
							$crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
19958
							$current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
19959
						} elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
19960
							$current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
19961
						} else {
19962
							$current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
19963
						}
19964
						$this->cell_padding = $current_cell_padding;
19965
						if (isset($dom[$key]['height'])) {
19966
							// minimum cell height
19967
							$cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
19968
						} else {
19969
							$cellh = 0;
19970
						}
19971
						if (isset($dom[$key]['content'])) {
19972
							$cell_content = $dom[$key]['content'];
19973
						} else {
19974
							$cell_content = '&nbsp;';
19975
						}
19976
						$tagtype = $dom[$key]['value'];
19977
						$parentid = $key;
19978
						while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
19979
							// move $key index forward
19980
							++$key;
19981
						}
19982
						if (!isset($dom[$trid]['startpage'])) {
19983
							$dom[$trid]['startpage'] = $this->page;
19984
						} else {
19985
							$this->setPage($dom[$trid]['startpage']);
19986
						}
19987
						if (!isset($dom[$trid]['startcolumn'])) {
19988
							$dom[$trid]['startcolumn'] = $this->current_column;
19989
						} elseif ($this->current_column != $dom[$trid]['startcolumn']) {
19990
							$tmpx = $this->x;
19991
							$this->selectColumn($dom[$trid]['startcolumn']);
19992
							$this->x = $tmpx;
19993
						}
19994
						if (!isset($dom[$trid]['starty'])) {
19995
							$dom[$trid]['starty'] = $this->y;
19996
						} else {
19997
							$this->y = $dom[$trid]['starty'];
19998
						}
19999
						if (!isset($dom[$trid]['startx'])) {
20000
							$dom[$trid]['startx'] = $this->x;
20001
							$this->x += $cellspacingx;
20002
						} else {
20003
							$this->x += ($cellspacingx / 2);
20004
						}
20005
						if (isset($dom[$parentid]['attribute']['rowspan'])) {
20006
							$rowspan = intval($dom[$parentid]['attribute']['rowspan']);
20007
						} else {
20008
							$rowspan = 1;
20009
						}
20010
						// skip row-spanned cells started on the previous rows
20011
						if (isset($dom[$table_el]['rowspans'])) {
20012
							$rsk = 0;
20013
							$rskmax = count($dom[$table_el]['rowspans']);
20014
							while ($rsk < $rskmax) {
20015
								$trwsp = $dom[$table_el]['rowspans'][$rsk];
20016
								$rsstartx = $trwsp['startx'];
20017
								$rsendx = $trwsp['endx'];
20018
								// account for margin changes
20019
								if ($trwsp['startpage'] < $this->page) {
20020
									if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
20021
										$dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
20022
										$rsstartx -= $dl;
20023
										$rsendx -= $dl;
20024
									} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
20025
										$dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
20026
										$rsstartx += $dl;
20027
										$rsendx += $dl;
20028
									}
20029
								}
20030
								if (($trwsp['rowspan'] > 0)
20031
									AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
20032
									AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
20033
									AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
20034
									// set the starting X position of the current cell
20035
									$this->x = $rsendx + $cellspacingx;
20036
									// increment column indicator
20037
									$colid += $trwsp['colspan'];
20038
									if (($trwsp['rowspan'] == 1)
20039
										AND (isset($dom[$trid]['endy']))
20040
										AND (isset($dom[$trid]['endpage']))
20041
										AND (isset($dom[$trid]['endcolumn']))
20042
										AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
20043
										AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
20044
										// set ending Y position for row
20045
										$dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
20046
										$dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
20047
									}
20048
									$rsk = 0;
20049
								} else {
20050
									++$rsk;
20051
								}
20052
							}
20053
						}
20054
						if (isset($dom[$parentid]['width'])) {
20055
							// user specified width
20056
							$cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
20057
							$tmpcw = ($cellw / $colspan);
20058
							for ($i = 0; $i < $colspan; ++$i) {
20059
								$table_colwidths[($colid + $i)] = $tmpcw;
20060
							}
20061
						} else {
20062
							// inherit column width
20063
							$cellw = 0;
20064
							for ($i = 0; $i < $colspan; ++$i) {
20065
								$cellw += $table_colwidths[($colid + $i)];
20066
							}
20067
						}
20068
						$cellw += (($colspan - 1) * $cellspacing['H']);
20069
						// increment column indicator
20070
						$colid += $colspan;
20071
						// add rowspan information to table element
20072
						if ($rowspan > 1) {
20073
							$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));
20074
						}
20075
						$cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
20076
						if ($rowspan > 1) {
20077
							$dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
20078
						}
20079
						// push background colors
20080
						if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
20081
							$dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
20082
						}
20083
						// store border info
20084
						if (isset($tdborder) AND !empty($tdborder)) {
20085
							$dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
20086
						}
20087
						$prevLastH = $this->lasth;
20088
						// store some info for multicolumn mode
20089
						if ($this->rtl) {
20090
							$this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
20091
						} else {
20092
							$this->colxshift['x'] = $this->x - $this->lMargin;
20093
						}
20094
						$this->colxshift['s'] = $cellspacing;
20095
						$this->colxshift['p'] = $current_cell_padding;
20096
						// ****** write the cell content ******
20097
						$this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true);
20098
						// restore some values
20099
						$this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
20100
						$this->lasth = $prevLastH;
20101
						$this->cell_padding = $old_cell_padding;
20102
						$dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
20103
						// update the end of row position
20104
						if ($rowspan <= 1) {
20105
							if (isset($dom[$trid]['endy'])) {
20106
								if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
20107
									$dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
20108
								} elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
20109
									$dom[$trid]['endy'] = $this->y;
20110
								}
20111
							} else {
20112
								$dom[$trid]['endy'] = $this->y;
20113
							}
20114
							if (isset($dom[$trid]['endpage'])) {
20115
								$dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
20116
							} else {
20117
								$dom[$trid]['endpage'] = $this->page;
20118
							}
20119
							if (isset($dom[$trid]['endcolumn'])) {
20120
								$dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
20121
							} else {
20122
								$dom[$trid]['endcolumn'] = $this->current_column;
20123
							}
20124
						} else {
20125
							// account for row-spanned cells
20126
							$dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
20127
							$dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
20128
							$dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
20129
							$dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
20130
						}
20131
						if (isset($dom[$table_el]['rowspans'])) {
20132
							// update endy and endpage on rowspanned cells
20133
							foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
20134
								if ($trwsp['rowspan'] > 0) {
20135
									if (isset($dom[$trid]['endpage'])) {
20136
										if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
20137
											$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
20138
										} elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
20139
											$dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
20140
											$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
20141
											$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
20142
										} else {
20143
											$dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
20144
										}
20145
									}
20146
								}
20147
							}
20148
						}
20149
						$this->x += ($cellspacingx / 2);
20150
					} else {
20151
						// opening tag (or self-closing tag)
20152
						if (!isset($opentagpos)) {
20153
							if ($this->inxobj) {
20154
								// we are inside an XObject template
20155
								$opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
20156
							} elseif (!$this->InFooter) {
20157
								if (isset($this->footerlen[$this->page])) {
20158
									$this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
20159
								} else {
20160
									$this->footerpos[$this->page] = $this->pagelen[$this->page];
20161
								}
20162
								$opentagpos = $this->footerpos[$this->page];
20163
							}
20164
						}
20165
						$this->openHTMLTagHandler($dom, $key, $cell);
20166
					}
20167
				} else { // closing tag
20168
					$prev_numpages = $this->numpages;
20169
					$old_bordermrk = $this->bordermrk[$this->page];
20170
					$this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
20171
					if ($this->bordermrk[$this->page] > $old_bordermrk) {
20172
						$startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
20173
					}
20174
					if ($prev_numpages > $this->numpages) {
20175
						$startlinepage = $this->page;
20176
					}
20177
				}
20178
			} elseif (strlen($dom[$key]['value']) > 0) {
20179
				// print list-item
20180
				if (!$this->empty_string($this->lispacer) AND ($this->lispacer != '^')) {
20181
					$this->SetFont($pfontname, $pfontstyle, $pfontsize);
20182
					$this->resetLastH();
20183
					$minstartliney = $this->y;
20184
					$maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
20185
					$this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
20186
					$this->SetFont($curfontname, $curfontstyle, $curfontsize);
20187
					$this->resetLastH();
20188
					if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
20189
						$pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
20190
						$pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
20191
						$this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
20192
						$minstartliney = min($this->y, $minstartliney);
20193
						$maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
20194
					}
20195
				}
20196
				// text
20197
				$this->htmlvspace = 0;
20198
				if ((!$this->premode) AND $this->isRTLTextDir()) {
20199
					// reverse spaces order
20200
					$lsp = ''; // left spaces
20201
					$rsp = ''; // right spaces
20202
					if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
20203
						$lsp = $matches[1];
20204
					}
20205
					if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
20206
						$rsp = $matches[1];
20207
					}
20208
					$dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
20209
				}
20210
				if ($newline) {
20211
					if (!$this->premode) {
20212
						$prelen = strlen($dom[$key]['value']);
20213
						if ($this->isRTLTextDir()) {
20214
							// right trim except non-breaking space
20215
							$dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
20216
						} else {
20217
							// left trim except non-breaking space
20218
							$dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
20219
						}
20220
						$postlen = strlen($dom[$key]['value']);
20221
						if (($postlen == 0) AND ($prelen > 0)) {
20222
							$dom[$key]['trimmed_space'] = true;
20223
						}
20224
					}
20225
					$newline = false;
20226
					$firstblock = true;
20227
				} else {
20228
					$firstblock = false;
20229
					// replace empty multiple spaces string with a single space
20230
					$dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
20231
				}
20232
				$strrest = '';
20233
				if ($this->rtl) {
20234
					$this->x -= $this->textindent;
20235
				} else {
20236
					$this->x += $this->textindent;
20237
				}
20238
				if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
20239
					if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
20240
						// HTML <a> Link
20241
						$hrefcolor = '';
20242
						if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
20243
							$hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
20244
						}
20245
						$hrefstyle = -1;
20246
						if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
20247
							$hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
20248
						}
20249
						$strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
20250
					} else {
20251
						$wadj = 0; // space to leave for block continuity
20252
						$adjblks = 0; // number of blocks
20253
						// check the next text blocks for continuity
20254
						$nkey = ($key + 1);
20255
						$write_block = true;
20256
						$same_textdir = true;
20257
						$tmp_fontname = $this->FontFamily;
20258
						$tmp_fontstyle = $this->FontStyle;
20259
						$tmp_fontsize = $this->FontSizePt;
20260
						while ($write_block AND isset($dom[$nkey])) {
20261
							if ($dom[$nkey]['tag']) {
20262
								if ($dom[$nkey]['block']) {
20263
									// end of block
20264
									$write_block = false;
20265
								}
20266
								$tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
20267
								$tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
20268
								$tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
20269
								$same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
20270
							} else {
20271
								$nextstr = preg_split('/'.$this->re_space['p'].'+/'.$this->re_space['m'], $dom[$nkey]['value']);
20272
								if (isset($nextstr[0])) {
20273
									if ($same_textdir) {
20274
										$wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
20275
									}
20276
									++$adjblks;
20277
								}
20278
								if (isset($nextstr[1])) {
20279
									$write_block = false;
20280
								}
20281
							}
20282
							++$nkey;
20283
						}
20284
						// check for reversed text direction
20285
						if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
20286
							// LTR text on RTL direction or RTL text on LTR direction
20287
							$reverse_dir = true;
20288
							$this->rtl = !$this->rtl;
20289
							$revshift = ($this->GetStringWidth($dom[$key]['value']) + $wadj) + 0.000001; // add little quantity for rounding problems
20290
							if ($this->rtl) {
20291
								$this->x += $revshift;
20292
							} else {
20293
								$this->x -= $revshift;
20294
							}
20295
							$xws = $this->x;
20296
						}
20297
						// ****** write only until the end of the line and get the rest ******
20298
						$strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
20299
						// restore default direction
20300
						if ($reverse_dir AND ($wadj == 0)) {
20301
							$this->x = $xws;
20302
							$this->rtl = !$this->rtl;
20303
							$reverse_dir = false;
20304
						}
20305
					}
20306
				}
20307
				$this->textindent = 0;
20308
				if (strlen($strrest) > 0) {
20309
					// store the remaining string on the previous $key position
20310
					$this->newline = true;
20311
					if ($strrest == $dom[$key]['value']) {
20312
						// used to avoid infinite loop
20313
						++$loop;
20314
					} else {
20315
						$loop = 0;
20316
					}
20317
					$dom[$key]['value'] = $strrest;
20318
					if ($cell) {
20319
						if ($this->rtl) {
20320
							$this->x -= $this->cell_padding['R'];
20321
						} else {
20322
							$this->x += $this->cell_padding['L'];
20323
						}
20324
					}
20325
					if ($loop < 3) {
20326
						--$key;
20327
					}
20328
				} else {
20329
					$loop = 0;
20330
				}
20331
			}
20332
			++$key;
20333
			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')) {
20334
				// check if we are on a new page or on a new column
20335
				if ((!$undo) AND ($this->y < $this->start_transaction_y)) {
20336
					// we are on a new page or on a new column and the total object height is less than the available vertical space.
20337
					// restore previous object
20338
					$this->rollbackTransaction(true);
20339
					// restore previous values
20340
					foreach ($this_method_vars as $vkey => $vval) {
20341
						$$vkey = $vval;
20342
					}
20343
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
20344
					$pre_y = $this->y;
20345
					if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
20346
						$startliney = $this->y;
20347
					}
20348
					$undo = true; // avoid infinite loop
20349
				} else {
20350
					$undo = false;
20351
				}
20352
			}
20353
		} // end for each $key
20354
		// align the last line
20355
		if (isset($startlinex)) {
20356
			$yshift = $minstartliney - $startliney;
20357
			if (($yshift > 0) OR ($this->page > $startlinepage)) {
20358
				$yshift = 0;
20359
			}
20360
			$t_x = 0;
20361
			// the last line must be shifted to be aligned as requested
20362
			$linew = abs($this->endlinex - $startlinex);
20363
			if ($this->inxobj) {
20364
				// we are inside an XObject template
20365
				$pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
20366
				if (isset($opentagpos)) {
20367
					$midpos = $opentagpos;
20368
				} else {
20369
					$midpos = 0;
20370
				}
20371
				if ($midpos > 0) {
20372
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
20373
					$pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
20374
				} else {
20375
					$pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
20376
					$pend = '';
20377
				}
20378
			} else {
20379
				$pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
20380
				if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
20381
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
20382
					$midpos = min($opentagpos, $this->footerpos[$startlinepage]);
20383
				} elseif (isset($opentagpos)) {
20384
					$midpos = $opentagpos;
20385
				} elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
20386
					$this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
20387
					$midpos = $this->footerpos[$startlinepage];
20388
				} else {
20389
					$midpos = 0;
20390
				}
20391
				if ($midpos > 0) {
20392
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
20393
					$pend = substr($this->getPageBuffer($startlinepage), $midpos);
20394
				} else {
20395
					$pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
20396
					$pend = '';
20397
				}
20398
			}
20399
			if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
20400
				// calculate shifting amount
20401
				$tw = $w;
20402
				if ($this->lMargin != $prevlMargin) {
20403
					$tw += ($prevlMargin - $this->lMargin);
20404
				}
20405
				if ($this->rMargin != $prevrMargin) {
20406
					$tw += ($prevrMargin - $this->rMargin);
20407
				}
20408
				$one_space_width = $this->GetStringWidth(chr(32));
20409
				$no = 0; // number of spaces on a line contained on a single block
20410
				if ($this->isRTLTextDir()) { // RTL
20411
					// remove left space if exist
20412
					$pos1 = $this->revstrpos($pmid, '[(');
20413
					if ($pos1 > 0) {
20414
						$pos1 = intval($pos1);
20415
						if ($this->isUnicodeFont()) {
20416
							$pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
20417
							$spacelen = 2;
20418
						} else {
20419
							$pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
20420
							$spacelen = 1;
20421
						}
20422
						if ($pos1 == $pos2) {
20423
							$pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
20424
							if (substr($pmid, $pos1, 4) == '[()]') {
20425
								$linew -= $one_space_width;
20426
							} elseif ($pos1 == strpos($pmid, '[(')) {
20427
								$no = 1;
20428
							}
20429
						}
20430
					}
20431
				} else { // LTR
20432
					// remove right space if exist
20433
					$pos1 = $this->revstrpos($pmid, ')]');
20434
					if ($pos1 > 0) {
20435
						$pos1 = intval($pos1);
20436
						if ($this->isUnicodeFont()) {
20437
							$pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
20438
							$spacelen = 2;
20439
						} else {
20440
							$pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
20441
							$spacelen = 1;
20442
						}
20443
						if ($pos1 == $pos2) {
20444
							$pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
20445
							$linew -= $one_space_width;
20446
						}
20447
					}
20448
				}
20449
				$mdiff = ($tw - $linew);
20450
				if ($plalign == 'C') {
20451
					if ($this->rtl) {
20452
						$t_x = -($mdiff / 2);
20453
					} else {
20454
						$t_x = ($mdiff / 2);
20455
					}
20456
				} elseif ($plalign == 'R') {
20457
					// right alignment on LTR document
20458
					$t_x = $mdiff;
20459
				} elseif ($plalign == 'L') {
20460
					// left alignment on RTL document
20461
					$t_x = -$mdiff;
20462
				}
20463
			} // end if startlinex
20464
			if (($t_x != 0) OR ($yshift < 0)) {
20465
				// shift the line
20466
				$trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
20467
				$pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
20468
				$endlinepos = strlen($pstart);
20469
				if ($this->inxobj) {
20470
					// we are inside an XObject template
20471
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
20472
					foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
20473
						if ($pak >= $pask) {
20474
							$this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
20475
							$this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
20476
						}
20477
					}
20478
				} else {
20479
					$this->setPageBuffer($startlinepage, $pstart.$pend);
20480
					// shift the annotations and links
20481
					if (isset($this->PageAnnots[$this->page])) {
20482
						foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
20483
							if ($pak >= $pask) {
20484
								$this->PageAnnots[$this->page][$pak]['x'] += $t_x;
20485
								$this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
20486
							}
20487
						}
20488
					}
20489
				}
20490
				$this->y -= $yshift;
20491
			}
20492
		}
20493
		// restore previous values
20494
		$this->setGraphicVars($gvars);
20495
		if ($this->num_columns > 1) {
20496
			$this->selectColumn();
20497
		} elseif ($this->page > $prevPage) {
20498
			$this->lMargin = $this->pagedim[$this->page]['olm'];
20499
			$this->rMargin = $this->pagedim[$this->page]['orm'];
20500
		}
20501
		// restore previous list state
20502
		$this->cell_height_ratio = $prev_cell_height_ratio;
20503
		$this->listnum = $prev_listnum;
20504
		$this->listordered = $prev_listordered;
20505
		$this->listcount = $prev_listcount;
20506
		$this->lispacer = $prev_lispacer;
20507
		if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
20508
			$this->Ln($this->lasth);
20509
			if ($this->y < $maxbottomliney) {
20510
				$this->y = $maxbottomliney;
20511
			}
20512
		}
20513
		unset($dom);
20514
	}
20515
 
20516
	/**
20517
	 * Process opening tags.
20518
	 * @param array $dom html dom array
20519
	 * @param int $key current element id
20520
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20521
	 * @access protected
20522
	 */
20523
	protected function openHTMLTagHandler(&$dom, $key, $cell) {
20524
		$tag = $dom[$key];
20525
		$parent = $dom[($dom[$key]['parent'])];
20526
		$firsttag = ($key == 1);
20527
		// check for text direction attribute
20528
		if (isset($tag['dir'])) {
20529
			$this->setTempRTL($tag['dir']);
20530
		} else {
20531
			$this->tmprtl = false;
20532
		}
20533
		if ($tag['block']) {
20534
			$hbz = 0; // distance from y to line bottom
20535
			$hb = 0; // vertical space between block tags
20536
			// calculate vertical space for block tags
20537
			if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
20538
				$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
20539
			} elseif (isset($tag['fontsize'])) {
20540
				$cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
20541
			} else {
20542
				$cur_h = $this->FontSize * $this->cell_height_ratio;
20543
			}
20544
			if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
20545
				$n = $this->tagvspaces[$tag['value']][0]['n'];
20546
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
20547
				$n = 0.6;
20548
			} else {
20549
				$n = 1;
20550
			}
20551
			$hb = ($n * $cur_h);
20552
			if (($this->htmlvspace <= 0) AND ($n > 0)) {
20553
				if (isset($parent['fontsize'])) {
20554
					$hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
20555
				} else {
20556
					$hbz = $this->FontSize * $this->cell_height_ratio;
20557
				}
20558
			}
20559
		}
20560
		// Opening tag
20561
		switch($tag['value']) {
20562
			case 'table': {
20563
				$cp = 0;
20564
				$cs = 0;
20565
				$dom[$key]['rowspans'] = array();
20566
				if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
20567
					// set table header
20568
					if (!$this->empty_string($dom[$key]['thead'])) {
20569
						// set table header
20570
						$this->thead = $dom[$key]['thead'];
20571
						if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
20572
							$this->theadMargins = array();
20573
							$this->theadMargins['cell_padding'] = $this->cell_padding;
20574
							$this->theadMargins['lmargin'] = $this->lMargin;
20575
							$this->theadMargins['rmargin'] = $this->rMargin;
20576
							$this->theadMargins['page'] = $this->page;
20577
						}
20578
					}
20579
				}
20580
				// store current margins and page
20581
				$dom[$key]['old_cell_padding'] = $this->cell_padding;
20582
				if (isset($tag['attribute']['cellpadding'])) {
20583
					$pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
20584
					$this->SetCellPadding($pad);
20585
				} elseif (isset($tag['padding'])) {
20586
					$this->cell_padding = $tag['padding'];
20587
				}
20588
				if (isset($tag['attribute']['cellspacing'])) {
20589
					$cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20590
				} elseif (isset($tag['border-spacing'])) {
20591
					$cs = $tag['border-spacing']['V'];
20592
				}
20593
				$prev_y = $this->y;
20594
				if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
20595
					$this->inthead = true;
20596
					// add a page (or trig AcceptPageBreak() for multicolumn mode)
20597
					$this->checkPageBreak($this->PageBreakTrigger + 1);
20598
				}
20599
				break;
20600
			}
20601
			case 'tr': {
20602
				// array of columns positions
20603
				$dom[$key]['cellpos'] = array();
20604
				break;
20605
			}
20606
			case 'hr': {
20607
				if ((isset($tag['height'])) AND ($tag['height'] != '')) {
20608
					$hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
20609
				} else {
20610
					$hrHeight = $this->GetLineWidth();
20611
				}
20612
				$this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
20613
				$x = $this->GetX();
20614
				$y = $this->GetY();
20615
				$wtmp = $this->w - $this->lMargin - $this->rMargin;
20616
				if ($cell) {
20617
					$wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
20618
				}
20619
				if ((isset($tag['width'])) AND ($tag['width'] != '')) {
20620
					$hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
20621
				} else {
20622
					$hrWidth = $wtmp;
20623
				}
20624
				$prevlinewidth = $this->GetLineWidth();
20625
				$this->SetLineWidth($hrHeight);
20626
				$this->Line($x, $y, $x + $hrWidth, $y);
20627
				$this->SetLineWidth($prevlinewidth);
20628
				$this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
20629
				break;
20630
			}
20631
			case 'a': {
20632
				if (array_key_exists('href', $tag['attribute'])) {
20633
					$this->HREF['url'] = $tag['attribute']['href'];
20634
				}
20635
				break;
20636
			}
20637
			case 'img': {
20638
				if (isset($tag['attribute']['src'])) {
20639
					// replace relative path with real server path
20640
					if (($tag['attribute']['src'][0] == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
20641
						$findroot = strpos($tag['attribute']['src'], $_SERVER['DOCUMENT_ROOT']);
20642
						if (($findroot === false) OR ($findroot > 1)) {
20643
							$tag['attribute']['src'] = $_SERVER['DOCUMENT_ROOT'].$tag['attribute']['src'];
20644
						}
20645
					}
20646
					$tag['attribute']['src'] = urldecode($tag['attribute']['src']);
20647
					$type = $this->getImageFileType($tag['attribute']['src']);
20648
					$testscrtype = @parse_url($tag['attribute']['src']);
20649
					if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
20650
						// convert URL to server path
20651
						$tag['attribute']['src'] = str_replace(K_PATH_URL, K_PATH_MAIN, $tag['attribute']['src']);
20652
					}
20653
					if (!isset($tag['width'])) {
20654
						$tag['width'] = 0;
20655
					}
20656
					if (!isset($tag['height'])) {
20657
						$tag['height'] = 0;
20658
					}
20659
					//if (!isset($tag['attribute']['align'])) {
20660
						// the only alignment supported is "bottom"
20661
						// further development is required for other modes.
20662
						$tag['attribute']['align'] = 'bottom';
20663
					//}
20664
					switch($tag['attribute']['align']) {
20665
						case 'top': {
20666
							$align = 'T';
20667
							break;
20668
						}
20669
						case 'middle': {
20670
							$align = 'M';
20671
							break;
20672
						}
20673
						case 'bottom': {
20674
							$align = 'B';
20675
							break;
20676
						}
20677
						default: {
20678
							$align = 'B';
20679
							break;
20680
						}
20681
					}
20682
					$prevy = $this->y;
20683
					$xpos = $this->x;
20684
					// eliminate marker spaces
20685
					if (isset($dom[($key - 1)])) {
20686
						if (($dom[($key - 1)]['value'] == ' ') OR (isset($dom[($key - 1)]['trimmed_space']))) {
20687
							$xpos -= $this->GetStringWidth(chr(32));
20688
						} elseif ($this->rtl AND $dom[($key - 1)]['value'] == '  ') {
20689
							$xpos += (2 * $this->GetStringWidth(chr(32)));
20690
						}
20691
					}
20692
					$imglink = '';
20693
					if (isset($this->HREF['url']) AND !$this->empty_string($this->HREF['url'])) {
20694
						$imglink = $this->HREF['url'];
20695
						if ($imglink{0} == '#') {
20696
							// convert url to internal link
20697
							$lnkdata = explode(',', $imglink);
20698
							if (isset($lnkdata[0])) {
20699
								$page = intval(substr($lnkdata[0], 1));
20700
								if (empty($page) OR ($page <= 0)) {
20701
									$page = $this->page;
20702
								}
20703
								if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
20704
									$lnky = floatval($lnkdata[1]);
20705
								} else {
20706
									$lnky = 0;
20707
								}
20708
								$imglink = $this->AddLink();
20709
								$this->SetLink($imglink, $lnky, $page);
20710
							}
20711
						}
20712
					}
20713
					$border = 0;
20714
					if (isset($tag['border']) AND !empty($tag['border'])) {
20715
						// currently only support 1 (frame) or a combination of 'LTRB'
20716
						$border = $tag['border'];
20717
					}
20718
					$iw = '';
20719
					if (isset($tag['width'])) {
20720
						$iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
20721
					}
20722
					$ih = '';
20723
					if (isset($tag['height'])) {
20724
						$ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
20725
					}
20726
					if (($type == 'eps') OR ($type == 'ai')) {
20727
						$this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
20728
					} elseif ($type == 'svg') {
20729
						$this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
20730
					} else {
20731
						$this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
20732
					}
20733
					switch($align) {
20734
						case 'T': {
20735
							$this->y = $prevy;
20736
							break;
20737
						}
20738
						case 'M': {
20739
							$this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
20740
							break;
20741
						}
20742
						case 'B': {
20743
							$this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
20744
							break;
20745
						}
20746
					}
20747
				}
20748
				break;
20749
			}
20750
			case 'dl': {
20751
				++$this->listnum;
20752
				if ($this->listnum == 1) {
20753
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20754
				} else {
20755
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
20756
				}
20757
				break;
20758
			}
20759
			case 'dt': {
20760
				$this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20761
				break;
20762
			}
20763
			case 'dd': {
20764
				if ($this->rtl) {
20765
					$this->rMargin += $this->listindent;
20766
				} else {
20767
					$this->lMargin += $this->listindent;
20768
				}
20769
				++$this->listindentlevel;
20770
				$this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20771
				break;
20772
			}
20773
			case 'ul':
20774
			case 'ol': {
20775
				++$this->listnum;
20776
				if ($tag['value'] == 'ol') {
20777
					$this->listordered[$this->listnum] = true;
20778
				} else {
20779
					$this->listordered[$this->listnum] = false;
20780
				}
20781
				if (isset($tag['attribute']['start'])) {
20782
					$this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
20783
				} else {
20784
					$this->listcount[$this->listnum] = 0;
20785
				}
20786
				if ($this->rtl) {
20787
					$this->rMargin += $this->listindent;
20788
					$this->x -= $this->listindent;
20789
				} else {
20790
					$this->lMargin += $this->listindent;
20791
					$this->x += $this->listindent;
20792
				}
20793
				++$this->listindentlevel;
20794
				if ($this->listnum == 1) {
20795
					$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20796
				} else {
20797
					$this->addHTMLVertSpace(0, 0, $cell, $firsttag);
20798
				}
20799
				break;
20800
			}
20801
			case 'li': {
20802
				$this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20803
				if ($this->listordered[$this->listnum]) {
20804
					// ordered item
20805
					if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
20806
						$this->lispacer = $parent['attribute']['type'];
20807
					} elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
20808
						$this->lispacer = $parent['listtype'];
20809
					} elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
20810
						$this->lispacer = $this->lisymbol;
20811
					} else {
20812
						$this->lispacer = '#';
20813
					}
20814
					++$this->listcount[$this->listnum];
20815
					if (isset($tag['attribute']['value'])) {
20816
						$this->listcount[$this->listnum] = intval($tag['attribute']['value']);
20817
					}
20818
				} else {
20819
					// unordered item
20820
					if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
20821
						$this->lispacer = $parent['attribute']['type'];
20822
					} elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
20823
						$this->lispacer = $parent['listtype'];
20824
					} elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
20825
						$this->lispacer = $this->lisymbol;
20826
					} else {
20827
						$this->lispacer = '!';
20828
					}
20829
				}
20830
				break;
20831
			}
20832
			case 'blockquote': {
20833
				if ($this->rtl) {
20834
					$this->rMargin += $this->listindent;
20835
				} else {
20836
					$this->lMargin += $this->listindent;
20837
				}
20838
				++$this->listindentlevel;
20839
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20840
				break;
20841
			}
20842
			case 'br': {
20843
				$this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20844
				break;
20845
			}
20846
			case 'div': {
20847
				$this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20848
				break;
20849
			}
20850
			case 'p': {
20851
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20852
				break;
20853
			}
20854
			case 'pre': {
20855
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20856
				$this->premode = true;
20857
				break;
20858
			}
20859
			case 'sup': {
20860
				$this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
20861
				break;
20862
			}
20863
			case 'sub': {
20864
				$this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
20865
				break;
20866
			}
20867
			case 'h1':
20868
			case 'h2':
20869
			case 'h3':
20870
			case 'h4':
20871
			case 'h5':
20872
			case 'h6': {
20873
				$this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20874
				break;
20875
			}
20876
			// Form fields (since 4.8.000 - 2009-09-07)
20877
			case 'form': {
20878
				if (isset($tag['attribute']['action'])) {
20879
					$this->form_action = $tag['attribute']['action'];
20880
				} else {
20881
					$this->form_action = K_PATH_URL.$_SERVER['SCRIPT_NAME'];
20882
				}
20883
				if (isset($tag['attribute']['enctype'])) {
20884
					$this->form_enctype = $tag['attribute']['enctype'];
20885
				} else {
20886
					$this->form_enctype = 'application/x-www-form-urlencoded';
20887
				}
20888
				if (isset($tag['attribute']['method'])) {
20889
					$this->form_mode = $tag['attribute']['method'];
20890
				} else {
20891
					$this->form_mode = 'post';
20892
				}
20893
				break;
20894
			}
20895
			case 'input': {
20896
				if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
20897
					$name = $tag['attribute']['name'];
20898
				} else {
20899
					break;
20900
				}
20901
				$prop = array();
20902
				$opt = array();
20903
				if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
20904
					$prop['readonly'] = true;
20905
				}
20906
				if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
20907
					$value = $tag['attribute']['value'];
20908
				}
20909
				if (isset($tag['attribute']['maxlength']) AND !$this->empty_string($tag['attribute']['maxlength'])) {
20910
					$opt['maxlen'] = intval($tag['attribute']['value']);
20911
				}
20912
				$h = $this->FontSize * $this->cell_height_ratio;
20913
				if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
20914
					$w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
20915
				} else {
20916
					$w = $h;
20917
				}
20918
				if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
20919
					$checked = true;
20920
				} else {
20921
					$checked = false;
20922
				}
20923
				switch ($tag['attribute']['type']) {
20924
					case 'text': {
20925
						if (isset($value)) {
20926
							$opt['v'] = $value;
20927
						}
20928
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20929
						break;
20930
					}
20931
					case 'password': {
20932
						if (isset($value)) {
20933
							$opt['v'] = $value;
20934
						}
20935
						$prop['password'] = 'true';
20936
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20937
						break;
20938
					}
20939
					case 'checkbox': {
20940
						$this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
20941
						break;
20942
					}
20943
					case 'radio': {
20944
						$this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
20945
						break;
20946
					}
20947
					case 'submit': {
20948
						$w = $this->GetStringWidth($value) * 1.5;
20949
						$h *= 1.6;
20950
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20951
						$action = array();
20952
						$action['S'] = 'SubmitForm';
20953
						$action['F'] = $this->form_action;
20954
						if ($this->form_enctype != 'FDF') {
20955
							$action['Flags'] = array('ExportFormat');
20956
						}
20957
						if ($this->form_mode == 'get') {
20958
							$action['Flags'] = array('GetMethod');
20959
						}
20960
						$this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
20961
						break;
20962
					}
20963
					case 'reset': {
20964
						$w = $this->GetStringWidth($value) * 1.5;
20965
						$h *= 1.6;
20966
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20967
						$this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
20968
						break;
20969
					}
20970
					case 'file': {
20971
						$prop['fileSelect'] = 'true';
20972
						$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20973
						if (!isset($value)) {
20974
							$value = '*';
20975
						}
20976
						$w = $this->GetStringWidth($value) * 2;
20977
						$h *= 1.2;
20978
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20979
						$jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
20980
						$this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
20981
						break;
20982
					}
20983
					case 'hidden': {
20984
						if (isset($value)) {
20985
							$opt['v'] = $value;
20986
						}
20987
						$opt['f'] = array('invisible', 'hidden');
20988
						$this->TextField($name, 0, 0, $prop, $opt, '', '', false);
20989
						break;
20990
					}
20991
					case 'image': {
20992
						// THIS TYPE MUST BE FIXED
20993
						if (isset($tag['attribute']['src']) AND !$this->empty_string($tag['attribute']['src'])) {
20994
							$img = $tag['attribute']['src'];
20995
						} else {
20996
							break;
20997
						}
20998
						$value = 'img';
20999
						//$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
21000
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
21001
							$jsaction = $tag['attribute']['onclick'];
21002
						} else {
21003
							$jsaction = '';
21004
						}
21005
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
21006
						break;
21007
					}
21008
					case 'button': {
21009
						$w = $this->GetStringWidth($value) * 1.5;
21010
						$h *= 1.6;
21011
						$prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
21012
						if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
21013
							$jsaction = $tag['attribute']['onclick'];
21014
						} else {
21015
							$jsaction = '';
21016
						}
21017
						$this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
21018
						break;
21019
					}
21020
				}
21021
				break;
21022
			}
21023
			case 'textarea': {
21024
				$prop = array();
21025
				$opt = array();
21026
				if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
21027
					$prop['readonly'] = true;
21028
				}
21029
				if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
21030
					$name = $tag['attribute']['name'];
21031
				} else {
21032
					break;
21033
				}
21034
				if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
21035
					$opt['v'] = $tag['attribute']['value'];
21036
				}
21037
				if (isset($tag['attribute']['cols']) AND !$this->empty_string($tag['attribute']['cols'])) {
21038
					$w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
21039
				} else {
21040
					$w = 40;
21041
				}
21042
				if (isset($tag['attribute']['rows']) AND !$this->empty_string($tag['attribute']['rows'])) {
21043
					$h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
21044
				} else {
21045
					$h = 10;
21046
				}
21047
				$prop['multiline'] = 'true';
21048
				$this->TextField($name, $w, $h, $prop, $opt, '', '', false);
21049
				break;
21050
			}
21051
			case 'select': {
21052
				$h = $this->FontSize * $this->cell_height_ratio;
21053
				if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
21054
					$h *= ($tag['attribute']['size'] + 1);
21055
				}
21056
				$prop = array();
21057
				$opt = array();
21058
				if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
21059
					$name = $tag['attribute']['name'];
21060
				} else {
21061
					break;
21062
				}
21063
				$w = 0;
21064
				if (isset($tag['attribute']['opt']) AND !$this->empty_string($tag['attribute']['opt'])) {
21065
					$options = explode('#!NwL!#', $tag['attribute']['opt']);
21066
					$values = array();
21067
					foreach ($options as $val) {
21068
						if (strpos($val, '#!TaB!#') !== false) {
21069
							$opts = explode('#!TaB!#', $val);
21070
							$values[] = $opts;
21071
							$w = max($w, $this->GetStringWidth($opts[1]));
21072
						} else {
21073
							$values[] = $val;
21074
							$w = max($w, $this->GetStringWidth($val));
21075
						}
21076
					}
21077
				} else {
21078
					break;
21079
				}
21080
				$w *= 2;
21081
				if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
21082
					$prop['multipleSelection'] = 'true';
21083
					$this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
21084
				} else {
21085
					$this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
21086
				}
21087
				break;
21088
			}
21089
			case 'tcpdf': {
21090
				if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
21091
					// Special tag used to call TCPDF methods
21092
					if (isset($tag['attribute']['method'])) {
21093
						$tcpdf_method = $tag['attribute']['method'];
21094
						if (method_exists($this, $tcpdf_method)) {
21095
							if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
21096
								$params = unserialize(urldecode($tag['attribute']['params']));
21097
								call_user_func_array(array($this, $tcpdf_method), $params);
21098
							} else {
21099
								$this->$tcpdf_method();
21100
							}
21101
							$this->newline = true;
21102
						}
21103
					}
21104
				}
21105
				break;
21106
			}
21107
			default: {
21108
				break;
21109
			}
21110
		}
21111
		// define tags that support borders and background colors
21112
		$bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
21113
		if (in_array($tag['value'], $bordertags)) {
21114
			// set border
21115
			$dom[$key]['borderposition'] = $this->getBorderStartPosition();
21116
		}
21117
		if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
21118
			$pba = $dom[$key]['attribute']['pagebreakafter'];
21119
			// check for pagebreak
21120
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
21121
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
21122
				$this->checkPageBreak($this->PageBreakTrigger + 1);
21123
			}
21124
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
21125
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
21126
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
21127
				$this->checkPageBreak($this->PageBreakTrigger + 1);
21128
			}
21129
		}
21130
	}
21131
 
21132
	/**
21133
	 * Process closing tags.
21134
	 * @param array $dom html dom array
21135
	 * @param int $key current element id
21136
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
21137
	 * @param int $maxbottomliney maximum y value of current line
21138
	 * @access protected
21139
	 */
21140
	protected function closeHTMLTagHandler(&$dom, $key, $cell, $maxbottomliney=0) {
21141
		$tag = $dom[$key];
21142
		$parent = $dom[($dom[$key]['parent'])];
21143
		$lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
21144
		$in_table_head = false;
21145
		// maximum x position (used to draw borders)
21146
		if ($this->rtl) {
21147
			$xmax = $this->w;
21148
		} else {
21149
			$xmax = 0;
21150
		}
21151
		if ($tag['block']) {
21152
			$hbz = 0; // distance from y to line bottom
21153
			$hb = 0; // vertical space between block tags
21154
			// calculate vertical space for block tags
21155
			if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
21156
				$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
21157
			} elseif (isset($parent['fontsize'])) {
21158
				$pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
21159
			} else {
21160
				$pre_h = $this->FontSize * $this->cell_height_ratio;
21161
			}
21162
			if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
21163
				$n = $this->tagvspaces[$tag['value']][1]['n'];
21164
			} elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
21165
				$n = 0.6;
21166
			} else {
21167
				$n = 1;
21168
			}
21169
			$hb = ($n * $pre_h);
21170
			if ($this->y < $maxbottomliney) {
21171
				$hbz = ($maxbottomliney - $this->y);
21172
			}
21173
		}
21174
		// Closing tag
21175
		switch($tag['value']) {
21176
			case 'tr': {
21177
				$table_el = $dom[($dom[$key]['parent'])]['parent'];
21178
				if (!isset($parent['endy'])) {
21179
					$dom[($dom[$key]['parent'])]['endy'] = $this->y;
21180
					$parent['endy'] = $this->y;
21181
				}
21182
				if (!isset($parent['endpage'])) {
21183
					$dom[($dom[$key]['parent'])]['endpage'] = $this->page;
21184
					$parent['endpage'] = $this->page;
21185
				}
21186
				if (!isset($parent['endcolumn'])) {
21187
					$dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
21188
					$parent['endcolumn'] = $this->current_column;
21189
				}
21190
				// update row-spanned cells
21191
				if (isset($dom[$table_el]['rowspans'])) {
21192
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
21193
						$dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
21194
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
21195
							if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
21196
								$dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
21197
							} elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
21198
								$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
21199
								$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
21200
								$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
21201
							}
21202
						}
21203
					}
21204
					// report new endy and endpage to the rowspanned cells
21205
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
21206
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
21207
							$dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
21208
							$dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
21209
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
21210
							$dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
21211
							$dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
21212
							$dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
21213
						}
21214
					}
21215
					// update remaining rowspanned cells
21216
					foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
21217
						if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
21218
							$dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
21219
							$dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
21220
							$dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
21221
						}
21222
					}
21223
				}
21224
				$this->setPage($dom[($dom[$key]['parent'])]['endpage']);
21225
				if ($this->num_columns > 1) {
21226
					$this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
21227
				}
21228
				$this->y = $dom[($dom[$key]['parent'])]['endy'];
21229
				if (isset($dom[$table_el]['attribute']['cellspacing'])) {
21230
					$this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
21231
				} elseif (isset($dom[$table_el]['border-spacing'])) {
21232
					$this->y += $dom[$table_el]['border-spacing']['V'];
21233
				}
21234
				$this->Ln(0, $cell);
21235
				if ($this->current_column == $parent['startcolumn']) {
21236
					$this->x = $parent['startx'];
21237
				}
21238
				// account for booklet mode
21239
				if ($this->page > $parent['startpage']) {
21240
					if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
21241
						$this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
21242
					} elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
21243
						$this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
21244
					}
21245
				}
21246
				break;
21247
			}
21248
			case 'tablehead':
21249
				// closing tag used for the thead part
21250
				$in_table_head = true;
21251
				$this->inthead = false;
21252
			case 'table': {
21253
				$table_el = $parent;
21254
				// set default border
21255
				if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
21256
					// set default border
21257
					$border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
21258
				} else {
21259
					$border = 0;
21260
				}
21261
				$default_border = $border;
21262
				// fix bottom line alignment of last line before page break
21263
				foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
21264
					// update row-spanned cells
21265
					if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
21266
						foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
21267
							if ($trwsp['trid'] == $trkey) {
21268
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
21269
							}
21270
							if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0)) {
21271
								$dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
21272
							}
21273
						}
21274
					}
21275
					if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
21276
						$pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
21277
						$dom[$prevtrkey]['endy'] = $pgendy;
21278
						// update row-spanned cells
21279
						if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
21280
							foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
21281
								if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
21282
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
21283
									$dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
21284
								}
21285
							}
21286
						}
21287
					}
21288
					$prevtrkey = $trkey;
21289
					$table_el = $dom[($dom[$key]['parent'])];
21290
				}
21291
				// for each row
21292
				unset($xmax);
21293
				foreach ($table_el['trids'] as $j => $trkey) {
21294
					$parent = $dom[$trkey];
21295
					if (!isset($xmax)) {
21296
						$xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
21297
					}
21298
					// for each cell on the row
21299
					foreach ($parent['cellpos'] as $k => $cellpos) {
21300
						if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
21301
							$cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
21302
							$cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
21303
							$endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
21304
							$startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
21305
							$endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
21306
							$startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
21307
							$endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
21308
						} else {
21309
							$endy = $parent['endy'];
21310
							$startpage = $parent['startpage'];
21311
							$endpage = $parent['endpage'];
21312
							$startcolumn = $parent['startcolumn'];
21313
							$endcolumn = $parent['endcolumn'];
21314
						}
21315
						if ($this->num_columns == 0) {
21316
							$this->num_columns = 1;
21317
						}
21318
						if (isset($cellpos['border'])) {
21319
							$border = $cellpos['border'];
21320
						}
21321
						if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
21322
							$this->SetFillColorArray($cellpos['bgcolor']);
21323
							$fill = true;
21324
						} else {
21325
							$fill = false;
21326
						}
21327
						$x = $cellpos['startx'];
21328
						$y = $parent['starty'];
21329
						$starty = $y;
21330
						$w = abs($cellpos['endx'] - $cellpos['startx']);
21331
						// get border modes
21332
						$border_start = $this->getBorderMode($border, $position='start');
21333
						$border_end = $this->getBorderMode($border, $position='end');
21334
						$border_middle = $this->getBorderMode($border, $position='middle');
21335
						// design borders around HTML cells.
21336
						for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
21337
							$ccode = '';
21338
							$this->setPage($page);
21339
							if ($this->num_columns < 2) {
21340
								// single-column mode
21341
								$this->x = $x;
21342
								$this->y = $this->tMargin;
21343
							}
21344
							// account for margin changes
21345
							if ($page > $startpage) {
21346
								if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
21347
									$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
21348
								} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
21349
									$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
21350
								}
21351
							}
21352
							if ($startpage == $endpage) { // single page
21353
								$deltacol = 0;
21354
								$deltath = 0;
21355
								for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
21356
									$this->selectColumn($column);
21357
									if ($startcolumn == $endcolumn) { // single column
21358
										$cborder = $border;
21359
										$h = $endy - $parent['starty'];
21360
										$this->y = $y;
21361
										$this->x = $x;
21362
									} elseif ($column == $startcolumn) { // first column
21363
										$cborder = $border_start;
21364
										$this->y = $starty;
21365
										$this->x = $x;
21366
										$h = $this->h - $this->y - $this->bMargin;
21367
										if ($this->rtl) {
21368
											$deltacol = $this->x + $this->rMargin - $this->w;
21369
										} else {
21370
											$deltacol = $this->x - $this->lMargin;
21371
										}
21372
									} elseif ($column == $endcolumn) { // end column
21373
										$cborder = $border_end;
21374
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21375
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21376
										}
21377
										$this->x += $deltacol;
21378
										$h = $endy - $this->y;
21379
									} else { // middle column
21380
										$cborder = $border_middle;
21381
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21382
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21383
										}
21384
										$this->x += $deltacol;
21385
										$h = $this->h - $this->y - $this->bMargin;
21386
									}
21387
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21388
								} // end for each column
21389
							} elseif ($page == $startpage) { // first page
21390
								$deltacol = 0;
21391
								$deltath = 0;
21392
								for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
21393
									$this->selectColumn($column);
21394
									if ($column == $startcolumn) { // first column
21395
										$cborder = $border_start;
21396
										$this->y = $starty;
21397
										$this->x = $x;
21398
										$h = $this->h - $this->y - $this->bMargin;
21399
										if ($this->rtl) {
21400
											$deltacol = $this->x + $this->rMargin - $this->w;
21401
										} else {
21402
											$deltacol = $this->x - $this->lMargin;
21403
										}
21404
									} else { // middle column
21405
										$cborder = $border_middle;
21406
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21407
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21408
										}
21409
										$this->x += $deltacol;
21410
										$h = $this->h - $this->y - $this->bMargin;
21411
									}
21412
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21413
								} // end for each column
21414
							} elseif ($page == $endpage) { // last page
21415
								$deltacol = 0;
21416
								$deltath = 0;
21417
								for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
21418
									$this->selectColumn($column);
21419
									if ($column == $endcolumn) { // end column
21420
										$cborder = $border_end;
21421
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21422
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21423
										}
21424
										$this->x += $deltacol;
21425
										$h = $endy - $this->y;
21426
									} else { // middle column
21427
										$cborder = $border_middle;
21428
										if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21429
											$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21430
										}
21431
										$this->x += $deltacol;
21432
										$h = $this->h - $this->y - $this->bMargin;
21433
									}
21434
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21435
								} // end for each column
21436
							} else { // middle page
21437
								$deltacol = 0;
21438
								$deltath = 0;
21439
								for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
21440
									$this->selectColumn($column);
21441
									$cborder = $border_middle;
21442
									if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21443
										$this->y = $this->columns[$column]['th']['\''.$page.'\''];
21444
									}
21445
									$this->x += $deltacol;
21446
									$h = $this->h - $this->y - $this->bMargin;
21447
									$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21448
								} // end for each column
21449
							}
21450
							if ($cborder OR $fill) {
21451
								// draw border and fill
21452
								if ($this->inxobj) {
21453
									// we are inside an XObject template
21454
									if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
21455
										$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
21456
										$pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
21457
									} else {
21458
										$pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
21459
									}
21460
									$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
21461
									$pstart = substr($pagebuff, 0, $pagemark);
21462
									$pend = substr($pagebuff, $pagemark);
21463
									$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
21464
									$pagemark += strlen($ccode);
21465
								} else {
21466
									// draw border and fill
21467
									if (end($this->transfmrk[$this->page]) !== false) {
21468
										$pagemarkkey = key($this->transfmrk[$this->page]);
21469
										$pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
21470
									} elseif ($this->InFooter) {
21471
										$pagemark = &$this->footerpos[$this->page];
21472
									} else {
21473
										$pagemark = &$this->intmrk[$this->page];
21474
									}
21475
									$pagebuff = $this->getPageBuffer($this->page);
21476
									$pstart = substr($pagebuff, 0, $pagemark);
21477
									$pend = substr($pagebuff, $pagemark);
21478
									$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
21479
									$pagemark += strlen($ccode);
21480
								}
21481
							}
21482
						} // end for each page
21483
						// restore default border
21484
						$border = $default_border;
21485
					} // end for each cell on the row
21486
					if (isset($table_el['attribute']['cellspacing'])) {
21487
						$this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
21488
					} elseif (isset($table_el['border-spacing'])) {
21489
						$this->y += $table_el['border-spacing']['V'];
21490
					}
21491
					$this->Ln(0, $cell);
21492
					$this->x = $parent['startx'];
21493
					if ($endpage > $startpage) {
21494
						if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
21495
							$this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
21496
						} elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
21497
							$this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
21498
						}
21499
					}
21500
				}
21501
				if (!$in_table_head) { // we are not inside a thead section
21502
					$this->cell_padding = $table_el['old_cell_padding'];
21503
					// reset row height
21504
					$this->resetLastH();
21505
					if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages]) AND ($this->emptypagemrk[$this->numpages] == $this->pagelen[$this->numpages])) {
21506
						// remove last blank page
21507
						$this->deletePage($this->numpages);
21508
					}
21509
					if (isset($this->theadMargins['top'])) {
21510
						// restore top margin
21511
						$this->tMargin = $this->theadMargins['top'];
21512
						$this->pagedim[$this->page]['tm'] = $this->tMargin;
21513
					}
21514
					if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
21515
						// reset main table header
21516
						$this->thead = '';
21517
						$this->theadMargins = array();
21518
					}
21519
				}
21520
				$parent = $table_el;
21521
				break;
21522
			}
21523
			case 'a': {
21524
				$this->HREF = '';
21525
				break;
21526
			}
21527
			case 'sup': {
21528
				$this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
21529
				break;
21530
			}
21531
			case 'sub': {
21532
				$this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize'])/$this->k));
21533
				break;
21534
			}
21535
			case 'div': {
21536
				$this->addHTMLVertSpace($hbz, 0, $cell, false, $lasttag);
21537
				break;
21538
			}
21539
			case 'blockquote': {
21540
				if ($this->rtl) {
21541
					$this->rMargin -= $this->listindent;
21542
				} else {
21543
					$this->lMargin -= $this->listindent;
21544
				}
21545
				--$this->listindentlevel;
21546
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21547
				break;
21548
			}
21549
			case 'p': {
21550
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21551
				break;
21552
			}
21553
			case 'pre': {
21554
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21555
				$this->premode = false;
21556
				break;
21557
			}
21558
			case 'dl': {
21559
				--$this->listnum;
21560
				if ($this->listnum <= 0) {
21561
					$this->listnum = 0;
21562
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21563
				} else {
21564
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21565
				}
21566
				$this->resetLastH();
21567
				break;
21568
			}
21569
			case 'dt': {
21570
				$this->lispacer = '';
21571
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21572
				break;
21573
			}
21574
			case 'dd': {
21575
				$this->lispacer = '';
21576
				if ($this->rtl) {
21577
					$this->rMargin -= $this->listindent;
21578
				} else {
21579
					$this->lMargin -= $this->listindent;
21580
				}
21581
				--$this->listindentlevel;
21582
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21583
				break;
21584
			}
21585
			case 'ul':
21586
			case 'ol': {
21587
				--$this->listnum;
21588
				$this->lispacer = '';
21589
				if ($this->rtl) {
21590
					$this->rMargin -= $this->listindent;
21591
				} else {
21592
					$this->lMargin -= $this->listindent;
21593
				}
21594
				--$this->listindentlevel;
21595
				if ($this->listnum <= 0) {
21596
					$this->listnum = 0;
21597
					$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21598
				} else {
21599
					$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21600
				}
21601
				$this->resetLastH();
21602
				break;
21603
			}
21604
			case 'li': {
21605
				$this->lispacer = '';
21606
				$this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21607
				break;
21608
			}
21609
			case 'h1':
21610
			case 'h2':
21611
			case 'h3':
21612
			case 'h4':
21613
			case 'h5':
21614
			case 'h6': {
21615
				$this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21616
				break;
21617
			}
21618
			// Form fields (since 4.8.000 - 2009-09-07)
21619
			case 'form': {
21620
				$this->form_action = '';
21621
				$this->form_enctype = 'application/x-www-form-urlencoded';
21622
				break;
21623
			}
21624
			default : {
21625
				break;
21626
			}
21627
		}
21628
		// draw border and background (if any)
21629
		$this->drawHTMLTagBorder($parent, $xmax);
21630
		if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
21631
			$pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
21632
			// check for pagebreak
21633
			if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
21634
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
21635
				$this->checkPageBreak($this->PageBreakTrigger + 1);
21636
			}
21637
			if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
21638
				OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
21639
				// add a page (or trig AcceptPageBreak() for multicolumn mode)
21640
				$this->checkPageBreak($this->PageBreakTrigger + 1);
21641
			}
21642
		}
21643
		$this->tmprtl = false;
21644
	}
21645
 
21646
	/**
21647
	 * Add vertical spaces if needed.
21648
	 * @param string $hbz Distance between current y and line bottom.
21649
	 * @param string $hb The height of the break.
21650
	 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
21651
	 * @param boolean $firsttag set to true when the tag is the first.
21652
	 * @param boolean $lasttag set to true when the tag is the last.
21653
	 * @access protected
21654
	 */
21655
	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
21656
		if ($firsttag) {
21657
			$this->Ln(0, $cell);
21658
			$this->htmlvspace = 0;
21659
			return;
21660
		}
21661
		if ($lasttag) {
21662
			$this->Ln($hbz, $cell);
21663
			$this->htmlvspace = 0;
21664
			return;
21665
		}
21666
		if ($hb < $this->htmlvspace) {
21667
			$hd = 0;
21668
		} else {
21669
			$hd = $hb - $this->htmlvspace;
21670
			$this->htmlvspace = $hb;
21671
		}
21672
		$this->Ln(($hbz + $hd), $cell);
21673
	}
21674
 
21675
	/**
21676
	 * Return the starting coordinates to draw an html border
21677
	 * @return array containing top-left border coordinates
21678
	 * @access protected
21679
	 * @since 5.7.000 (2010-08-03)
21680
	 */
21681
	protected function getBorderStartPosition() {
21682
		if ($this->rtl) {
21683
			$xmax = $this->lMargin;
21684
		} else {
21685
			$xmax = $this->w - $this->rMargin;
21686
		}
21687
		return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
21688
	}
21689
 
21690
	/**
21691
	 * Draw an HTML block border and fill
21692
	 * @param array $tag array of tag properties.
21693
	 * @param int $xmax end X coordinate for border.
21694
	 * @access protected
21695
	 * @since 5.7.000 (2010-08-03)
21696
	 */
21697
	protected function drawHTMLTagBorder($tag, $xmax) {
21698
		if (!isset($tag['borderposition'])) {
21699
			// nothing to draw
21700
			return;
21701
		}
21702
		$prev_x = $this->x;
21703
		$prev_y = $this->y;
21704
		$prev_lasth = $this->lasth;
21705
		$border = 0;
21706
		$fill = false;
21707
		$this->lasth = 0;
21708
		if (isset($tag['border']) AND !empty($tag['border'])) {
21709
			// get border style
21710
			$border = $tag['border'];
21711
			if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
21712
				// border for table header
21713
				$border = $this->getBorderMode($border, $position='middle');
21714
			}
21715
		}
21716
		if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
21717
			// get background color
21718
			$old_bgcolor = $this->bgcolor;
21719
			$this->SetFillColorArray($tag['bgcolor']);
21720
			$fill = true;
21721
		}
21722
		if (!$border AND !$fill) {
21723
			// nothing to draw
21724
			return;
21725
		}
21726
		if (isset($tag['attribute']['cellspacing'])) {
21727
			$clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
21728
			$cellspacing = array('H' => $clsp, 'V' => $clsp);
21729
		} elseif (isset($tag['border-spacing'])) {
21730
			$cellspacing = $tag['border-spacing'];
21731
		} else {
21732
			$cellspacing = array('H' => 0, 'V' => 0);
21733
		}
21734
		if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
21735
			// draw the border externally respect the sqare edge.
21736
			$border['mode'] = 'ext';
21737
		}
21738
		if ($this->rtl) {
21739
			if ($xmax >= $tag['borderposition']['x']) {
21740
				$xmax = $tag['borderposition']['xmax'];
21741
			}
21742
			$w = ($tag['borderposition']['x'] - $xmax);
21743
		} else {
21744
			if ($xmax <= $tag['borderposition']['x']) {
21745
				$xmax = $tag['borderposition']['xmax'];
21746
			}
21747
			$w = ($xmax - $tag['borderposition']['x']);
21748
		}
21749
		if ($w <= 0) {
21750
			return;
21751
		}
21752
		$w += $cellspacing['H'];
21753
		$startpage = $tag['borderposition']['page'];
21754
		$startcolumn = $tag['borderposition']['column'];
21755
		$x = $tag['borderposition']['x'];
21756
		$y = $tag['borderposition']['y'];
21757
		$endpage = $this->page;
21758
		$starty = $tag['borderposition']['y'] - $cellspacing['V'];
21759
		$currentY = $this->y;
21760
		$this->x = $x;
21761
		// get latest column
21762
		$endcolumn = $this->current_column;
21763
		if ($this->num_columns == 0) {
21764
			$this->num_columns = 1;
21765
		}
21766
		// get border modes
21767
		$border_start = $this->getBorderMode($border, $position='start');
21768
		$border_end = $this->getBorderMode($border, $position='end');
21769
		$border_middle = $this->getBorderMode($border, $position='middle');
21770
		// design borders around HTML cells.
21771
		for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
21772
			$ccode = '';
21773
			$this->setPage($page);
21774
			if ($this->num_columns < 2) {
21775
				// single-column mode
21776
				$this->x = $x;
21777
				$this->y = $this->tMargin;
21778
			}
21779
			// account for margin changes
21780
			if ($page > $startpage) {
21781
				if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
21782
					$this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
21783
				} elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
21784
					$this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
21785
				}
21786
			}
21787
			if ($startpage == $endpage) {
21788
				// single page
21789
				for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
21790
					$this->selectColumn($column);
21791
					if ($startcolumn == $endcolumn) { // single column
21792
						$cborder = $border;
21793
						$h = ($currentY - $y) + $cellspacing['V'];
21794
						$this->y = $starty;
21795
					} elseif ($column == $startcolumn) { // first column
21796
						$cborder = $border_start;
21797
						$this->y = $starty;
21798
						$h = $this->h - $this->y - $this->bMargin;
21799
					} elseif ($column == $endcolumn) { // end column
21800
						$cborder = $border_end;
21801
						$h = $currentY - $this->y;
21802
					} else { // middle column
21803
						$cborder = $border_middle;
21804
						$h = $this->h - $this->y - $this->bMargin;
21805
					}
21806
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21807
				} // end for each column
21808
			} elseif ($page == $startpage) { // first page
21809
				for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
21810
					$this->selectColumn($column);
21811
					if ($column == $startcolumn) { // first column
21812
						$cborder = $border_start;
21813
						$this->y = $starty;
21814
						$h = $this->h - $this->y - $this->bMargin;
21815
					} else { // middle column
21816
						$cborder = $border_middle;
21817
						$h = $this->h - $this->y - $this->bMargin;
21818
					}
21819
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21820
				} // end for each column
21821
			} elseif ($page == $endpage) { // last page
21822
				for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
21823
					$this->selectColumn($column);
21824
					if ($column == $endcolumn) {
21825
						// end column
21826
						$cborder = $border_end;
21827
						$h = $currentY - $this->y;
21828
					} else {
21829
						// middle column
21830
						$cborder = $border_middle;
21831
						$h = $this->h - $this->y - $this->bMargin;
21832
					}
21833
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21834
				} // end for each column
21835
			} else { // middle page
21836
				for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
21837
					$this->selectColumn($column);
21838
					$cborder = $border_middle;
21839
					$h = $this->h - $this->y - $this->bMargin;
21840
					$ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21841
				} // end for each column
21842
			}
21843
			if ($cborder OR $fill) {
21844
				// draw border and fill
21845
				if ($this->inxobj) {
21846
					// we are inside an XObject template
21847
					if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
21848
						$pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
21849
						$pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
21850
					} else {
21851
						$pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
21852
					}
21853
					$pagebuff = $this->xobjects[$this->xobjid]['outdata'];
21854
					$pstart = substr($pagebuff, 0, $pagemark);
21855
					$pend = substr($pagebuff, $pagemark);
21856
					$this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
21857
					$pagemark += strlen($ccode);
21858
				} else {
21859
					if (end($this->transfmrk[$this->page]) !== false) {
21860
						$pagemarkkey = key($this->transfmrk[$this->page]);
21861
						$pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
21862
					} elseif ($this->InFooter) {
21863
						$pagemark = &$this->footerpos[$this->page];
21864
					} else {
21865
						$pagemark = &$this->intmrk[$this->page];
21866
					}
21867
					$pagebuff = $this->getPageBuffer($this->page);
21868
					$pstart = substr($pagebuff, 0, $this->bordermrk[$this->page]);
21869
					$pend = substr($pagebuff, $this->bordermrk[$this->page]);
21870
					$this->setPageBuffer($this->page, $pstart.$ccode.$pend);
21871
					$offsetlen = strlen($ccode);
21872
					$this->bordermrk[$this->page] += $offsetlen;
21873
					$this->cntmrk[$this->page] += $offsetlen;
21874
					$pagemark += $offsetlen;
21875
				}
21876
			}
21877
		} // end for each page
21878
		if (isset($old_bgcolor)) {
21879
			// restore background color
21880
			$this->SetFillColorArray($old_bgcolor);
21881
		}
21882
		// restore pointer position
21883
		$this->x = $prev_x;
21884
		$this->y = $prev_y;
21885
		$this->lasth = $prev_lasth;
21886
	}
21887
 
21888
	/**
21889
	 * Set the default bullet to be used as LI bullet symbol
21890
	 * @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')
21891
	 * @access public
21892
	 * @since 4.0.028 (2008-09-26)
21893
	 */
21894
	public function setLIsymbol($symbol='!') {
21895
		// check for custom image symbol
21896
		if (substr($symbol, 0, 4) == 'img|') {
21897
			$this->lisymbol = $symbol;
21898
			return;
21899
		}
21900
		$symbol = strtolower($symbol);
21901
		switch ($symbol) {
21902
			case '!' :
21903
			case '#' :
21904
			case 'disc' :
21905
			case 'circle' :
21906
			case 'square' :
21907
			case '1':
21908
			case 'decimal':
21909
			case 'decimal-leading-zero':
21910
			case 'i':
21911
			case 'lower-roman':
21912
			case 'I':
21913
			case 'upper-roman':
21914
			case 'a':
21915
			case 'lower-alpha':
21916
			case 'lower-latin':
21917
			case 'A':
21918
			case 'upper-alpha':
21919
			case 'upper-latin':
21920
			case 'lower-greek': {
21921
				$this->lisymbol = $symbol;
21922
				break;
21923
			}
21924
			default : {
21925
				$this->lisymbol = '';
21926
			}
21927
		}
21928
	}
21929
 
21930
	/**
21931
	 * Set the booklet mode for double-sided pages.
21932
	 * @param boolean $booklet true set the booklet mode on, false otherwise.
21933
	 * @param float $inner Inner page margin.
21934
	 * @param float $outer Outer page margin.
21935
	 * @access public
21936
	 * @since 4.2.000 (2008-10-29)
21937
	 */
21938
	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
21939
		$this->booklet = $booklet;
21940
		if ($inner >= 0) {
21941
			$this->lMargin = $inner;
21942
		}
21943
		if ($outer >= 0) {
21944
			$this->rMargin = $outer;
21945
		}
21946
	}
21947
 
21948
	/**
21949
	 * Swap the left and right margins.
21950
	 * @param boolean $reverse if true swap left and right margins.
21951
	 * @access protected
21952
	 * @since 4.2.000 (2008-10-29)
21953
	 */
21954
	protected function swapMargins($reverse=true) {
21955
		if ($reverse) {
21956
			// swap left and right margins
21957
			$mtemp = $this->original_lMargin;
21958
			$this->original_lMargin = $this->original_rMargin;
21959
			$this->original_rMargin = $mtemp;
21960
			$deltam = $this->original_lMargin - $this->original_rMargin;
21961
			$this->lMargin += $deltam;
21962
			$this->rMargin -= $deltam;
21963
		}
21964
	}
21965
 
21966
	/**
21967
	 * Set the vertical spaces for HTML tags.
21968
	 * The array must have the following structure (example):
21969
	 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
21970
	 * The first array level contains the tag names,
21971
	 * the second level contains 0 for opening tags or 1 for closing tags,
21972
	 * the third level contains the vertical space unit (h) and the number spaces to add (n).
21973
	 * If the h parameter is not specified, default values are used.
21974
	 * @param array $tagvs array of tags and relative vertical spaces.
21975
	 * @access public
21976
	 * @since 4.2.001 (2008-10-30)
21977
	 */
21978
	public function setHtmlVSpace($tagvs) {
21979
		$this->tagvspaces = $tagvs;
21980
	}
21981
 
21982
	/**
21983
	 * Set custom width for list indentation.
21984
	 * @param float $width width of the indentation. Use negative value to disable it.
21985
	 * @access public
21986
	 * @since 4.2.007 (2008-11-12)
21987
	 */
21988
	public function setListIndentWidth($width) {
21989
		return $this->customlistindent = floatval($width);
21990
	}
21991
 
21992
	/**
21993
	 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
21994
	 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
21995
	 * @access public
21996
	 * @since 4.2.010 (2008-11-14)
21997
	 */
21998
	public function setOpenCell($isopen) {
21999
		$this->opencell = $isopen;
22000
	}
22001
 
22002
	/**
22003
	 * Set the color and font style for HTML links.
22004
	 * @param array $color RGB array of colors
22005
	 * @param string $fontstyle additional font styles to add
22006
	 * @access public
22007
	 * @since 4.4.003 (2008-12-09)
22008
	 */
22009
	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
22010
		$this->htmlLinkColorArray = $color;
22011
		$this->htmlLinkFontStyle = $fontstyle;
22012
	}
22013
 
22014
	/**
22015
	 * Convert HTML string containing value and unit of measure to user's units or points.
22016
	 * @param string $htmlval string containing values and unit
22017
	 * @param string $refsize reference value in points
22018
	 * @param string $defaultunit default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
22019
	 * @param boolean $point if true returns points, otherwise returns value in user's units
22020
	 * @return float value in user's unit or point if $points=true
22021
	 * @access public
22022
	 * @since 4.4.004 (2008-12-10)
22023
	 */
22024
	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
22025
		$supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
22026
		$retval = 0;
22027
		$value = 0;
22028
		$unit = 'px';
22029
		$k = $this->k;
22030
		if ($points) {
22031
			$k = 1;
22032
		}
22033
		if (in_array($defaultunit, $supportedunits)) {
22034
			$unit = $defaultunit;
22035
		}
22036
		if (is_numeric($htmlval)) {
22037
			$value = floatval($htmlval);
22038
		} elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
22039
			$value = floatval($mnum[1]);
22040
			if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
22041
				if (in_array($munit[1], $supportedunits)) {
22042
					$unit = $munit[1];
22043
				}
22044
			}
22045
		}
22046
		switch ($unit) {
22047
			// percentage
22048
			case '%': {
22049
				$retval = (($value * $refsize) / 100);
22050
				break;
22051
			}
22052
			// relative-size
22053
			case 'em': {
22054
				$retval = ($value * $refsize);
22055
				break;
22056
			}
22057
			// height of lower case 'x' (about half the font-size)
22058
			case 'ex': {
22059
				$retval = $value * ($refsize / 2);
22060
				break;
22061
			}
22062
			// absolute-size
22063
			case 'in': {
22064
				$retval = ($value * $this->dpi) / $k;
22065
				break;
22066
			}
22067
			// centimeters
22068
			case 'cm': {
22069
				$retval = ($value / 2.54 * $this->dpi) / $k;
22070
				break;
22071
			}
22072
			// millimeters
22073
			case 'mm': {
22074
				$retval = ($value / 25.4 * $this->dpi) / $k;
22075
				break;
22076
			}
22077
			// one pica is 12 points
22078
			case 'pc': {
22079
				$retval = ($value * 12) / $k;
22080
				break;
22081
			}
22082
			// points
22083
			case 'pt': {
22084
				$retval = $value / $k;
22085
				break;
22086
			}
22087
			// pixels
22088
			case 'px': {
22089
				$retval = $this->pixelsToUnits($value);
22090
				break;
22091
			}
22092
		}
22093
		return $retval;
22094
	}
22095
 
22096
	/**
22097
	 * Returns the Roman representation of an integer number
22098
	 * @param int number to convert
22099
	 * @return string roman representation of the specified number
22100
	 * @access public
22101
	 * @since 4.4.004 (2008-12-10)
22102
	 */
22103
	public function intToRoman($number) {
22104
		$roman = '';
22105
		while ($number >= 1000) {
22106
			$roman .= 'M';
22107
			$number -= 1000;
22108
		}
22109
		while ($number >= 900) {
22110
			$roman .= 'CM';
22111
			$number -= 900;
22112
		}
22113
		while ($number >= 500) {
22114
			$roman .= 'D';
22115
			$number -= 500;
22116
		}
22117
		while ($number >= 400) {
22118
			$roman .= 'CD';
22119
			$number -= 400;
22120
		}
22121
		while ($number >= 100) {
22122
			$roman .= 'C';
22123
			$number -= 100;
22124
		}
22125
		while ($number >= 90) {
22126
			$roman .= 'XC';
22127
			$number -= 90;
22128
		}
22129
		while ($number >= 50) {
22130
			$roman .= 'L';
22131
			$number -= 50;
22132
		}
22133
		while ($number >= 40) {
22134
			$roman .= 'XL';
22135
			$number -= 40;
22136
		}
22137
		while ($number >= 10) {
22138
			$roman .= 'X';
22139
			$number -= 10;
22140
		}
22141
		while ($number >= 9) {
22142
			$roman .= 'IX';
22143
			$number -= 9;
22144
		}
22145
		while ($number >= 5) {
22146
			$roman .= 'V';
22147
			$number -= 5;
22148
		}
22149
		while ($number >= 4) {
22150
			$roman .= 'IV';
22151
			$number -= 4;
22152
		}
22153
		while ($number >= 1) {
22154
			$roman .= 'I';
22155
			--$number;
22156
		}
22157
		return $roman;
22158
	}
22159
 
22160
	/**
22161
	 * Output an HTML list bullet or ordered item symbol
22162
	 * @param int $listdepth list nesting level
22163
	 * @param string $listtype type of list
22164
	 * @param float $size current font size
22165
	 * @access protected
22166
	 * @since 4.4.004 (2008-12-10)
22167
	 */
22168
	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
22169
		$size /= $this->k;
22170
		$fill = '';
22171
		$color = $this->fgcolor;
22172
		$width = 0;
22173
		$textitem = '';
22174
		$tmpx = $this->x;
22175
		$lspace = $this->GetStringWidth('  ');
22176
		if ($listtype == '^') {
22177
			// special symbol used for avoid justification of rect bullet
22178
			$this->lispacer = '';
22179
			return;
22180
		} elseif ($listtype == '!') {
22181
			// set default list type for unordered list
22182
			$deftypes = array('disc', 'circle', 'square');
22183
			$listtype = $deftypes[($listdepth - 1) % 3];
22184
		} elseif ($listtype == '#') {
22185
			// set default list type for ordered list
22186
			$listtype = 'decimal';
22187
		} elseif(substr($listtype, 0, 4) == 'img|') {
22188
			// custom image type ('img|type|width|height|image.ext')
22189
			$img = explode('|', $listtype);
22190
			$listtype = 'img';
22191
		}
22192
		switch ($listtype) {
22193
			// unordered types
22194
			case 'none': {
22195
				break;
22196
			}
22197
			case 'disc': {
22198
				$fill = 'F';
22199
			}
22200
			case 'circle': {
22201
				$fill .= 'D';
22202
				$r = $size / 6;
22203
				$lspace += (2 * $r);
22204
				if ($this->rtl) {
22205
					$this->x += $lspace;
22206
				} else {
22207
					$this->x -= $lspace;
22208
				}
22209
				$this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, $fill, array('color'=>$color), $color, 8);
22210
				break;
22211
			}
22212
			case 'square': {
22213
				$l = $size / 3;
22214
				$lspace += $l;
22215
				if ($this->rtl) {;
22216
					$this->x += $lspace;
22217
				} else {
22218
					$this->x -= $lspace;
22219
				}
22220
				$this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
22221
				break;
22222
			}
22223
			case 'img': {
22224
				// 1=>type, 2=>width, 3=>height, 4=>image.ext
22225
				$lspace += $img[2];
22226
				if ($this->rtl) {;
22227
					$this->x += $lspace;
22228
				} else {
22229
					$this->x -= $lspace;
22230
				}
22231
				$imgtype = strtolower($img[1]);
22232
				$prev_y = $this->y;
22233
				switch ($imgtype) {
22234
					case 'svg': {
22235
						$this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
22236
						break;
22237
					}
22238
					case 'ai':
22239
					case 'eps': {
22240
						$this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
22241
						break;
22242
					}
22243
					default: {
22244
						$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);
22245
						break;
22246
					}
22247
				}
22248
				$this->y = $prev_y;
22249
				break;
22250
			}
22251
			// ordered types
22252
			// $this->listcount[$this->listnum];
22253
			// $textitem
22254
			case '1':
22255
			case 'decimal': {
22256
				$textitem = $this->listcount[$this->listnum];
22257
				break;
22258
			}
22259
			case 'decimal-leading-zero': {
22260
				$textitem = sprintf('%02d', $this->listcount[$this->listnum]);
22261
				break;
22262
			}
22263
			case 'i':
22264
			case 'lower-roman': {
22265
				$textitem = strtolower($this->intToRoman($this->listcount[$this->listnum]));
22266
				break;
22267
			}
22268
			case 'I':
22269
			case 'upper-roman': {
22270
				$textitem = $this->intToRoman($this->listcount[$this->listnum]);
22271
				break;
22272
			}
22273
			case 'a':
22274
			case 'lower-alpha':
22275
			case 'lower-latin': {
22276
				$textitem = chr(97 + $this->listcount[$this->listnum] - 1);
22277
				break;
22278
			}
22279
			case 'A':
22280
			case 'upper-alpha':
22281
			case 'upper-latin': {
22282
				$textitem = chr(65 + $this->listcount[$this->listnum] - 1);
22283
				break;
22284
			}
22285
			case 'lower-greek': {
22286
				$textitem = $this->unichr(945 + $this->listcount[$this->listnum] - 1);
22287
				break;
22288
			}
22289
			/*
22290
			// Types to be implemented (special handling)
22291
			case 'hebrew': {
22292
				break;
22293
			}
22294
			case 'armenian': {
22295
				break;
22296
			}
22297
			case 'georgian': {
22298
				break;
22299
			}
22300
			case 'cjk-ideographic': {
22301
				break;
22302
			}
22303
			case 'hiragana': {
22304
				break;
22305
			}
22306
			case 'katakana': {
22307
				break;
22308
			}
22309
			case 'hiragana-iroha': {
22310
				break;
22311
			}
22312
			case 'katakana-iroha': {
22313
				break;
22314
			}
22315
			*/
22316
			default: {
22317
				$textitem = $this->listcount[$this->listnum];
22318
			}
22319
		}
22320
		if (!$this->empty_string($textitem)) {
22321
			// Check whether we need a new page or new column
22322
			$prev_y = $this->y;
22323
			$h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
22324
			if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
22325
				$tmpx = $this->x;
22326
			}
22327
			// print ordered item
22328
			if ($this->rtl) {
22329
				$textitem = '.'.$textitem;
22330
			} else {
22331
				$textitem = $textitem.'.';
22332
			}
22333
			$lspace += $this->GetStringWidth($textitem);
22334
			if ($this->rtl) {
22335
				$this->x += $lspace;
22336
			} else {
22337
				$this->x -= $lspace;
22338
			}
22339
			$this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
22340
		}
22341
		$this->x = $tmpx;
22342
		$this->lispacer = '^';
22343
	}
22344
 
22345
	/**
22346
	 * Returns current graphic variables as array.
22347
	 * @return array of graphic variables
22348
	 * @access protected
22349
	 * @since 4.2.010 (2008-11-14)
22350
	 */
22351
	protected function getGraphicVars() {
22352
		$grapvars = array(
22353
			'FontFamily' => $this->FontFamily,
22354
			'FontStyle' => $this->FontStyle,
22355
			'FontSizePt' => $this->FontSizePt,
22356
			'rMargin' => $this->rMargin,
22357
			'lMargin' => $this->lMargin,
22358
			'cell_padding' => $this->cell_padding,
22359
			'cell_margin' => $this->cell_margin,
22360
			'LineWidth' => $this->LineWidth,
22361
			'linestyleWidth' => $this->linestyleWidth,
22362
			'linestyleCap' => $this->linestyleCap,
22363
			'linestyleJoin' => $this->linestyleJoin,
22364
			'linestyleDash' => $this->linestyleDash,
22365
			'textrendermode' => $this->textrendermode,
22366
			'textstrokewidth' => $this->textstrokewidth,
22367
			'DrawColor' => $this->DrawColor,
22368
			'FillColor' => $this->FillColor,
22369
			'TextColor' => $this->TextColor,
22370
			'ColorFlag' => $this->ColorFlag,
22371
			'bgcolor' => $this->bgcolor,
22372
			'fgcolor' => $this->fgcolor,
22373
			'htmlvspace' => $this->htmlvspace,
22374
			'listindent' => $this->listindent,
22375
			'listindentlevel' => $this->listindentlevel,
22376
			'listnum' => $this->listnum,
22377
			'listordered' => $this->listordered,
22378
			'listcount' => $this->listcount,
22379
			'lispacer' => $this->lispacer,
22380
			'cell_height_ratio' => $this->cell_height_ratio,
22381
			'font_stretching' => $this->font_stretching,
22382
			'font_spacing' => $this->font_spacing,
22383
			// extended
22384
			'lasth' => $this->lasth,
22385
			'tMargin' => $this->tMargin,
22386
			'bMargin' => $this->bMargin,
22387
			'AutoPageBreak' => $this->AutoPageBreak,
22388
			'PageBreakTrigger' => $this->PageBreakTrigger,
22389
			'x' => $this->x,
22390
			'y' => $this->y,
22391
			'w' => $this->w,
22392
			'h' => $this->h,
22393
			'wPt' => $this->wPt,
22394
			'hPt' => $this->hPt,
22395
			'fwPt' => $this->fwPt,
22396
			'fhPt' => $this->fhPt,
22397
			'page' => $this->page,
22398
			'current_column' => $this->current_column,
22399
			'num_columns' => $this->num_columns
22400
			);
22401
		return $grapvars;
22402
	}
22403
 
22404
	/**
22405
	 * Set graphic variables.
22406
	 * @param array $gvars array of graphic variablesto restore
22407
	 * @param boolean $extended if true restore extended graphic variables
22408
	 * @access protected
22409
	 * @since 4.2.010 (2008-11-14)
22410
	 */
22411
	protected function setGraphicVars($gvars, $extended=false) {
22412
		$this->FontFamily = $gvars['FontFamily'];
22413
		$this->FontStyle = $gvars['FontStyle'];
22414
		$this->FontSizePt = $gvars['FontSizePt'];
22415
		$this->rMargin = $gvars['rMargin'];
22416
		$this->lMargin = $gvars['lMargin'];
22417
		$this->cell_padding = $gvars['cell_padding'];
22418
		$this->cell_margin = $gvars['cell_margin'];
22419
		$this->LineWidth = $gvars['LineWidth'];
22420
		$this->linestyleWidth = $gvars['linestyleWidth'];
22421
		$this->linestyleCap = $gvars['linestyleCap'];
22422
		$this->linestyleJoin = $gvars['linestyleJoin'];
22423
		$this->linestyleDash = $gvars['linestyleDash'];
22424
		$this->textrendermode = $gvars['textrendermode'];
22425
		$this->textstrokewidth = $gvars['textstrokewidth'];
22426
		$this->DrawColor = $gvars['DrawColor'];
22427
		$this->FillColor = $gvars['FillColor'];
22428
		$this->TextColor = $gvars['TextColor'];
22429
		$this->ColorFlag = $gvars['ColorFlag'];
22430
		$this->bgcolor = $gvars['bgcolor'];
22431
		$this->fgcolor = $gvars['fgcolor'];
22432
		$this->htmlvspace = $gvars['htmlvspace'];
22433
		$this->listindent = $gvars['listindent'];
22434
		$this->listindentlevel = $gvars['listindentlevel'];
22435
		$this->listnum = $gvars['listnum'];
22436
		$this->listordered = $gvars['listordered'];
22437
		$this->listcount = $gvars['listcount'];
22438
		$this->lispacer = $gvars['lispacer'];
22439
		$this->cell_height_ratio = $gvars['cell_height_ratio'];
22440
		$this->font_stretching = $gvars['font_stretching'];
22441
		$this->font_spacing = $gvars['font_spacing'];
22442
		if ($extended) {
22443
			// restore extended values
22444
			$this->lasth = $gvars['lasth'];
22445
			$this->tMargin = $gvars['tMargin'];
22446
			$this->bMargin = $gvars['bMargin'];
22447
			$this->AutoPageBreak = $gvars['AutoPageBreak'];
22448
			$this->PageBreakTrigger = $gvars['PageBreakTrigger'];
22449
			$this->x = $gvars['x'];
22450
			$this->y = $gvars['y'];
22451
			$this->w = $gvars['w'];
22452
			$this->h = $gvars['h'];
22453
			$this->wPt = $gvars['wPt'];
22454
			$this->hPt = $gvars['hPt'];
22455
			$this->fwPt = $gvars['fwPt'];
22456
			$this->fhPt = $gvars['fhPt'];
22457
			$this->page = $gvars['page'];
22458
			$this->current_column = $gvars['current_column'];
22459
			$this->num_columns = $gvars['num_columns'];
22460
		}
22461
		$this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
22462
		if (!$this->empty_string($this->FontFamily)) {
22463
			$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
22464
		}
22465
	}
22466
 
22467
	/**
22468
	 * Returns a temporary filename for caching object on filesystem.
22469
	 * @param string $prefix prefix to add to filename
22470
	 * return string filename.
22471
	 * @access protected
22472
	 * @since 4.5.000 (2008-12-31)
22473
	 */
22474
	protected function getObjFilename($name) {
22475
		return tempnam(K_PATH_CACHE, $name.'_');
22476
	}
22477
 
22478
	/**
22479
	 * Writes data to a temporary file on filesystem.
22480
	 * @param string $file file name
22481
	 * @param mixed $data data to write on file
22482
	 * @param boolean $append if true append data, false replace.
22483
	 * @access protected
22484
	 * @since 4.5.000 (2008-12-31)
22485
	 */
22486
	protected function writeDiskCache($filename, $data, $append=false) {
22487
		if ($append) {
22488
			$fmode = 'ab+';
22489
		} else {
22490
			$fmode = 'wb+';
22491
		}
22492
		$f = @fopen($filename, $fmode);
22493
		if (!$f) {
22494
			$this->Error('Unable to write cache file: '.$filename);
22495
		} else {
22496
			fwrite($f, $data);
22497
			fclose($f);
22498
		}
22499
		// update file length (needed for transactions)
22500
		if (!isset($this->cache_file_length['_'.$filename])) {
22501
			$this->cache_file_length['_'.$filename] = strlen($data);
22502
		} else {
22503
			$this->cache_file_length['_'.$filename] += strlen($data);
22504
		}
22505
	}
22506
 
22507
	/**
22508
	 * Read data from a temporary file on filesystem.
22509
	 * @param string $file file name
22510
	 * @return mixed retrieved data
22511
	 * @access protected
22512
	 * @since 4.5.000 (2008-12-31)
22513
	 */
22514
	protected function readDiskCache($filename) {
22515
		return file_get_contents($filename);
22516
	}
22517
 
22518
	/**
22519
	 * Set buffer content (always append data).
22520
	 * @param string $data data
22521
	 * @access protected
22522
	 * @since 4.5.000 (2009-01-02)
22523
	 */
22524
	protected function setBuffer($data) {
22525
		$this->bufferlen += strlen($data);
22526
		if ($this->diskcache) {
22527
			if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
22528
				$this->buffer = $this->getObjFilename('buffer');
22529
			}
22530
			$this->writeDiskCache($this->buffer, $data, true);
22531
		} else {
22532
			$this->buffer .= $data;
22533
		}
22534
	}
22535
 
22536
	/**
22537
	 * Replace the buffer content
22538
	 * @param string $data data
22539
	 * @access protected
22540
	 * @since 5.5.000 (2010-06-22)
22541
	 */
22542
	protected function replaceBuffer($data) {
22543
		$this->bufferlen = strlen($data);
22544
		if ($this->diskcache) {
22545
			if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
22546
				$this->buffer = $this->getObjFilename('buffer');
22547
			}
22548
			$this->writeDiskCache($this->buffer, $data, false);
22549
		} else {
22550
			$this->buffer = $data;
22551
		}
22552
	}
22553
 
22554
	/**
22555
	 * Get buffer content.
22556
	 * @return string buffer content
22557
	 * @access protected
22558
	 * @since 4.5.000 (2009-01-02)
22559
	 */
22560
	protected function getBuffer() {
22561
		if ($this->diskcache) {
22562
			return $this->readDiskCache($this->buffer);
22563
		} else {
22564
			return $this->buffer;
22565
		}
22566
	}
22567
 
22568
	/**
22569
	 * Set page buffer content.
22570
	 * @param int $page page number
22571
	 * @param string $data page data
22572
	 * @param boolean $append if true append data, false replace.
22573
	 * @access protected
22574
	 * @since 4.5.000 (2008-12-31)
22575
	 */
22576
	protected function setPageBuffer($page, $data, $append=false) {
22577
		if ($this->diskcache) {
22578
			if (!isset($this->pages[$page])) {
22579
				$this->pages[$page] = $this->getObjFilename('page'.$page);
22580
			}
22581
			$this->writeDiskCache($this->pages[$page], $data, $append);
22582
		} else {
22583
			if ($append) {
22584
				$this->pages[$page] .= $data;
22585
			} else {
22586
				$this->pages[$page] = $data;
22587
			}
22588
		}
22589
		if ($append AND isset($this->pagelen[$page])) {
22590
			$this->pagelen[$page] += strlen($data);
22591
		} else {
22592
			$this->pagelen[$page] = strlen($data);
22593
		}
22594
	}
22595
 
22596
	/**
22597
	 * Get page buffer content.
22598
	 * @param int $page page number
22599
	 * @return string page buffer content or false in case of error
22600
	 * @access protected
22601
	 * @since 4.5.000 (2008-12-31)
22602
	 */
22603
	protected function getPageBuffer($page) {
22604
		if ($this->diskcache) {
22605
			return $this->readDiskCache($this->pages[$page]);
22606
		} elseif (isset($this->pages[$page])) {
22607
			return $this->pages[$page];
22608
		}
22609
		return false;
22610
	}
22611
 
22612
	/**
22613
	 * Set image buffer content.
22614
	 * @param string $image image key
22615
	 * @param array $data image data
22616
	 * @access protected
22617
	 * @since 4.5.000 (2008-12-31)
22618
	 */
22619
	protected function setImageBuffer($image, $data) {
22620
		if ($this->diskcache) {
22621
			if (!isset($this->images[$image])) {
22622
				$this->images[$image] = $this->getObjFilename('image'.$image);
22623
			}
22624
			$this->writeDiskCache($this->images[$image], serialize($data));
22625
		} else {
22626
			$this->images[$image] = $data;
22627
		}
22628
		if (!in_array($image, $this->imagekeys)) {
22629
			$this->imagekeys[] = $image;
22630
			++$this->numimages;
22631
		}
22632
	}
22633
 
22634
	/**
22635
	 * Set image buffer content for a specified sub-key.
22636
	 * @param string $image image key
22637
	 * @param string $key image sub-key
22638
	 * @param array $data image data
22639
	 * @access protected
22640
	 * @since 4.5.000 (2008-12-31)
22641
	 */
22642
	protected function setImageSubBuffer($image, $key, $data) {
22643
		if (!isset($this->images[$image])) {
22644
			$this->setImageBuffer($image, array());
22645
		}
22646
		if ($this->diskcache) {
22647
			$tmpimg = $this->getImageBuffer($image);
22648
			$tmpimg[$key] = $data;
22649
			$this->writeDiskCache($this->images[$image], serialize($tmpimg));
22650
		} else {
22651
			$this->images[$image][$key] = $data;
22652
		}
22653
	}
22654
 
22655
	/**
22656
	 * Get image buffer content.
22657
	 * @param string $image image key
22658
	 * @return string image buffer content or false in case of error
22659
	 * @access protected
22660
	 * @since 4.5.000 (2008-12-31)
22661
	 */
22662
	protected function getImageBuffer($image) {
22663
		if ($this->diskcache AND isset($this->images[$image])) {
22664
			return unserialize($this->readDiskCache($this->images[$image]));
22665
		} elseif (isset($this->images[$image])) {
22666
			return $this->images[$image];
22667
		}
22668
		return false;
22669
	}
22670
 
22671
	/**
22672
	 * Set font buffer content.
22673
	 * @param string $font font key
22674
	 * @param array $data font data
22675
	 * @access protected
22676
	 * @since 4.5.000 (2009-01-02)
22677
	 */
22678
	protected function setFontBuffer($font, $data) {
22679
		if ($this->diskcache) {
22680
			if (!isset($this->fonts[$font])) {
22681
				$this->fonts[$font] = $this->getObjFilename('font');
22682
			}
22683
			$this->writeDiskCache($this->fonts[$font], serialize($data));
22684
		} else {
22685
			$this->fonts[$font] = $data;
22686
		}
22687
		if (!in_array($font, $this->fontkeys)) {
22688
			$this->fontkeys[] = $font;
22689
			// store object ID for current font
22690
			++$this->n;
22691
			$this->font_obj_ids[$font] = $this->n;
22692
			$this->setFontSubBuffer($font, 'n', $this->n);
22693
		}
22694
	}
22695
 
22696
	/**
22697
	 * Set font buffer content.
22698
	 * @param string $font font key
22699
	 * @param string $key font sub-key
22700
	 * @param array $data font data
22701
	 * @access protected
22702
	 * @since 4.5.000 (2009-01-02)
22703
	 */
22704
	protected function setFontSubBuffer($font, $key, $data) {
22705
		if (!isset($this->fonts[$font])) {
22706
			$this->setFontBuffer($font, array());
22707
		}
22708
		if ($this->diskcache) {
22709
			$tmpfont = $this->getFontBuffer($font);
22710
			$tmpfont[$key] = $data;
22711
			$this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
22712
		} else {
22713
			$this->fonts[$font][$key] = $data;
22714
		}
22715
	}
22716
 
22717
	/**
22718
	 * Get font buffer content.
22719
	 * @param string $font font key
22720
	 * @return string font buffer content or false in case of error
22721
	 * @access protected
22722
	 * @since 4.5.000 (2009-01-02)
22723
	 */
22724
	protected function getFontBuffer($font) {
22725
		if ($this->diskcache AND isset($this->fonts[$font])) {
22726
			return unserialize($this->readDiskCache($this->fonts[$font]));
22727
		} elseif (isset($this->fonts[$font])) {
22728
			return $this->fonts[$font];
22729
		}
22730
		return false;
22731
	}
22732
 
22733
	/**
22734
	 * Move a page to a previous position.
22735
	 * @param int $frompage number of the source page
22736
	 * @param int $topage number of the destination page (must be less than $frompage)
22737
	 * @return true in case of success, false in case of error.
22738
	 * @access public
22739
	 * @since 4.5.000 (2009-01-02)
22740
	 */
22741
	public function movePage($frompage, $topage) {
22742
		if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
22743
			return false;
22744
		}
22745
		if ($frompage == $this->page) {
22746
			// close the page before moving it
22747
			$this->endPage();
22748
		}
22749
		// move all page-related states
22750
		$tmppage = $this->pages[$frompage];
22751
		$tmppagedim = $this->pagedim[$frompage];
22752
		$tmppagelen = $this->pagelen[$frompage];
22753
		$tmpintmrk = $this->intmrk[$frompage];
22754
		$tmpbordermrk = $this->bordermrk[$frompage];
22755
		$tmpcntmrk = $this->cntmrk[$frompage];
22756
		if (isset($this->footerpos[$frompage])) {
22757
			$tmpfooterpos = $this->footerpos[$frompage];
22758
		}
22759
		if (isset($this->footerlen[$frompage])) {
22760
			$tmpfooterlen = $this->footerlen[$frompage];
22761
		}
22762
		if (isset($this->transfmrk[$frompage])) {
22763
			$tmptransfmrk = $this->transfmrk[$frompage];
22764
		}
22765
		if (isset($this->PageAnnots[$frompage])) {
22766
			$tmpannots = $this->PageAnnots[$frompage];
22767
		}
22768
		if (isset($this->newpagegroup[$frompage])) {
22769
			$tmpnewpagegroup = $this->newpagegroup[$frompage];
22770
		}
22771
		for ($i = $frompage; $i > $topage; --$i) {
22772
			$j = $i - 1;
22773
			// shift pages down
22774
			$this->pages[$i] = $this->pages[$j];
22775
			$this->pagedim[$i] = $this->pagedim[$j];
22776
			$this->pagelen[$i] = $this->pagelen[$j];
22777
			$this->intmrk[$i] = $this->intmrk[$j];
22778
			$this->bordermrk[$i] = $this->bordermrk[$j];
22779
			$this->cntmrk[$i] = $this->cntmrk[$j];
22780
			if (isset($this->footerpos[$j])) {
22781
				$this->footerpos[$i] = $this->footerpos[$j];
22782
			} elseif (isset($this->footerpos[$i])) {
22783
				unset($this->footerpos[$i]);
22784
			}
22785
			if (isset($this->footerlen[$j])) {
22786
				$this->footerlen[$i] = $this->footerlen[$j];
22787
			} elseif (isset($this->footerlen[$i])) {
22788
				unset($this->footerlen[$i]);
22789
			}
22790
			if (isset($this->transfmrk[$j])) {
22791
				$this->transfmrk[$i] = $this->transfmrk[$j];
22792
			} elseif (isset($this->transfmrk[$i])) {
22793
				unset($this->transfmrk[$i]);
22794
			}
22795
			if (isset($this->PageAnnots[$j])) {
22796
				$this->PageAnnots[$i] = $this->PageAnnots[$j];
22797
			} elseif (isset($this->PageAnnots[$i])) {
22798
				unset($this->PageAnnots[$i]);
22799
			}
22800
			if (isset($this->newpagegroup[$j])) {
22801
				$this->newpagegroup[$i] = $this->newpagegroup[$j];
22802
			} elseif (isset($this->newpagegroup[$i])) {
22803
				unset($this->newpagegroup[$i]);
22804
			}
22805
		}
22806
		$this->pages[$topage] = $tmppage;
22807
		$this->pagedim[$topage] = $tmppagedim;
22808
		$this->pagelen[$topage] = $tmppagelen;
22809
		$this->intmrk[$topage] = $tmpintmrk;
22810
		$this->bordermrk[$topage] = $tmpbordermrk;
22811
		$this->cntmrk[$topage] = $tmpcntmrk;
22812
		if (isset($tmpfooterpos)) {
22813
			$this->footerpos[$topage] = $tmpfooterpos;
22814
		} elseif (isset($this->footerpos[$topage])) {
22815
			unset($this->footerpos[$topage]);
22816
		}
22817
		if (isset($tmpfooterlen)) {
22818
			$this->footerlen[$topage] = $tmpfooterlen;
22819
		} elseif (isset($this->footerlen[$topage])) {
22820
			unset($this->footerlen[$topage]);
22821
		}
22822
		if (isset($tmptransfmrk)) {
22823
			$this->transfmrk[$topage] = $tmptransfmrk;
22824
		} elseif (isset($this->transfmrk[$topage])) {
22825
			unset($this->transfmrk[$topage]);
22826
		}
22827
		if (isset($tmpannots)) {
22828
			$this->PageAnnots[$topage] = $tmpannots;
22829
		} elseif (isset($this->PageAnnots[$topage])) {
22830
			unset($this->PageAnnots[$topage]);
22831
		}
22832
		if (isset($tmpnewpagegroup)) {
22833
			$this->newpagegroup[$topage] = $tmpnewpagegroup;
22834
		} elseif (isset($this->newpagegroup[$topage])) {
22835
			unset($this->newpagegroup[$topage]);
22836
		}
22837
		// adjust outlines
22838
		$tmpoutlines = $this->outlines;
22839
		foreach ($tmpoutlines as $key => $outline) {
22840
			if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
22841
				$this->outlines[$key]['p'] = $outline['p'] + 1;
22842
			} elseif ($outline['p'] == $frompage) {
22843
				$this->outlines[$key]['p'] = $topage;
22844
			}
22845
		}
22846
		// adjust links
22847
		$tmplinks = $this->links;
22848
		foreach ($tmplinks as $key => $link) {
22849
			if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
22850
				$this->links[$key][0] = $link[0] + 1;
22851
			} elseif ($link[0] == $frompage) {
22852
				$this->links[$key][0] = $topage;
22853
			}
22854
		}
22855
		// adjust javascript
22856
		$tmpjavascript = $this->javascript;
22857
		global $jfrompage, $jtopage;
22858
		$jfrompage = $frompage;
22859
		$jtopage = $topage;
22860
		$this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
22861
			create_function('$matches', 'global $jfrompage, $jtopage;
22862
			$pagenum = intval($matches[3]) + 1;
22863
			if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
22864
				$newpage = ($pagenum + 1);
22865
			} elseif ($pagenum == $jfrompage) {
22866
				$newpage = $jtopage;
22867
			} else {
22868
				$newpage = $pagenum;
22869
			}
22870
			--$newpage;
22871
			return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
22872
		// return to last page
22873
		$this->lastPage(true);
22874
		return true;
22875
	}
22876
 
22877
	/**
22878
	 * Remove the specified page.
22879
	 * @param int $page page to remove
22880
	 * @return true in case of success, false in case of error.
22881
	 * @access public
22882
	 * @since 4.6.004 (2009-04-23)
22883
	 */
22884
	public function deletePage($page) {
22885
		if (($page < 1) OR ($page > $this->numpages)) {
22886
			return false;
22887
		}
22888
		// delete current page
22889
		unset($this->pages[$page]);
22890
		unset($this->pagedim[$page]);
22891
		unset($this->pagelen[$page]);
22892
		unset($this->intmrk[$page]);
22893
		unset($this->bordermrk[$page]);
22894
		unset($this->cntmrk[$page]);
22895
		if (isset($this->footerpos[$page])) {
22896
			unset($this->footerpos[$page]);
22897
		}
22898
		if (isset($this->footerlen[$page])) {
22899
			unset($this->footerlen[$page]);
22900
		}
22901
		if (isset($this->transfmrk[$page])) {
22902
			unset($this->transfmrk[$page]);
22903
		}
22904
		if (isset($this->PageAnnots[$page])) {
22905
			unset($this->PageAnnots[$page]);
22906
		}
22907
		if (isset($this->newpagegroup[$page])) {
22908
			unset($this->newpagegroup[$page]);
22909
		}
22910
		if (isset($this->pageopen[$page])) {
22911
			unset($this->pageopen[$page]);
22912
		}
22913
		// update remaining pages
22914
		for ($i = $page; $i < $this->numpages; ++$i) {
22915
			$j = $i + 1;
22916
			// shift pages
22917
			$this->pages[$i] = $this->pages[$j];
22918
			$this->pagedim[$i] = $this->pagedim[$j];
22919
			$this->pagelen[$i] = $this->pagelen[$j];
22920
			$this->intmrk[$i] = $this->intmrk[$j];
22921
			$this->bordermrk[$i] = $this->bordermrk[$j];
22922
			$this->cntmrk[$i] = $this->cntmrk[$j];
22923
			if (isset($this->footerpos[$j])) {
22924
				$this->footerpos[$i] = $this->footerpos[$j];
22925
			} elseif (isset($this->footerpos[$i])) {
22926
				unset($this->footerpos[$i]);
22927
			}
22928
			if (isset($this->footerlen[$j])) {
22929
				$this->footerlen[$i] = $this->footerlen[$j];
22930
			} elseif (isset($this->footerlen[$i])) {
22931
				unset($this->footerlen[$i]);
22932
			}
22933
			if (isset($this->transfmrk[$j])) {
22934
				$this->transfmrk[$i] = $this->transfmrk[$j];
22935
			} elseif (isset($this->transfmrk[$i])) {
22936
				unset($this->transfmrk[$i]);
22937
			}
22938
			if (isset($this->PageAnnots[$j])) {
22939
				$this->PageAnnots[$i] = $this->PageAnnots[$j];
22940
			} elseif (isset($this->PageAnnots[$i])) {
22941
				unset($this->PageAnnots[$i]);
22942
			}
22943
			if (isset($this->newpagegroup[$j])) {
22944
				$this->newpagegroup[$i] = $this->newpagegroup[$j];
22945
			} elseif (isset($this->newpagegroup[$i])) {
22946
				unset($this->newpagegroup[$i]);
22947
			}
22948
			if (isset($this->pageopen[$j])) {
22949
				$this->pageopen[$i] = $this->pageopen[$j];
22950
			} elseif (isset($this->pageopen[$i])) {
22951
				unset($this->pageopen[$i]);
22952
			}
22953
		}
22954
		// remove last page
22955
		unset($this->pages[$this->numpages]);
22956
		unset($this->pagedim[$this->numpages]);
22957
		unset($this->pagelen[$this->numpages]);
22958
		unset($this->intmrk[$this->numpages]);
22959
		unset($this->bordermrk[$this->numpages]);
22960
		unset($this->cntmrk[$this->numpages]);
22961
		if (isset($this->footerpos[$this->numpages])) {
22962
			unset($this->footerpos[$this->numpages]);
22963
		}
22964
		if (isset($this->footerlen[$this->numpages])) {
22965
			unset($this->footerlen[$this->numpages]);
22966
		}
22967
		if (isset($this->transfmrk[$this->numpages])) {
22968
			unset($this->transfmrk[$this->numpages]);
22969
		}
22970
		if (isset($this->PageAnnots[$this->numpages])) {
22971
			unset($this->PageAnnots[$this->numpages]);
22972
		}
22973
		if (isset($this->newpagegroup[$this->numpages])) {
22974
			unset($this->newpagegroup[$this->numpages]);
22975
		}
22976
		if (isset($this->pageopen[$this->numpages])) {
22977
			unset($this->pageopen[$this->numpages]);
22978
		}
22979
		--$this->numpages;
22980
		$this->page = $this->numpages;
22981
		// adjust outlines
22982
		$tmpoutlines = $this->outlines;
22983
		foreach ($tmpoutlines as $key => $outline) {
22984
			if ($outline['p'] > $page) {
22985
				$this->outlines[$key]['p'] = $outline['p'] - 1;
22986
			} elseif ($outline['p'] == $page) {
22987
				unset($this->outlines[$key]);
22988
			}
22989
		}
22990
		// adjust links
22991
		$tmplinks = $this->links;
22992
		foreach ($tmplinks as $key => $link) {
22993
			if ($link[0] > $page) {
22994
				$this->links[$key][0] = $link[0] - 1;
22995
			} elseif ($link[0] == $page) {
22996
				unset($this->links[$key]);
22997
			}
22998
		}
22999
		// adjust javascript
23000
		$tmpjavascript = $this->javascript;
23001
		global $jpage;
23002
		$jpage = $page;
23003
		$this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
23004
			create_function('$matches', 'global $jpage;
23005
			$pagenum = intval($matches[3]) + 1;
23006
			if ($pagenum >= $jpage) {
23007
				$newpage = ($pagenum - 1);
23008
			} elseif ($pagenum == $jpage) {
23009
				$newpage = 1;
23010
			} else {
23011
				$newpage = $pagenum;
23012
			}
23013
			--$newpage;
23014
			return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
23015
		// return to last page
23016
		$this->lastPage(true);
23017
		return true;
23018
	}
23019
 
23020
	/**
23021
	 * Clone the specified page to a new page.
23022
	 * @param int $page number of page to copy (0 = current page)
23023
	 * @return true in case of success, false in case of error.
23024
	 * @access public
23025
	 * @since 4.9.015 (2010-04-20)
23026
	 */
23027
	public function copyPage($page=0) {
23028
		if ($page == 0) {
23029
			// default value
23030
			$page = $this->page;
23031
		}
23032
		if (($page < 1) OR ($page > $this->numpages)) {
23033
			return false;
23034
		}
23035
		if ($page == $this->page) {
23036
			// close the page before cloning it
23037
			$this->endPage();
23038
		}
23039
		// copy all page-related states
23040
		++$this->numpages;
23041
		$this->page = $this->numpages;
23042
		$this->pages[$this->page] = $this->pages[$page];
23043
		$this->pagedim[$this->page] = $this->pagedim[$page];
23044
		$this->pagelen[$this->page] = $this->pagelen[$page];
23045
		$this->intmrk[$this->page] = $this->intmrk[$page];
23046
		$this->bordermrk[$this->page] = $this->bordermrk[$page];
23047
		$this->cntmrk[$this->page] = $this->cntmrk[$page];
23048
		$this->pageopen[$this->page] = false;
23049
		if (isset($this->footerpos[$page])) {
23050
			$this->footerpos[$this->page] = $this->footerpos[$page];
23051
		}
23052
		if (isset($this->footerlen[$page])) {
23053
			$this->footerlen[$this->page] = $this->footerlen[$page];
23054
		}
23055
		if (isset($this->transfmrk[$page])) {
23056
			$this->transfmrk[$this->page] = $this->transfmrk[$page];
23057
		}
23058
		if (isset($this->PageAnnots[$page])) {
23059
			$this->PageAnnots[$this->page] = $this->PageAnnots[$page];
23060
		}
23061
		if (isset($this->newpagegroup[$page])) {
23062
			$this->newpagegroup[$this->page] = $this->newpagegroup[$page];
23063
		}
23064
		// copy outlines
23065
		$tmpoutlines = $this->outlines;
23066
		foreach ($tmpoutlines as $key => $outline) {
23067
			if ($outline['p'] == $page) {
23068
				$this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'y' => $outline['y'], 'p' => $this->page);
23069
			}
23070
		}
23071
		// copy links
23072
		$tmplinks = $this->links;
23073
		foreach ($tmplinks as $key => $link) {
23074
			if ($link[0] == $page) {
23075
				$this->links[] = array($this->page, $link[1]);
23076
			}
23077
		}
23078
		// return to last page
23079
		$this->lastPage(true);
23080
		return true;
23081
	}
23082
 
23083
	/**
23084
	 * Output a Table of Content Index (TOC).
23085
	 * Before calling this method you have to open the page using the addTOCPage() method.
23086
	 * After calling this method you have to call endTOCPage() to close the TOC page.
23087
	 * You can override this method to achieve different styles.
23088
	 * @param int $page page number where this TOC should be inserted (leave empty for current page).
23089
	 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
23090
	 * @param string $filler string used to fill the space between text and page number.
23091
	 * @param string $toc_name name to use for TOC bookmark.
23092
	 * @access public
23093
	 * @author Nicola Asuni
23094
	 * @since 4.5.000 (2009-01-02)
23095
	 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
23096
	 */
23097
	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC') {
23098
		$fontsize = $this->FontSizePt;
23099
		$fontfamily = $this->FontFamily;
23100
		$fontstyle = $this->FontStyle;
23101
		$w = $this->w - $this->lMargin - $this->rMargin;
23102
		$spacer = $this->GetStringWidth(chr(32)) * 4;
23103
		$page_first = $this->getPage();
23104
		$lmargin = $this->lMargin;
23105
		$rmargin = $this->rMargin;
23106
		$x_start = $this->GetX();
23107
		$current_page = $this->page;
23108
		$current_column = $this->current_column;
23109
		if ($this->empty_string($numbersfont)) {
23110
			$numbersfont = $this->default_monospaced_font;
23111
		}
23112
		if ($this->empty_string($filler)) {
23113
			$filler = ' ';
23114
		}
23115
		if ($this->empty_string($page)) {
23116
			$gap = ' ';
23117
		} else {
23118
			$gap = '';
23119
			if ($page < 1) {
23120
				$page = 1;
23121
			}
23122
		}
23123
		foreach ($this->outlines as $key => $outline) {
23124
			if ($this->rtl) {
23125
				$aligntext = 'R';
23126
				$alignnum = 'L';
23127
			} else {
23128
				$aligntext = 'L';
23129
				$alignnum = 'R';
23130
			}
23131
			if ($outline['l'] == 0) {
23132
				$this->SetFont($fontfamily, $fontstyle.'B', $fontsize);
23133
			} else {
23134
				$this->SetFont($fontfamily, $fontstyle, $fontsize - $outline['l']);
23135
			}
23136
			// check for page break
23137
			$this->checkPageBreak(($this->FontSize * $this->cell_height_ratio));
23138
			// set margins and X position
23139
			if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
23140
				$this->lMargin = $lmargin;
23141
				$this->rMargin = $rmargin;
23142
			} else {
23143
				if ($this->current_column != $current_column) {
23144
					if ($this->rtl) {
23145
						$x_start = $this->w - $this->columns[$this->current_column]['x'];
23146
					} else {
23147
						$x_start = $this->columns[$this->current_column]['x'];
23148
					}
23149
				}
23150
				$lmargin = $this->lMargin;
23151
				$rmargin = $this->rMargin;
23152
				$current_page = $this->page;
23153
				$current_column = $this->current_column;
23154
			}
23155
			$this->SetX($x_start);
23156
			$indent = ($spacer * $outline['l']);
23157
			if ($this->rtl) {
23158
				$this->rMargin += $indent;
23159
				$this->x -= $indent;
23160
			} else {
23161
				$this->lMargin += $indent;
23162
				$this->x += $indent;
23163
			}
23164
			$link = $this->AddLink();
23165
			$this->SetLink($link, $outline['y'], $outline['p']);
23166
			// write the text
23167
			$this->Write(0, $outline['t'], $link, 0, $aligntext, false, 0, false, false, 0);
23168
			$this->SetFont($numbersfont, $fontstyle, $fontsize);
23169
			if ($this->empty_string($page)) {
23170
				$pagenum = $outline['p'];
23171
			} else {
23172
				// placemark to be replaced with the correct number
23173
				$pagenum = '{#'.($outline['p']).'}';
23174
				if ($this->isUnicodeFont()) {
23175
					$pagenum = '{'.$pagenum.'}';
23176
				}
23177
			}
23178
			$numwidth = $this->GetStringWidth($pagenum);
23179
			if ($this->rtl) {
23180
				$tw = $this->x - $this->lMargin;
23181
			} else {
23182
				$tw = $this->w - $this->rMargin - $this->x;
23183
			}
23184
			$fw = $tw - $numwidth - $this->GetStringWidth(chr(32));
23185
			$numfills = floor($fw / $this->GetStringWidth($filler));
23186
			if ($numfills > 0) {
23187
				$rowfill = str_repeat($filler, $numfills);
23188
			} else {
23189
				$rowfill = '';
23190
			}
23191
			if ($this->rtl) {
23192
				$pagenum = $pagenum.$gap.$rowfill.' ';
23193
			} else {
23194
				$pagenum = ' '.$rowfill.$gap.$pagenum;
23195
			}
23196
			// write the number
23197
			$this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
23198
		}
23199
		$page_last = $this->getPage();
23200
		$numpages = $page_last - $page_first + 1;
23201
		if (!$this->empty_string($page)) {
23202
			for ($p = $page_first; $p <= $page_last; ++$p) {
23203
				// get page data
23204
				$temppage = $this->getPageBuffer($p);
23205
				for ($n = 1; $n <= $this->numpages; ++$n) {
23206
					// update page numbers
23207
					$k = '{#'.$n.'}';
23208
					$ku = '{'.$k.'}';
23209
					$alias_a = $this->_escape($k);
23210
					$alias_au = $this->_escape($ku);
23211
					if ($this->isunicode) {
23212
						$alias_b = $this->_escape($this->UTF8ToLatin1($k));
23213
						$alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
23214
						$alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
23215
						$alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
23216
					}
23217
					if ($n >= $page) {
23218
						$np = $n + $numpages;
23219
					} else {
23220
						$np = $n;
23221
					}
23222
					$ns = $this->formatTOCPageNumber($np);
23223
					$nu = $ns;
23224
					$sdiff = strlen($k) - strlen($ns) - 1;
23225
					$sdiffu = strlen($ku) - strlen($ns) - 1;
23226
					$sfill = str_repeat($filler, $sdiff);
23227
					$sfillu = str_repeat($filler, $sdiffu);
23228
					if ($this->rtl) {
23229
						$ns = $ns.' '.$sfill;
23230
						$nu = $nu.' '.$sfillu;
23231
					} else {
23232
						$ns = $sfill.' '.$ns;
23233
						$nu = $sfillu.' '.$nu;
23234
					}
23235
					$nu = $this->UTF8ToUTF16BE($nu, false);
23236
					$temppage = str_replace($alias_au, $nu, $temppage);
23237
					if ($this->isunicode) {
23238
						$temppage = str_replace($alias_bu, $nu, $temppage);
23239
						$temppage = str_replace($alias_cu, $nu, $temppage);
23240
						$temppage = str_replace($alias_b, $ns, $temppage);
23241
						$temppage = str_replace($alias_c, $ns, $temppage);
23242
					}
23243
					$temppage = str_replace($alias_a, $ns, $temppage);
23244
				}
23245
				// save changes
23246
				$this->setPageBuffer($p, $temppage);
23247
			}
23248
			// move pages
23249
			$this->Bookmark($toc_name, 0, 0, $page_first);
23250
			for ($i = 0; $i < $numpages; ++$i) {
23251
				$this->movePage($page_last, $page);
23252
			}
23253
		}
23254
	}
23255
 
23256
	/**
23257
	 * Output a Table Of Content Index (TOC) using HTML templates.
23258
	 * Before calling this method you have to open the page using the addTOCPage() method.
23259
	 * After calling this method you have to call endTOCPage() to close the TOC page.
23260
	 * @param int $page page number where this TOC should be inserted (leave empty for current page).
23261
	 * @param string $toc_name name to use for TOC bookmark.
23262
	 * @param array $templates array of html templates. Use: #TOC_DESCRIPTION# for bookmark title, #TOC_PAGE_NUMBER# for page number.
23263
	 * @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)
23264
	 * @access public
23265
	 * @author Nicola Asuni
23266
	 * @since 5.0.001 (2010-05-06)
23267
	 * @see addTOCPage(), endTOCPage(), addTOC()
23268
	 */
23269
	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true) {
23270
		$prev_htmlLinkColorArray = $this->htmlLinkColorArray;
23271
		$prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
23272
		// set new style for link
23273
		$this->htmlLinkColorArray = array();
23274
		$this->htmlLinkFontStyle = '';
23275
		$page_first = $this->getPage();
23276
		// get the font type used for numbers in each template
23277
		$current_font = $this->FontFamily;
23278
		foreach ($templates as $level => $html) {
23279
			$dom = $this->getHtmlDomArray($html);
23280
			foreach ($dom as $key => $value) {
23281
				if ($value['value'] == '#TOC_PAGE_NUMBER#') {
23282
					$this->SetFont($dom[($key - 1)]['fontname']);
23283
					$templates['F'.$level] = $this->isUnicodeFont();
23284
				}
23285
			}
23286
		}
23287
		$this->SetFont($current_font);
23288
		foreach ($this->outlines as $key => $outline) {
23289
			// get HTML template
23290
			$row = $templates[$outline['l']];
23291
			if ($this->empty_string($page)) {
23292
				$pagenum = $outline['p'];
23293
			} else {
23294
				// placemark to be replaced with the correct number
23295
				$pagenum = '{#'.($outline['p']).'}';
23296
				if ($templates['F'.$outline['l']]) {
23297
					$pagenum = '{'.$pagenum.'}';
23298
				}
23299
			}
23300
			// replace templates with current values
23301
			$row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
23302
			$row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
23303
			// add link to page
23304
			$row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
23305
			// write bookmark entry
23306
			$this->writeHTML($row, false, false, true, false, '');
23307
		}
23308
		// restore link styles
23309
		$this->htmlLinkColorArray = $prev_htmlLinkColorArray;
23310
		$this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
23311
		// move TOC page and replace numbers
23312
		$page_last = $this->getPage();
23313
		$numpages = $page_last - $page_first + 1;
23314
		if (!$this->empty_string($page)) {
23315
			for ($p = $page_first; $p <= $page_last; ++$p) {
23316
				// get page data
23317
				$temppage = $this->getPageBuffer($p);
23318
				for ($n = 1; $n <= $this->numpages; ++$n) {
23319
					// update page numbers
23320
					$k = '{#'.$n.'}';
23321
					$ku = '{'.$k.'}';
23322
					$alias_a = $this->_escape($k);
23323
					$alias_au = $this->_escape('{'.$k.'}');
23324
					if ($this->isunicode) {
23325
						$alias_b = $this->_escape($this->UTF8ToLatin1($k));
23326
						$alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
23327
						$alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
23328
						$alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
23329
					}
23330
					if ($n >= $page) {
23331
						$np = $n + $numpages;
23332
					} else {
23333
						$np = $n;
23334
					}
23335
					$ns = $this->formatTOCPageNumber($np);
23336
					$nu = $ns;
23337
					if ($correct_align) {
23338
						$sdiff = strlen($k) - strlen($ns);
23339
						$sdiffu = strlen($ku) - strlen($ns);
23340
						$sfill = str_repeat(' ', $sdiff);
23341
						$sfillu = str_repeat(' ', $sdiffu);
23342
						if ($this->rtl) {
23343
							$ns = $ns.$sfill;
23344
							$nu = $nu.$sfillu;
23345
						} else {
23346
							$ns = $sfill.$ns;
23347
							$nu = $sfillu.$nu;
23348
						}
23349
					}
23350
					$nu = $this->UTF8ToUTF16BE($nu, false);
23351
					$temppage = str_replace($alias_au, $nu, $temppage);
23352
					if ($this->isunicode) {
23353
						$temppage = str_replace($alias_bu, $nu, $temppage);
23354
						$temppage = str_replace($alias_cu, $nu, $temppage);
23355
						$temppage = str_replace($alias_b, $ns, $temppage);
23356
						$temppage = str_replace($alias_c, $ns, $temppage);
23357
					}
23358
					$temppage = str_replace($alias_a, $ns, $temppage);
23359
				}
23360
				// save changes
23361
				$this->setPageBuffer($p, $temppage);
23362
			}
23363
			// move pages
23364
			$this->Bookmark($toc_name, 0, 0, $page_first);
23365
			for ($i = 0; $i < $numpages; ++$i) {
23366
				$this->movePage($page_last, $page);
23367
			}
23368
		}
23369
	}
23370
 
23371
	/**
23372
	 * Stores a copy of the current TCPDF object used for undo operation.
23373
	 * @access public
23374
	 * @since 4.5.029 (2009-03-19)
23375
	 */
23376
	public function startTransaction() {
23377
		if (isset($this->objcopy)) {
23378
			// remove previous copy
23379
			$this->commitTransaction();
23380
		}
23381
		// record current page number and Y position
23382
		$this->start_transaction_page = $this->page;
23383
		$this->start_transaction_y = $this->y;
23384
		// clone current object
23385
		$this->objcopy = $this->objclone($this);
23386
	}
23387
 
23388
	/**
23389
	 * Delete the copy of the current TCPDF object used for undo operation.
23390
	 * @access public
23391
	 * @since 4.5.029 (2009-03-19)
23392
	 */
23393
	public function commitTransaction() {
23394
		if (isset($this->objcopy)) {
23395
			$this->objcopy->_destroy(true, true);
23396
			unset($this->objcopy);
23397
		}
23398
	}
23399
 
23400
	/**
23401
	 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
23402
	 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
23403
	 * @return TCPDF object.
23404
	 * @access public
23405
	 * @since 4.5.029 (2009-03-19)
23406
	 */
23407
	public function rollbackTransaction($self=false) {
23408
		if (isset($this->objcopy)) {
23409
			if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
23410
				// truncate files to previous values
23411
				foreach ($this->objcopy->cache_file_length as $file => $length) {
23412
					$file = substr($file, 1);
23413
					$handle = fopen($file, 'r+');
23414
					ftruncate($handle, $length);
23415
				}
23416
			}
23417
			$this->_destroy(true, true);
23418
			if ($self) {
23419
				$objvars = get_object_vars($this->objcopy);
23420
				foreach ($objvars as $key => $value) {
23421
					$this->$key = $value;
23422
				}
23423
			}
23424
			return $this->objcopy;
23425
		}
23426
		return $this;
23427
	}
23428
 
23429
	/**
23430
	 * Creates a copy of a class object
23431
	 * @param object $object class object to be cloned
23432
	 * @return cloned object
23433
	 * @access public
23434
	 * @since 4.5.029 (2009-03-19)
23435
	 */
23436
	public function objclone($object) {
23437
		return @clone($object);
23438
	}
23439
 
23440
	/**
23441
	 * Determine whether a string is empty.
23442
	 * @param string $str string to be checked
23443
	 * @return boolean true if string is empty
23444
	 * @access public
23445
	 * @since 4.5.044 (2009-04-16)
23446
	 */
23447
	public function empty_string($str) {
23448
		return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
23449
	}
23450
 
23451
	/**
23452
	 * Find position of last occurrence of a substring in a string
23453
	 * @param string $haystack The string to search in.
23454
	 * @param string $needle substring to search.
23455
	 * @param int $offset May be specified to begin searching an arbitrary number of characters into the string.
23456
	 * @return Returns the position where the needle exists. Returns FALSE if the needle was not found.
23457
	 * @access public
23458
	 * @since 4.8.038 (2010-03-13)
23459
	 */
23460
	public function revstrpos($haystack, $needle, $offset = 0) {
23461
		$length = strlen($haystack);
23462
		$offset = ($offset > 0)?($length - $offset):abs($offset);
23463
		$pos = strpos(strrev($haystack), strrev($needle), $offset);
23464
		return ($pos === false)?false:($length - $pos - strlen($needle));
23465
	}
23466
 
23467
	// --- MULTI COLUMNS METHODS -----------------------
23468
 
23469
	/**
23470
	 * Set multiple columns of the same size
23471
	 * @param int $numcols number of columns (set to zero to disable columns mode)
23472
	 * @param int $width column width
23473
	 * @param int $y column starting Y position (leave empty for current Y position)
23474
	 * @access public
23475
	 * @since 4.9.001 (2010-03-28)
23476
	 */
23477
	public function setEqualColumns($numcols=0, $width=0, $y='') {
23478
		$this->columns = array();
23479
		if ($numcols < 2) {
23480
			$numcols = 0;
23481
			$this->columns = array();
23482
		} else {
23483
			// maximum column width
23484
			$maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
23485
			if (($width == 0) OR ($width > $maxwidth)) {
23486
				$width = $maxwidth;
23487
			}
23488
			if ($this->empty_string($y)) {
23489
				$y = $this->y;
23490
			}
23491
			// space between columns
23492
			$space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
23493
			// fill the columns array (with, space, starting Y position)
23494
			for ($i = 0; $i < $numcols; ++$i) {
23495
				$this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
23496
			}
23497
		}
23498
		$this->num_columns = $numcols;
23499
		$this->current_column = 0;
23500
		$this->column_start_page = $this->page;
23501
	}
23502
 
23503
	/**
23504
	 * Set columns array.
23505
	 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
23506
	 * @param array $columns
23507
	 * @access public
23508
	 * @since 4.9.001 (2010-03-28)
23509
	 */
23510
	public function setColumnsArray($columns) {
23511
		$this->columns = $columns;
23512
		$this->num_columns = count($columns);
23513
		$this->current_column = 0;
23514
		$this->column_start_page = $this->page;
23515
	}
23516
 
23517
	/**
23518
	 * Set position at a given column
23519
	 * @param int $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
23520
	 * @access public
23521
	 * @since 4.9.001 (2010-03-28)
23522
	 */
23523
	public function selectColumn($col='') {
23524
		if (is_string($col)) {
23525
			$col = $this->current_column;
23526
		} elseif($col >= $this->num_columns) {
23527
			$col = 0;
23528
		}
23529
		$xshift = 0;
23530
		$enable_thead = false;
23531
		if ($this->num_columns > 1) {
23532
			if ($col != $this->current_column) {
23533
				// move Y pointer at the top of the column
23534
				if ($this->column_start_page == $this->page) {
23535
					$this->y = $this->columns[$col]['y'];
23536
				} else {
23537
					$this->y = $this->tMargin;
23538
				}
23539
				// Avoid to write table headers more than once
23540
				if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
23541
					$enable_thead = true;
23542
					$this->maxselcol['page'] = $this->page;
23543
					$this->maxselcol['column'] = $col;
23544
				}
23545
			}
23546
			$xshift = $this->colxshift;
23547
			// set X position of the current column by case
23548
			$listindent = ($this->listindentlevel * $this->listindent);
23549
			$colpos = ($col * ($this->columns[$col]['w'] + $this->columns[$col]['s']));
23550
			if ($this->rtl) {
23551
				$x = $this->w - $this->original_rMargin - $colpos;
23552
				$this->rMargin = ($this->w - $x + $listindent);
23553
				$this->lMargin = ($x - $this->columns[$col]['w']);
23554
				$this->x = $x - $listindent;
23555
			} else {
23556
				$x = $this->original_lMargin + $colpos;
23557
				$this->lMargin = ($x + $listindent);
23558
				$this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
23559
				$this->x = $x + $listindent;
23560
			}
23561
			$this->columns[$col]['x'] = $x;
23562
		}
23563
		$this->current_column = $col;
23564
		// fix for HTML mode
23565
		$this->newline = true;
23566
		// print HTML table header (if any)
23567
		if ((!$this->empty_string($this->thead)) AND (!$this->inthead)) {
23568
			if ($enable_thead) {
23569
				// print table header
23570
				$this->writeHTML($this->thead, false, false, false, false, '');
23571
				$this->y += $xshift['s']['V'];
23572
				// store end of header position
23573
				if (!isset($this->columns[$col]['th'])) {
23574
					$this->columns[$col]['th'] = array();
23575
				}
23576
				$this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
23577
				$this->lasth = 0;
23578
			} elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
23579
				$this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
23580
			}
23581
		}
23582
		// account for an html table cell over multiple columns
23583
		if ($this->rtl) {
23584
			$this->rMargin += $xshift['x'];
23585
			$this->x -= ($xshift['x'] + $xshift['p']['R']);
23586
		} else {
23587
			$this->lMargin += $xshift['x'];
23588
			$this->x += $xshift['x'] + $xshift['p']['L'];
23589
		}
23590
	}
23591
 
23592
	/**
23593
	 * Return the current column number
23594
	 * @return int current column number
23595
	 * @access public
23596
	 * @since 5.5.011 (2010-07-08)
23597
	 */
23598
	public function getColumn() {
23599
		return $this->current_column;
23600
	}
23601
 
23602
	/**
23603
	 * Return the current number of columns.
23604
	 * @return int number of columns
23605
	 * @access public
23606
	 * @since 5.8.018 (2010-08-25)
23607
	 */
23608
	public function getNumberOfColumns() {
23609
		return $this->num_columns;
23610
	}
23611
 
23612
	/**
23613
	 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
23614
	 * @param array $pararray parameters array
23615
	 * @return sting containing serialized data
23616
	 * @access public
23617
	 * @since 4.9.006 (2010-04-02)
23618
	 */
23619
	public function serializeTCPDFtagParameters($pararray) {
23620
		return urlencode(serialize($pararray));
23621
	}
23622
 
23623
	/**
23624
	 * Set Text rendering mode.
23625
	 * @param int $stroke outline size in user units (0 = disable).
23626
	 * @param boolean $fill if true fills the text (default).
23627
	 * @param boolean $clip if true activate clipping mode
23628
	 * @access public
23629
	 * @since 4.9.008 (2009-04-02)
23630
	 */
23631
	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
23632
		// Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
23633
		// convert text rendering parameters
23634
		if ($stroke < 0) {
23635
			$stroke = 0;
23636
		}
23637
		if ($fill === true) {
23638
			if ($stroke > 0) {
23639
				if ($clip === true) {
23640
					// Fill, then stroke text and add to path for clipping
23641
					$textrendermode = 6;
23642
				} else {
23643
					// Fill, then stroke text
23644
					$textrendermode = 2;
23645
				}
23646
				$textstrokewidth = $stroke;
23647
			} else {
23648
				if ($clip === true) {
23649
					// Fill text and add to path for clipping
23650
					$textrendermode = 4;
23651
				} else {
23652
					// Fill text
23653
					$textrendermode = 0;
23654
				}
23655
			}
23656
		} else {
23657
			if ($stroke > 0) {
23658
				if ($clip === true) {
23659
					// Stroke text and add to path for clipping
23660
					$textrendermode = 5;
23661
				} else {
23662
					// Stroke text
23663
					$textrendermode = 1;
23664
				}
23665
				$textstrokewidth = $stroke;
23666
			} else {
23667
				if ($clip === true) {
23668
					// Add text to path for clipping
23669
					$textrendermode = 7;
23670
				} else {
23671
					// Neither fill nor stroke text (invisible)
23672
					$textrendermode = 3;
23673
				}
23674
			}
23675
		}
23676
		$this->textrendermode = $textrendermode;
23677
		$this->textstrokewidth = $stroke * $this->k;
23678
	}
23679
 
23680
	/**
23681
	 * Returns an array of chars containing soft hyphens.
23682
	 * @param array $word array of chars
23683
	 * @param array $patterns Array of hypenation patterns.
23684
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algoritm.
23685
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
23686
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
23687
	 * @param int $charmin Minimum word lenght to apply the hyphenation algoritm.
23688
	 * @param int $charmax Maximum lenght of broken piece of word.
23689
	 * @return array text with soft hyphens
23690
	 * @author Nicola Asuni
23691
	 * @since 4.9.012 (2010-04-12)
23692
	 * @access protected
23693
	 */
23694
	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
23695
		$hyphenword = array(); // hyphens positions
23696
		$numchars = count($word);
23697
		if ($numchars <= $charmin) {
23698
			return $word;
23699
		}
23700
		$word_string = $this->UTF8ArrSubString($word);
23701
		// some words will be returned as-is
23702
		$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})(\]?)$/';
23703
		if (preg_match($pattern, $word_string) > 0) {
23704
			// email
23705
			return $word;
23706
		}
23707
		$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})(\]?)$/';
23708
		if (preg_match($pattern, $word_string) > 0) {
23709
			// URL
23710
			return $word;
23711
		}
23712
		if (isset($dictionary[$word_string])) {
23713
			return $this->UTF8StringToArray($dictionary[$word_string]);
23714
		}
23715
		// suround word with '_' characters
23716
		$tmpword = array_merge(array(95), $word, array(95));
23717
		$tmpnumchars = $numchars + 2;
23718
		$maxpos = $tmpnumchars - $charmin;
23719
		for ($pos = 0; $pos < $maxpos; ++$pos) {
23720
			$imax = min(($tmpnumchars - $pos), $charmax);
23721
			for ($i = $charmin; $i <= $imax; ++$i) {
23722
				$subword = strtolower($this->UTF8ArrSubString($tmpword, $pos, $pos + $i));
23723
				if (isset($patterns[$subword])) {
23724
					$pattern = $this->UTF8StringToArray($patterns[$subword]);
23725
					$pattern_length = count($pattern);
23726
					$digits = 1;
23727
					for ($j = 0; $j < $pattern_length; ++$j) {
23728
						// check if $pattern[$j] is a number
23729
						if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
23730
							if ($j == 0) {
23731
								$zero = $pos - 1;
23732
							} else {
23733
								$zero = $pos + $j - $digits;
23734
							}
23735
							if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
23736
								$hyphenword[$zero] = $this->unichr($pattern[$j]);
23737
							}
23738
							++$digits;
23739
						}
23740
					}
23741
				}
23742
			}
23743
		}
23744
		$inserted = 0;
23745
		$maxpos = $numchars - $rightmin;
23746
		for($i = $leftmin; $i <= $maxpos; ++$i) {
23747
			if(isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
23748
				// 173 = soft hyphen character
23749
				array_splice($word, $i + $inserted, 0, 173);
23750
				++$inserted;
23751
			}
23752
		}
23753
		return $word;
23754
	}
23755
 
23756
	/**
23757
	 * Returns an array of hyphenation patterns.
23758
	 * @param string $file TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
23759
	 * @return array of hyphenation patterns
23760
	 * @author Nicola Asuni
23761
	 * @since 4.9.012 (2010-04-12)
23762
	 * @access public
23763
	 */
23764
	public function getHyphenPatternsFromTEX($file) {
23765
		// TEX patterns are available at:
23766
		// http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
23767
		$data = file_get_contents($file);
23768
		$patterns = array();
23769
		// remove comments
23770
		$data = preg_replace('/\%[^\n]*/', '', $data);
23771
		// extract the patterns part
23772
		preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
23773
		$data = trim(substr($matches[0], 10, -1));
23774
		// extract each pattern
23775
		$patterns_array = preg_split('/[\s]+/', $data);
23776
		// create new language array of patterns
23777
		$patterns = array();
23778
		foreach($patterns_array as $val) {
23779
			if (!$this->empty_string($val)) {
23780
				$val = trim($val);
23781
				$val = str_replace('\'', '\\\'', $val);
23782
				$key = preg_replace('/[0-9]+/', '', $val);
23783
				$patterns[$key] = $val;
23784
			}
23785
		}
23786
		return $patterns;
23787
	}
23788
 
23789
	/**
23790
	 * Returns text with soft hyphens.
23791
	 * @param string $text text to process
23792
	 * @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/
23793
	 * @param array $dictionary Array of words to be returned without applying the hyphenation algoritm.
23794
	 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
23795
	 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
23796
	 * @param int $charmin Minimum word lenght to apply the hyphenation algoritm.
23797
	 * @param int $charmax Maximum lenght of broken piece of word.
23798
	 * @return array text with soft hyphens
23799
	 * @author Nicola Asuni
23800
	 * @since 4.9.012 (2010-04-12)
23801
	 * @access public
23802
	 */
23803
	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
23804
		$text = $this->unhtmlentities($text);
23805
		$word = array(); // last word
23806
		$txtarr = array(); // text to be returned
23807
		$intag = false; // true if we are inside an HTML tag
23808
		if (!is_array($patterns)) {
23809
			$patterns = $this->getHyphenPatternsFromTEX($patterns);
23810
		}
23811
		// get array of characters
23812
		$unichars = $this->UTF8StringToArray($text);
23813
		// for each char
23814
		foreach ($unichars as $char) {
23815
			if ((!$intag) AND $this->unicode->uni_type[$char] == 'L') {
23816
				// letter character
23817
				$word[] = $char;
23818
			} else {
23819
				// other type of character
23820
				if (!$this->empty_string($word)) {
23821
					// hypenate the word
23822
					$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
23823
					$word = array();
23824
				}
23825
				$txtarr[] = $char;
23826
				if (chr($char) == '<') {
23827
					// we are inside an HTML tag
23828
					$intag = true;
23829
				} elseif ($intag AND (chr($char) == '>')) {
23830
					// end of HTML tag
23831
					$intag = false;
23832
				}
23833
			}
23834
		}
23835
		if (!$this->empty_string($word)) {
23836
			// hypenate the word
23837
			$txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
23838
		}
23839
		// convert char array to string and return
23840
		return $this->UTF8ArrSubString($txtarr);
23841
	}
23842
 
23843
	/**
23844
	 * Enable/disable rasterization of vector images using ImageMagick library.
23845
	 * @param boolean $mode if true enable rasterization, false otherwise.
23846
	 * @access public
23847
	 * @since 5.0.000 (2010-04-27)
23848
	 */
23849
	public function setRasterizeVectorImages($mode) {
23850
		$this->rasterize_vector_images = $mode;
23851
	}
23852
 
23853
	/**
23854
	 * Get the Path-Painting Operators.
23855
	 * @param string $style Style of rendering. Possible values are:
23856
	 * <ul>
23857
	 *   <li>S or D: Stroke the path.</li>
23858
	 *   <li>s or d: Close and stroke the path.</li>
23859
	 *   <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
23860
	 *   <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
23861
	 *   <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
23862
	 *   <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
23863
	 *   <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
23864
	 *   <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
23865
	 *   <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
23866
	 *   <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
23867
	 *   <li>n: End the path object without filling or stroking it.</li>
23868
	 * </ul>
23869
	 * @param string $default default style
23870
	 * @param boolean $mode if true enable rasterization, false otherwise.
23871
	 * @author Nicola Asuni
23872
	 * @access protected
23873
	 * @since 5.0.000 (2010-04-30)
23874
	 */
23875
	protected function getPathPaintOperator($style, $default='S') {
23876
		$op = '';
23877
		switch($style) {
23878
			case 'S':
23879
			case 'D': {
23880
				$op = 'S';
23881
				break;
23882
			}
23883
			case 's':
23884
			case 'd': {
23885
				$op = 's';
23886
				break;
23887
			}
23888
			case 'f':
23889
			case 'F': {
23890
				$op = 'f';
23891
				break;
23892
			}
23893
			case 'f*':
23894
			case 'F*': {
23895
				$op = 'f*';
23896
				break;
23897
			}
23898
			case 'B':
23899
			case 'FD':
23900
			case 'DF': {
23901
				$op = 'B';
23902
				break;
23903
			}
23904
			case 'B*':
23905
			case 'F*D':
23906
			case 'DF*': {
23907
				$op = 'B*';
23908
				break;
23909
			}
23910
			case 'b':
23911
			case 'fd':
23912
			case 'df': {
23913
				$op = 'b';
23914
				break;
23915
			}
23916
			case 'b*':
23917
			case 'f*d':
23918
			case 'df*': {
23919
				$op = 'b*';
23920
				break;
23921
			}
23922
			case 'CNZ': {
23923
				$op = 'W n';
23924
				break;
23925
			}
23926
			case 'CEO': {
23927
				$op = 'W* n';
23928
				break;
23929
			}
23930
			case 'n': {
23931
				$op = 'n';
23932
				break;
23933
			}
23934
			default: {
23935
				if (!empty($default)) {
23936
					$op = $this->getPathPaintOperator($default, '');
23937
				} else {
23938
					$op = '';
23939
				}
23940
			}
23941
		}
23942
		return $op;
23943
	}
23944
 
23945
	/**
23946
	 * Enable or disable default option for font subsetting.
23947
	 * @param boolean $enable if true enable font subsetting by default.
23948
	 * @author Nicola Asuni
23949
	 * @access public
23950
	 * @since 5.3.002 (2010-06-07)
23951
	 */
23952
	public function setFontSubsetting($enable=true) {
23953
		$this->font_subsetting = $enable ? true : false;
23954
	}
23955
 
23956
	/**
23957
	 * Return the default option for font subsetting.
23958
	 * @return boolean default font subsetting state.
23959
	 * @author Nicola Asuni
23960
	 * @access public
23961
	 * @since 5.3.002 (2010-06-07)
23962
	 */
23963
	public function getFontSubsetting() {
23964
		return $this->font_subsetting;
23965
	}
23966
 
23967
	/**
23968
	 * Left trim the input string
23969
	 * @param string $str string to trim
23970
	 * @param string $replace string that replace spaces.
23971
	 * @return left trimmed string
23972
	 * @author Nicola Asuni
23973
	 * @access public
23974
	 * @since 5.8.000 (2010-08-11)
23975
	 */
23976
	public function stringLeftTrim($str, $replace='') {
23977
		return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
23978
	}
23979
 
23980
	/**
23981
	 * Right trim the input string
23982
	 * @param string $str string to trim
23983
	 * @param string $replace string that replace spaces.
23984
	 * @return right trimmed string
23985
	 * @author Nicola Asuni
23986
	 * @access public
23987
	 * @since 5.8.000 (2010-08-11)
23988
	 */
23989
	public function stringRightTrim($str, $replace='') {
23990
		return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
23991
	}
23992
 
23993
	/**
23994
	 * Trim the input string
23995
	 * @param string $str string to trim
23996
	 * @param string $replace string that replace spaces.
23997
	 * @return trimmed string
23998
	 * @author Nicola Asuni
23999
	 * @access public
24000
	 * @since 5.8.000 (2010-08-11)
24001
	 */
24002
	public function stringTrim($str, $replace='') {
24003
		$str = $this->stringLeftTrim($str, $replace);
24004
		$str = $this->stringRightTrim($str, $replace);
24005
		return $str;
24006
	}
24007
 
24008
	/**
24009
	 * Return true if the current font is unicode type.
24010
	 * @return true for unicode font, false otherwise.
24011
	 * @author Nicola Asuni
24012
	 * @access public
24013
	 * @since 5.8.002 (2010-08-14)
24014
	 */
24015
	public function isUnicodeFont() {
24016
		return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
24017
	}
24018
 
24019
	/**
24020
	 * Return normalized font name
24021
	 * @param string $fontfamily property string containing font family names
24022
	 * @return string normalized font name
24023
	 * @author Nicola Asuni
24024
	 * @access public
24025
	 * @since 5.8.004 (2010-08-17)
24026
	 */
24027
	public function getFontFamilyName($fontfamily) {
24028
		// remove spaces and symbols
24029
		$fontfamily = preg_replace('/[^a-z0-9\,]/', '', strtolower($fontfamily));
24030
		// extract all font names
24031
		$fontslist = preg_split('/[,]/', $fontfamily);
24032
		// find first valid font name
24033
		foreach ($fontslist as $font) {
24034
			// replace font variations
24035
			$font = preg_replace('/italic$/', 'I', $font);
24036
			$font = preg_replace('/oblique$/', 'I', $font);
24037
			$font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
24038
			// replace common family names and core fonts
24039
			$pattern = array();
24040
			$replacement = array();
24041
			$pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
24042
			$replacement[] = 'times';
24043
			$pattern[] = '/^sansserif/';
24044
			$replacement[] = 'helvetica';
24045
			$pattern[] = '/^monospace/';
24046
			$replacement[] = 'courier';
24047
			$font = preg_replace($pattern, $replacement, $font);
24048
			if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
24049
				return $font;
24050
			}
24051
		}
24052
		// return current font as default
24053
		return $this->CurrentFont['fontkey'];
24054
	}
24055
 
24056
	/**
24057
	 * Start a new XObject Template.
24058
	 * 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).
24059
	 * 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.
24060
	 * Note: X,Y coordinates will be reset to 0,0.
24061
	 * @param int $w Template width in user units (empty string or zero = page width less margins)
24062
	 * @param int $h Template height in user units (empty string or zero = page height less margins)
24063
	 * @return int the XObject Template ID in case of success or false in case of error.
24064
	 * @author Nicola Asuni
24065
	 * @access public
24066
	 * @since 5.8.017 (2010-08-24)
24067
	 * @see endTemplate(), printTemplate()
24068
	 */
24069
	public function startTemplate($w=0, $h=0) {
24070
		if ($this->inxobj) {
24071
			// we are already inside an XObject template
24072
			return false;
24073
		}
24074
		$this->inxobj = true;
24075
		++$this->n;
24076
		// XObject ID
24077
		$this->xobjid = 'XT'.$this->n;
24078
		// object ID
24079
		$this->xobjects[$this->xobjid] = array('n' => $this->n);
24080
		// store current graphic state
24081
		$this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
24082
		// initialize data
24083
		$this->xobjects[$this->xobjid]['intmrk'] = 0;
24084
		$this->xobjects[$this->xobjid]['transfmrk'] = array();
24085
		$this->xobjects[$this->xobjid]['outdata'] = '';
24086
		$this->xobjects[$this->xobjid]['xobjects'] = array();
24087
		$this->xobjects[$this->xobjid]['images'] = array();
24088
		$this->xobjects[$this->xobjid]['fonts'] = array();
24089
		$this->xobjects[$this->xobjid]['annotations'] = array();
24090
		// set new environment
24091
		$this->num_columns = 1;
24092
		$this->current_column = 0;
24093
		$this->SetAutoPageBreak(false);
24094
		if (($w === '') OR ($w <= 0)) {
24095
			$w = $this->w - $this->lMargin - $this->rMargin;
24096
		}
24097
		if (($h === '') OR ($h <= 0)) {
24098
			$h = $this->h - $this->tMargin - $this->bMargin;
24099
		}
24100
		$this->xobjects[$this->xobjid]['x'] = 0;
24101
		$this->xobjects[$this->xobjid]['y'] = 0;
24102
		$this->xobjects[$this->xobjid]['w'] = $w;
24103
		$this->xobjects[$this->xobjid]['h'] = $h;
24104
		$this->w = $w;
24105
		$this->h = $h;
24106
		$this->wPt = $this->w * $this->k;
24107
		$this->hPt = $this->h * $this->k;
24108
		$this->fwPt = $this->wPt;
24109
		$this->fhPt = $this->hPt;
24110
		$this->x = 0;
24111
		$this->y = 0;
24112
		$this->lMargin = 0;
24113
		$this->rMargin = 0;
24114
		$this->tMargin = 0;
24115
		$this->bMargin = 0;
24116
		return $this->xobjid;
24117
	}
24118
 
24119
	/**
24120
	 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
24121
	 * 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).
24122
	 * 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.
24123
	 * @return int the XObject Template ID in case of success or false in case of error.
24124
	 * @author Nicola Asuni
24125
	 * @access public
24126
	 * @since 5.8.017 (2010-08-24)
24127
	 * @see startTemplate(), printTemplate()
24128
	 */
24129
	public function endTemplate() {
24130
		if (!$this->inxobj) {
24131
			// we are not inside a template
24132
			return false;
24133
		}
24134
		$this->inxobj = false;
24135
		// restore previous graphic state
24136
		$this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
24137
		return $this->xobjid;
24138
	}
24139
 
24140
	/**
24141
	 * Print an XObject Template.
24142
	 * You can print an XObject Template inside the currently opened Template.
24143
	 * 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).
24144
	 * 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.
24145
	 * @param string $id The ID of XObject Template to print.
24146
	 * @param int $x X position in user units (empty string = current x position)
24147
	 * @param int $y Y position in user units (empty string = current y position)
24148
	 * @param int $w Width in user units (zero = remaining page width)
24149
	 * @param int $h Height in user units (zero = remaining page height)
24150
	 * @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>
24151
	 * @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>
24152
	 * @param boolean $fitonpage if true the template is resized to not exceed page dimensions.
24153
	 * @author Nicola Asuni
24154
	 * @access public
24155
	 * @since 5.8.017 (2010-08-24)
24156
	 * @see startTemplate(), endTemplate()
24157
	 */
24158
	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
24159
		if (!isset($this->xobjects[$id])) {
24160
			$this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
24161
		}
24162
		if ($this->inxobj) {
24163
			if ($id == $this->xobjid) {
24164
				// close current template
24165
				$this->endTemplate();
24166
			} else {
24167
				// use the template as resource for the template currently opened
24168
				$this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
24169
			}
24170
		}
24171
		// set default values
24172
		if ($x === '') {
24173
			$x = $this->x;
24174
		}
24175
		if ($y === '') {
24176
			$y = $this->y;
24177
		}
24178
		// check page for no-write regions and adapt page margins if necessary
24179
		$this->checkPageRegions($h, $x, $y);
24180
		$ow = $this->xobjects[$id]['w'];
24181
		$oh = $this->xobjects[$id]['h'];
24182
		// calculate template width and height on document
24183
		if (($w <= 0) AND ($h <= 0)) {
24184
			$w = $ow;
24185
			$h = $oh;
24186
		} elseif ($w <= 0) {
24187
			$w = $h * $ow / $oh;
24188
		} elseif ($h <= 0) {
24189
			$h = $w * $oh / $ow;
24190
		}
24191
		// fit the template on available space
24192
		$this->fitBlock($w, $h, $x, $y, $fitonpage);
24193
		// set page alignment
24194
		$rb_y = $y + $h;
24195
		// set alignment
24196
		if ($this->rtl) {
24197
			if ($palign == 'L') {
24198
				$xt = $this->lMargin;
24199
			} elseif ($palign == 'C') {
24200
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24201
			} elseif ($palign == 'R') {
24202
				$xt = $this->w - $this->rMargin - $w;
24203
			} else {
24204
				$xt = $x - $w;
24205
			}
24206
			$rb_x = $xt;
24207
		} else {
24208
			if ($palign == 'L') {
24209
				$xt = $this->lMargin;
24210
			} elseif ($palign == 'C') {
24211
				$xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24212
			} elseif ($palign == 'R') {
24213
				$xt = $this->w - $this->rMargin - $w;
24214
			} else {
24215
				$xt = $x;
24216
			}
24217
			$rb_x = $xt + $w;
24218
		}
24219
		// print XObject Template + Transformation matrix
24220
		$this->StartTransform();
24221
		// translate and scale
24222
		$sx = ($w / $this->xobjects[$id]['w']);
24223
		$sy = ($h / $this->xobjects[$id]['h']);
24224
		$tm = array();
24225
		$tm[0] = $sx;
24226
		$tm[1] = 0;
24227
		$tm[2] = 0;
24228
		$tm[3] = $sy;
24229
		$tm[4] = $xt * $this->k;
24230
		$tm[5] = ($this->h - $h - $y) * $this->k;
24231
		$this->Transform($tm);
24232
		// set object
24233
		$this->_out('/'.$id.' Do');
24234
		$this->StopTransform();
24235
		// add annotations
24236
		if (!empty($this->xobjects[$id]['annotations'])) {
24237
			foreach ($this->xobjects[$id]['annotations'] as $annot) {
24238
				// transform original coordinates
24239
				$coordlt = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
24240
				$ax = ($coordlt[4] / $this->k);
24241
				$ay = ($this->h - $h - ($coordlt[5] / $this->k));
24242
				$coordrb = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
24243
				$aw = ($coordrb[4] / $this->k) - $ax;
24244
				$ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
24245
				$this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
24246
			}
24247
		}
24248
		// set pointer to align the next text/objects
24249
		switch($align) {
24250
			case 'T': {
24251
				$this->y = $y;
24252
				$this->x = $rb_x;
24253
				break;
24254
			}
24255
			case 'M': {
24256
				$this->y = $y + round($h/2);
24257
				$this->x = $rb_x;
24258
				break;
24259
			}
24260
			case 'B': {
24261
				$this->y = $rb_y;
24262
				$this->x = $rb_x;
24263
				break;
24264
			}
24265
			case 'N': {
24266
				$this->SetY($rb_y);
24267
				break;
24268
			}
24269
			default:{
24270
				break;
24271
			}
24272
		}
24273
	}
24274
 
24275
	/**
24276
	 * Set the percentage of character stretching.
24277
	 * @param int $perc percentage of stretching (100 = no stretching)
24278
	 * @author Nicola Asuni
24279
	 * @access public
24280
	 * @since 5.9.000 (2010-09-29)
24281
	 */
24282
	public function setFontStretching($perc=100) {
24283
		$this->font_stretching = $perc;
24284
	}
24285
 
24286
	/**
24287
	 * Get the percentage of character stretching.
24288
	 * @return float stretching value
24289
	 * @author Nicola Asuni
24290
	 * @access public
24291
	 * @since 5.9.000 (2010-09-29)
24292
	 */
24293
	public function getFontStretching() {
24294
		return $this->font_stretching;
24295
	}
24296
 
24297
	/**
24298
	 * Set the amount to increase or decrease the space between characters in a text.
24299
	 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
24300
	 * @author Nicola Asuni
24301
	 * @access public
24302
	 * @since 5.9.000 (2010-09-29)
24303
	 */
24304
	public function setFontSpacing($spacing=0) {
24305
		$this->font_spacing = $spacing;
24306
	}
24307
 
24308
	/**
24309
	 * Get the amount to increase or decrease the space between characters in a text.
24310
	 * @return int font spacing (tracking/kerning) value
24311
	 * @author Nicola Asuni
24312
	 * @access public
24313
	 * @since 5.9.000 (2010-09-29)
24314
	 */
24315
	public function getFontSpacing() {
24316
		return $this->font_spacing;
24317
	}
24318
 
24319
	/**
24320
	 * Return an array of no-write page regions
24321
	 * @return array of no-write page regions
24322
	 * @author Nicola Asuni
24323
	 * @access public
24324
	 * @since 5.9.003 (2010-10-13)
24325
	 * @see setPageRegions(), addPageRegion()
24326
	 */
24327
	public function getPageRegions() {
24328
		return $this->page_regions;
24329
	}
24330
 
24331
	/**
24332
	 * Set no-write regions on page.
24333
	 * 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.
24334
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
24335
	 * You can set multiple regions for the same page.
24336
	 * @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.
24337
	 * @author Nicola Asuni
24338
	 * @access public
24339
	 * @since 5.9.003 (2010-10-13)
24340
	 * @see addPageRegion(), getPageRegions()
24341
	 */
24342
	public function setPageRegions($regions=array()) {
24343
		// empty current regions array
24344
		$this->page_regions = array();
24345
		// add regions
24346
		foreach ($regions as $data) {
24347
			$this->addPageRegion($data);
24348
		}
24349
	}
24350
 
24351
	/**
24352
	 * Add a single no-write region on selected page.
24353
	 * 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.
24354
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
24355
	 * You can set multiple regions for the same page.
24356
	 * @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).
24357
	 * @author Nicola Asuni
24358
	 * @access public
24359
	 * @since 5.9.003 (2010-10-13)
24360
	 * @see setPageRegions(), getPageRegions()
24361
	 */
24362
	public function addPageRegion($region) {
24363
		if (!isset($region['page']) OR empty($region['page'])) {
24364
			$region['page'] = $this->page;
24365
		}
24366
		if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
24367
			AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
24368
			AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
24369
			$this->page_regions[] = $region;
24370
		}
24371
	}
24372
 
24373
	/**
24374
	 * Remove a single no-write region.
24375
	 * @param int $key region key
24376
	 * @author Nicola Asuni
24377
	 * @access public
24378
	 * @since 5.9.003 (2010-10-13)
24379
	 * @see setPageRegions(), getPageRegions()
24380
	 */
24381
	public function removePageRegion($key) {
24382
		if (isset($this->page_regions[$key])) {
24383
			unset($this->page_regions[$key]);
24384
		}
24385
	}
24386
 
24387
	/**
24388
	 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
24389
	 * 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.
24390
	 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
24391
	 * @param float $h height of the text/image/object to print in user units
24392
	 * @param float $x current X coordinate in user units
24393
	 * @param float $y current Y coordinate in user units
24394
	 * @author Nicola Asuni
24395
	 * @access protected
24396
	 * @since 5.9.003 (2010-10-13)
24397
	 */
24398
	protected function checkPageRegions($h, &$x, &$y) {
24399
		// set default values
24400
		if ($x === '') {
24401
			$x = &$this->x;
24402
		}
24403
		if ($y === '') {
24404
			$y = &$this->y;
24405
		}
24406
		if (empty($this->page_regions)) {
24407
			// no page regions defined
24408
			return;
24409
		}
24410
		if (empty($h)) {
24411
			$h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
24412
		}
24413
		if ($this->rtl) {
24414
			$this->lMargin = $this->original_lMargin;
24415
		} else {
24416
			$this->rMargin = $this->original_rMargin;
24417
		}
24418
		if ($this->AutoPageBreak AND !$this->InFooter AND (($y + $h) > $this->PageBreakTrigger)) {
24419
			// the content will be printed on a new page
24420
			return;
24421
		}
24422
		// adjust coordinates and page margins
24423
		foreach ($this->page_regions as $regid => $regdata) {
24424
			if ($regdata['page'] == $this->page) {
24425
				// check region boundaries
24426
				if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
24427
					// Y is inside the region
24428
					$minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
24429
					$yt = max($y, $regdata['yt']);
24430
					$yb = min(($yt + $h), $regdata['yb']);
24431
					$xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
24432
					$xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
24433
					if ($regdata['side'] == 'L') { // left side
24434
						$new_margin = max($xt, $xb);
24435
						if ($this->lMargin < $new_margin) {
24436
							if ($this->rtl) {
24437
								// adjust left page margin
24438
								$this->lMargin = $new_margin;
24439
							}
24440
							if ($x < $new_margin) {
24441
								// adjust x position
24442
								$x = $new_margin;
24443
							}
24444
						}
24445
					} elseif ($regdata['side'] == 'R') { // right side
24446
						$new_margin = min($xt, $xb);
24447
						if (($this->w - $this->rMargin) > $new_margin) {
24448
							if (!$this->rtl) {
24449
								// adjust right page margin
24450
								$this->rMargin = ($this->w - $new_margin);
24451
							}
24452
							if ($x > $new_margin) {
24453
								// adjust x position
24454
								$x = $new_margin;
24455
							}
24456
						}
24457
					}
24458
				}
24459
			}
24460
		}
24461
	}
24462
 
24463
	// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
24464
	// SVG METHODS
24465
	// -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
24466
 
24467
	/**
24468
	 * Embedd a Scalable Vector Graphics (SVG) image.
24469
	 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
24470
	 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
24471
	 * @param float $x Abscissa of the upper-left corner.
24472
	 * @param float $y Ordinate of the upper-left corner.
24473
	 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
24474
	 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
24475
	 * @param mixed $link URL or identifier returned by AddLink().
24476
	 * @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.
24477
	 * @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>
24478
	 * @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)))
24479
	 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
24480
	 * @author Nicola Asuni
24481
	 * @since 5.0.000 (2010-05-02)
24482
	 * @access public
24483
	 */
24484
	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
24485
		if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
24486
			// convert SVG to raster image using GD or ImageMagick libraries
24487
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
24488
		}
24489
		if ($file{0} === '@') { // image from string
24490
			$this->svgdir = '';
24491
			$svgdata = substr($file, 1);
24492
		} else { // SVG file
24493
			$this->svgdir = dirname($file);
24494
			$svgdata = file_get_contents($file);
24495
		}
24496
		if ($svgdata === false) {
24497
			$this->Error('SVG file not found: '.$file);
24498
		}
24499
		if ($x === '') {
24500
			$x = $this->x;
24501
		}
24502
		if ($y === '') {
24503
			$y = $this->y;
24504
		}
24505
		// check page for no-write regions and adapt page margins if necessary
24506
		$this->checkPageRegions($h, $x, $y);
24507
		$k = $this->k;
24508
		$ox = 0;
24509
		$oy = 0;
24510
		$ow = $w;
24511
		$oh = $h;
24512
		$aspect_ratio_align = 'xMidYMid';
24513
		$aspect_ratio_ms = 'meet';
24514
		$regs = array();
24515
		// get original image width and height
24516
		preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
24517
		if (isset($regs[1]) AND !empty($regs[1])) {
24518
			$tmp = array();
24519
			if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24520
				$ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
24521
			}
24522
			$tmp = array();
24523
			if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24524
				$oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
24525
			}
24526
			$tmp = array();
24527
			if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24528
				$ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
24529
			}
24530
			$tmp = array();
24531
			if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24532
				$oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
24533
			}
24534
			$tmp = array();
24535
			$view_box = array();
24536
			if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
24537
				if (count($tmp) == 5) {
24538
					array_shift($tmp);
24539
					foreach ($tmp as $key => $val) {
24540
						$view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24541
					}
24542
					$ox = $view_box[0];
24543
					$oy = $view_box[1];
24544
				}
24545
				// get aspect ratio
24546
				$tmp = array();
24547
				if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24548
					$aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
24549
					switch (count($aspect_ratio)) {
24550
						case 3: {
24551
							$aspect_ratio_align = $aspect_ratio[1];
24552
							$aspect_ratio_ms = $aspect_ratio[2];
24553
							break;
24554
						}
24555
						case 2: {
24556
							$aspect_ratio_align = $aspect_ratio[0];
24557
							$aspect_ratio_ms = $aspect_ratio[1];
24558
							break;
24559
						}
24560
						case 1: {
24561
							$aspect_ratio_align = $aspect_ratio[0];
24562
							$aspect_ratio_ms = 'meet';
24563
							break;
24564
						}
24565
					}
24566
				}
24567
			}
24568
		}
24569
		// calculate image width and height on document
24570
		if (($w <= 0) AND ($h <= 0)) {
24571
			// convert image size to document unit
24572
			$w = $ow;
24573
			$h = $oh;
24574
		} elseif ($w <= 0) {
24575
			$w = $h * $ow / $oh;
24576
		} elseif ($h <= 0) {
24577
			$h = $w * $oh / $ow;
24578
		}
24579
		// fit the image on available space
24580
		$this->fitBlock($w, $h, $x, $y, $fitonpage);
24581
		if ($this->rasterize_vector_images) {
24582
			// convert SVG to raster image using GD or ImageMagick libraries
24583
			return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
24584
		}
24585
		// set alignment
24586
		$this->img_rb_y = $y + $h;
24587
		// set alignment
24588
		if ($this->rtl) {
24589
			if ($palign == 'L') {
24590
				$ximg = $this->lMargin;
24591
			} elseif ($palign == 'C') {
24592
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24593
			} elseif ($palign == 'R') {
24594
				$ximg = $this->w - $this->rMargin - $w;
24595
			} else {
24596
				$ximg = $x - $w;
24597
			}
24598
			$this->img_rb_x = $ximg;
24599
		} else {
24600
			if ($palign == 'L') {
24601
				$ximg = $this->lMargin;
24602
			} elseif ($palign == 'C') {
24603
				$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24604
			} elseif ($palign == 'R') {
24605
				$ximg = $this->w - $this->rMargin - $w;
24606
			} else {
24607
				$ximg = $x;
24608
			}
24609
			$this->img_rb_x = $ximg + $w;
24610
		}
24611
		// store current graphic vars
24612
		$gvars = $this->getGraphicVars();
24613
		// store SVG position and scale factors
24614
		$svgoffset_x = ($ximg - $ox) * $this->k;
24615
		$svgoffset_y = -($y - $oy) * $this->k;
24616
		if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
24617
			$ow = $view_box[2];
24618
			$oh = $view_box[3];
24619
		} else {
24620
			if ($ow <= 0) {
24621
				$ow = $w;
24622
			}
24623
			if ($oh <= 0) {
24624
				$oh = $h;
24625
			}
24626
		}
24627
		$svgscale_x = $w / $ow;
24628
		$svgscale_y = $h / $oh;
24629
		// scaling and alignment
24630
		if ($aspect_ratio_align != 'none') {
24631
			// store current scaling values
24632
			$svgscale_old_x = $svgscale_x;
24633
			$svgscale_old_y = $svgscale_y;
24634
			// force uniform scaling
24635
			if ($aspect_ratio_ms == 'slice') {
24636
				// the entire viewport is covered by the viewBox
24637
				if ($svgscale_x > $svgscale_y) {
24638
					$svgscale_y = $svgscale_x;
24639
				} elseif ($svgscale_x < $svgscale_y) {
24640
					$svgscale_x = $svgscale_y;
24641
				}
24642
			} else { // meet
24643
				// the entire viewBox is visible within the viewport
24644
				if ($svgscale_x < $svgscale_y) {
24645
					$svgscale_y = $svgscale_x;
24646
				} elseif ($svgscale_x > $svgscale_y) {
24647
					$svgscale_x = $svgscale_y;
24648
				}
24649
			}
24650
			// correct X alignment
24651
			switch (substr($aspect_ratio_align, 1, 3)) {
24652
				case 'Min': {
24653
					// do nothing
24654
					break;
24655
				}
24656
				case 'Max': {
24657
					$svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
24658
					break;
24659
				}
24660
				default:
24661
				case 'Mid': {
24662
					$svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
24663
					break;
24664
				}
24665
			}
24666
			// correct Y alignment
24667
			switch (substr($aspect_ratio_align, 5)) {
24668
				case 'Min': {
24669
					// do nothing
24670
					break;
24671
				}
24672
				case 'Max': {
24673
					$svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
24674
					break;
24675
				}
24676
				default:
24677
				case 'Mid': {
24678
					$svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
24679
					break;
24680
				}
24681
			}
24682
		}
24683
		// store current page break mode
24684
		$page_break_mode = $this->AutoPageBreak;
24685
		$page_break_margin = $this->getBreakMargin();
24686
		$cell_padding = $this->cell_padding;
24687
		$this->SetCellPadding(0);
24688
		$this->SetAutoPageBreak(false);
24689
		// save the current graphic state
24690
		$this->_out('q'.$this->epsmarker);
24691
		// set initial clipping mask
24692
		$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24693
		// scale and translate
24694
		$e = $ox * $this->k * (1 - $svgscale_x);
24695
		$f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
24696
		$this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $svgscale_x, 0, 0, $svgscale_y, $e + $svgoffset_x, $f + $svgoffset_y));
24697
		// creates a new XML parser to be used by the other XML functions
24698
		$this->parser = xml_parser_create('UTF-8');
24699
		// the following function allows to use parser inside object
24700
		xml_set_object($this->parser, $this);
24701
		// disable case-folding for this XML parser
24702
		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
24703
		// sets the element handler functions for the XML parser
24704
		xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
24705
		// sets the character data handler function for the XML parser
24706
		xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
24707
		// start parsing an XML document
24708
		if(!xml_parse($this->parser, $svgdata)) {
24709
			$error_message = sprintf("SVG Error: %s at line %d", xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
24710
			$this->Error($error_message);
24711
		}
24712
		// free this XML parser
24713
		xml_parser_free($this->parser);
24714
		// restore previous graphic state
24715
		$this->_out($this->epsmarker.'Q');
24716
		// restore graphic vars
24717
		$this->setGraphicVars($gvars);
24718
		$this->lasth = $gvars['lasth'];
24719
		if (!empty($border)) {
24720
			$bx = $this->x;
24721
			$by = $this->y;
24722
			$this->x = $ximg;
24723
			if ($this->rtl) {
24724
				$this->x += $w;
24725
			}
24726
			$this->y = $y;
24727
			$this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
24728
			$this->x = $bx;
24729
			$this->y = $by;
24730
		}
24731
		if ($link) {
24732
			$this->Link($ximg, $y, $w, $h, $link, 0);
24733
		}
24734
		// set pointer to align the next text/objects
24735
		switch($align) {
24736
			case 'T':{
24737
				$this->y = $y;
24738
				$this->x = $this->img_rb_x;
24739
				break;
24740
			}
24741
			case 'M':{
24742
				$this->y = $y + round($h/2);
24743
				$this->x = $this->img_rb_x;
24744
				break;
24745
			}
24746
			case 'B':{
24747
				$this->y = $this->img_rb_y;
24748
				$this->x = $this->img_rb_x;
24749
				break;
24750
			}
24751
			case 'N':{
24752
				$this->SetY($this->img_rb_y);
24753
				break;
24754
			}
24755
			default:{
24756
				// restore pointer to starting position
24757
				$this->x = $gvars['x'];
24758
				$this->y = $gvars['y'];
24759
				$this->page = $gvars['page'];
24760
				$this->current_column = $gvars['current_column'];
24761
				$this->tMargin = $gvars['tMargin'];
24762
				$this->bMargin = $gvars['bMargin'];
24763
				$this->w = $gvars['w'];
24764
				$this->h = $gvars['h'];
24765
				$this->wPt = $gvars['wPt'];
24766
				$this->hPt = $gvars['hPt'];
24767
				$this->fwPt = $gvars['fwPt'];
24768
				$this->fhPt = $gvars['fhPt'];
24769
				break;
24770
			}
24771
		}
24772
		$this->endlinex = $this->img_rb_x;
24773
		// restore page break
24774
		$this->SetAutoPageBreak($page_break_mode, $page_break_margin);
24775
		$this->cell_padding = $cell_padding;
24776
	}
24777
 
24778
	/**
24779
	 * Get the tranformation matrix from SVG transform attribute
24780
	 * @param string transformation
24781
	 * @return array of transformations
24782
	 * @author Nicola Asuni
24783
	 * @since 5.0.000 (2010-05-02)
24784
	 * @access protected
24785
	 */
24786
	protected function getSVGTransformMatrix($attribute) {
24787
		// identity matrix
24788
		$tm = array(1, 0, 0, 1, 0, 0);
24789
		$transform = array();
24790
		if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
24791
			foreach ($transform as $key => $data) {
24792
				if (!empty($data[2])) {
24793
					$a = 1;
24794
					$b = 0;
24795
					$c = 0;
24796
					$d = 1;
24797
					$e = 0;
24798
					$f = 0;
24799
					$regs = array();
24800
					switch ($data[1]) {
24801
						case 'matrix': {
24802
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24803
								$a = $regs[1];
24804
								$b = $regs[2];
24805
								$c = $regs[3];
24806
								$d = $regs[4];
24807
								$e = $regs[5];
24808
								$f = $regs[6];
24809
							}
24810
							break;
24811
						}
24812
						case 'translate': {
24813
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24814
								$e = $regs[1];
24815
								$f = $regs[2];
24816
							} elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24817
								$e = $regs[1];
24818
							}
24819
							break;
24820
						}
24821
						case 'scale': {
24822
							if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24823
								$a = $regs[1];
24824
								$d = $regs[2];
24825
							} elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24826
								$a = $regs[1];
24827
								$d = $a;
24828
							}
24829
							break;
24830
						}
24831
						case 'rotate': {
24832
							if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24833
								$ang = deg2rad($regs[1]);
24834
								$x = $regs[2];
24835
								$y = $regs[3];
24836
								$a = cos($ang);
24837
								$b = sin($ang);
24838
								$c = -$b;
24839
								$d = $a;
24840
								$e = ($x * (1 - $a)) - ($y * $c);
24841
								$f = ($y * (1 - $d)) - ($x * $b);
24842
							} elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24843
								$ang = deg2rad($regs[1]);
24844
								$a = cos($ang);
24845
								$b = sin($ang);
24846
								$c = -$b;
24847
								$d = $a;
24848
								$e = 0;
24849
								$f = 0;
24850
							}
24851
							break;
24852
						}
24853
						case 'skewX': {
24854
							if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24855
								$c = tan(deg2rad($regs[1]));
24856
							}
24857
							break;
24858
						}
24859
						case 'skewY': {
24860
							if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24861
								$b = tan(deg2rad($regs[1]));
24862
							}
24863
							break;
24864
						}
24865
					}
24866
					$tm = $this->getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
24867
				}
24868
			}
24869
		}
24870
		return $tm;
24871
	}
24872
 
24873
	/**
24874
	 * Get the product of two SVG tranformation matrices
24875
	 * @param array $ta first SVG tranformation matrix
24876
	 * @param array $tb second SVG tranformation matrix
24877
	 * @return transformation array
24878
	 * @author Nicola Asuni
24879
	 * @since 5.0.000 (2010-05-02)
24880
	 * @access protected
24881
	 */
24882
	protected function getTransformationMatrixProduct($ta, $tb) {
24883
		$tm = array();
24884
		$tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
24885
		$tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
24886
		$tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
24887
		$tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
24888
		$tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
24889
		$tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
24890
		return $tm;
24891
	}
24892
 
24893
	/**
24894
	 * Convert SVG transformation matrix to PDF.
24895
	 * @param array $tm original SVG transformation matrix
24896
	 * @return array transformation matrix
24897
	 * @access protected
24898
	 * @since 5.0.000 (2010-05-02)
24899
	 */
24900
	protected function convertSVGtMatrix($tm) {
24901
		$a = $tm[0];
24902
		$b = -$tm[1];
24903
		$c = -$tm[2];
24904
		$d = $tm[3];
24905
		$e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
24906
		$f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
24907
		$x = 0;
24908
		$y = $this->h * $this->k;
24909
		$e = ($x * (1 - $a)) - ($y * $c) + $e;
24910
		$f = ($y * (1 - $d)) - ($x * $b) + $f;
24911
		return array($a, $b, $c, $d, $e, $f);
24912
	}
24913
 
24914
	/**
24915
	 * Apply SVG graphic transformation matrix.
24916
	 * @param array $tm original SVG transformation matrix
24917
	 * @access protected
24918
	 * @since 5.0.000 (2010-05-02)
24919
	 */
24920
	protected function SVGTransform($tm) {
24921
		$this->Transform($this->convertSVGtMatrix($tm));
24922
	}
24923
 
24924
	/**
24925
	 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
24926
	 * @param array $svgstyle array of SVG styles to apply
24927
	 * @param array $prevsvgstyle array of previous SVG style
24928
	 * @param int $x X origin of the bounding box
24929
	 * @param int $y Y origin of the bounding box
24930
	 * @param int $w width of the bounding box
24931
	 * @param int $h height of the bounding box
24932
	 * @param string $clip_function clip function
24933
	 * @param array $clip_params array of parameters for clipping function
24934
	 * @return object style
24935
	 * @author Nicola Asuni
24936
	 * @since 5.0.000 (2010-05-02)
24937
	 * @access protected
24938
	 */
24939
	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
24940
		$objstyle = '';
24941
		if(!isset($svgstyle['opacity'])) {
24942
			return $objstyle;
24943
		}
24944
		// clip-path
24945
		$regs = array();
24946
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
24947
			$clip_path = $this->svgclippaths[$regs[1]];
24948
			foreach ($clip_path as $cp) {
24949
				$this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
24950
			}
24951
		}
24952
		// opacity
24953
		if ($svgstyle['opacity'] != 1) {
24954
			$this->SetAlpha($svgstyle['opacity']);
24955
		}
24956
		// color
24957
		$fill_color = $this->convertHTMLColorToDec($svgstyle['color']);
24958
		$this->SetFillColorArray($fill_color);
24959
		// text color
24960
		$text_color = $this->convertHTMLColorToDec($svgstyle['text-color']);
24961
		$this->SetTextColorArray($text_color);
24962
		// clip
24963
		if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
24964
			$top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
24965
			$right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
24966
			$bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
24967
			$left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
24968
			$cx = $x + $left;
24969
			$cy = $y + $top;
24970
			$cw = $w - $left - $right;
24971
			$ch = $h - $top - $bottom;
24972
			if ($svgstyle['clip-rule'] == 'evenodd') {
24973
				$clip_rule = 'CNZ';
24974
			} else {
24975
				$clip_rule = 'CEO';
24976
			}
24977
			$this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
24978
		}
24979
		// fill
24980
		$regs = array();
24981
		if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
24982
			// gradient
24983
			$gradient = $this->svggradients[$regs[1]];
24984
			if (isset($gradient['xref'])) {
24985
				// reference to another gradient definition
24986
				$newgradient = $this->svggradients[$gradient['xref']];
24987
				$newgradient['coords'] = $gradient['coords'];
24988
				$newgradient['mode'] = $gradient['mode'];
24989
				$newgradient['gradientUnits'] = $gradient['gradientUnits'];
24990
				if (isset($gradient['gradientTransform'])) {
24991
					$newgradient['gradientTransform'] = $gradient['gradientTransform'];
24992
				}
24993
				$gradient = $newgradient;
24994
			}
24995
			//save current Graphic State
24996
			$this->_out('q');
24997
			//set clipping area
24998
			if (!empty($clip_function) AND method_exists($this, $clip_function)) {
24999
				$bbox = call_user_func_array(array($this, $clip_function), $clip_params);
25000
				if (is_array($bbox) AND (count($bbox) == 4)) {
25001
					list($x, $y, $w, $h) = $bbox;
25002
				}
25003
			}
25004
			if ($gradient['mode'] == 'measure') {
25005
				if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
25006
					$gtm = $gradient['gradientTransform'];
25007
					// apply transformation matrix
25008
					$xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
25009
					$ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
25010
					$xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
25011
					$yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
25012
					if (isset($gradient['coords'][4])) {
25013
						$gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
25014
					}
25015
					$gradient['coords'][0] = $xa;
25016
					$gradient['coords'][1] = $ya;
25017
					$gradient['coords'][2] = $xb;
25018
					$gradient['coords'][3] = $yb;
25019
				}
25020
				// convert SVG coordinates to user units
25021
				$gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
25022
				$gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
25023
				$gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
25024
				$gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
25025
				if (isset($gradient['coords'][4])) {
25026
					$gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
25027
				}
25028
				// shift units
25029
				if ($gradient['gradientUnits'] == 'objectBoundingBox') {
25030
					// convert to SVG coordinate system
25031
					$gradient['coords'][0] += $x;
25032
					$gradient['coords'][1] += $y;
25033
					$gradient['coords'][2] += $x;
25034
					$gradient['coords'][3] += $y;
25035
				}
25036
				if ($w <= 0) {
25037
					$w = 1;
25038
				}
25039
				if ($h <= 0) {
25040
					$h = 1;
25041
				}
25042
				// calculate percentages
25043
				$gradient['coords'][0] = ($gradient['coords'][0] - $x) / $w;
25044
				$gradient['coords'][1] = ($gradient['coords'][1] - $y) / $h;
25045
				$gradient['coords'][2] = ($gradient['coords'][2] - $x) / $w;
25046
				$gradient['coords'][3] = ($gradient['coords'][3] - $y) / $h;
25047
				if (isset($gradient['coords'][4])) {
25048
					$gradient['coords'][4] /= $w;
25049
				}
25050
			} elseif ($gradient['mode'] == 'percentage') {
25051
				foreach($gradient['coords'] as $key => $val) {
25052
					$gradient['coords'][$key] = (intval($val) / 100);
25053
				}
25054
			}
25055
			// fix values
25056
			foreach($gradient['coords'] as $key => $val) {
25057
				if ($val < 0) {
25058
					$gradient['coords'][$key] = 0;
25059
				} elseif ($val > 1) {
25060
					$gradient['coords'][$key] = 1;
25061
				}
25062
			}
25063
			if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
25064
				// single color (no shading)
25065
				$gradient['coords'][0] = 1;
25066
				$gradient['coords'][1] = 0;
25067
				$gradient['coords'][2] = 0.999;
25068
				$gradient['coords'][3] = 0;
25069
			}
25070
			// swap Y coordinates
25071
			$tmp = $gradient['coords'][1];
25072
			$gradient['coords'][1] = $gradient['coords'][3];
25073
			$gradient['coords'][3] = $tmp;
25074
			// set transformation map for gradient
25075
			if ($gradient['type'] == 3) {
25076
				// gradient is always circular
25077
				$cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
25078
				$this->_out(sprintf('%.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $w*$this->k, $x*$this->k, $cy*$this->k));
25079
			} else {
25080
				$this->_out(sprintf('%.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k));
25081
			}
25082
			if (count($gradient['stops']) > 1) {
25083
				$this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
25084
			}
25085
		} elseif ($svgstyle['fill'] != 'none') {
25086
			$fill_color = $this->convertHTMLColorToDec($svgstyle['fill']);
25087
			if ($svgstyle['fill-opacity'] != 1) {
25088
				$this->SetAlpha($svgstyle['fill-opacity']);
25089
			}
25090
			$this->SetFillColorArray($fill_color);
25091
			if ($svgstyle['fill-rule'] == 'evenodd') {
25092
				$objstyle .= 'F*';
25093
			} else {
25094
				$objstyle .= 'F';
25095
			}
25096
		}
25097
		// stroke
25098
		if ($svgstyle['stroke'] != 'none') {
25099
			$stroke_style = array(
25100
				'color' => $this->convertHTMLColorToDec($svgstyle['stroke']),
25101
				'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
25102
				'cap' => $svgstyle['stroke-linecap'],
25103
				'join' => $svgstyle['stroke-linejoin']
25104
				);
25105
			if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
25106
				$stroke_style['dash'] = $svgstyle['stroke-dasharray'];
25107
			}
25108
			$this->SetLineStyle($stroke_style);
25109
			$objstyle .= 'D';
25110
		}
25111
		// font
25112
		$regs = array();
25113
		if (!empty($svgstyle['font'])) {
25114
			if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
25115
				$font_family = $this->getFontFamilyName($regs[1]);
25116
			} else {
25117
				$font_family = $svgstyle['font-family'];
25118
			}
25119
			if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
25120
				$font_size = trim($regs[1]);
25121
			} else {
25122
				$font_size = $svgstyle['font-size'];
25123
			}
25124
			if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
25125
				$font_style = trim($regs[1]);
25126
			} else {
25127
				$font_style = $svgstyle['font-style'];
25128
			}
25129
			if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
25130
				$font_weight = trim($regs[1]);
25131
			} else {
25132
				$font_weight = $svgstyle['font-weight'];
25133
			}
25134
			if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
25135
				$font_stretch = trim($regs[1]);
25136
			} else {
25137
				$font_stretch = $svgstyle['font-stretch'];
25138
			}
25139
			if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
25140
				$font_spacing = trim($regs[1]);
25141
			} else {
25142
				$font_spacing = $svgstyle['letter-spacing'];
25143
			}
25144
		} else {
25145
			$font_family = $this->getFontFamilyName($svgstyle['font-family']);
25146
			$font_size = $svgstyle['font-size'];
25147
			$font_style = $svgstyle['font-style'];
25148
			$font_weight = $svgstyle['font-weight'];
25149
			$font_stretch = $svgstyle['font-stretch'];
25150
			$font_spacing = $svgstyle['letter-spacing'];
25151
		}
25152
		$font_size = $this->getHTMLUnitToUnits($font_size, $prevsvgstyle['font-size'], $this->svgunit, false) * $this->k;
25153
		$font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
25154
		$font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
25155
		switch ($font_style) {
25156
			case 'italic': {
25157
				$font_style = 'I';
25158
				break;
25159
			}
25160
			case 'oblique': {
25161
				$font_style = 'I';
25162
				break;
25163
			}
25164
			default:
25165
			case 'normal': {
25166
				$font_style = '';
25167
				break;
25168
			}
25169
		}
25170
		switch ($font_weight) {
25171
			case 'bold':
25172
			case 'bolder': {
25173
				$font_style .= 'B';
25174
				break;
25175
			}
25176
		}
25177
		switch ($svgstyle['text-decoration']) {
25178
			case 'underline': {
25179
				$font_style .= 'U';
25180
				break;
25181
			}
25182
			case 'overline': {
25183
				$font_style .= 'O';
25184
				break;
25185
			}
25186
			case 'line-through': {
25187
				$font_style .= 'D';
25188
				break;
25189
			}
25190
			default:
25191
			case 'none': {
25192
				break;
25193
			}
25194
		}
25195
		$this->SetFont($font_family, $font_style, $font_size);
25196
		$this->setFontStretching($font_stretch);
25197
		$this->setFontSpacing($font_spacing);
25198
		return $objstyle;
25199
	}
25200
 
25201
	/**
25202
	 * Draws an SVG path
25203
	 * @param string $d attribute d of the path SVG element
25204
	 * @param string $style Style of rendering. Possible values are:
25205
	 * <ul>
25206
	 *	 <li>D or empty string: Draw (default).</li>
25207
	 *	 <li>F: Fill.</li>
25208
	 *	 <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
25209
	 *	 <li>DF or FD: Draw and fill.</li>
25210
	 *	 <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
25211
	 *	 <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
25212
	 *	 <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
25213
	 * </ul>
25214
	 * @return array of container box measures (x, y, w, h)
25215
	 * @author Nicola Asuni
25216
	 * @since 5.0.000 (2010-05-02)
25217
	 * @access protected
25218
	 */
25219
	protected function SVGPath($d, $style='') {
25220
		// set fill/stroke style
25221
		$op = $this->getPathPaintOperator($style, '');
25222
		if (empty($op)) {
25223
			return;
25224
		}
25225
		$paths = array();
25226
		$d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
25227
		preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
25228
		$x = 0;
25229
		$y = 0;
25230
		$x1 = 0;
25231
		$y1 = 0;
25232
		$x2 = 0;
25233
		$y2 = 0;
25234
		$xmin = 2147483647;
25235
		$xmax = 0;
25236
		$ymin = 2147483647;
25237
		$ymax = 0;
25238
		$relcoord = false;
25239
		// draw curve pieces
25240
		foreach ($paths as $key => $val) {
25241
			// get curve type
25242
			$cmd = trim($val[1]);
25243
			if (strtolower($cmd) == $cmd) {
25244
				// use relative coordinated instead of absolute
25245
				$relcoord = true;
25246
				$xoffset = $x;
25247
				$yoffset = $y;
25248
			} else {
25249
				$relcoord = false;
25250
				$xoffset = 0;
25251
				$yoffset = 0;
25252
			}
25253
			$params = array();
25254
			if (isset($val[2])) {
25255
				// get curve parameters
25256
				$rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
25257
				$params = array();
25258
				foreach ($rawparams as $ck => $cp) {
25259
					$params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
25260
				}
25261
			}
25262
			switch (strtoupper($cmd)) {
25263
				case 'M': { // moveto
25264
					foreach ($params as $ck => $cp) {
25265
						if (($ck % 2) == 0) {
25266
							$x = $cp + $xoffset;
25267
						} else {
25268
							$y = $cp + $yoffset;
25269
							if ($ck == 1) {
25270
								$this->_outPoint($x, $y);
25271
							} else {
25272
								$this->_outLine($x, $y);
25273
							}
25274
							$xmin = min($xmin, $x);
25275
							$ymin = min($ymin, $y);
25276
							$xmax = max($xmax, $x);
25277
							$ymax = max($ymax, $y);
25278
							if ($relcoord) {
25279
								$xoffset = $x;
25280
								$yoffset = $y;
25281
							}
25282
						}
25283
					}
25284
					break;
25285
				}
25286
				case 'L': { // lineto
25287
					foreach ($params as $ck => $cp) {
25288
						if (($ck % 2) == 0) {
25289
							$x = $cp + $xoffset;
25290
						} else {
25291
							$y = $cp + $yoffset;
25292
							$this->_outLine($x, $y);
25293
							$xmin = min($xmin, $x);
25294
							$ymin = min($ymin, $y);
25295
							$xmax = max($xmax, $x);
25296
							$ymax = max($ymax, $y);
25297
							if ($relcoord) {
25298
								$xoffset = $x;
25299
								$yoffset = $y;
25300
							}
25301
						}
25302
					}
25303
					break;
25304
				}
25305
				case 'H': { // horizontal lineto
25306
					foreach ($params as $ck => $cp) {
25307
						$x = $cp + $xoffset;
25308
						$this->_outLine($x, $y);
25309
						$xmin = min($xmin, $x);
25310
						$xmax = max($xmax, $x);
25311
						if ($relcoord) {
25312
							$xoffset = $x;
25313
						}
25314
					}
25315
					break;
25316
				}
25317
				case 'V': { // vertical lineto
25318
					foreach ($params as $ck => $cp) {
25319
						$y = $cp + $yoffset;
25320
						$this->_outLine($x, $y);
25321
						$ymin = min($ymin, $y);
25322
						$ymax = max($ymax, $y);
25323
						if ($relcoord) {
25324
							$yoffset = $y;
25325
						}
25326
					}
25327
					break;
25328
				}
25329
				case 'C': { // curveto
25330
					foreach ($params as $ck => $cp) {
25331
						$params[$ck] = $cp;
25332
						if ((($ck + 1) % 6) == 0) {
25333
							$x1 = $params[($ck - 5)] + $xoffset;
25334
							$y1 = $params[($ck - 4)] + $yoffset;
25335
							$x2 = $params[($ck - 3)] + $xoffset;
25336
							$y2 = $params[($ck - 2)] + $yoffset;
25337
							$x = $params[($ck - 1)] + $xoffset;
25338
							$y = $params[($ck)] + $yoffset;
25339
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
25340
							$xmin = min($xmin, $x, $x1, $x2);
25341
							$ymin = min($ymin, $y, $y1, $y2);
25342
							$xmax = max($xmax, $x, $x1, $x2);
25343
							$ymax = max($ymax, $y, $y1, $y2);
25344
							if ($relcoord) {
25345
								$xoffset = $x;
25346
								$yoffset = $y;
25347
							}
25348
						}
25349
					}
25350
					break;
25351
				}
25352
				case 'S': { // shorthand/smooth curveto
25353
					foreach ($params as $ck => $cp) {
25354
						$params[$ck] = $cp;
25355
						if ((($ck + 1) % 4) == 0) {
25356
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
25357
								$x1 = (2 * $x) - $x2;
25358
								$y1 = (2 * $y) - $y2;
25359
							} else {
25360
								$x1 = $x;
25361
								$y1 = $y;
25362
							}
25363
							$x2 = $params[($ck - 3)] + $xoffset;
25364
							$y2 = $params[($ck - 2)] + $yoffset;
25365
							$x = $params[($ck - 1)] + $xoffset;
25366
							$y = $params[($ck)] + $yoffset;
25367
							$this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
25368
							$xmin = min($xmin, $x, $x1, $x2);
25369
							$ymin = min($ymin, $y, $y1, $y2);
25370
							$xmax = max($xmax, $x, $x1, $x2);
25371
							$ymax = max($ymax, $y, $y1, $y2);
25372
							if ($relcoord) {
25373
								$xoffset = $x;
25374
								$yoffset = $y;
25375
							}
25376
						}
25377
					}
25378
					break;
25379
				}
25380
				case 'Q': { // quadratic B�zier curveto
25381
					foreach ($params as $ck => $cp) {
25382
						$params[$ck] = $cp;
25383
						if ((($ck + 1) % 4) == 0) {
25384
							// convert quadratic points to cubic points
25385
							$x1 = $params[($ck - 3)] + $xoffset;
25386
							$y1 = $params[($ck - 2)] + $yoffset;
25387
							$xa = ($x + (2 * $x1)) / 3;
25388
							$ya = ($y + (2 * $y1)) / 3;
25389
							$x = $params[($ck - 1)] + $xoffset;
25390
							$y = $params[($ck)] + $yoffset;
25391
							$xb = ($x + (2 * $x1)) / 3;
25392
							$yb = ($y + (2 * $y1)) / 3;
25393
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
25394
							$xmin = min($xmin, $x, $xa, $xb);
25395
							$ymin = min($ymin, $y, $ya, $yb);
25396
							$xmax = max($xmax, $x, $xa, $xb);
25397
							$ymax = max($ymax, $y, $ya, $yb);
25398
							if ($relcoord) {
25399
								$xoffset = $x;
25400
								$yoffset = $y;
25401
							}
25402
						}
25403
					}
25404
					break;
25405
				}
25406
				case 'T': { // shorthand/smooth quadratic B�zier curveto
25407
					foreach ($params as $ck => $cp) {
25408
						$params[$ck] = $cp;
25409
						if (($ck % 2) != 0) {
25410
							if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
25411
								$x1 = (2 * $x) - $x1;
25412
								$y1 = (2 * $y) - $y1;
25413
							} else {
25414
								$x1 = $x;
25415
								$y1 = $y;
25416
							}
25417
							// convert quadratic points to cubic points
25418
							$xa = ($x + (2 * $x1)) / 3;
25419
							$ya = ($y + (2 * $y1)) / 3;
25420
							$x = $params[($ck - 1)] + $xoffset;
25421
							$y = $params[($ck)] + $yoffset;
25422
							$xb = ($x + (2 * $x1)) / 3;
25423
							$yb = ($y + (2 * $y1)) / 3;
25424
							$this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
25425
							$xmin = min($xmin, $x, $xa, $xb);
25426
							$ymin = min($ymin, $y, $ya, $yb);
25427
							$xmax = max($xmax, $x, $xa, $xb);
25428
							$ymax = max($ymax, $y, $ya, $yb);
25429
							if ($relcoord) {
25430
								$xoffset = $x;
25431
								$yoffset = $y;
25432
							}
25433
						}
25434
					}
25435
					break;
25436
				}
25437
				case 'A': { // elliptical arc
25438
					foreach ($params as $ck => $cp) {
25439
						$params[$ck] = $cp;
25440
						if ((($ck + 1) % 7) == 0) {
25441
							$x0 = $x;
25442
							$y0 = $y;
25443
							$rx = abs($params[($ck - 6)]);
25444
							$ry = abs($params[($ck - 5)]);
25445
							$ang = -$rawparams[($ck - 4)];
25446
							$angle = deg2rad($ang);
25447
							$fa = $rawparams[($ck - 3)]; // large-arc-flag
25448
							$fs = $rawparams[($ck - 2)]; // sweep-flag
25449
							$x = $params[($ck - 1)] + $xoffset;
25450
							$y = $params[$ck] + $yoffset;
25451
							$cos_ang = cos($angle);
25452
							$sin_ang = sin($angle);
25453
							$a = ($x0 - $x) / 2;
25454
							$b = ($y0 - $y) / 2;
25455
							$xa = ($a * $cos_ang) - ($b * $sin_ang);
25456
							$ya = ($a * $sin_ang) + ($b * $cos_ang);
25457
							$rx2 = $rx * $rx;
25458
							$ry2 = $ry * $ry;
25459
							$xa2 = $xa * $xa;
25460
							$ya2 = $ya * $ya;
25461
							$delta = ($xa2 / $rx2) + ($ya2 / $ry2);
25462
							if ($delta > 1) {
25463
								$rx *= sqrt($delta);
25464
								$ry *= sqrt($delta);
25465
								$rx2 = $rx * $rx;
25466
								$ry2 = $ry * $ry;
25467
							}
25468
							$numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
25469
							if ($numerator < 0) {
25470
								$root = 0;
25471
							} else {
25472
								$root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
25473
							}
25474
							if ($fa == $fs) {
25475
								$root *= -1;
25476
							}
25477
							$cax = $root * (($rx * $ya) / $ry);
25478
							$cay = -$root * (($ry * $xa) / $rx);
25479
							// coordinates of ellipse center
25480
							$cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
25481
							$cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
25482
							// get angles
25483
							$angs = $this->getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
25484
							$dang = $this->getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
25485
							if (($fs == 0) AND ($dang > 0)) {
25486
								$dang -= (2 * M_PI);
25487
							} elseif (($fs == 1) AND ($dang < 0)) {
25488
								$dang += (2 * M_PI);
25489
							}
25490
							$angf = $angs - $dang;
25491
							if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
25492
								// reverse angles
25493
								$tmp = $angs;
25494
								$angs = $angf;
25495
								$angf = $tmp;
25496
							}
25497
							$angs = round(rad2deg($angs), 6);
25498
							$angf = round(rad2deg($angf), 6);
25499
							// covent angles to positive values
25500
							if (($angs < 0) AND ($angf < 0)) {
25501
								$angs += 360;
25502
								$angf += 360;
25503
							}
25504
							$pie = false;
25505
							if (($key==0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
25506
								$pie = true;
25507
							}
25508
							list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0));
25509
							$xmin = min($xmin, $x, $axmin);
25510
							$ymin = min($ymin, $y, $aymin);
25511
							$xmax = max($xmax, $x, $axmax);
25512
							$ymax = max($ymax, $y, $aymax);
25513
							if ($relcoord) {
25514
								$xoffset = $x;
25515
								$yoffset = $y;
25516
							}
25517
						}
25518
					}
25519
					break;
25520
				}
25521
				case 'Z': {
25522
					$this->_out('h');
25523
					break;
25524
				}
25525
			}
25526
		} // end foreach
25527
		if (!empty($op)) {
25528
			$this->_out($op);
25529
		}
25530
		return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
25531
	}
25532
 
25533
	/**
25534
	 * Returns the angle in radiants between two vectors
25535
	 * @param int $x1 X coordiante of first vector point
25536
	 * @param int $y1 Y coordiante of first vector point
25537
	 * @param int $x2 X coordiante of second vector point
25538
	 * @param int $y2 Y coordiante of second vector point
25539
	 * @author Nicola Asuni
25540
	 * @since 5.0.000 (2010-05-04)
25541
	 * @access protected
25542
	 */
25543
	protected function getVectorsAngle($x1, $y1, $x2, $y2) {
25544
		$dprod = ($x1 * $x2) + ($y1 * $y2);
25545
		$dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
25546
		$dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
25547
		$angle = acos($dprod / ($dist1 * $dist2));
25548
		if (is_nan($angle)) {
25549
			$angle = M_PI;
25550
		}
25551
		if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
25552
			$angle *= -1;
25553
		}
25554
		return $angle;
25555
	}
25556
 
25557
	/**
25558
	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
25559
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
25560
	 * @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.
25561
	 * @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.
25562
	 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
25563
	 * @author Nicola Asuni
25564
	 * @since 5.0.000 (2010-05-02)
25565
	 * @access protected
25566
	 */
25567
	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
25568
		// check if we are in clip mode
25569
		if ($this->svgclipmode) {
25570
			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
25571
			return;
25572
		}
25573
		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
25574
			if (!isset($attribs['id'])) {
25575
				$attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
25576
			}
25577
			$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
25578
			return;
25579
		}
25580
		$clipping = false;
25581
		if ($parser == 'clip-path') {
25582
			// set clipping mode
25583
			$clipping = true;
25584
		}
25585
		// get styling properties
25586
		$prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
25587
		$svgstyle = $this->svgstyles[0]; // set default style
25588
		if (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
25589
			// fix style for regular expression
25590
			$attribs['style'] = ';'.$attribs['style'];
25591
		}
25592
		foreach ($prev_svgstyle as $key => $val) {
25593
			if (in_array($key, $this->svginheritprop)) {
25594
				// inherit previous value
25595
				$svgstyle[$key] = $val;
25596
			}
25597
			if (isset($attribs[$key]) AND !$this->empty_string($attribs[$key])) {
25598
				// specific attribute settings
25599
				if ($attribs[$key] == 'inherit') {
25600
					$svgstyle[$key] = $val;
25601
				} else {
25602
					$svgstyle[$key] = $attribs[$key];
25603
				}
25604
			} elseif (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
25605
				// CSS style syntax
25606
				$attrval = array();
25607
				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
25608
					if ($attrval[1] == 'inherit') {
25609
						$svgstyle[$key] = $val;
25610
					} else {
25611
						$svgstyle[$key] = $attrval[1];
25612
					}
25613
				}
25614
			}
25615
		}
25616
		// transformation matrix
25617
		if (!empty($ctm)) {
25618
			$tm = $ctm;
25619
		} else {
25620
			$tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
25621
		}
25622
		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
25623
			$tm = $this->getTransformationMatrixProduct($tm, $this->getSVGTransformMatrix($attribs['transform']));
25624
		}
25625
		$svgstyle['transfmatrix'] = $tm;
25626
		$invisible = false;
25627
		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
25628
			// the current graphics element is invisible (nothing is painted)
25629
			$invisible = true;
25630
		}
25631
		// process tag
25632
		switch($name) {
25633
			case 'defs': {
25634
				$this->svgdefsmode = true;
25635
				break;
25636
			}
25637
			// clipPath
25638
			case 'clipPath': {
25639
				if ($invisible) {
25640
					break;
25641
				}
25642
				$this->svgclipmode = true;
25643
				if (!isset($attribs['id'])) {
25644
					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
25645
				}
25646
				$this->svgclipid = $attribs['id'];
25647
				$this->svgclippaths[$this->svgclipid] = array();
25648
				$this->svgcliptm[$this->svgclipid] = $tm;
25649
				break;
25650
			}
25651
			case 'svg': {
25652
				// start of SVG object
25653
				break;
25654
			}
25655
			case 'g': {
25656
				// group together related graphics elements
25657
				array_push($this->svgstyles, $svgstyle);
25658
				$this->StartTransform();
25659
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
25660
				break;
25661
			}
25662
			case 'linearGradient': {
25663
				if (!isset($attribs['id'])) {
25664
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
25665
				}
25666
				$this->svggradientid = $attribs['id'];
25667
				$this->svggradients[$this->svggradientid] = array();
25668
				$this->svggradients[$this->svggradientid]['type'] = 2;
25669
				$this->svggradients[$this->svggradientid]['stops'] = array();
25670
				if (isset($attribs['gradientUnits'])) {
25671
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
25672
				} else {
25673
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
25674
				}
25675
				//$attribs['spreadMethod']
25676
				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0%');
25677
				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0%');
25678
				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100%');
25679
				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0%');
25680
				if (substr($x1, -1) != '%') {
25681
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
25682
				} else {
25683
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
25684
				}
25685
				if (isset($attribs['gradientTransform'])) {
25686
					$this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
25687
				}
25688
				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
25689
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
25690
					// gradient is defined on another place
25691
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
25692
				}
25693
				break;
25694
			}
25695
			case 'radialGradient': {
25696
				if (!isset($attribs['id'])) {
25697
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
25698
				}
25699
				$this->svggradientid = $attribs['id'];
25700
				$this->svggradients[$this->svggradientid] = array();
25701
				$this->svggradients[$this->svggradientid]['type'] = 3;
25702
				$this->svggradients[$this->svggradientid]['stops'] = array();
25703
				if (isset($attribs['gradientUnits'])) {
25704
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
25705
				} else {
25706
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
25707
				}
25708
				//$attribs['spreadMethod']
25709
				$cx = (isset($attribs['cx'])?$attribs['cx']:0.5);
25710
				$cy = (isset($attribs['cy'])?$attribs['cy']:0.5);
25711
				$fx = (isset($attribs['fx'])?$attribs['fx']:$cx);
25712
				$fy = (isset($attribs['fy'])?$attribs['fy']:$cy);
25713
				$r = (isset($attribs['r'])?$attribs['r']:0.5);
25714
				if (isset($attribs['cx']) AND (substr($attribs['cx'], -1) != '%')) {
25715
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
25716
				} else {
25717
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
25718
				}
25719
				if (isset($attribs['gradientTransform'])) {
25720
					$this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
25721
				}
25722
				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
25723
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
25724
					// gradient is defined on another place
25725
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
25726
				}
25727
				break;
25728
			}
25729
			case 'stop': {
25730
				// gradient stops
25731
				if (substr($attribs['offset'], -1) == '%') {
25732
					$offset = floatval(substr($attribs['offset'], -1)) / 100;
25733
				} else {
25734
					$offset = floatval($attribs['offset']);
25735
					if ($offset > 1) {
25736
						$offset /= 100;
25737
					}
25738
				}
25739
				$stop_color = isset($svgstyle['stop-color'])?$this->convertHTMLColorToDec($svgstyle['stop-color']):'black';
25740
				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
25741
				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
25742
				break;
25743
			}
25744
			// paths
25745
			case 'path': {
25746
				if ($invisible) {
25747
					break;
25748
				}
25749
				if (isset($attribs['d'])) {
25750
					$d = trim($attribs['d']);
25751
					if (!empty($d)) {
25752
						if ($clipping) {
25753
							$this->SVGTransform($tm);
25754
							$this->SVGPath($d, 'CNZ');
25755
						} else {
25756
							$this->StartTransform();
25757
							$this->SVGTransform($tm);
25758
							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
25759
							if (!empty($obstyle)) {
25760
								$this->SVGPath($d, $obstyle);
25761
							}
25762
							$this->StopTransform();
25763
						}
25764
					}
25765
				}
25766
				break;
25767
			}
25768
			// shapes
25769
			case 'rect': {
25770
				if ($invisible) {
25771
					break;
25772
				}
25773
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
25774
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
25775
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
25776
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
25777
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
25778
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
25779
				if ($clipping) {
25780
					$this->SVGTransform($tm);
25781
					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
25782
				} else {
25783
					$this->StartTransform();
25784
					$this->SVGTransform($tm);
25785
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
25786
					if (!empty($obstyle)) {
25787
						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
25788
					}
25789
					$this->StopTransform();
25790
				}
25791
				break;
25792
			}
25793
			case 'circle': {
25794
				if ($invisible) {
25795
					break;
25796
				}
25797
				$cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
25798
				$cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
25799
				$r = (isset($attribs['r'])?$this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false):0);
25800
				$x = $cx - $r;
25801
				$y = $cy - $r;
25802
				$w = 2 * $r;
25803
				$h = $w;
25804
				if ($clipping) {
25805
					$this->SVGTransform($tm);
25806
					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
25807
				} else {
25808
					$this->StartTransform();
25809
					$this->SVGTransform($tm);
25810
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
25811
					if (!empty($obstyle)) {
25812
						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
25813
					}
25814
					$this->StopTransform();
25815
				}
25816
				break;
25817
			}
25818
			case 'ellipse': {
25819
				if ($invisible) {
25820
					break;
25821
				}
25822
				$cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
25823
				$cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
25824
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
25825
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):0);
25826
				$x = $cx - $rx;
25827
				$y = $cy - $ry;
25828
				$w = 2 * $rx;
25829
				$h = 2 * $ry;
25830
				if ($clipping) {
25831
					$this->SVGTransform($tm);
25832
					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
25833
				} else {
25834
					$this->StartTransform();
25835
					$this->SVGTransform($tm);
25836
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
25837
					if (!empty($obstyle)) {
25838
						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
25839
					}
25840
					$this->StopTransform();
25841
				}
25842
				break;
25843
			}
25844
			case 'line': {
25845
				if ($invisible) {
25846
					break;
25847
				}
25848
				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
25849
				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
25850
				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
25851
				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
25852
				$x = $x1;
25853
				$y = $y1;
25854
				$w = abs($x2 - $x1);
25855
				$h = abs($y2 - $y1);
25856
				if (!$clipping) {
25857
					$this->StartTransform();
25858
					$this->SVGTransform($tm);
25859
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
25860
					$this->Line($x1, $y1, $x2, $y2);
25861
					$this->StopTransform();
25862
				}
25863
				break;
25864
			}
25865
			case 'polyline':
25866
			case 'polygon': {
25867
				if ($invisible) {
25868
					break;
25869
				}
25870
				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
25871
				$points = trim($points);
25872
				// note that point may use a complex syntax not covered here
25873
				$points = preg_split('/[\,\s]+/si', $points);
25874
				if (count($points) < 4) {
25875
					break;
25876
				}
25877
				$p = array();
25878
				$xmin = 2147483647;
25879
				$xmax = 0;
25880
				$ymin = 2147483647;
25881
				$ymax = 0;
25882
				foreach ($points as $key => $val) {
25883
					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
25884
					if (($key % 2) == 0) {
25885
						// X coordinate
25886
						$xmin = min($xmin, $p[$key]);
25887
						$xmax = max($xmax, $p[$key]);
25888
					} else {
25889
						// Y coordinate
25890
						$ymin = min($ymin, $p[$key]);
25891
						$ymax = max($ymax, $p[$key]);
25892
					}
25893
				}
25894
				$x = $xmin;
25895
				$y = $ymin;
25896
				$w = ($xmax - $xmin);
25897
				$h = ($ymax - $ymin);
25898
				if ($name == 'polyline') {
25899
					$this->StartTransform();
25900
					$this->SVGTransform($tm);
25901
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
25902
					$this->PolyLine($p, 'D', array(), array());
25903
					$this->StopTransform();
25904
				} else { // polygon
25905
					if ($clipping) {
25906
						$this->SVGTransform($tm);
25907
						$this->Polygon($p, 'CNZ', array(), array(), true);
25908
					} else {
25909
						$this->StartTransform();
25910
						$this->SVGTransform($tm);
25911
						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
25912
						if (!empty($obstyle)) {
25913
							$this->Polygon($p, $obstyle, array(), array(), true);
25914
						}
25915
						$this->StopTransform();
25916
					}
25917
				}
25918
				break;
25919
			}
25920
			// image
25921
			case 'image': {
25922
				if ($invisible) {
25923
					break;
25924
				}
25925
				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
25926
					break;
25927
				}
25928
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
25929
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
25930
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
25931
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
25932
				$img = $attribs['xlink:href'];
25933
				if (!$clipping) {
25934
					$this->StartTransform();
25935
					$this->SVGTransform($tm);
25936
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
25937
					// fix image path
25938
					if (!$this->empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
25939
						// replace relative path with full server path
25940
						$img = $this->svgdir.'/'.$img;
25941
					}
25942
					if (($img{0} == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
25943
						$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
25944
						if (($findroot === false) OR ($findroot > 1)) {
25945
							// replace relative path with full server path
25946
							$img = $_SERVER['DOCUMENT_ROOT'].$img;
25947
						}
25948
					}
25949
					$img = urldecode($img);
25950
					$testscrtype = @parse_url($img);
25951
					if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
25952
						// convert URL to server path
25953
						$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
25954
					}
25955
					$this->Image($img, $x, $y, $w, $h);
25956
					$this->StopTransform();
25957
				}
25958
				break;
25959
			}
25960
			// text
25961
			case 'text':
25962
			case 'tspan': {
25963
				$this->svgtextmode['invisible'] = $invisible;
25964
				if ($invisible) {
25965
					break;
25966
				}
25967
				array_push($this->svgstyles, $svgstyle);
25968
				// only basic support - advanced features must be implemented
25969
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):$this->x);
25970
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):$this->y);
25971
				$svgstyle['text-color'] = $svgstyle['fill'];
25972
				$this->svgtext = '';
25973
				if (isset($svgstyle['text-anchor'])) {
25974
					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
25975
				} else {
25976
					$this->svgtextmode['text-anchor'] = 'start';
25977
				}
25978
				if (isset($svgstyle['direction'])) {
25979
					if ($svgstyle['direction'] == 'rtl') {
25980
						$this->svgtextmode['rtl'] = true;
25981
					} else {
25982
						$this->svgtextmode['rtl'] = false;
25983
					}
25984
				} else {
25985
					$this->svgtextmode['rtl'] = false;
25986
				}
25987
				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
25988
					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
25989
				} else {
25990
					$this->svgtextmode['stroke'] = false;
25991
				}
25992
				$this->StartTransform();
25993
				$this->SVGTransform($tm);
25994
				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
25995
				$this->x = $x;
25996
				$this->y = $y;
25997
				break;
25998
			}
25999
			// use
26000
			case 'use': {
26001
				if (isset($attribs['xlink:href'])) {
26002
					$use = $this->svgdefs[substr($attribs['xlink:href'], 1)];
26003
					if (isset($attribs['xlink:href'])) {
26004
						unset($attribs['xlink:href']);
26005
					}
26006
					if (isset($attribs['id'])) {
26007
						unset($attribs['id']);
26008
					}
26009
					$attribs = array_merge($use['attribs'], $attribs);
26010
					$this->startSVGElementHandler($parser, $use['name'], $use['attribs']);
26011
				}
26012
				break;
26013
			}
26014
			default: {
26015
				break;
26016
			}
26017
		} // end of switch
26018
	}
26019
 
26020
	/**
26021
	 * Sets the closing SVG element handler function for the XML parser.
26022
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
26023
	 * @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.
26024
	 * @author Nicola Asuni
26025
	 * @since 5.0.000 (2010-05-02)
26026
	 * @access protected
26027
	 */
26028
	protected function endSVGElementHandler($parser, $name) {
26029
		switch($name) {
26030
			case 'defs': {
26031
				$this->svgdefsmode = false;
26032
				break;
26033
			}
26034
			// clipPath
26035
			case 'clipPath': {
26036
				$this->svgclipmode = false;
26037
				break;
26038
			}
26039
			case 'g': {
26040
				// ungroup: remove last style from array
26041
				array_pop($this->svgstyles);
26042
				$this->StopTransform();
26043
				break;
26044
			}
26045
			case 'text':
26046
			case 'tspan': {
26047
				if ($this->svgtextmode['invisible']) {
26048
					// This implementation must be fixed to following the rule:
26049
					// 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.
26050
					break;
26051
				}
26052
				// print text
26053
				$text = $this->stringTrim($this->svgtext);
26054
				if ($this->svgtextmode['text-anchor'] != 'start') {
26055
					$textlen = $this->GetStringWidth($text);
26056
					// check if string is RTL text
26057
					if ($this->svgtextmode['text-anchor'] == 'end') {
26058
						if ($this->svgtextmode['rtl']) {
26059
							$this->x += $textlen;
26060
						} else {
26061
							$this->x -= $textlen;
26062
						}
26063
					} elseif ($this->svgtextmode['text-anchor'] == 'middle') {
26064
						if ($this->svgtextmode['rtl']) {
26065
							$this->x += ($textlen / 2);
26066
						} else {
26067
							$this->x -= ($textlen / 2);
26068
						}
26069
					}
26070
				}
26071
				$textrendermode = $this->textrendermode;
26072
				$textstrokewidth = $this->textstrokewidth;
26073
				$this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
26074
				$this->Cell(0, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
26075
				// restore previous rendering mode
26076
				$this->textrendermode = $textrendermode;
26077
				$this->textstrokewidth = $textstrokewidth;
26078
				$this->svgtext = '';
26079
				$this->StopTransform();
26080
				array_pop($this->svgstyles);
26081
				break;
26082
			}
26083
			default: {
26084
				break;
26085
			}
26086
		}
26087
	}
26088
 
26089
	/**
26090
	 * Sets the character data handler function for the XML parser.
26091
	 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
26092
	 * @param string $data The second parameter, data, contains the character data as a string.
26093
	 * @author Nicola Asuni
26094
	 * @since 5.0.000 (2010-05-02)
26095
	 * @access protected
26096
	 */
26097
	protected function segSVGContentHandler($parser, $data) {
26098
		$this->svgtext .= $data;
26099
	}
26100
 
26101
	// --- END SVG METHODS -----------------------------
26102
 
26103
} // END OF TCPDF CLASS
26104
 
26105
//============================================================+
26106
// END OF FILE
26107
//============================================================+
26108
?>